| // 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.buildjar; |
| |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| |
| import com.google.common.base.Splitter; |
| import com.google.common.collect.Iterables; |
| import com.google.devtools.build.buildjar.javac.JavacOptions; |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.nio.file.Paths; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Deque; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Parses options that the {@link JavaLibraryBuildRequest} needs to construct a build request from |
| * command-line flags and options files and provides them via getters. |
| */ |
| public final class OptionsParser { |
| private final List<String> javacOpts = new ArrayList<>(); |
| |
| private final Set<String> directJars = new LinkedHashSet<>(); |
| |
| private String strictJavaDeps; |
| private String fixDepsTool; |
| |
| private String outputDepsProtoFile; |
| private final Set<String> depsArtifacts = new LinkedHashSet<>(); |
| |
| /** This modes controls how a probablistic Java classpath reduction is used. */ |
| public enum ReduceClasspathMode { |
| BAZEL_REDUCED, |
| BAZEL_FALLBACK, |
| JAVABUILDER_REDUCED, |
| NONE |
| } |
| |
| /** |
| * The flag --reduce_classpath_mode can be passed to JavaBuilder to request a compilation with |
| * reduced classpath, computed from the compilations direct dependencies plus what was actually |
| * required to build those. If this compilation fails with a specific error code, then a fallback |
| * is done using the full (transitive) classpath. |
| */ |
| private ReduceClasspathMode reduceClasspathMode = ReduceClasspathMode.NONE; |
| |
| private int fullClasspathLength = -1; |
| private int reducedClasspathLength = -1; |
| |
| private String generatedSourcesOutputJar; |
| private String manifestProtoPath; |
| |
| private final List<String> sourceFiles = new ArrayList<>(); |
| private final List<String> sourceJars = new ArrayList<>(); |
| |
| private final List<String> classPath = new ArrayList<>(); |
| private final List<String> sourcePath = new ArrayList<>(); |
| private final List<String> bootClassPath = new ArrayList<>(); |
| private String system; |
| |
| private final List<String> processorPath = new ArrayList<>(); |
| private final List<String> processorNames = new ArrayList<>(); |
| private final List<String> builtinProcessorNames = new ArrayList<>(); |
| |
| private String outputJar; |
| @Nullable private String nativeHeaderOutput; |
| |
| private final Map<String, List<String>> postProcessors = new LinkedHashMap<>(); |
| |
| private boolean compressJar; |
| |
| private String targetLabel; |
| private String injectingRuleKind; |
| |
| @Nullable private String profile; |
| |
| @Nullable private final JavacOptions normalizer; |
| |
| /** |
| * Constructs an {@code OptionsParser} from a list of command args. Sets the same JavacRunner for |
| * both compilation and annotation processing. |
| * |
| * @param args the list of command line args. |
| * @throws InvalidCommandLineException on any command line error. |
| */ |
| public OptionsParser(List<String> args) throws InvalidCommandLineException, IOException { |
| this(args, null); |
| } |
| |
| /** |
| * Constructs an {@code OptionsParser} from a list of command args. Sets the same JavacRunner for |
| * both compilation and annotation processing. |
| * |
| * @param args the list of command line args. |
| * @throws InvalidCommandLineException on any command line error. |
| */ |
| public OptionsParser(List<String> args, @Nullable JavacOptions normalizer) |
| throws InvalidCommandLineException, IOException { |
| this.normalizer = normalizer; |
| processCommandlineArgs(expandArguments(args)); |
| } |
| |
| /** |
| * Processes the command line arguments. |
| * |
| * @throws InvalidCommandLineException on an invalid option being passed. |
| */ |
| private void processCommandlineArgs(Deque<String> argQueue) throws InvalidCommandLineException { |
| for (String arg = argQueue.pollFirst(); arg != null; arg = argQueue.pollFirst()) { |
| switch (arg) { |
| case "--javacopts": |
| readJavacopts(javacOpts, argQueue); |
| sourcePathFromJavacOpts(); |
| break; |
| case "--direct_dependencies": |
| collectFlagArguments(directJars, argQueue, "--"); |
| break; |
| case "--strict_java_deps": |
| strictJavaDeps = getArgument(argQueue, arg); |
| break; |
| case "--experimental_fix_deps_tool": |
| fixDepsTool = getArgument(argQueue, arg); |
| break; |
| case "--output_deps_proto": |
| outputDepsProtoFile = getArgument(argQueue, arg); |
| break; |
| case "--deps_artifacts": |
| collectFlagArguments(depsArtifacts, argQueue, "--"); |
| break; |
| case "--reduce_classpath": |
| reduceClasspathMode = ReduceClasspathMode.JAVABUILDER_REDUCED; |
| break; |
| case "--reduce_classpath_mode": |
| reduceClasspathMode = ReduceClasspathMode.valueOf(getArgument(argQueue, arg)); |
| break; |
| case "--full_classpath_length": |
| fullClasspathLength = Integer.parseInt(getArgument(argQueue, arg)); |
| break; |
| case "--reduced_classpath_length": |
| reducedClasspathLength = Integer.parseInt(getArgument(argQueue, arg)); |
| break; |
| case "--generated_sources_output": |
| generatedSourcesOutputJar = getArgument(argQueue, arg); |
| break; |
| case "--output_manifest_proto": |
| manifestProtoPath = getArgument(argQueue, arg); |
| break; |
| case "--sources": |
| collectFlagArguments(sourceFiles, argQueue, "-"); |
| break; |
| case "--source_jars": |
| collectFlagArguments(sourceJars, argQueue, "-"); |
| break; |
| case "--classpath": |
| collectFlagArguments(classPath, argQueue, "-"); |
| break; |
| case "--sourcepath": |
| // TODO(#970): Consider whether we want to use --sourcepath for resolving of #970. |
| collectFlagArguments(sourcePath, argQueue, "-"); |
| break; |
| case "--bootclasspath": |
| collectFlagArguments(bootClassPath, argQueue, "-"); |
| break; |
| case "--system": |
| system = getArgument(argQueue, arg); |
| break; |
| case "--processorpath": |
| collectFlagArguments(processorPath, argQueue, "-"); |
| break; |
| case "--processors": |
| collectProcessorArguments(processorNames, argQueue, "-"); |
| break; |
| case "--builtin_processors": |
| collectProcessorArguments(builtinProcessorNames, argQueue, "-"); |
| break; |
| case "--output": |
| outputJar = getArgument(argQueue, arg); |
| break; |
| case "--native_header_output": |
| nativeHeaderOutput = getArgument(argQueue, arg); |
| break; |
| case "--post_processor": |
| addExternalPostProcessor(argQueue, arg); |
| break; |
| case "--compress_jar": |
| compressJar = true; |
| break; |
| case "--target_label": |
| targetLabel = getArgument(argQueue, arg); |
| break; |
| case "--injecting_rule_kind": |
| injectingRuleKind = getArgument(argQueue, arg); |
| break; |
| case "--profile": |
| profile = getArgument(argQueue, arg); |
| break; |
| default: |
| throw new InvalidCommandLineException("unknown option : '" + arg + "'"); |
| } |
| } |
| } |
| |
| private void sourcePathFromJavacOpts() { |
| Iterator<String> it = javacOpts.iterator(); |
| while (it.hasNext()) { |
| String curr = it.next(); |
| if (curr.equals("-sourcepath") && it.hasNext()) { |
| it.remove(); |
| Iterables.addAll(sourcePath, CLASSPATH_SPLITTER.split(it.next())); |
| it.remove(); |
| } |
| } |
| } |
| |
| /** |
| * Pre-processes an argument list, expanding options @filename to read in the content of the file |
| * and add it to the list of arguments. |
| * |
| * @param args the List of arguments to pre-process. |
| * @return the List of pre-processed arguments. |
| * @throws java.io.IOException if one of the files containing options cannot be read. |
| */ |
| private static Deque<String> expandArguments(List<String> args) throws IOException { |
| Deque<String> expanded = new ArrayDeque<>(args.size()); |
| for (String arg : args) { |
| expandArgument(expanded, arg); |
| } |
| return expanded; |
| } |
| |
| /** |
| * Expands a single argument, expanding options @filename to read in the content of the file and |
| * add it to the list of processed arguments. The @ itself can be escaped with @@. |
| * |
| * @param expanded the list of processed arguments. |
| * @param arg the argument to pre-process. |
| * @throws java.io.IOException if one of the files containing options cannot be read. |
| */ |
| private static void expandArgument(Deque<String> expanded, String arg) throws IOException { |
| if (arg.startsWith("@@")) { |
| expanded.add(arg.substring(1)); |
| } else if (arg.startsWith("@")) { |
| for (String line : Files.readAllLines(Paths.get(arg.substring(1)), UTF_8)) { |
| if (line.length() > 0) { |
| expandArgument(expanded, line); |
| } |
| } |
| } else { |
| expanded.add(arg); |
| } |
| } |
| |
| /** |
| * Collects the arguments for a command line flag until it finds a flag that starts with the |
| * terminatorPrefix. |
| * |
| * @param output where to put the collected flag arguments. |
| * @param args |
| * @param terminatorPrefix the terminator prefix to stop collecting of argument flags. |
| */ |
| private static void collectFlagArguments( |
| Collection<String> output, Deque<String> args, String terminatorPrefix) { |
| for (String arg = args.pollFirst(); arg != null; arg = args.pollFirst()) { |
| if (arg.startsWith(terminatorPrefix)) { |
| args.addFirst(arg); |
| break; |
| } |
| output.add(arg); |
| } |
| } |
| |
| /** |
| * Returns a list of javacopts. Reads options until a terminating {@code "--"} is reached, to |
| * support parsing javacopts that start with {@code --} (e.g. --release). |
| */ |
| private static void readJavacopts(List<String> javacopts, Deque<String> argumentDeque) { |
| while (!argumentDeque.isEmpty()) { |
| String arg = argumentDeque.pollFirst(); |
| if (arg.equals("--")) { |
| return; |
| } |
| javacopts.add(arg); |
| } |
| throw new IllegalArgumentException("javacopts should be terminated by `--`"); |
| } |
| |
| private static final Splitter CLASSPATH_SPLITTER = |
| Splitter.on(File.pathSeparatorChar).trimResults().omitEmptyStrings(); |
| |
| /** |
| * Collects the arguments for the --processors command line flag until it finds a flag that starts |
| * with the terminatorPrefix. |
| * |
| * @param output where to put the collected flag arguments. |
| * @param args |
| * @param terminatorPrefix the terminator prefix to stop collecting of argument flags. |
| */ |
| private static void collectProcessorArguments( |
| List<String> output, Deque<String> args, String terminatorPrefix) |
| throws InvalidCommandLineException { |
| for (String arg = args.pollFirst(); arg != null; arg = args.pollFirst()) { |
| if (arg.startsWith(terminatorPrefix)) { |
| args.addFirst(arg); |
| break; |
| } |
| if (arg.contains(",")) { |
| throw new InvalidCommandLineException("processor argument may not contain commas: " + arg); |
| } |
| output.add(arg); |
| } |
| } |
| |
| private static String getArgument(Deque<String> args, String arg) |
| throws InvalidCommandLineException { |
| try { |
| return args.remove(); |
| } catch (NoSuchElementException e) { |
| throw new InvalidCommandLineException(arg + ": missing argument", e); |
| } |
| } |
| |
| private void addExternalPostProcessor(Deque<String> args, String arg) |
| throws InvalidCommandLineException { |
| String processorName = getArgument(args, arg); |
| List<String> arguments = new ArrayList<>(); |
| collectFlagArguments(arguments, args, "--"); |
| postProcessors.put(processorName, arguments); |
| } |
| |
| public List<String> getJavacOpts() { |
| return normalizer != null ? normalizer.normalize(javacOpts) : javacOpts; |
| } |
| |
| public Set<String> directJars() { |
| return directJars; |
| } |
| |
| public String getStrictJavaDeps() { |
| return strictJavaDeps; |
| } |
| |
| public String getFixDepsTool() { |
| return fixDepsTool; |
| } |
| |
| public String getOutputDepsProtoFile() { |
| return outputDepsProtoFile; |
| } |
| |
| public Set<String> getDepsArtifacts() { |
| return depsArtifacts; |
| } |
| |
| public ReduceClasspathMode reduceClasspathMode() { |
| return reduceClasspathMode; |
| } |
| |
| public int fullClasspathLength() { |
| return fullClasspathLength; |
| } |
| |
| public int reducedClasspathLength() { |
| return reducedClasspathLength; |
| } |
| |
| public String getGeneratedSourcesOutputJar() { |
| return generatedSourcesOutputJar; |
| } |
| |
| public String getManifestProtoPath() { |
| return manifestProtoPath; |
| } |
| |
| public List<String> getSourceFiles() { |
| return sourceFiles; |
| } |
| |
| public List<String> getSourceJars() { |
| return sourceJars; |
| } |
| |
| public List<String> getClassPath() { |
| return classPath; |
| } |
| |
| public List<String> getBootClassPath() { |
| return bootClassPath; |
| } |
| |
| public String getSystem() { |
| return system; |
| } |
| |
| public List<String> getSourcePath() { |
| return sourcePath; |
| } |
| |
| public List<String> getProcessorPath() { |
| return processorPath; |
| } |
| |
| public List<String> getProcessorNames() { |
| return processorNames; |
| } |
| |
| public List<String> getBuiltinProcessorNames() { |
| return builtinProcessorNames; |
| } |
| |
| public String getOutputJar() { |
| return outputJar; |
| } |
| |
| @Nullable |
| public String getNativeHeaderOutput() { |
| return nativeHeaderOutput; |
| } |
| |
| public Map<String, List<String>> getPostProcessors() { |
| return postProcessors; |
| } |
| |
| public boolean compressJar() { |
| return compressJar; |
| } |
| |
| public String getTargetLabel() { |
| return targetLabel; |
| } |
| |
| public String getInjectingRuleKind() { |
| return injectingRuleKind; |
| } |
| |
| public String getProfile() { |
| return profile; |
| } |
| } |