diff options
author | 2016-09-02 23:01:04 +0000 | |
---|---|---|
committer | 2016-09-06 15:38:13 +0000 | |
commit | 6b4b269adf1ae367a7df006d6c86b4ff6a592678 (patch) | |
tree | 5ba2216a451fd332d9ce5073be200ed8954419fe /src/tools/android | |
parent | 7382c11818cff73851ae484dfc201126db06a1b5 (diff) |
Adds a jar filter to the IntelliJ IDE aspect.
Any java rule that mixes generated and non-generated sources
will produce a filtered jar containing only the generated
output.
For a java rule with only "normal" sources or only generated
source files, no filtered jar is produced.
This will allow the IDE to resolve those generated sources.
RELNOTES:None
--
MOS_MIGRATED_REVID=132113568
Diffstat (limited to 'src/tools/android')
3 files changed, 248 insertions, 6 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/ideinfo/BUILD b/src/tools/android/java/com/google/devtools/build/android/ideinfo/BUILD index c5c27e4a36..719aca67a1 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ideinfo/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/ideinfo/BUILD @@ -2,13 +2,44 @@ filegroup( name = "embedded_tools", srcs = [ "BUILD.tools", - "classes_deploy.jar", + "jar_filter_classes_deploy.jar", + "package_parser_classes_deploy.jar", ], visibility = ["//src:__pkg__"], ) java_binary( - name = "classes", + name = "jar_filter_classes", + main_class = "does.not.exist", + runtime_deps = [":jar_filter_lib"], +) + +java_binary( + name = "JarFilter", + main_class = "com.google.devtools.build.android.ideinfo.JarFilter", + visibility = ["//visibility:public"], + runtime_deps = [":jar_filter_lib"], +) + +java_library( + name = "jar_filter_lib", + srcs = ["JarFilter.java"], + visibility = [ + "//devtools/blaze/integration:__pkg__", + "//src/test/java/com/google/devtools/build/android/ideinfo:__pkg__", + ], + deps = [ + "//src/main/java/com/google/devtools/common/options", + "//src/main/protobuf:package_manifest_java_proto", + "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib", + "//third_party:guava", + "//third_party:jsr305", + "//third_party/protobuf", + ], +) + +java_binary( + name = "package_parser_classes", main_class = "does.not.exist", runtime_deps = [":package_parser_lib"], ) @@ -22,7 +53,12 @@ java_binary( java_library( name = "package_parser_lib", - srcs = glob(["*.java"]), + srcs = [ + "ArtifactLocationConverter.java", + "ArtifactLocationListConverter.java", + "PackageParser.java", + "PackageParserIoProvider.java", + ], visibility = [ "//devtools/blaze/integration:__pkg__", "//src/test/java/com/google/devtools/build/android/ideinfo:__pkg__", diff --git a/src/tools/android/java/com/google/devtools/build/android/ideinfo/BUILD.tools b/src/tools/android/java/com/google/devtools/build/android/ideinfo/BUILD.tools index efa64fb710..e6515db7e7 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ideinfo/BUILD.tools +++ b/src/tools/android/java/com/google/devtools/build/android/ideinfo/BUILD.tools @@ -1,12 +1,23 @@ package(default_visibility = ["//visibility:public"]) java_import( - name = "classes", - jars = [":classes_deploy.jar"], + name = "jar_filter_classes", + jars = [":jar_filter_classes_deploy.jar"], +) + +java_binary( + name = "JarFilter", + main_class = "com.google.devtools.build.android.ideinfo.JarFilter", + runtime_deps = [":jar_filter_classes"], +) + +java_import( + name = "package_parser_classes", + jars = [":package_parser_classes_deploy.jar"], ) java_binary( name = "PackageParser", main_class = "com.google.devtools.build.android.ideinfo.PackageParser", - runtime_deps = [":classes"], + runtime_deps = [":package_parser_classes"], ) diff --git a/src/tools/android/java/com/google/devtools/build/android/ideinfo/JarFilter.java b/src/tools/android/java/com/google/devtools/build/android/ideinfo/JarFilter.java new file mode 100644 index 0000000000..2a373565cf --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/ideinfo/JarFilter.java @@ -0,0 +1,195 @@ +// Copyright 2015 The Bazel Authors. All rights reserved. +// +// 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.google.devtools.build.android.ideinfo; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.google.common.io.Files; +import com.google.devtools.build.android.Converters.PathConverter; +import com.google.devtools.build.android.Converters.PathListConverter; +import com.google.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.ArtifactLocation; +import com.google.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.JavaSourcePackage; +import com.google.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.PackageManifest; +import com.google.devtools.common.options.Option; +import com.google.devtools.common.options.OptionsBase; +import com.google.devtools.common.options.OptionsParser; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Filters a jar, keeping only the classes that are contained + * in the supplied package manifest. + */ +public final class JarFilter { + + /** The options for a {@JarFilter} action. */ + public static final class JarFilterOptions extends OptionsBase { + @Option(name = "jars", + defaultValue = "null", + converter = PathListConverter.class, + category = "input", + help = "A list of the paths to jars to filter for generated sources.") + public List<Path> jars; + + @Option(name = "manifest", + defaultValue = "null", + converter = PathConverter.class, + category = "input", + help = "The path to a package manifest generated only from generated sources.") + public Path manifest; + + @Option(name = "output", + defaultValue = "null", + converter = PathConverter.class, + category = "output", + help = "The path to the jar to output.") + public Path output; + } + + private static final Logger logger = Logger.getLogger(JarFilter.class.getName()); + + public static void main(String[] args) throws Exception { + JarFilterOptions options = parseArgs(args); + Preconditions.checkNotNull(options.jars); + Preconditions.checkNotNull(options.manifest); + Preconditions.checkNotNull(options.output); + + try { + List<String> archiveFileNamePrefixes = parsePackageManifest(options.manifest); + filterJars(options.jars, options.output, archiveFileNamePrefixes); + } catch (Throwable e) { + logger.log(Level.SEVERE, "Error parsing package strings", e); + System.exit(1); + } + System.exit(0); + } + + @VisibleForTesting + static JarFilterOptions parseArgs(String[] args) { + args = parseParamFileIfUsed(args); + OptionsParser optionsParser = OptionsParser.newOptionsParser(JarFilterOptions.class); + optionsParser.parseAndExitUponError(args); + return optionsParser.getOptions(JarFilterOptions.class); + } + + private static String[] parseParamFileIfUsed(@Nonnull String[] args) { + if (args.length != 1 || !args[0].startsWith("@")) { + return args; + } + File paramFile = new File(args[0].substring(1)); + try { + return Files.readLines(paramFile, StandardCharsets.UTF_8).toArray(new String[0]); + } catch (IOException e) { + throw new RuntimeException("Error parsing param file: " + args[0], e); + } + } + + private static void filterJars(List<Path> jars, Path output, + List<String> archiveFileNamePrefixes) throws IOException { + final int bufferSize = 8 * 1024; + byte[] buffer = new byte[bufferSize]; + + try (ZipOutputStream outputStream = new ZipOutputStream( + new FileOutputStream(output.toFile()))) { + for (Path jar : jars) { + try (ZipFile sourceZipFile = new ZipFile(jar.toFile())) { + Enumeration<? extends ZipEntry> entries = sourceZipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (!shouldKeep(archiveFileNamePrefixes, entry.getName())) { + continue; + } + + ZipEntry newEntry = new ZipEntry(entry.getName()); + outputStream.putNextEntry(newEntry); + try (InputStream inputStream = sourceZipFile.getInputStream(entry)) { + int len; + while ((len = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, len); + } + } + } + } + } + } + } + + @VisibleForTesting + static boolean shouldKeep(List<String> archiveFileNamePrefixes, String name) { + for (String archiveFileNamePrefix : archiveFileNamePrefixes) { + if (name.startsWith(archiveFileNamePrefix) + && name.length() > archiveFileNamePrefix.length()) { + char c = name.charAt(archiveFileNamePrefix.length()); + if (c == '.' || c == '$') { + return true; + } + } + } + return false; + } + + @Nullable + private static List<String> parsePackageManifest(Path manifest) throws IOException { + try (InputStream inputStream = java.nio.file.Files.newInputStream(manifest)) { + PackageManifest packageManifest = PackageManifest.parseFrom(inputStream); + return parsePackageManifest(packageManifest); + } + } + + /** + * Reads the package manifest and computes a list of the expected jar archive + * file names. + * + * Eg.: + * file java/com/google/foo/Foo.java, package com.google.foo -> + * com/google/foo/Foo + */ + @VisibleForTesting + static List<String> parsePackageManifest(PackageManifest packageManifest) { + List<String> result = Lists.newArrayList(); + for (JavaSourcePackage javaSourcePackage : packageManifest.getSourcesList()) { + ArtifactLocation artifactLocation = javaSourcePackage.getArtifactLocation(); + String packageString = javaSourcePackage.getPackageString(); + String archiveFileNamePrefix = getArchiveFileNamePrefix(artifactLocation, packageString); + result.add(archiveFileNamePrefix); + } + return result; + } + + @Nullable + private static String getArchiveFileNamePrefix(ArtifactLocation artifactLocation, + String packageString) { + String relativePath = artifactLocation.getRelativePath(); + int lastSlashIndex = relativePath.lastIndexOf('/'); + String fileName = lastSlashIndex != -1 + ? relativePath.substring(lastSlashIndex + 1) : relativePath; + String className = fileName.substring(0, fileName.length() - ".java".length()); + return packageString.replace('.', '/') + '/' + className; + } +} |