| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.build.gradle.tasks |
| |
| import com.android.annotations.NonNull |
| import com.android.build.gradle.BasePlugin |
| import com.android.build.gradle.internal.variant.BaseVariantData |
| import com.android.build.gradle.tasks.annotations.ApiDatabase |
| import com.android.build.gradle.tasks.annotations.Extractor |
| import com.android.tools.lint.EcjParser |
| import com.google.common.collect.Lists |
| import com.google.common.collect.Maps |
| import org.eclipse.jdt.core.compiler.IProblem |
| import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration |
| import org.eclipse.jdt.internal.compiler.batch.CompilationUnit |
| import org.eclipse.jdt.internal.compiler.env.ICompilationUnit |
| import org.eclipse.jdt.internal.compiler.impl.CompilerOptions |
| import org.eclipse.jdt.internal.compiler.util.Util |
| import org.gradle.api.file.EmptyFileVisitor |
| import org.gradle.api.file.FileVisitDetails |
| import org.gradle.api.tasks.Input |
| import org.gradle.api.tasks.InputFile |
| import org.gradle.api.tasks.Optional |
| import org.gradle.api.tasks.OutputFile |
| import org.gradle.api.tasks.TaskAction |
| import org.gradle.api.tasks.compile.AbstractCompile |
| import org.gradle.tooling.BuildException |
| |
| import static com.android.SdkConstants.DOT_JAVA |
| import static com.android.SdkConstants.UTF_8 |
| |
| class ExtractAnnotations extends AbstractCompile { |
| public BasePlugin plugin |
| public BaseVariantData variant |
| |
| /** Boot classpath: typically android.jar */ |
| @Input |
| public List<String> bootClasspath |
| |
| /** The output .zip file to write the annotations database to, if any */ |
| @OutputFile |
| public File output |
| |
| /** |
| * An optional pointer to an API file to filter the annotations by (any annotations |
| * not found in the API file are considered hidden/not exposed.) This is in the same |
| * format as the api-versions.xml file found in the SDK. |
| */ |
| @Optional |
| @InputFile |
| public File apiFilter |
| |
| /** |
| * A list of existing annotation zip files (or dirs) to merge in. This can be used to merge in |
| * a hardcoded set of annotations that are not present in the source code, such as |
| * {@code @Contract} annotations we'd like to record without actually having a dependency |
| * on the IDEA annotations library. |
| */ |
| @Optional |
| @InputFile |
| public List<File> mergeJars |
| |
| /** |
| * The encoding to use when reading source files. The output file will ignore this and |
| * will always be a UTF-8 encoded .xml file inside the annotations zip file. |
| */ |
| @Optional |
| @Input |
| public String encoding |
| |
| /** |
| * Location of class files. If set, any non-public typedef source retention annotations |
| * will be removed prior to .jar packaging. |
| */ |
| @Optional |
| @InputFile |
| public File classDir |
| |
| @Override |
| @TaskAction |
| protected void compile() { |
| if (!hasAndroidAnnotations()) { |
| return |
| } |
| |
| if (encoding == null) { |
| encoding = UTF_8 |
| } |
| |
| Collection<CompilationUnitDeclaration> parsedUnits = parseSources() |
| |
| for (CompilationUnitDeclaration unit : parsedUnits) { |
| // so maybe I don't need my map!! |
| def problems = unit.compilationResult().allProblems |
| for (IProblem problem : problems) { |
| if (problem.error) { |
| println "Not extracting annotations (compilation problems encountered)"; |
| println "Error: " + problem.getOriginatingFileName() + ":" + |
| problem.getSourceLineNumber() + ": " + problem.getMessage() |
| // TODO: Consider whether we abort the build at this point! |
| return |
| } |
| } |
| } |
| |
| // API definition file |
| ApiDatabase database = null; |
| if (apiFilter != null && apiFilter.exists()) { |
| try { |
| database = new ApiDatabase(apiFilter); |
| } catch (IOException e) { |
| throw new BuildException("Could not open API database " + apiFilter, e) |
| } |
| } |
| |
| Extractor extractor = new Extractor(database, classDir); |
| extractor.extractFromProjectSource(parsedUnits) |
| if (mergeJars != null) { |
| for (File jar : mergeJars) { |
| extractor.mergeExisting(jar); |
| } |
| } |
| extractor.export(output) |
| extractor.removeTypedefClasses(); |
| } |
| |
| @Input |
| public boolean hasAndroidAnnotations() { |
| return variant.variantDependency.annotationsPresent |
| } |
| |
| @NonNull |
| private Collection<CompilationUnitDeclaration> parseSources() { |
| List<ICompilationUnit> sourceUnits = Lists.newArrayListWithExpectedSize(100); |
| |
| source.visit(new EmptyFileVisitor() { |
| @Override |
| void visitFile(FileVisitDetails fileVisitDetails) { |
| def file = fileVisitDetails.file; |
| def path = file.getPath() |
| if (path.endsWith(DOT_JAVA) && file.isFile()) { |
| char[] contents = Util.getFileCharContent(file, encoding); |
| ICompilationUnit unit = new CompilationUnit(contents, path, encoding); |
| sourceUnits.add(unit); |
| } |
| } |
| }) |
| |
| Map<ICompilationUnit, CompilationUnitDeclaration> outputMap = Maps. |
| newHashMapWithExpectedSize(sourceUnits.size()) |
| List<String> jars = Lists.newArrayList(); |
| if (bootClasspath != null) { |
| jars.addAll(bootClasspath) |
| } |
| if (classpath != null) { |
| for (File jar : classpath) { |
| jars.add(jar.getPath()); |
| } |
| } |
| |
| CompilerOptions options = EcjParser.createCompilerOptions(); |
| options.docCommentSupport = true; // So I can find @hide |
| |
| // Note: We can *not* set options.ignoreMethodBodies=true because it disables |
| // type attribution! |
| |
| def level = getLanguageLevel(sourceCompatibility) |
| options.sourceLevel = level |
| options.complianceLevel = options.sourceLevel |
| // We don't generate code, but just in case the parser consults this flag |
| // and makes sure that it's not greater than the source level: |
| options.targetJDK = options.sourceLevel |
| options.originalComplianceLevel = options.sourceLevel; |
| options.originalSourceLevel = options.sourceLevel; |
| options.inlineJsrBytecode = true; // >= 1.5 |
| |
| EcjParser.parse(options, sourceUnits, jars, outputMap, null); |
| |
| Collection<CompilationUnitDeclaration> parsedUnits = outputMap.values() |
| parsedUnits |
| } |
| |
| private static long getLanguageLevel(String version) { |
| if ("1.6".equals(version)) { |
| return EcjParser.getLanguageLevel(1, 6); |
| } else if ("1.7".equals(version)) { |
| return EcjParser.getLanguageLevel(1, 7); |
| } else if ("1.5") { |
| return EcjParser.getLanguageLevel(1, 5); |
| } else { |
| return EcjParser.getLanguageLevel(1, 7); |
| } |
| } |
| |
| private def addSources(List<ICompilationUnit> sourceUnits, File file) { |
| if (file.isDirectory()) { |
| def files = file.listFiles(); |
| if (files != null) { |
| for (File sub : files) { |
| addSources(sourceUnits, sub); |
| } |
| } |
| } else if (file.getPath().endsWith(DOT_JAVA) && file.isFile()) { |
| char[] contents = Util.getFileCharContent(file, encoding); |
| ICompilationUnit unit = new CompilationUnit(contents, file.getPath(), encoding); |
| sourceUnits.add(unit); |
| } |
| } |
| } |