// 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.lib.rules.java;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.MiddlemanProvider;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.Runfiles;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsMode;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.packages.ClassObjectConstructor;
import com.google.devtools.build.lib.rules.SkylarkRuleContext;
import com.google.devtools.build.lib.rules.java.proto.StrictDepsUtils;
import com.google.devtools.build.lib.skylarkinterface.Param;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
import java.util.List;
/** A module that contains Skylark utilities for Java support. */
@SkylarkModule(name = "java_common", doc = "Utilities for Java compilation support in Skylark.")
public class JavaSkylarkCommon {
private final JavaSemantics javaSemantics;
public JavaSkylarkCommon(JavaSemantics javaSemantics) {
this.javaSemantics = javaSemantics;
}
@SkylarkCallable(
name = "provider",
structField = true,
doc = "Returns the Java declared provider."
)
public ClassObjectConstructor getJavaProvider() {
return JavaProvider.JAVA_PROVIDER;
}
@SkylarkCallable(
name = "create_provider",
documented = false,
parameters = {
@Param(
name = "compile_time_jars",
positional = false,
named = true,
type = SkylarkNestedSet.class,
generic1 = Artifact.class,
defaultValue = "[]"
),
@Param(
name = "runtime_jars",
positional = false,
named = true,
type = SkylarkNestedSet.class,
generic1 = Artifact.class,
defaultValue = "[]"
)
}
)
public JavaProvider create(SkylarkNestedSet compileTimeJars, SkylarkNestedSet runtimeJars) {
JavaCompilationArgs javaCompilationArgs =
JavaCompilationArgs.builder()
.addTransitiveRuntimeJars(runtimeJars.getSet(Artifact.class))
.addTransitiveCompileTimeJars(compileTimeJars.getSet(Artifact.class))
.build();
JavaProvider javaProvider =
JavaProvider.Builder.create()
.addProvider(
JavaCompilationArgsProvider.class,
JavaCompilationArgsProvider.create(javaCompilationArgs, javaCompilationArgs))
.build();
return javaProvider;
}
@SkylarkCallable(
name = "compile",
doc = "Compiles Java source files/jars from the implementation of a Skylark rule and returns a "
+ "provider that represents the results of the compilation and can be added to the set of "
+ "providers emitted by this rule.",
// There is one mandatory positional: the Skylark rule context.
mandatoryPositionals = 1,
parameters = {
@Param(
name = "source_jars",
positional = false,
named = true,
type = SkylarkList.class,
generic1 = Artifact.class,
defaultValue = "[]",
doc = "A list of the jars to be compiled. At least one of source_jars or source_files"
+ " should be specified."
),
@Param(
name = "source_files",
positional = false,
named = true,
type = SkylarkList.class,
generic1 = Artifact.class,
defaultValue = "[]",
doc = "A list of the Java source files to be compiled. At least one of source_jars or "
+ "source_files should be specified."
),
@Param(
name = "output",
positional = false,
named = true,
type = Artifact.class
),
@Param(
name = "javac_opts",
positional = false,
named = true,
type = SkylarkList.class,
generic1 = String.class,
defaultValue = "[]",
doc = "A list of the desired javac options. Optional."
),
@Param(
name = "deps",
positional = false,
named = true,
type = SkylarkList.class,
generic1 = JavaProvider.class,
defaultValue = "[]",
doc = "A list of dependencies. Optional."
),
@Param(
name = "strict_deps",
defaultValue = "'ERROR'",
positional = false,
named = true,
type = String.class,
doc = "A string that specifies how to handle strict deps. Possible values: 'OFF' (silently"
+ " allowing referencing transitive dependencies) and 'ERROR' (failing to build when"
+ " transitive dependencies are used directly). By default 'OFF'."
),
@Param(
name = "java_toolchain",
positional = false,
named = true,
type = ConfiguredTarget.class,
doc = "A label pointing to a java_toolchain rule to be used for this compilation. "
+ "Mandatory."
),
@Param(
name = "host_javabase",
positional = false,
named = true,
type = ConfiguredTarget.class,
doc = "A label pointing to a JDK to be used for this compilation. Mandatory."
),
@Param(
name = "sourcepath",
positional = false,
named = true,
type = SkylarkList.class,
generic1 = Artifact.class,
defaultValue = "[]"
),
@Param(
name = "resources",
positional = false,
named = true,
type = SkylarkList.class,
generic1 = Artifact.class,
defaultValue = "[]"
)
}
)
public JavaProvider createJavaCompileAction(
SkylarkRuleContext skylarkRuleContext,
SkylarkList<Artifact> sourceJars,
SkylarkList<Artifact> sourceFiles,
Artifact outputJar,
SkylarkList<String> javacOpts,
SkylarkList<JavaProvider> deps,
String strictDepsMode,
ConfiguredTarget javaToolchain,
ConfiguredTarget hostJavabase,
SkylarkList<Artifact> sourcepathEntries,
SkylarkList<Artifact> resources) throws EvalException {
JavaLibraryHelper helper =
new JavaLibraryHelper(skylarkRuleContext.getRuleContext())
.setOutput(outputJar)
.addSourceJars(sourceJars)
.addSourceFiles(sourceFiles)
.addResources(resources)
.setSourcePathEntries(sourcepathEntries)
.setJavacOpts(javacOpts);
List<JavaCompilationArgsProvider> compilationArgsProviders =
JavaProvider.fetchProvidersFromList(deps, JavaCompilationArgsProvider.class);
helper.addAllDeps(compilationArgsProviders);
helper.setCompilationStrictDepsMode(getStrictDepsMode(strictDepsMode));
MiddlemanProvider hostJavabaseProvider = hostJavabase.getProvider(MiddlemanProvider.class);
NestedSet<Artifact> hostJavabaseArtifacts =
hostJavabaseProvider == null
? NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER)
: hostJavabaseProvider.getMiddlemanArtifact();
JavaToolchainProvider javaToolchainProvider =
checkNotNull(javaToolchain.getProvider(JavaToolchainProvider.class));
JavaCompilationArgs artifacts =
helper.build(
javaSemantics,
javaToolchainProvider,
hostJavabaseArtifacts,
SkylarkList.createImmutable(ImmutableList.<Artifact>of()));
JavaRuleOutputJarsProvider javaRuleOutputJarsProvider =
JavaRuleOutputJarsProvider.builder().addOutputJar(
new JavaRuleOutputJarsProvider.OutputJar(outputJar, /* ijar */ null, sourceJars))
.build();
JavaCompilationArgsProvider javaCompilationArgsProvider =
helper.buildCompilationArgsProvider(artifacts, true);
Runfiles runfiles = new Runfiles.Builder(skylarkRuleContext.getWorkspaceName()).addArtifacts(
javaCompilationArgsProvider.getRecursiveJavaCompilationArgs().getRuntimeJars()).build();
return JavaProvider.Builder.create()
.addProvider(JavaCompilationArgsProvider.class, javaCompilationArgsProvider)
.addProvider(JavaSourceJarsProvider.class, createJavaSourceJarsProvider(sourceJars))
.addProvider(JavaRuleOutputJarsProvider.class, javaRuleOutputJarsProvider)
.addProvider(JavaRunfilesProvider.class, new JavaRunfilesProvider(runfiles))
.build();
}
/**
* Creates a {@link JavaSourceJarsProvider} from the given list of source jars.
*/
private static JavaSourceJarsProvider createJavaSourceJarsProvider(List<Artifact> sourceJars) {
NestedSet<Artifact> javaSourceJars =
NestedSetBuilder.<Artifact>stableOrder().addAll(sourceJars).build();
return JavaSourceJarsProvider.create(
NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER), javaSourceJars);
}
@SkylarkCallable(
name = "default_javac_opts",
// This function is experimental for now.
documented = false,
// There's only one mandatory positional,the Skylark context
mandatoryPositionals = 1,
parameters = {
@Param(name = "java_toolchain_attr", positional = false, named = true, type = String.class)
}
)
public static ImmutableList<String> getDefaultJavacOpts(
SkylarkRuleContext skylarkRuleContext, String javaToolchainAttr) throws EvalException {
RuleContext ruleContext = skylarkRuleContext.getRuleContext();
ConfiguredTarget javaToolchainConfigTarget =
(ConfiguredTarget) checkNotNull(skylarkRuleContext.getAttr().getValue(javaToolchainAttr));
JavaToolchainProvider toolchain =
checkNotNull(javaToolchainConfigTarget.getProvider(JavaToolchainProvider.class));
return ImmutableList.copyOf(Iterables.concat(
toolchain.getJavacOptions(), ruleContext.getTokenizedStringListAttr("javacopts")));
}
@SkylarkCallable(
name = "merge",
doc = "Merges the given providers into a single java_common.provider.",
// We have one positional argument: the list of providers to merge.
mandatoryPositionals = 1
)
public static JavaProvider mergeJavaProviders(SkylarkList<JavaProvider> providers) {
return JavaProvider.merge(providers);
}
@SkylarkCallable(
name = "make_non_strict",
doc = "Returns a new Java provider whose direct-jars part is the union of both the direct and"
+ " indirect jars of the given Java provider.",
// There's only one mandatory positional, the Java provider.
mandatoryPositionals = 1
)
public static JavaProvider makeNonStrict(JavaProvider javaProvider) {
JavaCompilationArgsProvider directCompilationArgs =
StrictDepsUtils.makeNonStrict(javaProvider.getProvider(JavaCompilationArgsProvider.class));
return JavaProvider.Builder.copyOf(javaProvider)
// Overwrites the old provider.
.addProvider(JavaCompilationArgsProvider.class, directCompilationArgs)
.build();
}
private static StrictDepsMode getStrictDepsMode(String strictDepsMode) {
switch (strictDepsMode) {
case "OFF":
return StrictDepsMode.OFF;
case "ERROR":
return StrictDepsMode.ERROR;
default:
throw new IllegalArgumentException(
"StrictDepsMode "
+ strictDepsMode
+ " not allowed."
+ " Only OFF and ERROR values are accepted.");
}
}
}