Home | History | Annotate | Download | only in aosp
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.google.currysrc.aosp;
     17 
     18 import static com.google.currysrc.api.process.Rules.createMandatoryRule;
     19 import static com.google.currysrc.api.process.Rules.createOptionalRule;
     20 
     21 import com.google.common.collect.ImmutableList;
     22 import com.google.currysrc.Main;
     23 import com.google.currysrc.api.RuleSet;
     24 import com.google.currysrc.api.input.DirectoryInputFileGenerator;
     25 import com.google.currysrc.api.input.InputFileGenerator;
     26 import com.google.currysrc.api.output.BasicOutputSourceFileGenerator;
     27 import com.google.currysrc.api.output.OutputSourceFileGenerator;
     28 import com.google.currysrc.api.process.Rule;
     29 import com.google.currysrc.api.process.ast.ChangeLog;
     30 import com.google.currysrc.api.process.ast.TypeLocator;
     31 import com.google.currysrc.processors.AddAnnotation;
     32 import com.google.currysrc.processors.AddDefaultConstructor;
     33 import com.google.currysrc.processors.HidePublicClasses;
     34 import com.google.currysrc.processors.InsertHeader;
     35 import com.google.currysrc.processors.ModifyQualifiedNames;
     36 import com.google.currysrc.processors.ModifyStringLiterals;
     37 import com.google.currysrc.processors.RenamePackage;
     38 import java.io.File;
     39 import java.nio.file.Path;
     40 import java.nio.file.Paths;
     41 import java.util.Collections;
     42 import java.util.List;
     43 import java.util.Map;
     44 import java.util.stream.Collectors;
     45 import joptsimple.BuiltinHelpFormatter;
     46 import joptsimple.OptionParser;
     47 import joptsimple.OptionSet;
     48 import joptsimple.OptionSpec;
     49 import joptsimple.ValueConversionException;
     50 import joptsimple.ValueConverter;
     51 import org.eclipse.jdt.core.JavaCore;
     52 import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
     53 
     54 /**
     55  * General transformation tool for use by various external repositories that require repackaging.
     56  */
     57 public class RepackagingTransform {
     58 
     59   private static final PathConverter PATH_CONVERTER = new PathConverter();
     60   private static final PackageTransformationConverter PACKAGE_TRANSFORMATION_CONVERTER =
     61       new PackageTransformationConverter();
     62 
     63   /**
     64    * Usage: java ConscryptTransform {source dir} {target dir}
     65    */
     66   public static void main(String[] args) throws Exception {
     67 
     68     OptionParser optionParser = new OptionParser();
     69 
     70     OptionSpec<Path> sourceDirOption = optionParser
     71         .accepts("source-dir", "directory containing the source that will be transformed")
     72         .withRequiredArg()
     73         .describedAs("source directory")
     74         .required()
     75         .withValuesConvertedBy(PATH_CONVERTER);
     76 
     77     OptionSpec<Path> targetDirOption = optionParser
     78         .accepts("target-dir", "directory into which the transformed source will be written")
     79         .withRequiredArg()
     80         .describedAs("target directory")
     81         .required()
     82         .withValuesConvertedBy(PATH_CONVERTER);
     83 
     84     OptionSpec<PackageTransformation> packageTransformationOption = optionParser
     85         .accepts("package-transformation", "transformation to apply to the package")
     86         .withRequiredArg().withValuesConvertedBy(PACKAGE_TRANSFORMATION_CONVERTER);
     87 
     88     OptionSpec<Path> corePlatformApiFileOption =
     89         optionParser.accepts("core-platform-api-file",
     90             "flat file containing body declaration identifiers for those classes and members that"
     91                 + " form part of the core platform api and so require a CorePlatformApi annotation"
     92                 + " to be added during transformation.")
     93             .withRequiredArg()
     94             .withValuesConvertedBy(PATH_CONVERTER);
     95 
     96     OptionSpec<Path> defaultConstructorsFileOption =
     97         optionParser.accepts("default-constructors-file",
     98             "flat file containing body declaration identifiers for those classes that require a"
     99                 + " default constructor to be inserted, usually as a place to which annotations can"
    100                 + " be added.")
    101             .withRequiredArg()
    102             .withValuesConvertedBy(PATH_CONVERTER);
    103 
    104     OptionSpec<Path> intraCoreApiFileOption =
    105         optionParser.accepts("intra-core-api-file",
    106             "flat file containing body declaration identifiers for those classes and members that"
    107                 + " form part of the intra core api and so require an IntraCoreApi annotation to be"
    108                 + " added during transformation.")
    109             .withRequiredArg()
    110             .withValuesConvertedBy(PATH_CONVERTER);
    111 
    112     OptionSpec<Path> unsupportedAppUsageFileOption =
    113         optionParser.accepts("unsupported-app-usage-file",
    114             "json file containing body declaration identifiers and annotation properties for those"
    115                 + " members that form part of the hiddenapi and so require an UnsupportedAppUsage"
    116                 + " annotation to be added during transformation.")
    117             .withRequiredArg()
    118             .withValuesConvertedBy(PATH_CONVERTER);
    119 
    120     OptionSpec<Integer> tabSizeOption = optionParser.accepts("tab-size",
    121         "the number of spaces that represent a single tabulation; set to the default indent used in"
    122             + " the transformed code otherwise the transformed code may be incorrectly formatted")
    123         .withRequiredArg()
    124         .ofType(Integer.class)
    125         .defaultsTo(4);
    126 
    127     OptionSpec<Path> changeLogOption =
    128         optionParser.accepts("change-log",
    129             "file into which information about what changes are made is appended. The file is"
    130                 + " intended to be used to aggregate information about the changes made on"
    131                 + " successive calls to this executable after which the changes made can be"
    132                 + " verified, e.g. check to make sure that annotations were added at all locations"
    133                 + " specified in the file specified by --unsupported-app-usage-file.")
    134             .withRequiredArg()
    135             .withValuesConvertedBy(PATH_CONVERTER);
    136 
    137     optionParser.formatHelpWith(new BuiltinHelpFormatter(120, 2));
    138 
    139     OptionSet optionSet;
    140     try {
    141       optionSet = optionParser.parse(args);
    142       if (!optionSet.nonOptionArguments().isEmpty()) {
    143         throw new IllegalStateException(String.format("unexpected trailing arguments %s",
    144             optionSet.nonOptionArguments().stream()
    145                 .map(Object::toString)
    146                 .collect(Collectors.joining(", "))));
    147       }
    148     } catch (RuntimeException e) {
    149       optionParser.printHelpOn(System.err);
    150       throw e;
    151     }
    152 
    153     Path changeLogPath = optionSet.valueOf(changeLogOption);
    154     try (ChangeLog changeLog = ChangeLog.createChangeLog(changeLogPath)) {
    155 
    156       Path sourceDir = optionSet.valueOf(sourceDirOption);
    157       Path targetDir = optionSet.valueOf(targetDirOption);
    158 
    159       ImmutableList.Builder<Rule> ruleBuilder = ImmutableList.builder();
    160 
    161       // Doc change: Insert a warning about the source code being generated.
    162       ruleBuilder.add(
    163           createMandatoryRule(
    164               new InsertHeader("/* GENERATED SOURCE. DO NOT MODIFY. */\n")));
    165 
    166       List<PackageTransformation> packageTransformations = optionSet
    167           .valuesOf(packageTransformationOption);
    168       for (PackageTransformation packageTransformation : packageTransformations) {
    169         String originalPackage = packageTransformation.prefixToRemove;
    170         String androidPackage = packageTransformation.prefixToAdd;
    171         ruleBuilder.add(
    172             // AST change: Change the package of each CompilationUnit
    173             createOptionalRule(new RenamePackage(originalPackage, androidPackage)),
    174             // AST change: Change all qualified names in code and javadoc.
    175             createOptionalRule(new ModifyQualifiedNames(originalPackage, androidPackage)),
    176             // AST change: Change all string literals containing package names in code.
    177             createOptionalRule(new ModifyStringLiterals(originalPackage, androidPackage)),
    178             // AST change: Change all string literals containing package paths in code.
    179             createOptionalRule(new ModifyStringLiterals(
    180                 packageToPath(originalPackage), packageToPath(androidPackage)))
    181         );
    182       }
    183 
    184       // Doc change: Insert @hide on all public classes.
    185       ruleBuilder.add(createHidePublicClassesRule());
    186 
    187       Path defaultConstructorsFile = optionSet.valueOf(defaultConstructorsFileOption);
    188       if (defaultConstructorsFile != null) {
    189         // AST change: Add default constructors, must come before processors that add
    190         // annotations.
    191         AddDefaultConstructor processor = new AddDefaultConstructor(
    192             TypeLocator.readTypeLocators(defaultConstructorsFile));
    193         processor.setListener(changeLog.asAddDefaultConstructorListener());
    194         ruleBuilder.add(createOptionalRule(processor));
    195       }
    196 
    197       Path corePlatformApiFile = optionSet.valueOf(corePlatformApiFileOption);
    198       if (corePlatformApiFile != null) {
    199         // AST change: Add CorePlatformApi to specified classes and members
    200         AddAnnotation processor = AddAnnotation.markerAnnotationFromFlatFile(
    201             "libcore.api.CorePlatformApi", corePlatformApiFile);
    202         processor.setListener(changeLog.asAddAnnotationListener());
    203         ruleBuilder.add(createOptionalRule(processor));
    204       }
    205 
    206       Path intraCoreApiFile = optionSet.valueOf(intraCoreApiFileOption);
    207       if (intraCoreApiFile != null) {
    208         // AST change: Add IntraCoreApi to specified classes and members
    209         AddAnnotation processor = AddAnnotation.markerAnnotationFromFlatFile(
    210             "libcore.api.IntraCoreApi", intraCoreApiFile);
    211         processor.setListener(changeLog.asAddAnnotationListener());
    212         ruleBuilder.add(createOptionalRule(processor));
    213       }
    214 
    215       Path unsupportedAppUsageFile = optionSet.valueOf(unsupportedAppUsageFileOption);
    216       if (unsupportedAppUsageFile != null) {
    217         // AST Change: Add UnsupportedAppUsage to specified class members.
    218         AddAnnotation processor = Annotations.addUnsupportedAppUsage(unsupportedAppUsageFile);
    219         processor.setListener(changeLog.asAddAnnotationListener());
    220         ruleBuilder.add(createOptionalRule(processor));
    221       }
    222 
    223       Map<String, String> options = JavaCore.getOptions();
    224       options.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_1_8);
    225       options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8);
    226       options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED);
    227       options.put(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaCore.SPACE);
    228       options.put(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE,
    229           String.valueOf(optionSet.valueOf(tabSizeOption)));
    230 
    231       new Main(false /* debug */)
    232           .setJdtOptions(options)
    233           .execute(new TransformRules(sourceDir, targetDir, ruleBuilder.build()));
    234     }
    235   }
    236 
    237   private static String packageToPath(String originalPackage) {
    238     return "/" + originalPackage.replace('.', '/');
    239   }
    240 
    241   private static Rule createHidePublicClassesRule() {
    242     List<TypeLocator> publicApiClassesWhitelist = Collections.emptyList();
    243     return createOptionalRule(new HidePublicClasses(publicApiClassesWhitelist,
    244         "This class is not part of the Android public SDK API"));
    245   }
    246 
    247   static class TransformRules implements RuleSet {
    248 
    249     private final Path sourceDir;
    250     private final Path targetDir;
    251     private final List<Rule> rules;
    252 
    253     TransformRules(Path sourceDir, Path targetDir, List<Rule> rules) {
    254       this.sourceDir = sourceDir;
    255       this.targetDir = targetDir;
    256       this.rules = rules;
    257     }
    258 
    259     @Override
    260     public InputFileGenerator getInputFileGenerator() {
    261       return new DirectoryInputFileGenerator(sourceDir.toFile());
    262     }
    263 
    264     @Override
    265     public List<Rule> getRuleList(File ignored) {
    266       return rules;
    267     }
    268 
    269     @Override
    270     public OutputSourceFileGenerator getOutputSourceFileGenerator() {
    271       File outputDir = targetDir.toFile();
    272       return new BasicOutputSourceFileGenerator(outputDir);
    273     }
    274   }
    275 
    276   private RepackagingTransform() {
    277   }
    278 
    279   private static class PackageTransformation {
    280 
    281     final String prefixToRemove;
    282     final String prefixToAdd;
    283 
    284     PackageTransformation(String prefixToRemove, String prefixToAdd) {
    285       this.prefixToRemove = prefixToRemove;
    286       this.prefixToAdd = prefixToAdd;
    287     }
    288 
    289     @Override
    290     public String toString() {
    291       return String.format("%s:%s", prefixToRemove, prefixToAdd);
    292     }
    293   }
    294 
    295   private static class PackageTransformationConverter implements
    296       ValueConverter<PackageTransformation> {
    297 
    298     @Override
    299     public PackageTransformation convert(String value) {
    300       String[] strings = value.split(":");
    301       if (strings.length != 2) {
    302         throw new ValueConversionException(
    303             String.format("Expected '<prefix-to-remove>:<prefix-to-replace>' but got'%s'", value));
    304       }
    305       return new PackageTransformation(strings[0], strings[1]);
    306     }
    307 
    308     @Override
    309     public Class<? extends PackageTransformation> valueType() {
    310       return PackageTransformation.class;
    311     }
    312 
    313     @Override
    314     public String valuePattern() {
    315       return "prefix-to-remove:prefix-to-replace";
    316     }
    317   }
    318 
    319   private static class PathConverter implements ValueConverter<Path> {
    320 
    321     @Override
    322     public Path convert(String value) {
    323       return Paths.get(value);
    324     }
    325 
    326     @Override
    327     public Class<? extends Path> valueType() {
    328       return Path.class;
    329     }
    330 
    331     @Override
    332     public String valuePattern() {
    333       return "";
    334     }
    335   }
    336 }
    337