Home | History | Annotate | Download | only in source-transformer
      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