1 #!/usr/bin/python 2 3 # This script does package renamings on source code (and .xml files and build files, etc), as part of migrating code to Jetpack. 4 # For example, this script may replace the text 5 # import android.support.annotation.RequiresApi; 6 # with 7 # import androidx.annotation.RequiresApi; 8 9 # See also b/74074903 10 import json 11 import os.path 12 import subprocess 13 14 HARDCODED_RULES_REVERSE = [ 15 "s|androidx.core.os.ResultReceiver|android.support.v4.os.ResultsReceiver|g\n", 16 "s|androidx.core.media.MediaBrowserCompat|android.support.v4.media.MediaBrowserCompat|g\n", 17 "s|androidx.core.media.MediaDescriptionCompat|android.support.v4.media.MediaDescriptionCompat|g\n", 18 "s|androidx.core.media.MediaMetadataCompat|android.support.v4.media.MediaMetadataCompat|g\n", 19 "s|androidx.core.media.RatingCompat|android.support.v4.media.RatingCompat|g\n", 20 "s|androidx.core.media.session.MediaControllerCompat|android.support.v4.media.session.MediaControllerCompat|g\n", 21 "s|androidx.core.media.session.MediaSessionCompat|android.support.v4.media.session.MediaSessionCompat|g\n", 22 "s|androidx.core.media.session.ParcelableVolumeInfo|android.support.v4.media.session.ParcelableVolumeInfo|g\n", 23 "s|androidx.core.media.session.PlaybackStateCompat|android.support.v4.media.session.PlaybackStateCompat|g\n", 24 ] 25 26 class StringBuilder(object): 27 def __init__(self, item=None): 28 self.items = [] 29 if item is not None: 30 self.add(item) 31 32 def add(self, item): 33 self.items.append(str(item)) 34 return self 35 36 def __str__(self): 37 return "".join(self.items) 38 39 class ExecutionConfig(object): 40 """Stores configuration about this execution of the package renaming. 41 For example, file paths of the source code. 42 This config could potentially be affected by command-line arguments. 43 """ 44 def __init__(self, jetifierConfig, sourceRoots, excludeDirs): 45 self.jetifierConfig = jetifierConfig 46 self.sourceRoots = sourceRoots 47 self.excludeDirs = excludeDirs 48 49 50 class SourceRewriteRule(object): 51 def __init__(self, fromName, toName): 52 self.fromName = fromName 53 self.toName = toName 54 55 def serialize(self): 56 return self.fromName + ":" + self.toName 57 58 class JetifierConfig(object): 59 """Stores configuration about the renaming itself, such as package rename rules. 60 This config isn't supposed to be affected by command-line arguments. 61 """ 62 @staticmethod 63 def parse(filePath): 64 with open(filePath) as f: 65 lines = f.readlines() 66 nonCommentLines = [line for line in lines if not line.strip().startswith("#")] 67 parsed = json.loads("".join(nonCommentLines)) 68 return JetifierConfig(parsed) 69 70 def __init__(self, parsedJson): 71 self.json = parsedJson 72 73 def getTypesMap(self): 74 rules = [] 75 for rule in self.json["rules"]: 76 fromName = rule["from"].replace("/", ".").replace("(.*)", "") 77 toName = rule["to"].replace("/", ".").replace("{0}", "") 78 if not toName.startswith("ignore"): 79 rules.append(SourceRewriteRule(fromName, toName)) 80 81 return rules 82 83 84 def createRewriteCommand(executionConfig): 85 # create command to find source files 86 finderTextBuilder = StringBuilder("find") 87 for sourceRoot in executionConfig.sourceRoots: 88 finderTextBuilder.add(" ").add(sourceRoot) 89 for exclusion in executionConfig.excludeDirs: 90 finderTextBuilder.add(" -name ").add(exclusion).add(" -prune -o") 91 finderTextBuilder.add(" -iregex '.*\.java\|.*\.xml\|.*\.cfg\|.*\.flags' -print") 92 93 # create command to rewrite one source 94 print("Building sed instructions") 95 rewriterTextBuilder = StringBuilder() 96 rewriteRules = executionConfig.jetifierConfig.getTypesMap() 97 for rule in rewriteRules: 98 rewriterTextBuilder.add("s|").add(rule.fromName.replace(".", "\.")).add("|").add(rule.toName).add("|g\n") 99 for rule in HARDCODED_RULES_REVERSE: 100 rewriterTextBuilder.add(rule) 101 scriptPath = "/tmp/jetifier-sed-script.txt" 102 print("Writing " + scriptPath) 103 with open(scriptPath, 'w') as scriptFile: 104 scriptFile.write(str(rewriterTextBuilder)) 105 106 # create the command to do the rewrites 107 fullCommand = "time " + str(finderTextBuilder) + " | xargs -n 1 --no-run-if-empty -P 64 sed -i -f " + scriptPath 108 109 return fullCommand 110 111 def processConfig(executionConfig): 112 print("Building rewrite command") 113 rewriteCommand = createRewriteCommand(executionConfig) 114 commandLength = len(rewriteCommand) 115 print(""" 116 Will run command: 117 118 """ + rewriteCommand + """ 119 120 """) 121 response = raw_input("Ok? [y/n]") 122 if response == "y": 123 subprocess.check_output(rewriteCommand, shell=True) 124 125 126 def main(): 127 pathOfThisFile = os.path.realpath(__file__) 128 jetifierPath = os.path.abspath(os.path.join(pathOfThisFile, "..", "..")) 129 130 jetifierConfigPath = os.path.join(jetifierPath, "source-transformer", "default.config") 131 print("Parsing " + jetifierConfigPath) 132 jetifierConfig = JetifierConfig.parse(jetifierConfigPath) 133 134 sourceRoot = os.getcwd() 135 excludeDirs = ["out", ".git", ".repo"] 136 137 executionConfig = ExecutionConfig(jetifierConfig, [sourceRoot], excludeDirs) 138 139 processConfig(executionConfig) 140 141 main() 142 143 144