diff options
author | Liam Miller-Cushon <cushon@google.com> | 2016-02-04 02:38:03 +0000 |
---|---|---|
committer | David Chen <dzc@google.com> | 2016-02-04 18:11:18 +0000 |
commit | 28bb362b52dfa379e10115d766658f68a125deeb (patch) | |
tree | c52a7f1c23359947783e9f5b7b95d869b8969aee | |
parent | 092dee3fad52c2f9c2b17447c58f31dba5be438a (diff) |
Turbine options parser
--
MOS_MIGRATED_REVID=113806383
3 files changed, 661 insertions, 0 deletions
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/TurbineOptions.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/TurbineOptions.java new file mode 100644 index 0000000000..e7434c0e2d --- /dev/null +++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/TurbineOptions.java @@ -0,0 +1,291 @@ +// Copyright 2016 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.java.turbine; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import javax.annotation.Nullable; + +/** Header compilation options. */ +public class TurbineOptions { + + private final String output; + private final ImmutableList<String> classPath; + private final ImmutableList<String> bootClassPath; + private final ImmutableList<String> sources; + private final ImmutableList<String> processorPath; + private final ImmutableList<String> processors; + private final String tempDir; + private final ImmutableList<String> sourceJars; + private final Optional<String> outputDeps; + private final ImmutableMap<String, String> directJarsToTargets; + private final ImmutableMap<String, String> indirectJarsToTargets; + private final String targetLabel; + private final ImmutableList<String> depsArtifacts; + private final String strictDepsMode; + private final String ruleKind; + private final ImmutableList<String> javacOpts; + + private TurbineOptions( + String output, + ImmutableList<String> classPath, + ImmutableList<String> bootClassPath, + ImmutableList<String> sources, + ImmutableList<String> processorPath, + ImmutableList<String> processors, + String tempDir, + ImmutableList<String> sourceJars, + @Nullable String outputDeps, + ImmutableMap<String, String> directJarsToTargets, + ImmutableMap<String, String> indirectJarsToTargets, + String targetLabel, + ImmutableList<String> depsArtifacts, + String strictDepsMode, + String ruleKind, + ImmutableList<String> javacOpts) { + this.output = checkNotNull(output, "output must not be null"); + this.classPath = checkNotNull(classPath, "classPath must not be null"); + this.bootClassPath = checkNotNull(bootClassPath, "bootClassPath must not be null"); + this.sources = checkNotNull(sources, "sources must not be null"); + this.processorPath = checkNotNull(processorPath, "processorPath must not be null"); + this.processors = checkNotNull(processors, "processors must not be null"); + this.tempDir = checkNotNull(tempDir, "tempDir must not be null"); + this.sourceJars = checkNotNull(sourceJars, "sourceJars must not be null"); + this.outputDeps = Optional.fromNullable(outputDeps); + this.directJarsToTargets = + checkNotNull(directJarsToTargets, "directJarsToTargets must not be null"); + this.indirectJarsToTargets = + checkNotNull(indirectJarsToTargets, "indirectJarsToTargets must not be null"); + this.targetLabel = checkNotNull(targetLabel, "targetLabel must not be null"); + this.depsArtifacts = checkNotNull(depsArtifacts, "depsArtifacts must not be null"); + this.strictDepsMode = checkNotNull(strictDepsMode, "strictDepsMode must not be null"); + this.ruleKind = checkNotNull(ruleKind, "ruleKind must not be null"); + this.javacOpts = checkNotNull(javacOpts, "javacOpts must not be null"); + } + + /** Paths to the Java source files to compile. */ + public ImmutableList<String> sources() { + return sources; + } + + /** Paths to classpath artifacts. */ + public ImmutableList<String> classPath() { + return classPath; + } + + /** Paths to compilation bootclasspath artifacts. */ + public ImmutableList<String> bootClassPath() { + return bootClassPath; + } + + /** The output jar. */ + public String outputFile() { + return output; + } + + /** A temporary directory, e.g. for extracting sourcejar entries to before compilation. */ + public String tempDir() { + return tempDir; + } + + /** Paths to annotation processor artifacts. */ + public ImmutableList<String> processorPath() { + return processorPath; + } + + /** Annotation processor class names. */ + public ImmutableList<String> processors() { + return processors; + } + + /** Source jars for compilation. */ + public ImmutableList<String> sourceJars() { + return sourceJars; + } + + /** Output jdeps file. */ + public Optional<String> outputDeps() { + return outputDeps; + } + + /** The mapping from the path to a direct dependency to its build label. */ + public ImmutableMap<String, String> directJarsToTargets() { + return directJarsToTargets; + } + + /** The mapping from the path to an indirect dependency to its build label. */ + public ImmutableMap<String, String> indirectJarsToTargets() { + return indirectJarsToTargets; + } + + /** The label of the target being compiled. */ + public String targetLabel() { + return targetLabel; + } + + /** The .jdeps artifacts for direct dependencies. */ + public ImmutableList<String> depsArtifacts() { + return depsArtifacts; + } + + /** + * The Strict Java Deps mode. + * + * <p>See {@link com.google.devtools.build.buildjar.javac.plugins.dependency.DependencyModule.StrictJavaDeps}. + */ + public String strictDepsMode() { + return strictDepsMode; + } + + /** The kind of the build rule being compiled (e.g. {@code java_library}). */ + public String ruleKind() { + return ruleKind; + } + + /** Additional Java compiler flags. */ + public ImmutableList<String> javacOpts() { + return javacOpts; + } + + public static Builder builder() { + return new Builder(); + } + + /** A {@link Builder} for {@link TurbineOptions}. */ + public static class Builder { + + private String output; + private final ImmutableList.Builder<String> classPath = ImmutableList.builder(); + private final ImmutableList.Builder<String> sources = ImmutableList.builder(); + private final ImmutableList.Builder<String> processorPath = ImmutableList.builder(); + private final ImmutableList.Builder<String> processors = ImmutableList.builder(); + private String tempDir; + private final ImmutableList.Builder<String> sourceJars = ImmutableList.builder(); + private final ImmutableList.Builder<String> bootClassPath = ImmutableList.builder(); + private String outputDeps; + private final ImmutableMap.Builder<String, String> directJarsToTargets = ImmutableMap.builder(); + private final ImmutableMap.Builder<String, String> indirectJarsToTargets = + ImmutableMap.builder(); + private String targetLabel; + private final ImmutableList.Builder<String> depsArtifacts = ImmutableList.builder(); + private String strictDepsMode = "OFF"; + private String ruleKind; + private final ImmutableList.Builder<String> javacOpts = ImmutableList.builder(); + + public TurbineOptions build() { + return new TurbineOptions( + output, + classPath.build(), + bootClassPath.build(), + sources.build(), + processorPath.build(), + processors.build(), + tempDir, + sourceJars.build(), + outputDeps, + directJarsToTargets.build(), + indirectJarsToTargets.build(), + targetLabel, + depsArtifacts.build(), + strictDepsMode, + ruleKind, + javacOpts.build()); + } + + public Builder setStrictJavaDeps(String strictDepsMode) { + this.strictDepsMode = strictDepsMode; + return this; + } + + public Builder setOutput(String output) { + this.output = output; + return this; + } + + public Builder addClassPathEntries(Iterable<String> classPath) { + this.classPath.addAll(classPath); + return this; + } + + public Builder addBootClassPathEntries(Iterable<String> bootClassPath) { + this.bootClassPath.addAll(bootClassPath); + return this; + } + + public Builder addSources(Iterable<String> sources) { + this.sources.addAll(sources); + return this; + } + + public Builder addProcessorPathEntries(Iterable<String> processorPath) { + this.processorPath.addAll(processorPath); + return this; + } + + public Builder setProcessors(Iterable<String> processors) { + this.processors.addAll(processors); + return this; + } + + public Builder setTempDir(String tempDir) { + this.tempDir = tempDir; + return this; + } + + public Builder setSourceJars(Iterable<String> sourceJars) { + this.sourceJars.addAll(sourceJars); + return this; + } + + public Builder setOutputDeps(String outputDeps) { + this.outputDeps = outputDeps; + return this; + } + + public Builder addDirectJarToTarget(String jar, String target) { + directJarsToTargets.put(jar, target); + return this; + } + + public Builder addIndirectJarToTarget(String jar, String target) { + indirectJarsToTargets.put(jar, target); + return this; + } + + public Builder setTargetLabel(String targetLabel) { + this.targetLabel = targetLabel; + return this; + } + + public Builder addAllDepsArtifacts(Iterable<String> depsArtifacts) { + this.depsArtifacts.addAll(depsArtifacts); + return this; + } + + public Builder setRuleKind(String ruleKind) { + this.ruleKind = ruleKind; + return this; + } + + public Builder addAllJavacOpts(Iterable<String> javacOpts) { + this.javacOpts.addAll(javacOpts); + return this; + } + } +} diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/TurbineOptionsParser.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/TurbineOptionsParser.java new file mode 100644 index 0000000000..a0e5f5a729 --- /dev/null +++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/TurbineOptionsParser.java @@ -0,0 +1,167 @@ +// Copyright 2016 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.java.turbine; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayDeque; +import java.util.Deque; + +import javax.annotation.Nullable; + +/** A command line options parser for {@link TurbineOptions}. */ +public class TurbineOptionsParser { + + /** + * Parses a list of command-line options into a {@link TurbineOptions}, expanding any + * {@code @params} files. + */ + public static TurbineOptions parse(Iterable<String> args) throws IOException { + Deque<String> argumentDeque = new ArrayDeque<>(); + expandParamsFiles(argumentDeque, args); + TurbineOptions.Builder builder = TurbineOptions.builder(); + parse(builder, argumentDeque); + return builder.build(); + } + + private static final Splitter ARG_SPLITTER = + Splitter.on(CharMatcher.BREAKING_WHITESPACE).omitEmptyStrings().trimResults(); + + /** + * Pre-processes an argument list, expanding arguments of the form {@code @filename} + * by reading the content of the file and appending whitespace-delimited options to + * {@code argumentDeque}. + */ + private static void expandParamsFiles(Deque<String> argumentDeque, Iterable<String> args) + throws IOException { + for (String arg : args) { + if (arg.isEmpty()) { + continue; + } + if (arg.startsWith("@") && !arg.startsWith("@@")) { + Path paramsPath = Paths.get(arg.substring(1)); + expandParamsFiles( + argumentDeque, ARG_SPLITTER.split(new String(Files.readAllBytes(paramsPath), UTF_8))); + } else { + argumentDeque.addLast(arg); + } + } + } + + private static void parse(TurbineOptions.Builder builder, Deque<String> argumentDeque) { + while (!argumentDeque.isEmpty()) { + String next = argumentDeque.pollFirst(); + switch (next) { + case "--output": + builder.setOutput(readOne(argumentDeque)); + break; + case "--source_jars": + builder.setSourceJars(readList(argumentDeque)); + break; + case "--temp_dir": + builder.setTempDir(readOne(argumentDeque)); + break; + case "--processors": + builder.setProcessors(readList(argumentDeque)); + break; + case "--processorpath": + builder.addProcessorPathEntries(splitClasspath(readOne(argumentDeque))); + break; + case "--classpath": + builder.addClassPathEntries(splitClasspath(readOne(argumentDeque))); + break; + case "--bootclasspath": + builder.addBootClassPathEntries(splitClasspath(readOne(argumentDeque))); + break; + case "--javacopts": + builder.addAllJavacOpts(readList(argumentDeque)); + break; + case "--sources": + builder.addSources(readList(argumentDeque)); + break; + case "--output_deps": + builder.setOutputDeps(readOne(argumentDeque)); + break; + case "--direct_dependency": + { + String jar = readOne(argumentDeque); + String target = readOne(argumentDeque); + builder.addDirectJarToTarget(jar, target); + break; + } + case "--indirect_dependency": + { + String jar = readOne(argumentDeque); + String target = readOne(argumentDeque); + builder.addIndirectJarToTarget(jar, target); + break; + } + case "--deps_artifacts": + builder.addAllDepsArtifacts(readList(argumentDeque)); + break; + case "--target_label": + builder.setTargetLabel(readOne(argumentDeque)); + break; + case "--strict_java_deps": + builder.setStrictJavaDeps(readOne(argumentDeque)); + break; + case "--rule_kind": + builder.setRuleKind(readOne(argumentDeque)); + break; + default: + if (next.isEmpty() && !argumentDeque.isEmpty()) { + throw new IllegalArgumentException("unknown option: " + next); + } + } + } + } + + /** Returns the value of an option, or {@code null}. */ + @Nullable + private static String readOne(Deque<String> argumentDeque) { + if (argumentDeque.isEmpty() || argumentDeque.peekFirst().startsWith("-")) { + return null; + } + return argumentDeque.pollFirst(); + } + + /** Returns a list of option values. */ + private static ImmutableList<String> readList(Deque<String> argumentDeque) { + ImmutableList.Builder<String> result = ImmutableList.builder(); + while (!argumentDeque.isEmpty() && !argumentDeque.peekFirst().startsWith("--")) { + result.add(argumentDeque.pollFirst()); + } + return result.build(); + } + + private static final Splitter CLASSPATH_SPLITTER = + Splitter.on(':').trimResults().omitEmptyStrings(); + + private static ImmutableList<String> splitClasspath(String path) { + ImmutableList.Builder<String> classpath = ImmutableList.builder(); + if (path != null) { + classpath.addAll(CLASSPATH_SPLITTER.split(path)); + } + return classpath.build(); + } +} diff --git a/src/java_tools/buildjar/javatests/com/google/devtools/build/java/turbine/TurbineOptionsTest.java b/src/java_tools/buildjar/javatests/com/google/devtools/build/java/turbine/TurbineOptionsTest.java new file mode 100644 index 0000000000..6eeda1cd1d --- /dev/null +++ b/src/java_tools/buildjar/javatests/com/google/devtools/build/java/turbine/TurbineOptionsTest.java @@ -0,0 +1,203 @@ +// Copyright 2016 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.java.turbine; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +@RunWith(JUnit4.class) +public class TurbineOptionsTest { + + @Rule public final TemporaryFolder tmpFolder = new TemporaryFolder(); + + static final ImmutableList<String> BASE_ARGS = + ImmutableList.of( + "--output", + "out.jar", + "--temp_dir", + "_tmp", + "--target_label", + "//java/com/google/test", + "--rule_kind", + "java_library"); + + @Test + public void exhaustiveArgs() throws Exception { + String[] lines = { + "--output", + "out.jar", + "--source_jars", + "sources1.srcjar", + "sources2.srcjar", + "--temp_dir", + "_tmp", + "--processors", + "com.foo.MyProcessor", + "com.foo.OtherProcessor", + "--processorpath", + "libproc1.jar:libproc2.jar", + "--classpath", + "lib1.jar:lib2.jar", + "--bootclasspath", + "rt.jar:zipfs.jar", + "--javacopts", + "-source", + "8", + "-target", + "8", + "--sources", + "Source1.java", + "Source2.java", + "--output_deps", + "out.jdeps", + "--target_label", + "//java/com/google/test", + "--rule_kind", + "java_library", + }; + + TurbineOptions options = + TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines))); + + assertThat(options.outputFile()).isEqualTo("out.jar"); + assertThat(options.sourceJars()) + .containsExactly("sources1.srcjar", "sources2.srcjar") + .inOrder(); + assertThat(options.tempDir()).isEqualTo("_tmp"); + assertThat(options.processors()) + .containsExactly("com.foo.MyProcessor", "com.foo.OtherProcessor") + .inOrder(); + assertThat(options.processorPath()).containsExactly("libproc1.jar", "libproc2.jar").inOrder(); + assertThat(options.classPath()).containsExactly("lib1.jar", "lib2.jar").inOrder(); + assertThat(options.bootClassPath()).containsExactly("rt.jar", "zipfs.jar").inOrder(); + assertThat(options.javacOpts()).containsExactly("-source", "8", "-target", "8").inOrder(); + assertThat(options.sources()).containsExactly("Source1.java", "Source2.java"); + assertThat(options.outputDeps()).hasValue("out.jdeps"); + assertThat(options.targetLabel()).isEqualTo("//java/com/google/test"); + assertThat(options.ruleKind()).isEqualTo("java_library"); + } + + @Test + public void strictJavaDepsArgs() throws Exception { + String[] lines = { + "--strict_java_deps", + "OFF", + "--direct_dependency", + "blaze-out/foo/libbar.jar", + "//foo/bar", + "--indirect_dependency", + "blaze-out/foo/libbaz.jar", + "//foo/baz", + "blaze-out/foo/libbaz1.jar", + "//foo/baz1", + "--deps_artifacts", + "foo.jdeps", + "bar.jdeps", + "", + }; + + TurbineOptions options = + TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines))); + + assertThat(options.strictDepsMode()).isEqualTo("OFF"); + assertThat(options.targetLabel()).isEqualTo("//java/com/google/test"); + assertThat(options.directJarsToTargets()) + .containsExactlyEntriesIn(ImmutableMap.of("blaze-out/foo/libbar.jar", "//foo/bar")); + assertThat(options.indirectJarsToTargets()) + .containsExactlyEntriesIn( + ImmutableMap.of( + "blaze-out/foo/libbaz1.jar", "//foo/baz1", + "blaze-out/foo/libbaz2.jar", "//foo/baz2")); + assertThat(options.depsArtifacts()).containsExactly("foo.jdeps", "bar.jdeps"); + } + + @Test + public void classpathArgs() throws Exception { + String[] lines = { + "--classpath", + "liba.jar:libb.jar:libc.jar", + "--processorpath", + "libpa.jar:libpb.jar:libpc.jar", + }; + + TurbineOptions options = + TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines))); + + assertThat(options.classPath()).containsExactly("liba.jar", "libb.jar", "libc.jar").inOrder(); + assertThat(options.processorPath()) + .containsExactly("libpa.jar", "libpb.jar", "libpc.jar") + .inOrder(); + } + + @Test + public void paramsFile() throws Exception { + Iterable<String> paramsArgs = + Iterables.concat(BASE_ARGS, Arrays.asList("--javacopts", "-source", "8", "-target", "8")); + Path params = tmpFolder.newFile("params.txt").toPath(); + Files.write(params, paramsArgs, StandardCharsets.UTF_8); + + // @ is a prefix for external repository targets, and the prefix for params files. Targets + // are disambiguated by prepending an extra @. + String[] lines = { + "@" + params.toAbsolutePath(), "--target_label", "//custom/label", + }; + + TurbineOptions options = TurbineOptionsParser.parse(Arrays.asList(lines)); + + // assert that options were read from params file + assertThat(options.javacOpts()).containsExactly("-source", "8", "-target", "8").inOrder(); + // ... and directly from the command line + assertThat(options.targetLabel()).isEqualTo("//custom/label"); + } + + @Test + public void escapedExternalRepositoryLabel() throws Exception { + // @ is a prefix for external repository targets, and the prefix for params files. Targets + // are disambiguated by prepending an extra @. + String[] lines = { + "--target_label", "@@other-repo//foo:local-jam", + }; + + TurbineOptions options = + TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines))); + + assertThat(options.targetLabel()).isEqualTo("@@other-repo//foo:local-jam"); + } + + @Test + public void failIfMissingExpectedArgs() throws Exception { + try { + TurbineOptions.builder().build(); + fail(); + } catch (NullPointerException e) { + assertThat(e).hasMessage("output must not be null"); + } + } +} |