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