diff options
Diffstat (limited to 'src/tools/android/java')
6 files changed, 335 insertions, 0 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD b/src/tools/android/java/com/google/devtools/build/android/BUILD index 74c12552bf..9abd062082 100644 --- a/src/tools/android/java/com/google/devtools/build/android/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/BUILD @@ -37,10 +37,12 @@ java_library( srcs = glob(["*.java"]), deps = [ "//src/main/java/com/google/devtools/common/options", + "//src/main/protobuf:package_manifest_proto", "//third_party:android_common", "//third_party:apache_commons_compress", "//third_party:asm", "//third_party:guava", "//third_party:jsr305", + "//third_party:protobuf", ], ) diff --git a/src/tools/android/java/com/google/devtools/build/android/Converters.java b/src/tools/android/java/com/google/devtools/build/android/Converters.java index 8a10c52285..f69aac162d 100644 --- a/src/tools/android/java/com/google/devtools/build/android/Converters.java +++ b/src/tools/android/java/com/google/devtools/build/android/Converters.java @@ -26,6 +26,8 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -154,4 +156,30 @@ public final class Converters { super(VariantConfiguration.Type.class, "variant configuration type"); } } + + /** + * Validating converter for a list of Paths. + * A Path is considered valid if it resolves to a file. + */ + public static class PathListConverter implements Converter<List<Path>> { + + final PathConverter baseConverter = new PathConverter(); + + @Override + public List<Path> convert(String input) throws OptionsParsingException { + List<Path> list = new ArrayList<>(); + for (String piece : input.split(":")) { + if (!piece.isEmpty()) { + list.add(baseConverter.convert(piece)); + } + } + return Collections.unmodifiableList(list); + } + + @Override + public String getTypeDescription() { + return "a colon-separated list of paths"; + } + } + } 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 new file mode 100644 index 0000000000..b520a81b3a --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/ideinfo/BUILD @@ -0,0 +1,38 @@ +filegroup( + name = "embedded_tools", + srcs = [ + "BUILD.tools", + "classes_deploy.jar", + ], + visibility = ["//src:__pkg__"], +) + +java_binary( + name = "classes", + main_class = "does.not.exist", + runtime_deps = [":package_parser_lib"], +) + +java_binary( + name = "PackageParser", + main_class = "com.google.devtools.build.android.ideinfo.PackageParser", + visibility = ["//visibility:public"], + runtime_deps = [":package_parser_lib"], +) + +java_library( + name = "package_parser_lib", + srcs = glob(["*.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_proto", + "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib", + "//third_party:guava", + "//third_party:jsr305", + "//third_party:protobuf", + ], +) 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 new file mode 100644 index 0000000000..efa64fb710 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/ideinfo/BUILD.tools @@ -0,0 +1,12 @@ +package(default_visibility = ["//visibility:public"]) + +java_import( + name = "classes", + jars = [":classes_deploy.jar"], +) + +java_binary( + name = "PackageParser", + main_class = "com.google.devtools.build.android.ideinfo.PackageParser", + runtime_deps = [":classes"], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/ideinfo/PackageParser.java b/src/tools/android/java/com/google/devtools/build/android/ideinfo/PackageParser.java new file mode 100644 index 0000000000..5e84d3c20e --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/ideinfo/PackageParser.java @@ -0,0 +1,206 @@ +// 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.Maps; +import com.google.common.io.Files; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +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.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.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Parses the package string from each of the source .java files + */ +public class PackageParser { + + /** The options for a {@PackageParser} action. */ + public static final class PackageParserOptions extends OptionsBase { + @Option(name = "sources_absolute_paths", + defaultValue = "null", + converter = PathListConverter.class, + category = "input", + help = "The absolute paths of the java source files. The expected format is a " + + "colon-separated list.") + public List<Path> sourcesAbsolutePaths; + + @Option(name = "sources_execution_paths", + defaultValue = "null", + converter = PathListConverter.class, + category = "input", + help = "The execution paths of the java source files. The expected format is a " + + "colon-separated list.") + public List<Path> sourcesExecutionPaths; + + @Option(name = "output_manifest", + defaultValue = "null", + converter = PathConverter.class, + category = "output", + help = "The path to the manifest file this parser writes to.") + public Path outputManifest; + } + + private static final Logger logger = Logger.getLogger(PackageParser.class.getName()); + + private static final Pattern JAVA_PACKAGE_PATTERN = + Pattern.compile("^\\s*package\\s+([\\w\\.]+);"); + + public static void main(String[] args) throws Exception { + PackageParserOptions options = parseArgs(args); + Preconditions.checkNotNull(options.sourcesAbsolutePaths); + Preconditions.checkNotNull(options.sourcesExecutionPaths); + Preconditions.checkState( + options.sourcesAbsolutePaths.size() == options.sourcesExecutionPaths.size()); + Preconditions.checkNotNull(options.outputManifest); + + try { + PackageParser parser = new PackageParser(PackageParserIoProvider.INSTANCE); + Map<Path, String> outputMap = parser.parsePackageStrings(options.sourcesAbsolutePaths, + options.sourcesExecutionPaths); + parser.writeManifest(outputMap, options.outputManifest); + } catch (Throwable e) { + logger.log(Level.SEVERE, "Error parsing package strings", e); + System.exit(1); + } + System.exit(0); + } + + @VisibleForTesting + public static PackageParserOptions parseArgs(String[] args) { + args = parseParamFileIfUsed(args); + OptionsParser optionsParser = OptionsParser.newOptionsParser(PackageParserOptions.class); + optionsParser.parseAndExitUponError(args); + return optionsParser.getOptions(PackageParserOptions.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 final PackageParserIoProvider ioProvider; + + @VisibleForTesting + public PackageParser(@Nonnull PackageParserIoProvider ioProvider) { + this.ioProvider = ioProvider; + } + + @VisibleForTesting + public void writeManifest(@Nonnull Map<Path, String> sourceToPackageMap, Path outputFile) + throws IOException { + if (sourceToPackageMap.isEmpty()) { + return; + } + PackageManifest.Builder builder = PackageManifest.newBuilder(); + for (Entry<Path, String> entry : sourceToPackageMap.entrySet()) { + builder.addSources(JavaSourcePackage.newBuilder() + .setAbsolutePath(entry.getKey().toAbsolutePath().toString()) + .setPackageString(entry.getValue())); + } + + try { + ioProvider.writeProto(builder.build(), outputFile); + } catch (IOException e) { + logger.log(Level.SEVERE, "Error writing package manifest", e); + throw e; + } + } + + @Nonnull + @VisibleForTesting + public Map<Path, String> parsePackageStrings(@Nonnull List<Path> absolutePaths, + @Nonnull List<Path> executionPaths) throws Exception { + + ListeningExecutorService executorService = MoreExecutors.listeningDecorator( + Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())); + + Map<Path, ListenableFuture<String>> futures = Maps.newHashMap(); + for (int i = 0; i < absolutePaths.size(); i++) { + final Path source = executionPaths.get(i); + futures.put(absolutePaths.get(i), executorService.submit(new Callable<String>() { + @Override + public String call() throws Exception { + return getDeclaredPackageOfJavaFile(source); + } + })); + } + Map<Path, String> map = Maps.newHashMap(); + for (Entry<Path, ListenableFuture<String>> entry : futures.entrySet()) { + String value = entry.getValue().get(); + if (value != null) { + map.put(entry.getKey(), value); + } + } + return map; + } + + @Nullable + private String getDeclaredPackageOfJavaFile(@Nonnull Path source) { + try (BufferedReader reader = ioProvider.getReader(source)) { + return parseDeclaredPackage(reader); + + } catch (IOException e) { + logger.log(Level.WARNING, "Error parsing package string from java source: " + source, e); + return null; + } + } + + @VisibleForTesting + @Nullable + public static String parseDeclaredPackage(@Nonnull BufferedReader reader) throws IOException { + String line; + while ((line = reader.readLine()) != null) { + Matcher packageMatch = JAVA_PACKAGE_PATTERN.matcher(line); + if (packageMatch.find()) { + return packageMatch.group(1); + } + } + return null; + } + +} diff --git a/src/tools/android/java/com/google/devtools/build/android/ideinfo/PackageParserIoProvider.java b/src/tools/android/java/com/google/devtools/build/android/ideinfo/PackageParserIoProvider.java new file mode 100644 index 0000000000..be555c8d0e --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/ideinfo/PackageParserIoProvider.java @@ -0,0 +1,49 @@ +// 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.protobuf.MessageLite; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import javax.annotation.Nonnull; + +/** + * Provides a BufferedReader for the source java files, + * and a writer for the output proto + */ +@VisibleForTesting +public class PackageParserIoProvider { + + public static final PackageParserIoProvider INSTANCE = new PackageParserIoProvider(); + + public void writeProto(@Nonnull MessageLite message, @Nonnull Path file) throws IOException { + try (OutputStream out = Files.newOutputStream(file)) { + message.writeTo(out); + } + } + + @Nonnull + public BufferedReader getReader(Path file) throws IOException { + return Files.newBufferedReader(file, StandardCharsets.UTF_8); + } + +} |