Update from Google. -- MOE_MIGRATED_REVID=85702957
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AbstractConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/AbstractConfiguredTarget.java new file mode 100644 index 0000000..e574978 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/AbstractConfiguredTarget.java
@@ -0,0 +1,111 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +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.PackageSpecification; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.syntax.ClassObject; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.SkylarkNestedSet; + +/** + * An abstract implementation of ConfiguredTarget in which all properties are + * assigned trivial default values. + */ +public abstract class AbstractConfiguredTarget + implements ConfiguredTarget, VisibilityProvider, ClassObject { + private final Target target; + private final BuildConfiguration configuration; + + private final NestedSet<PackageSpecification> visibility; + + AbstractConfiguredTarget(Target target, + BuildConfiguration configuration) { + this.target = target; + this.configuration = configuration; + this.visibility = NestedSetBuilder.emptySet(Order.STABLE_ORDER); + } + + AbstractConfiguredTarget(TargetContext targetContext) { + this.target = targetContext.getTarget(); + this.configuration = targetContext.getConfiguration(); + this.visibility = targetContext.getVisibility(); + } + + @Override + public final NestedSet<PackageSpecification> getVisibility() { + return visibility; + } + + @Override + public Target getTarget() { + return target; + } + + @Override + public BuildConfiguration getConfiguration() { + return configuration; + } + + @Override + public Label getLabel() { + return getTarget().getLabel(); + } + + @Override + public String toString() { + return "ConfiguredTarget(" + getTarget().getLabel() + ", " + getConfiguration() + ")"; + } + + @Override + public <P extends TransitiveInfoProvider> P getProvider(Class<P> provider) { + AnalysisUtils.checkProvider(provider); + if (provider.isAssignableFrom(getClass())) { + return provider.cast(this); + } else { + return null; + } + } + + @Override + public Object getValue(String name) { + if (name.equals("label")) { + return getLabel(); + } else if (name.equals("files")) { + // A shortcut for files to build in Skylark. FileConfiguredTarget and RunleConfiguredTarget + // always has FileProvider and Error- and PackageGroupConfiguredTarget-s shouldn't be + // accessible in Skylark. + return SkylarkNestedSet.of(Artifact.class, getProvider(FileProvider.class).getFilesToBuild()); + } + return get(name); + } + + @Override + public String errorMessage(String name) { + return null; + } + + @Override + public ImmutableCollection<String> getKeys() { + return ImmutableList.<String>builder().add("label").add("files").build(); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AlwaysBuiltArtifactsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/AlwaysBuiltArtifactsProvider.java new file mode 100644 index 0000000..e4d40fc --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/AlwaysBuiltArtifactsProvider.java
@@ -0,0 +1,46 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; + +/** + * Artifacts that should be built when a target is mentioned in the command line, but are neither in + * the {@code filesToBuild} nor in the runfiles. + * + * <p> + * Link actions, may not run a link for their transitive dependencies, so it does not force the + * source files in the transitive closure to be built by default. However, users expect builds to + * fail when there is an error in a dependent library, so we use this mechanism to force their + * compilation. + */ +@Immutable +public final class AlwaysBuiltArtifactsProvider implements TransitiveInfoProvider { + + private final NestedSet<Artifact> artifactsToAlwaysBuild; + + public AlwaysBuiltArtifactsProvider(NestedSet<Artifact> artifactsToAlwaysBuild) { + this.artifactsToAlwaysBuild = artifactsToAlwaysBuild; + } + + /** + * Returns the collection of artifacts to be built. + */ + public NestedSet<Artifact> getArtifactsToAlwaysBuild() { + return artifactsToAlwaysBuild; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java new file mode 100644 index 0000000..0bccc72 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java
@@ -0,0 +1,128 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.ActionRegistry; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.MiddlemanFactory; +import com.google.devtools.build.lib.actions.Root; +import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory.BuildInfoKey; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.skyframe.SkyFunction; + +/** + * The set of services that are provided to {@link ConfiguredTarget} objects + * during initialization. + */ +public interface AnalysisEnvironment extends ActionRegistry { + /** + * Returns a callback to be used in this build for reporting analysis errors. + */ + EventHandler getEventHandler(); + + /** + * Returns whether any errors were reported to this instance. + */ + boolean hasErrors(); + + /** + * Returns the artifact for the derived file {@code rootRelativePath}. + * + * <p>Creates the artifact if necessary and sets the root of that artifact to {@code root}. + */ + Artifact getDerivedArtifact(PathFragment rootRelativePath, Root root); + + /** + * Returns an artifact for the derived file {@code rootRelativePath} whose changes do not cause + * a rebuild. + * + * <p>Creates the artifact if necessary and sets the root of that artifact to {@code root}. + * + * <p>This is useful for files that store data that changes very frequently (e.g. current time) + * but does not substantially affect the result of the build. + */ + Artifact getConstantMetadataArtifact(PathFragment rootRelativePath, + Root root); + + /** + * Returns the artifact for the derived file {@code rootRelativePath}, + * creating it if necessary, and setting the root of that artifact to + * {@code root}. The artifact will represent the output directory of a {@code Fileset}. + */ + Artifact getFilesetArtifact(PathFragment rootRelativePath, Root root); + + /** + * Returns the artifact for the specified tool. + */ + Artifact getEmbeddedToolArtifact(String embeddedPath); + + /** + * Returns the middleman factory associated with the build. + */ + // TODO(bazel-team): remove this method and replace it with delegate methods. + MiddlemanFactory getMiddlemanFactory(); + + /** + * Returns the generating action for the given local artifact. + * + * If the artifact was created in another analysis environment (e.g. by a different configured + * target instance) or the artifact is a source artifact, it returns null. + */ + Action getLocalGeneratingAction(Artifact artifact); + + /** + * Returns the actions that were registered so far with this analysis environment, that is, all + * the actions that were created by the current target being analyzed. + */ + Iterable<Action> getRegisteredActions(); + + /** + * Returns the Skyframe SkyFunction.Environment if available. Otherwise, null. + * + * <p>If you need to use this for something other than genquery, please think long and hard + * about that. + */ + SkyFunction.Environment getSkyframeEnv(); + + /** + * Returns the Artifact that is used to hold the non-volatile workspace status for the current + * build request. + */ + Artifact getStableWorkspaceStatusArtifact(); + + /** + * Returns the Artifact that is used to hold the volatile workspace status (e.g. build + * changelist) for the current build request. + */ + Artifact getVolatileWorkspaceStatusArtifact(); + + /** + * Returns the Artifacts that contain the workspace status for the current build request. + * + * @param ruleContext the rule to use for error reporting and to determine the + * configuration + */ + ImmutableList<Artifact> getBuildInfo(RuleContext ruleContext, BuildInfoKey key); + + /** + * Returns the set of orphan Artifacts (i.e. Artifacts without generating action). Should only be + * called after the ConfiguredTarget is created. + */ + ImmutableSet<Artifact> getOrphanArtifacts(); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisFailureEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisFailureEvent.java new file mode 100644 index 0000000..5064163 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisFailureEvent.java
@@ -0,0 +1,40 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.syntax.Label; + +/** + * This event is fired during the build, when it becomes known that the analysis + * of a target cannot be completed because of an error in one of its + * dependencies. + */ +public class AnalysisFailureEvent { + private final LabelAndConfiguration failedTarget; + private final Label failureReason; + + public AnalysisFailureEvent(LabelAndConfiguration failedTarget, Label failureReason) { + this.failedTarget = failedTarget; + this.failureReason = failureReason; + } + + public LabelAndConfiguration getFailedTarget() { + return failedTarget; + } + + public Label getFailureReason() { + return failureReason; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisHooks.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisHooks.java new file mode 100644 index 0000000..82c4485 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisHooks.java
@@ -0,0 +1,36 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.pkgcache.PackageManager; + +/** + * This interface resolves target - configuration pairs to {@link ConfiguredTarget} instances. + * + * <p>This interface is used to provide analysis phase functionality to actions that need it in + * the execution phase. + */ +public interface AnalysisHooks { + /** + * Returns the package manager used during the analysis phase. + */ + PackageManager getPackageManager(); + + /** + * Resolves an existing configured target. Returns null if it is not in the cache. + */ + ConfiguredTarget getExistingConfiguredTarget(Target target, BuildConfiguration configuration); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisPhaseCompleteEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisPhaseCompleteEvent.java new file mode 100644 index 0000000..0d1e565 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisPhaseCompleteEvent.java
@@ -0,0 +1,59 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableList; + +import java.util.Collection; + +/** + * This event is fired after the analysis phase is complete. + */ +public class AnalysisPhaseCompleteEvent { + + private final Collection<ConfiguredTarget> targets; + private final long timeInMs; + private int targetsVisited; + + /** + * Construct the event. + * @param targets The set of active targets that remain. + */ + public AnalysisPhaseCompleteEvent(Collection<? extends ConfiguredTarget> targets, + int targetsVisited, long timeInMs) { + this.timeInMs = timeInMs; + this.targets = ImmutableList.copyOf(targets); + this.targetsVisited = targetsVisited; + } + + /** + * @return The set of active targets remaining, which is a subset + * of the targets we attempted to analyze. + */ + public Collection<ConfiguredTarget> getTargets() { + return targets; + } + + /** + * @return The number of targets freshly visited during analysis + */ + public int getTargetsVisited() { + return targetsVisited; + } + + public long getTimeInMs() { + return timeInMs; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisPhaseStartedEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisPhaseStartedEvent.java new file mode 100644 index 0000000..fc97c60 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisPhaseStartedEvent.java
@@ -0,0 +1,51 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.syntax.Label; + +import java.util.Collection; + +/** + * This event is fired before the analysis phase is started. + */ +public class AnalysisPhaseStartedEvent { + + private final Iterable<Label> labels; + + /** + * Construct the event. + * @param targets The set of active targets that remain. + */ + public AnalysisPhaseStartedEvent(Collection<Target> targets) { + this.labels = Iterables.transform(targets, new Function<Target, Label>() { + @Override + public Label apply(Target input) { + return input.getLabel(); + } + }); + } + + /** + * @return The set of active targets remaining, which is a subset + * of the targets we attempted to load. + */ + public Iterable<Label> getLabels() { + return labels; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java new file mode 100644 index 0000000..2e4c251 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java
@@ -0,0 +1,146 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +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.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +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.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.TriState; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; + +/** + * Utility functions for use during analysis. + */ +public final class AnalysisUtils { + + private AnalysisUtils() { + throw new IllegalStateException(); // utility class + } + + /** + * Returns whether link stamping is enabled for a rule. + * + * <p>This returns false for unstampable rule classes and for rules in the + * host configuration. Otherwise it returns the value of the stamp attribute, + * or of the stamp option if the attribute value is -1. + */ + public static boolean isStampingEnabled(RuleContext ruleContext) { + BuildConfiguration config = ruleContext.getConfiguration(); + Rule rule = ruleContext.getRule(); + if (config.isHostConfiguration() + || !rule.getRuleClassObject().hasAttr("stamp", Type.TRISTATE)) { + return false; + } + TriState stamp = ruleContext.attributes().get("stamp", Type.TRISTATE); + return stamp == TriState.YES || (stamp == TriState.AUTO && config.stampBinaries()); + } + + // TODO(bazel-team): These need Iterable<? extends TransitiveInfoCollection> because they need to + // be called with Iterable<ConfiguredTarget>. Once the configured target lockdown is complete, we + // can eliminate the "extends" clauses. + /** + * Returns the list of providers of the specified type from a set of transitive info + * collections. + */ + public static <C extends TransitiveInfoProvider> Iterable<C> getProviders( + Iterable<? extends TransitiveInfoCollection> prerequisites, Class<C> provider) { + Collection<C> result = new ArrayList<>(); + for (TransitiveInfoCollection prerequisite : prerequisites) { + C prerequisiteProvider = prerequisite.getProvider(provider); + if (prerequisiteProvider != null) { + result.add(prerequisiteProvider); + } + } + return ImmutableList.copyOf(result); + } + + /** + * Returns the iterable of collections that have the specified provider. + */ + public static <S extends TransitiveInfoCollection, C extends TransitiveInfoProvider> Iterable<S> + filterByProvider(Iterable<S> prerequisites, final Class<C> provider) { + return Iterables.filter(prerequisites, new Predicate<S>() { + @Override + public boolean apply(S target) { + return target.getProvider(provider) != null; + } + }); + } + + /** + * Returns the path of the associated manifest file for the path of a Fileset. Works for both + * exec paths and root relative paths. + */ + public static PathFragment getManifestPathFromFilesetPath(PathFragment filesetDir) { + PathFragment manifestDir = filesetDir.replaceName("_" + filesetDir.getBaseName()); + PathFragment outputManifestFrag = manifestDir.getRelative("MANIFEST"); + return outputManifestFrag; + } + + /** + * Returns the middleman artifact on the specified attribute of the specified rule, or an empty + * set if it does not exist. + */ + public static NestedSet<Artifact> getMiddlemanFor(RuleContext rule, String attribute) { + TransitiveInfoCollection prereq = rule.getPrerequisite(attribute, Mode.HOST); + if (prereq == null) { + return NestedSetBuilder.emptySet(Order.STABLE_ORDER); + } + MiddlemanProvider provider = prereq.getProvider(MiddlemanProvider.class); + if (provider == null) { + return NestedSetBuilder.emptySet(Order.STABLE_ORDER); + } + return provider.getMiddlemanArtifact(); + } + + /** + * Returns a path fragment qualified by the rule name and unique fragment to + * disambiguate artifacts produced from the source file appearing in + * multiple rules. + * + * <p>For example "//pkg:target" -> "pkg/<fragment>/target. + */ + public static PathFragment getUniqueDirectory(Label label, PathFragment fragment) { + return label.getPackageFragment().getRelative(fragment) + .getRelative(label.getName()); + } + + /** + * Checks that the given provider class either refers to an interface or to a value class. + */ + public static <T extends TransitiveInfoProvider> void checkProvider(Class<T> clazz) { + if (!clazz.isInterface()) { + Preconditions.checkArgument(Modifier.isFinal(clazz.getModifiers()), + clazz.getName() + " has to be final"); + Preconditions.checkArgument(clazz.isAnnotationPresent(Immutable.class), + clazz.getName() + " has to be tagged with @Immutable"); + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/Aspect.java b/src/main/java/com/google/devtools/build/lib/analysis/Aspect.java new file mode 100644 index 0000000..3f4a06e --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/Aspect.java
@@ -0,0 +1,82 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.UnmodifiableIterator; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Extra information about a configured target computed on request of a dependent. + * + * <p>Analogous to {@link ConfiguredTarget}: contains a bunch of transitive info providers, which + * are merged with the providers of the associated configured target before they are passed to + * the configured target factories that depend on the configured target to which this aspect is + * added. + * + * <p>Aspects are created alongside configured targets on request from dependents. + */ +@Immutable +public final class Aspect implements Iterable<TransitiveInfoProvider> { + private final + ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers; + + private Aspect( + ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers) { + this.providers = providers; + } + + /** + * Returns the providers created by the aspect. + */ + public ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> + getProviders() { + return providers; + } + + @Override + public UnmodifiableIterator<TransitiveInfoProvider> iterator() { + return providers.values().iterator(); + } + + /** + * Builder for {@link Aspect}. + */ + public static class Builder { + private final Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> + providers = new LinkedHashMap<>(); + + /** + * Adds a provider to the aspect. + */ + public Builder addProvider( + Class<? extends TransitiveInfoProvider> key, TransitiveInfoProvider value) { + Preconditions.checkNotNull(key); + Preconditions.checkNotNull(value); + AnalysisUtils.checkProvider(key); + Preconditions.checkState(!providers.containsKey(key)); + providers.put(key, value); + return this; + } + + public Aspect build() { + return new Aspect(ImmutableMap.copyOf(providers)); + } + } +} \ No newline at end of file
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java b/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java new file mode 100644 index 0000000..ad5756e --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java
@@ -0,0 +1,265 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.DATA; +import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST; +import static com.google.devtools.build.lib.packages.Attribute.attr; +import static com.google.devtools.build.lib.packages.Type.BOOLEAN; +import static com.google.devtools.build.lib.packages.Type.DISTRIBUTIONS; +import static com.google.devtools.build.lib.packages.Type.INTEGER; +import static com.google.devtools.build.lib.packages.Type.LABEL; +import static com.google.devtools.build.lib.packages.Type.LABEL_LIST; +import static com.google.devtools.build.lib.packages.Type.LICENSE; +import static com.google.devtools.build.lib.packages.Type.NODEP_LABEL_LIST; +import static com.google.devtools.build.lib.packages.Type.STRING; +import static com.google.devtools.build.lib.packages.Type.STRING_LIST; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.config.RunUnder; +import com.google.devtools.build.lib.analysis.constraints.EnvironmentRule; +import com.google.devtools.build.lib.packages.Attribute; +import com.google.devtools.build.lib.packages.Attribute.LateBoundLabel; +import com.google.devtools.build.lib.packages.Attribute.LateBoundLabelList; +import com.google.devtools.build.lib.packages.AttributeMap; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.RuleClass.Builder; +import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType; +import com.google.devtools.build.lib.packages.TestSize; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.util.FileTypeSet; + +import java.util.List; + +/** + * Rule class definitions used by (almost) every rule. + */ +public class BaseRuleClasses { + /** + * Label of the pseudo-filegroup that contains all the targets that are needed + * for running tests in coverage mode. + */ + private static final Label COVERAGE_SUPPORT_LABEL = + Label.parseAbsoluteUnchecked("//tools/defaults:coverage"); + + private static final Attribute.ComputedDefault obsoleteDefault = + new Attribute.ComputedDefault() { + @Override + public Object getDefault(AttributeMap rule) { + return rule.getPackageDefaultObsolete(); + } + }; + + private static final Attribute.ComputedDefault testonlyDefault = + new Attribute.ComputedDefault() { + @Override + public Object getDefault(AttributeMap rule) { + return rule.getPackageDefaultTestOnly(); + } + }; + + private static final Attribute.ComputedDefault deprecationDefault = + new Attribute.ComputedDefault() { + @Override + public Object getDefault(AttributeMap rule) { + return rule.getPackageDefaultDeprecation(); + } + }; + + /** + * Implementation for the :action_listener attribute. + */ + private static final LateBoundLabelList<BuildConfiguration> ACTION_LISTENER = + new LateBoundLabelList<BuildConfiguration>() { + @Override + public List<Label> getDefault(Rule rule, BuildConfiguration configuration) { + // action_listeners are special rules; they tell the build system to add extra_actions to + // existing rules. As such they need an edge to every ConfiguredTarget with the limitation + // that they only run on the target configuration and should not operate on action_listeners + // and extra_actions themselves (to avoid cycles). + return configuration.getActionListeners(); + } + }; + + private static final LateBoundLabelList<BuildConfiguration> COVERAGE_SUPPORT = + new LateBoundLabelList<BuildConfiguration>(ImmutableList.of(COVERAGE_SUPPORT_LABEL)) { + @Override + public List<Label> getDefault(Rule rule, BuildConfiguration configuration) { + return configuration.isCodeCoverageEnabled() + ? ImmutableList.<Label>copyOf(configuration.getCoverageLabels()) + : ImmutableList.<Label>of(); + } + }; + + private static final LateBoundLabelList<BuildConfiguration> COVERAGE_REPORT_GENERATOR = + new LateBoundLabelList<BuildConfiguration>(ImmutableList.of(COVERAGE_SUPPORT_LABEL)) { + @Override + public List<Label> getDefault(Rule rule, BuildConfiguration configuration) { + return configuration.isCodeCoverageEnabled() + ? ImmutableList.<Label>copyOf(configuration.getCoverageReportGeneratorLabels()) + : ImmutableList.<Label>of(); + } + }; + + /** + * Implementation for the :run_under attribute. + */ + private static final LateBoundLabel<BuildConfiguration> RUN_UNDER = + new LateBoundLabel<BuildConfiguration>() { + @Override + public Label getDefault(Rule rule, BuildConfiguration configuration) { + RunUnder runUnder = configuration.getRunUnder(); + return runUnder == null ? null : runUnder.getLabel(); + } + }; + + /** + * A base rule for all test rules. + */ + @BlazeRule(name = "$test_base_rule", + type = RuleClassType.ABSTRACT) + public static final class TestBaseRule implements RuleDefinition { + @Override + public RuleClass build(Builder builder, RuleDefinitionEnvironment env) { + return builder + .add(attr("size", STRING).value("medium").taggable() + .nonconfigurable("policy decision: should be consistent across configurations")) + .add(attr("timeout", STRING).taggable() + .nonconfigurable("policy decision: should be consistent across configurations") + .value(new Attribute.ComputedDefault() { + @Override + public Object getDefault(AttributeMap rule) { + TestSize size = TestSize.getTestSize(rule.get("size", Type.STRING)); + if (size != null) { + String timeout = size.getDefaultTimeout().toString(); + if (timeout != null) { + return timeout; + } + } + return "illegal"; + } + })) + .add(attr("flaky", BOOLEAN).value(false).taggable() + .nonconfigurable("policy decision: should be consistent across configurations")) + .add(attr("shard_count", INTEGER).value(-1)) + .add(attr("local", BOOLEAN).value(false).taggable() + .nonconfigurable("policy decision: should be consistent across configurations")) + .add(attr("args", STRING_LIST) + .nonconfigurable("policy decision: should be consistent across configurations")) + .add(attr("$test_runtime", LABEL_LIST).cfg(HOST).value(ImmutableList.of( + env.getLabel("//tools/test:runtime")))) + + // TODO(bazel-team): TestActions may need to be run with coverage, so all tests + // implicitly depend on crosstool, which provides gcov. We could add gcov to + // InstrumentedFilesProvider.getInstrumentationMetadataFiles() (or a new method) for + // all the test rules that have C++ in their transitive closure. Then this could go. + .add(attr(":coverage_support", LABEL_LIST).cfg(HOST).value(COVERAGE_SUPPORT)) + .add(attr(":coverage_report_generator", LABEL_LIST).cfg(HOST) + .value(COVERAGE_REPORT_GENERATOR)) + + // The target itself and run_under both run on the same machine. We use the DATA config + // here because the run_under acts like a data dependency (e.g. no LIPO optimization). + .add(attr(":run_under", LABEL).cfg(DATA).value(RUN_UNDER)) + .build(); + } + } + + /** + * Share common attributes across both base and Skylark base rules. + */ + public static RuleClass.Builder commonCoreAndSkylarkAttributes(RuleClass.Builder builder) { + return builder + // The visibility attribute is special: it is a nodep label, and loading the + // necessary package groups is handled by {@link LabelVisitor#visitTargetVisibility}. + // Package groups always have the null configuration so that they are not duplicated + // needlessly. + .add(attr("visibility", NODEP_LABEL_LIST).orderIndependent().cfg(HOST) + .nonconfigurable("special attribute integrated more deeply into Bazel's core logic")) + .add(attr("deprecation", STRING).value(deprecationDefault) + .nonconfigurable("Used in core loading phase logic with no access to configs")) + .add(attr("tags", STRING_LIST).orderIndependent().taggable() + .nonconfigurable("low-level attribute, used in TargetUtils without configurations")) + .add(attr("generator_name", STRING).undocumented("internal")) + .add(attr("generator_function", STRING).undocumented("internal")) + .add(attr("testonly", BOOLEAN).value(testonlyDefault) + .nonconfigurable("policy decision: rules testability should be consistent")) + .add(attr(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, LABEL_LIST) + .allowedRuleClasses(EnvironmentRule.RULE_NAME) + .cfg(Attribute.ConfigurationTransition.HOST) + .allowedFileTypes(FileTypeSet.NO_FILE) + .undocumented("not yet released")) + .add(attr(RuleClass.RESTRICTED_ENVIRONMENT_ATTR, LABEL_LIST) + .allowedRuleClasses(EnvironmentRule.RULE_NAME) + .cfg(Attribute.ConfigurationTransition.HOST) + .allowedFileTypes(FileTypeSet.NO_FILE) + .undocumented("not yet released")); + } + + /** + * Common parts of rules. + */ + @BlazeRule(name = "$base_rule", + type = RuleClassType.ABSTRACT) + public static final class BaseRule implements RuleDefinition { + @Override + public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { + return commonCoreAndSkylarkAttributes(builder) + // The name attribute is handled specially, so it does not appear here. + // + // Aggregates the labels of all {@link ConfigRuleClasses} rules this rule uses (e.g. + // keys for configurable attributes). This is specially populated in + // {@RuleClass#populateRuleAttributeValues}. + // + // This attribute is not needed for actual builds. Its main purpose is so query's + // proto/XML output includes the labels of config dependencies, so, e.g., depserver + // reverse dependency lookups remain accurate. These can't just be added to the + // attribute definitions proto/XML queries already output because not all attributes + // contain labels. + // + // Builds and Blaze-interactive queries don't need this because they find dependencies + // through direct Rule label visitation, which already factors these in. + .add(attr("$config_dependencies", LABEL_LIST) + .nonconfigurable("not intended for actual builds")) + .add(attr("licenses", LICENSE) + .nonconfigurable("Used in core loading phase logic with no access to configs")) + .add(attr("distribs", DISTRIBUTIONS) + .nonconfigurable("Used in core loading phase logic with no access to configs")) + .add(attr("obsolete", BOOLEAN).value(obsoleteDefault) + .nonconfigurable("Used in core loading phase logic with no access to configs")) + .add(attr(":action_listener", LABEL_LIST).cfg(HOST).value(ACTION_LISTENER)) + .build(); + } + } + + /** + * Common ancestor class for all rules. + */ + @BlazeRule(name = "$rule", + type = RuleClassType.ABSTRACT, + ancestors = { BaseRule.class }) + public static final class RuleBase implements RuleDefinition { + @Override + public RuleClass build(Builder builder, RuleDefinitionEnvironment env) { + return builder + .add(attr("deps", LABEL_LIST).legacyAllowAnyFileType()) + .add(attr("data", LABEL_LIST).cfg(DATA).allowedFileTypes(FileTypeSet.ANY_FILE)) + .build(); + } + } + +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BaselineCoverageArtifactsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/BaselineCoverageArtifactsProvider.java new file mode 100644 index 0000000..ab74581 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/BaselineCoverageArtifactsProvider.java
@@ -0,0 +1,42 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; + +/** + * A {@link TransitiveInfoProvider} that has baseline coverage artifacts. + */ +@Immutable +public final class BaselineCoverageArtifactsProvider implements TransitiveInfoProvider { + private final ImmutableList<Artifact> baselineCoverageArtifacts; + + public BaselineCoverageArtifactsProvider(ImmutableList<Artifact> baselineCoverageArtifacts) { + this.baselineCoverageArtifacts = baselineCoverageArtifacts; + } + + /** + * Returns a set of baseline coverage artifacts for a given set of configured targets. + * + * <p>These artifacts represent "empty" code coverage data for non-test libraries and binaries and + * used to establish correct baseline when calculating code coverage ratios since they would cover + * completely non-tested code as well. + */ + public ImmutableList<Artifact> getBaselineCoverageArtifacts() { + return baselineCoverageArtifacts; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BlazeDirectories.java b/src/main/java/com/google/devtools/build/lib/analysis/BlazeDirectories.java new file mode 100644 index 0000000..38a8d41 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/BlazeDirectories.java
@@ -0,0 +1,183 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.devtools.build.lib.Constants; +import com.google.devtools.build.lib.actions.Root; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.util.StringCanonicalizer; +import com.google.devtools.build.lib.vfs.FileSystem; +import com.google.devtools.build.lib.vfs.Path; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** + * Encapsulation of all of the interesting top-level directories in any Blaze application. + * + * <p>The <code>installBase</code> is the directory where the Blaze binary has been installed.The + * <code>workspace</code> is the top-level directory in the user's client (possibly read-only).The + * <code>outputBase</code> is the directory below which Blaze puts all its state. The + * <code>execRoot</code> is the working directory for all spawned tools, which is generally below + * <code>outputBase</code>. + * + * <p>There is a 1:1 correspondence between a running Blaze instance and an output base directory; + * however, multiple Blaze instances may compile code that's in the same workspace, even on the same + * machine. If the user does not qualify an output base directory, the startup code will derive it + * deterministically from the workspace. Note also that while the Blaze server process runs with the + * workspace directory as its working directory, the client process may have a different working + * directory, typically a subdirectory. + * + * <p>Do not put shortcuts to specific files here! + */ +@Immutable +public final class BlazeDirectories implements Serializable { + + // Output directory name, relative to the execRoot. + // TODO(bazel-team): (2011) make this private? + public static final String RELATIVE_OUTPUT_PATH = StringCanonicalizer.intern( + Constants.PRODUCT_NAME + "-out"); + + // Include directory name, relative to execRoot/blaze-out/configuration. + public static final String RELATIVE_INCLUDE_DIR = StringCanonicalizer.intern("include"); + + private final Path installBase; // Where Blaze gets unpacked + private final Path workspace; // Workspace root and server CWD + private final Path outputBase; // The root of the temp and output trees + private final Path execRoot; // the root of all build actions + + // These two are kept to avoid creating new objects every time they are accessed. This showed up + // in a profiler. + private final Path outputPath; + private final Path localOutputPath; + + public BlazeDirectories(Path installBase, Path outputBase, @Nullable Path workspace) { + this.installBase = installBase; + this.workspace = workspace; + this.outputBase = outputBase; + if (this.workspace == null) { + // TODO(bazel-team): this should be null, but at the moment there is a lot of code that + // depends on it being non-null. + this.execRoot = outputBase.getChild("default-exec-root"); + } else { + this.execRoot = outputBase.getChild(workspace.getBaseName()); + } + this.outputPath = execRoot.getRelative(RELATIVE_OUTPUT_PATH); + Preconditions.checkState(this.workspace == null || outputPath.asFragment().equals( + outputPathFromOutputBase(outputBase.asFragment(), workspace.asFragment()))); + this.localOutputPath = outputBase.getRelative(BlazeDirectories.RELATIVE_OUTPUT_PATH); + } + + /** + * Returns the Filesystem that all of our directories belong to. Handy for + * resolving absolute paths. + */ + public FileSystem getFileSystem() { + return installBase.getFileSystem(); + } + + /** + * Returns the installation base directory. Currently used by info command only. + */ + public Path getInstallBase() { + return installBase; + } + + /** + * Returns the workspace directory, which is also the working dir of the server. + */ + public Path getWorkspace() { + return workspace; + } + + /** + * Returns if the workspace directory is a valid workspace. + */ + public boolean inWorkspace() { + return this.workspace != null; + } + + /** + * Returns the base of the output tree, which hosts all build and scratch + * output for a user and workspace. + */ + public Path getOutputBase() { + return outputBase; + } + + /** + * Returns the execution root. This is the directory underneath which Blaze builds the source + * symlink forest, to represent the merged view of different workspaces specified + * with --package_path. + */ + public Path getExecRoot() { + return execRoot; + } + + /** + * Returns the output path used by this Blaze instance. + */ + public Path getOutputPath() { + return outputPath; + } + + /** + * @param outputBase the outputBase as a path fragment. + * @param workspace the workspace as a path fragment. + * @return the outputPath as a path fragment, given the outputBase. + */ + public static PathFragment outputPathFromOutputBase( + PathFragment outputBase, PathFragment workspace) { + if (workspace.equals(PathFragment.EMPTY_FRAGMENT)) { + return outputBase; + } + return outputBase.getRelative(workspace.getBaseName() + "/" + RELATIVE_OUTPUT_PATH); + } + + /** + * Returns the local output path used by this Blaze instance. + */ + public Path getLocalOutputPath() { + return localOutputPath; + } + + /** + * Returns the directory where the stdout/stderr for actions can be stored + * temporarily for a build. If the directory already exists, the directory + * is cleaned. + */ + public Path getActionConsoleOutputDirectory() { + return getOutputBase().getRelative("action_outs"); + } + + /** + * Returns the installed embedded binaries directory, under the shared + * installBase location. + */ + public Path getEmbeddedBinariesRoot() { + return installBase.getChild("_embedded_binaries"); + } + + /** + * Returns the configuration-independent root where the build-data should be placed, given the + * {@link BlazeDirectories} of this server instance. Nothing else should be placed here. + */ + public Root getBuildDataDirectory() { + return Root.asDerivedRoot(getExecRoot(), getOutputPath()); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BlazeRule.java b/src/main/java/com/google/devtools/build/lib/analysis/BlazeRule.java new file mode 100644 index 0000000..c349e65 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/BlazeRule.java
@@ -0,0 +1,55 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType; +import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation for rule classes. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface BlazeRule { + /** + * The name of the rule, as it appears in the BUILD file. If it starts with + * '$', the rule will be hidden from users and will only be usable from + * inside Blaze. + */ + String name(); + + /** + * The type of the rule. It can be an abstract rule, a normal rule or a test + * rule. If the rule type is abstract, the configured class must not be set. + */ + RuleClassType type() default RuleClassType.NORMAL; + + /** + * The {@link RuleConfiguredTargetFactory} class that implements this rule. If the rule is + * abstract, this must not be set. + */ + Class<? extends RuleConfiguredTargetFactory> factoryClass() + default RuleConfiguredTargetFactory.class; + + /** + * The list of other rule classes this rule inherits from. + */ + Class<? extends RuleDefinition>[] ancestors() default {}; +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BlazeVersionInfo.java b/src/main/java/com/google/devtools/build/lib/analysis/BlazeVersionInfo.java new file mode 100644 index 0000000..d5b5f94 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/BlazeVersionInfo.java
@@ -0,0 +1,113 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.devtools.build.lib.util.StringUtilities; + +import java.util.Map; +import java.util.logging.Logger; + +/** + * Determines the version information of the current process. + * + * <p>The version information is a dictionary mapping from string keys to string values. For + * build stamping, it should have the key "Build label", which contains among others a + * XXXXXXX-YYYY.MM.DD string to indicate the version of the release. If no data is available + * (eg. when running non-released version), {@link #isAvailable()} returns false. + */ +public class BlazeVersionInfo { + private final Map<String, String> buildData = Maps.newTreeMap(); + private static BlazeVersionInfo instance = null; + private static final String BUILD_LABEL = "Build label"; + + private static final Logger LOG = Logger.getLogger(BlazeVersionInfo.class.getName()); + + public BlazeVersionInfo(Map<String, String> info) { + buildData.putAll(info); + } + + /** + * Accessor method for BlazeVersionInfo singleton. + * + * <p>If setBuildInfo was not called, returns an empty BlazeVersionInfo instance, which should + * not be persisted. + */ + public static synchronized BlazeVersionInfo instance() { + if (instance == null) { + return new BlazeVersionInfo(ImmutableMap.<String, String>of()); + } + return instance; + } + + private static void logVersionInfo(BlazeVersionInfo info) { + if (info.getSummary() == null) { + LOG.warning("Blaze release version information not available"); + } else { + LOG.info("Blaze version info: " + info.getSummary()); + } + } + + /** + * Sets build info. + * + * <p>This should be called once in the program execution, as early soon as possible, so we + * can have the version information even before modules are initialized. + */ + public static synchronized void setBuildInfo(Map<String, String> info) { + if (instance != null) { + throw new IllegalStateException("setBuildInfo called twice."); + } + instance = new BlazeVersionInfo(info); + logVersionInfo(instance); + } + + /** + * Indicates whether version information is available. + */ + public boolean isAvailable() { + return !buildData.isEmpty(); + } + + /** + * Returns the summary which gets displayed in the 'version' command. + * The summary is a list of formatted key / value pairs. + */ + public String getSummary() { + if (buildData.isEmpty()) { + return null; + } + return StringUtilities.layoutTable(buildData); + } + + /** + * Returns true iff this binary is released--that is, a + * binary built with a release label. + */ + public boolean isReleasedBlaze() { + String buildLabel = buildData.get(BUILD_LABEL); + return buildLabel != null && buildLabel.length() > 0; + } + + /** + * Returns the release label, if any, or "development version". + */ + public String getReleaseName() { + String buildLabel = buildData.get(BUILD_LABEL); + return (buildLabel != null && buildLabel.length() > 0) + ? "release " + buildLabel + : "development version"; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildInfoEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildInfoEvent.java new file mode 100644 index 0000000..59d1514 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildInfoEvent.java
@@ -0,0 +1,40 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +/** + * This event is fired once build info data is available. + */ +public final class BuildInfoEvent { + private final Map<String, String> buildInfoMap; + + /** + * Construct the event from a map. + */ + public BuildInfoEvent(Map<String, String> buildInfo) { + buildInfoMap = ImmutableMap.copyOf(buildInfo); + } + + /** + * Return immutable map populated with build info key/value pairs. + */ + public Map<String, String> getBuildInfoMap() { + return buildInfoMap; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildInfoHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildInfoHelper.java new file mode 100644 index 0000000..755df9f --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildInfoHelper.java
@@ -0,0 +1,31 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.actions.AbstractActionOwner; +import com.google.devtools.build.lib.actions.ActionOwner; + +// TODO(bazel-team): move BUILD_INFO_ACTION_OWNER somewhere else and remove this class. +/** + * Helper class for the CompatibleWriteBuildInfoAction, which holds the + * methods for generating build information. + * Abstracted away to allow non-action code to also generate build info under + * --nobuild or --check_up_to_date. + */ +public abstract class BuildInfoHelper { + /** ActionOwner for BuildInfoActions. */ + public static final ActionOwner BUILD_INFO_ACTION_OWNER = + AbstractActionOwner.SYSTEM_ACTION_OWNER; +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java new file mode 100644 index 0000000..052d0a2 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
@@ -0,0 +1,1056 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimaps; +import com.google.common.collect.Sets; +import com.google.common.eventbus.EventBus; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.ActionGraph; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.ArtifactFactory; +import com.google.devtools.build.lib.actions.PackageRootResolver; +import com.google.devtools.build.lib.actions.Root; +import com.google.devtools.build.lib.analysis.DependencyResolver.Dependency; +import com.google.devtools.build.lib.analysis.ExtraActionArtifactsProvider.ExtraArtifactSet; +import com.google.devtools.build.lib.analysis.config.BinTools; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection; +import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible; +import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; +import com.google.devtools.build.lib.events.DelegatingEventHandler; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.events.EventKind; +import com.google.devtools.build.lib.events.StoredEventHandler; +import com.google.devtools.build.lib.events.WarningsAsErrorsEventHandler; +import com.google.devtools.build.lib.packages.Attribute; +import com.google.devtools.build.lib.packages.NoSuchPackageException; +import com.google.devtools.build.lib.packages.NoSuchTargetException; +import com.google.devtools.build.lib.packages.NoSuchThingException; +import com.google.devtools.build.lib.packages.PackageIdentifier; +import com.google.devtools.build.lib.packages.PackageSpecification; +import com.google.devtools.build.lib.packages.RawAttributeMapper; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.packages.TargetUtils; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner.LoadingResult; +import com.google.devtools.build.lib.pkgcache.PackageManager; +import com.google.devtools.build.lib.rules.test.CoverageReportActionFactory; +import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; +import com.google.devtools.build.lib.skyframe.CoverageReportValue; +import com.google.devtools.build.lib.skyframe.SkyframeBuildView; +import com.google.devtools.build.lib.skyframe.SkyframeExecutor; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.util.RegexFilter; +import com.google.devtools.build.lib.vfs.Path; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.skyframe.SkyKey; +import com.google.devtools.common.options.Option; +import com.google.devtools.common.options.OptionsBase; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; + +import javax.annotation.Nullable; + +/** + * <p>The BuildView presents a semantically-consistent and transitively-closed + * dependency graph for some set of packages. + * + * <h2>Package design</h2> + * + * <p>This package contains the Blaze dependency analysis framework (aka + * "analysis phase"). The goal of this code is to perform semantic analysis of + * all of the build targets required for a given build, to report + * errors/warnings for any problems in the input, and to construct an "action + * graph" (see {@code lib.actions} package) correctly representing the work to + * be done during the execution phase of the build. + * + * <p><b>Configurations</b> the inputs to a build come from two sources: the + * intrinsic inputs, specified in the BUILD file, are called <em>targets</em>. + * The environmental inputs, coming from the build tool, the command-line, or + * configuration files, are called the <em>configuration</em>. Only when a + * target and a configuration are combined is there sufficient information to + * perform a build. </p> + * + * <p>Targets are implemented by the {@link Target} hierarchy in the {@code + * lib.packages} code. Configurations are implemented by {@link + * BuildConfiguration}. The pair of these together is represented by an + * instance of class {@link ConfiguredTarget}; this is the root of a hierarchy + * with different implementations for each kind of target: source file, derived + * file, rules, etc. + * + * <p>The framework code in this package (as opposed to its subpackages) is + * responsible for constructing the {@code ConfiguredTarget} graph for a given + * target and configuration, taking care of such issues as: + * <ul> + * <li>caching common subgraphs. + * <li>detecting and reporting cycles. + * <li>correct propagation of errors through the graph. + * <li>reporting universal errors, such as dependencies from production code + * to tests, or to experimental branches. + * <li>capturing and replaying errors. + * <li>maintaining the graph from one build to the next to + * avoid unnecessary recomputation. + * <li>checking software licenses. + * </ul> + * + * <p>See also {@link ConfiguredTarget} which documents some important + * invariants. + */ +public class BuildView { + + /** + * Options that affect the <i>mechanism</i> of analysis. These are distinct from {@link + * com.google.devtools.build.lib.analysis.config.BuildOptions}, which affect the <i>value</i> + * of a BuildConfiguration. + */ + public static class Options extends OptionsBase { + + @Option(name = "keep_going", + abbrev = 'k', + defaultValue = "false", + category = "strategy", + help = "Continue as much as possible after an error. While the " + + "target that failed, and those that depend on it, cannot be " + + "analyzed (or built), the other prerequisites of these " + + "targets can be analyzed (or built) all the same.") + public boolean keepGoing; + + @Option(name = "analysis_warnings_as_errors", + defaultValue = "false", + category = "strategy", + help = "Treat visible analysis warnings as errors.") + public boolean analysisWarningsAsErrors; + + @Option(name = "discard_analysis_cache", + defaultValue = "false", + category = "strategy", + help = "Discard the analysis cache immediately after the analysis phase completes. " + + "Reduces memory usage by ~10%, but makes further incremental builds slower.") + public boolean discardAnalysisCache; + + @Option(name = "keep_forward_graph", + deprecationWarning = "keep_forward_graph is now a no-op and will be removed in an " + + "upcoming Blaze release", + defaultValue = "false", + category = "undocumented", + help = "Cache the forward action graph across builds for faster " + + "incremental rebuilds. May slightly increase memory while Blaze " + + "server is idle." + ) + public boolean keepForwardGraph; + + @Option(name = "experimental_extra_action_filter", + defaultValue = "", + category = "experimental", + converter = RegexFilter.RegexFilterConverter.class, + help = "Filters set of targets to schedule extra_actions for.") + public RegexFilter extraActionFilter; + + @Option(name = "experimental_extra_action_top_level_only", + defaultValue = "false", + category = "experimental", + help = "Only schedules extra_actions for top level targets.") + public boolean extraActionTopLevelOnly; + + @Option(name = "version_window_for_dirty_node_gc", + defaultValue = "0", + category = "undocumented", + help = "Nodes that have been dirty for more than this many versions will be deleted" + + " from the graph upon the next update. Values must be non-negative long integers," + + " or -1 indicating the maximum possible window.") + public long versionWindowForDirtyNodeGc; + } + + private static Logger LOG = Logger.getLogger(BuildView.class.getName()); + + private final BlazeDirectories directories; + + private final SkyframeExecutor skyframeExecutor; + private final SkyframeBuildView skyframeBuildView; + + private final PackageManager packageManager; + + private final BinTools binTools; + + private BuildConfigurationCollection configurations; + + private final ConfiguredRuleClassProvider ruleClassProvider; + + private final ArtifactFactory artifactFactory; + + /** + * A factory class to create the coverage report action. May be null. + */ + @Nullable private final CoverageReportActionFactory coverageReportActionFactory; + + /** + * A union of package roots of all previous incremental analysis results. This is used to detect + * changes of package roots between incremental analysis instances. + */ + private final Map<PackageIdentifier, Path> cumulativePackageRoots = new HashMap<>(); + + /** + * Used only for testing that we clear Skyframe caches correctly. + * TODO(bazel-team): Remove this once we get rid of legacy Skyframe synchronization. + */ + private boolean skyframeCacheWasInvalidated = false; + + /** + * If the last build was executed with {@code Options#discard_analysis_cache} and we are not + * running Skyframe full, we should clear the legacy data since it is out-of-sync. + */ + private boolean skyframeAnalysisWasDiscarded = false; + + @VisibleForTesting + public Set<SkyKey> getSkyframeEvaluatedTargetKeysForTesting() { + return skyframeBuildView.getEvaluatedTargetKeys(); + } + + /** The number of targets freshly evaluated in the last analysis run. */ + public int getTargetsVisited() { + return skyframeBuildView.getEvaluatedTargetKeys().size(); + } + + /** + * Returns true iff Skyframe was invalidated during the analysis phase. + * TODO(bazel-team): Remove this once we do not need to keep legacy in sync with Skyframe. + */ + @VisibleForTesting + boolean wasSkyframeCacheInvalidatedDuringAnalysis() { + return skyframeCacheWasInvalidated; + } + + public BuildView(BlazeDirectories directories, PackageManager packageManager, + ConfiguredRuleClassProvider ruleClassProvider, + SkyframeExecutor skyframeExecutor, + BinTools binTools, CoverageReportActionFactory coverageReportActionFactory) { + this.directories = directories; + this.packageManager = packageManager; + this.binTools = binTools; + this.coverageReportActionFactory = coverageReportActionFactory; + this.artifactFactory = new ArtifactFactory(directories.getExecRoot()); + this.ruleClassProvider = ruleClassProvider; + this.skyframeExecutor = Preconditions.checkNotNull(skyframeExecutor); + this.skyframeBuildView = + new SkyframeBuildView( + new ConfiguredTargetFactory(ruleClassProvider), + artifactFactory, + skyframeExecutor, + new Runnable() { + @Override + public void run() { + clear(); + } + }, + binTools); + skyframeExecutor.setSkyframeBuildView(skyframeBuildView); + } + + /** Returns the action graph. */ + public ActionGraph getActionGraph() { + return new ActionGraph() { + @Override + public Action getGeneratingAction(Artifact artifact) { + return skyframeExecutor.getGeneratingAction(artifact); + } + }; + } + + /** + * Returns whether the given configured target has errors. + */ + @VisibleForTesting + public boolean hasErrors(ConfiguredTarget configuredTarget) { + return configuredTarget == null; + } + + /** + * Sets the configurations. Not thread-safe. DO NOT CALL except from tests! + */ + @VisibleForTesting + void setConfigurationsForTesting(BuildConfigurationCollection configurations) { + this.configurations = configurations; + } + + public BuildConfigurationCollection getConfigurationCollection() { + return configurations; + } + + /** + * Clear the graphs of ConfiguredTargets and Artifacts. + */ + @VisibleForTesting + public void clear() { + cumulativePackageRoots.clear(); + artifactFactory.clear(); + } + + public ArtifactFactory getArtifactFactory() { + return artifactFactory; + } + + @VisibleForTesting + WorkspaceStatusAction getLastWorkspaceBuildInfoActionForTesting() { + return skyframeExecutor.getLastWorkspaceStatusActionForTesting(); + } + + /** + * Returns a corresponding ConfiguredTarget, if one exists; otherwise throws an {@link + * NoSuchConfiguredTargetException}. + */ + @ThreadSafe + private ConfiguredTarget getConfiguredTarget(Target target, BuildConfiguration config) + throws NoSuchConfiguredTargetException { + ConfiguredTarget result = + getExistingConfiguredTarget(target.getLabel(), config); + if (result == null) { + throw new NoSuchConfiguredTargetException(target.getLabel(), config); + } + return result; + } + + /** + * Obtains a {@link ConfiguredTarget} given a {@code label}, by delegating + * to the package cache and + * {@link #getConfiguredTarget(Target, BuildConfiguration)}. + */ + public ConfiguredTarget getConfiguredTarget(Label label, BuildConfiguration config) + throws NoSuchPackageException, NoSuchTargetException, NoSuchConfiguredTargetException { + return getConfiguredTarget(packageManager.getLoadedTarget(label), config); + } + + public Iterable<ConfiguredTarget> getDirectPrerequisites(ConfiguredTarget ct) { + return getDirectPrerequisites(ct, null); + } + + public Iterable<ConfiguredTarget> getDirectPrerequisites(ConfiguredTarget ct, + @Nullable final LoadingCache<Label, Target> targetCache) { + if (!(ct.getTarget() instanceof Rule)) { + return ImmutableList.of(); + } + + class SilentDependencyResolver extends DependencyResolver { + @Override + protected void invalidVisibilityReferenceHook(TargetAndConfiguration node, Label label) { + // The error must have been reported already during analysis. + } + + @Override + protected void invalidPackageGroupReferenceHook(TargetAndConfiguration node, Label label) { + // The error must have been reported already during analysis. + } + + @Override + protected Target getTarget(Label label) throws NoSuchThingException { + if (targetCache == null) { + return packageManager.getLoadedTarget(label); + } + + try { + return targetCache.get(label); + } catch (ExecutionException e) { + // All lookups should succeed because we should not be looking up any targets in error. + throw new IllegalStateException(e); + } + } + } + + DependencyResolver dependencyResolver = new SilentDependencyResolver(); + TargetAndConfiguration ctgNode = + new TargetAndConfiguration(ct.getTarget(), ct.getConfiguration()); + return skyframeExecutor.getConfiguredTargets( + dependencyResolver.dependentNodes(ctgNode, getConfigurableAttributeKeys(ctgNode))); + } + + /** + * Returns ConfigMatchingProvider instances corresponding to the configurable attribute keys + * present in this rule's attributes. + */ + private Set<ConfigMatchingProvider> getConfigurableAttributeKeys(TargetAndConfiguration ctg) { + if (!(ctg.getTarget() instanceof Rule)) { + return ImmutableSet.of(); + } + Rule rule = (Rule) ctg.getTarget(); + ImmutableSet.Builder<ConfigMatchingProvider> keys = ImmutableSet.builder(); + RawAttributeMapper mapper = RawAttributeMapper.of(rule); + for (Attribute attribute : rule.getAttributes()) { + for (Label label : mapper.getConfigurabilityKeys(attribute.getName(), attribute.getType())) { + if (Type.Selector.isReservedLabel(label)) { + continue; + } + try { + ConfiguredTarget ct = getConfiguredTarget(label, ctg.getConfiguration()); + keys.add(Preconditions.checkNotNull(ct.getProvider(ConfigMatchingProvider.class))); + } catch (NoSuchPackageException e) { + // All lookups should succeed because we should not be looking up any targets in error. + throw new IllegalStateException(e); + } catch (NoSuchTargetException e) { + // All lookups should succeed because we should not be looking up any targets in error. + throw new IllegalStateException(e); + } catch (NoSuchConfiguredTargetException e) { + // All lookups should succeed because we should not be looking up any targets in error. + throw new IllegalStateException(e); + } + } + } + return keys.build(); + } + + public TransitiveInfoCollection getGeneratingRule(OutputFileConfiguredTarget target) { + return target.getGeneratingRule(); + } + + @Override + public int hashCode() { + throw new UnsupportedOperationException(); // avoid nondeterminism + } + + /** + * Return value for {@link BuildView#update} and {@code BuildTool.prepareToBuild}. + */ + public static final class AnalysisResult { + + public static final AnalysisResult EMPTY = new AnalysisResult( + ImmutableList.<ConfiguredTarget>of(), null, null, null, + ImmutableList.<Artifact>of(), + ImmutableList.<ConfiguredTarget>of(), + ImmutableList.<ConfiguredTarget>of(), + null); + + private final ImmutableList<ConfiguredTarget> targetsToBuild; + @Nullable private final ImmutableList<ConfiguredTarget> targetsToTest; + @Nullable private final String error; + private final ActionGraph actionGraph; + private final ImmutableSet<Artifact> artifactsToBuild; + private final ImmutableSet<ConfiguredTarget> parallelTests; + private final ImmutableSet<ConfiguredTarget> exclusiveTests; + @Nullable private final TopLevelArtifactContext topLevelContext; + + private AnalysisResult( + Collection<ConfiguredTarget> targetsToBuild, Collection<ConfiguredTarget> targetsToTest, + @Nullable String error, ActionGraph actionGraph, + Collection<Artifact> artifactsToBuild, Collection<ConfiguredTarget> parallelTests, + Collection<ConfiguredTarget> exclusiveTests, TopLevelArtifactContext topLevelContext) { + this.targetsToBuild = ImmutableList.copyOf(targetsToBuild); + this.targetsToTest = targetsToTest == null ? null : ImmutableList.copyOf(targetsToTest); + this.error = error; + this.actionGraph = actionGraph; + this.artifactsToBuild = ImmutableSet.copyOf(artifactsToBuild); + this.parallelTests = ImmutableSet.copyOf(parallelTests); + this.exclusiveTests = ImmutableSet.copyOf(exclusiveTests); + this.topLevelContext = topLevelContext; + } + + /** + * Returns configured targets to build. + */ + public Collection<ConfiguredTarget> getTargetsToBuild() { + return targetsToBuild; + } + + /** + * Returns the configured targets to run as tests, or {@code null} if testing was not + * requested (e.g. "build" command rather than "test" command). + */ + @Nullable + public Collection<ConfiguredTarget> getTargetsToTest() { + return targetsToTest; + } + + public ImmutableSet<Artifact> getAdditionalArtifactsToBuild() { + return artifactsToBuild; + } + + public ImmutableSet<ConfiguredTarget> getExclusiveTests() { + return exclusiveTests; + } + + public ImmutableSet<ConfiguredTarget> getParallelTests() { + return parallelTests; + } + + /** + * Returns an error description (if any). + */ + @Nullable public String getError() { + return error; + } + + /** + * Returns the action graph. + */ + public ActionGraph getActionGraph() { + return actionGraph; + } + + public TopLevelArtifactContext getTopLevelContext() { + return topLevelContext; + } + } + + + /** + * Returns the collection of configured targets corresponding to any of the provided targets. + */ + @VisibleForTesting + static Iterable<? extends ConfiguredTarget> filterTestsByTargets( + Collection<? extends ConfiguredTarget> targets, + final Set<? extends Target> allowedTargets) { + return Iterables.filter(targets, + new Predicate<ConfiguredTarget>() { + @Override + public boolean apply(ConfiguredTarget rule) { + return allowedTargets.contains(rule.getTarget()); + } + }); + } + + private void prepareToBuild(PackageRootResolver resolver) throws ViewCreationFailedException { + for (BuildConfiguration config : configurations.getTargetConfigurations()) { + config.prepareToBuild(directories.getExecRoot(), getArtifactFactory(), resolver); + } + } + + @ThreadCompatible + public AnalysisResult update(LoadingResult loadingResult, + BuildConfigurationCollection configurations, Options viewOptions, + TopLevelArtifactContext topLevelOptions, EventHandler eventHandler, EventBus eventBus) + throws ViewCreationFailedException, InterruptedException { + + // Detect errors during analysis and don't attempt a build. + // + // (Errors reported during the previous step, package loading, that do + // not cause the visitation of the transitive closure to abort, are + // recoverable. For example, an error encountered while evaluating an + // irrelevant rule in a visited package causes an error to be reported, + // but visitation still succeeds.) + ErrorCollector errorCollector = null; + if (!viewOptions.keepGoing) { + eventHandler = errorCollector = new ErrorCollector(eventHandler); + } + + // Treat analysis warnings as errors, to enable strict builds. + // + // Warnings reported during analysis are converted to errors, ultimately + // triggering failure. This check needs to be added after the keep-going check + // above so that it is invoked first (FIFO eventHandler chain). This way, detected + // warnings are converted to errors first, and then the proper error handling + // logic is invoked. + WarningsAsErrorsEventHandler warningsHandler = null; + if (viewOptions.analysisWarningsAsErrors) { + eventHandler = warningsHandler = new WarningsAsErrorsEventHandler(eventHandler); + } + + skyframeBuildView.setWarningListener(eventHandler); + skyframeExecutor.setErrorEventListener(eventHandler); + + LOG.info("Starting analysis"); + pollInterruptedStatus(); + + skyframeBuildView.resetEvaluatedConfiguredTargetKeysSet(); + + Collection<Target> targets = loadingResult.getTargets(); + eventBus.post(new AnalysisPhaseStartedEvent(targets)); + + skyframeCacheWasInvalidated = false; + // Clear all cached ConfiguredTargets on configuration change. We need to do this explicitly + // because we need to make sure that the legacy action graph does not contain multiple actions + // with different versions of the same (target/host/etc.) configuration. + // In the future the action graph will be probably be keyed by configurations, which should + // obviate the need for this workaround. + // + // Also if --discard_analysis_cache was used in the last build we want to clear the legacy + // data. + if ((this.configurations != null && !configurations.equals(this.configurations)) + || skyframeAnalysisWasDiscarded) { + skyframeExecutor.dropConfiguredTargets(); + skyframeCacheWasInvalidated = true; + clear(); + } + skyframeAnalysisWasDiscarded = false; + ImmutableMap<PackageIdentifier, Path> packageRoots = loadingResult.getPackageRoots(); + + if (buildHasIncompatiblePackageRoots(packageRoots)) { + // When a package root changes source artifacts with the new root will be created, but we + // cannot be sure that there are no references remaining to the corresponding artifacts + // with the old root. To avoid that scenario, the analysis cache is simply dropped when + // a package root change is detected. + LOG.info("Discarding analysis cache: package roots have changed."); + + skyframeExecutor.dropConfiguredTargets(); + skyframeCacheWasInvalidated = true; + clear(); + } + cumulativePackageRoots.putAll(packageRoots); + this.configurations = configurations; + setArtifactRoots(packageRoots); + + // Determine the configurations. + List<TargetAndConfiguration> nodes = nodesForTargets(targets); + + List<ConfiguredTargetKey> targetSpecs = + Lists.transform(nodes, new Function<TargetAndConfiguration, ConfiguredTargetKey>() { + @Override + public ConfiguredTargetKey apply(TargetAndConfiguration node) { + return new ConfiguredTargetKey(node.getLabel(), node.getConfiguration()); + } + }); + + prepareToBuild(new SkyframePackageRootResolver(skyframeExecutor)); + skyframeBuildView.setWarningListener(warningsHandler); + skyframeExecutor.injectWorkspaceStatusData(); + Collection<ConfiguredTarget> configuredTargets; + try { + configuredTargets = skyframeBuildView.configureTargets( + targetSpecs, eventBus, viewOptions.keepGoing); + } finally { + skyframeBuildView.clearInvalidatedConfiguredTargets(); + } + + int numTargetsToAnalyze = nodes.size(); + int numSuccessful = configuredTargets.size(); + boolean analysisSuccessful = (numSuccessful == numTargetsToAnalyze); + if (0 < numSuccessful && numSuccessful < numTargetsToAnalyze) { + String msg = String.format("Analysis succeeded for only %d of %d top-level targets", + numSuccessful, numTargetsToAnalyze); + eventHandler.handle(Event.info(msg)); + LOG.info(msg); + } + + postUpdateValidation(errorCollector, warningsHandler); + + AnalysisResult result = createResult(loadingResult, topLevelOptions, + viewOptions, configuredTargets, analysisSuccessful); + LOG.info("Finished analysis"); + return result; + } + + // Validates that the update has been done correctly + private void postUpdateValidation(ErrorCollector errorCollector, + WarningsAsErrorsEventHandler warningsHandler) throws ViewCreationFailedException { + if (warningsHandler != null && warningsHandler.warningsEncountered()) { + throw new ViewCreationFailedException("Warnings being treated as errors"); + } + + if (errorCollector != null && !errorCollector.getEvents().isEmpty()) { + // This assertion ensures that if any errors were reported during the + // initialization phase, the call to configureTargets will fail with a + // ViewCreationFailedException. Violation of this invariant leads to + // incorrect builds, because the fact that errors were encountered is not + // properly recorded in the view (i.e. the graph of configured targets). + // Rule errors must be reported via RuleConfiguredTarget.reportError, + // which causes the rule's hasErrors() flag to be set, and thus the + // hasErrors() flag of anything that depends on it transitively. If the + // toplevel rule hasErrors, then analysis is aborted and we do not + // proceed to the execution phase of a build. + // + // Reporting errors directly through the Reporter does not set the error + // flag, so analysis may succeed spuriously, allowing the execution + // phase to begin with unpredictable consequences. + // + // The use of errorCollector (rather than an ErrorSensor) makes the + // assertion failure messages more informative. + // Note we tolerate errors iff --keep-going, because some of the + // requested targets may have had problems during analysis, but that's ok. + StringBuilder message = new StringBuilder("Unexpected errors reported during analysis:"); + for (Event event : errorCollector.getEvents()) { + message.append('\n').append(event); + } + throw new IllegalStateException(message.toString()); + } + } + + /** + * Skyframe implementation of {@link PackageRootResolver}. + * + * <p> Note: you should not use this class inside of any SkyFunction. + */ + @VisibleForTesting + public static final class SkyframePackageRootResolver implements PackageRootResolver { + private final SkyframeExecutor executor; + + public SkyframePackageRootResolver(SkyframeExecutor executor) { + this.executor = executor; + } + + @Override + public Map<PathFragment, Root> findPackageRoots(Iterable<PathFragment> execPaths) { + return executor.getArtifactRoots(execPaths); + } + } + + private AnalysisResult createResult(LoadingResult loadingResult, + TopLevelArtifactContext topLevelOptions, BuildView.Options viewOptions, + Collection<ConfiguredTarget> configuredTargets, boolean analysisSuccessful) + throws InterruptedException { + Collection<Target> testsToRun = loadingResult.getTestsToRun(); + Collection<ConfiguredTarget> allTargetsToTest = null; + if (testsToRun != null) { + // Determine the subset of configured targets that are meant to be run as tests. + allTargetsToTest = Lists.newArrayList( + filterTestsByTargets(configuredTargets, Sets.newHashSet(testsToRun))); + } + + skyframeExecutor.injectTopLevelContext(topLevelOptions); + + Set<Artifact> artifactsToBuild = new HashSet<>(); + Set<ConfiguredTarget> parallelTests = new HashSet<>(); + Set<ConfiguredTarget> exclusiveTests = new HashSet<>(); + Collection<Artifact> buildInfoArtifacts; + buildInfoArtifacts = skyframeExecutor.getWorkspaceStatusArtifacts(); + // build-info and build-changelist. + Preconditions.checkState(buildInfoArtifacts.size() == 2, buildInfoArtifacts); + artifactsToBuild.addAll(buildInfoArtifacts); + addExtraActionsIfRequested(viewOptions, artifactsToBuild, configuredTargets); + if (coverageReportActionFactory != null) { + Action action = coverageReportActionFactory.createCoverageReportAction( + allTargetsToTest, + getBaselineCoverageArtifacts(configuredTargets), + artifactFactory, + CoverageReportValue.ARTIFACT_OWNER); + if (action != null) { + skyframeExecutor.injectCoverageReportData(action); + artifactsToBuild.addAll(action.getOutputs()); + } + } + + // Note that this must come last, so that the tests are scheduled after all artifacts are built. + scheduleTestsIfRequested(parallelTests, exclusiveTests, topLevelOptions, allTargetsToTest); + + String error = !loadingResult.hasLoadingError() + ? (analysisSuccessful + ? null + : "execution phase succeeded, but not all targets were analyzed") + : "execution phase succeeded, but there were loading phase errors"; + return new AnalysisResult(configuredTargets, allTargetsToTest, error, getActionGraph(), + artifactsToBuild, parallelTests, exclusiveTests, topLevelOptions); + } + + private static ImmutableSet<Artifact> getBaselineCoverageArtifacts( + Collection<ConfiguredTarget> configuredTargets) { + Set<Artifact> baselineCoverageArtifacts = Sets.newHashSet(); + for (ConfiguredTarget target : configuredTargets) { + BaselineCoverageArtifactsProvider provider = + target.getProvider(BaselineCoverageArtifactsProvider.class); + if (provider != null) { + baselineCoverageArtifacts.addAll(provider.getBaselineCoverageArtifacts()); + } + } + return ImmutableSet.copyOf(baselineCoverageArtifacts); + } + + private void addExtraActionsIfRequested(BuildView.Options viewOptions, + Set<Artifact> artifactsToBuild, Iterable<ConfiguredTarget> topLevelTargets) { + NestedSetBuilder<ExtraArtifactSet> builder = NestedSetBuilder.stableOrder(); + for (ConfiguredTarget topLevel : topLevelTargets) { + ExtraActionArtifactsProvider provider = topLevel.getProvider( + ExtraActionArtifactsProvider.class); + if (provider != null) { + if (viewOptions.extraActionTopLevelOnly) { + builder.add(ExtraArtifactSet.of(topLevel.getLabel(), provider.getExtraActionArtifacts())); + } else { + builder.addTransitive(provider.getTransitiveExtraActionArtifacts()); + } + } + } + + RegexFilter filter = viewOptions.extraActionFilter; + for (ExtraArtifactSet set : builder.build()) { + boolean filterMatches = filter == null || filter.isIncluded(set.getLabel().toString()); + if (filterMatches) { + Iterables.addAll(artifactsToBuild, set.getArtifacts()); + } + } + } + + private static void scheduleTestsIfRequested(Collection<ConfiguredTarget> targetsToTest, + Collection<ConfiguredTarget> targetsToTestExclusive, TopLevelArtifactContext topLevelOptions, + Collection<ConfiguredTarget> allTestTargets) { + if (!topLevelOptions.compileOnly() && !topLevelOptions.compilationPrerequisitesOnly() + && allTestTargets != null) { + scheduleTests(targetsToTest, targetsToTestExclusive, allTestTargets, + topLevelOptions.runTestsExclusively()); + } + } + + + /** + * Returns set of artifacts representing test results, writing into targetsToTest and + * targetsToTestExclusive. + */ + private static void scheduleTests(Collection<ConfiguredTarget> targetsToTest, + Collection<ConfiguredTarget> targetsToTestExclusive, + Collection<ConfiguredTarget> allTestTargets, + boolean isExclusive) { + for (ConfiguredTarget target : allTestTargets) { + if (target.getTarget() instanceof Rule) { + boolean exclusive = + isExclusive || TargetUtils.isExclusiveTestRule((Rule) target.getTarget()); + Collection<ConfiguredTarget> testCollection = exclusive + ? targetsToTestExclusive + : targetsToTest; + testCollection.add(target); + } + } + } + + @VisibleForTesting + List<TargetAndConfiguration> nodesForTargets(Collection<Target> targets) { + // We use a hash set here to remove duplicate nodes; this can happen for input files and package + // groups. + LinkedHashSet<TargetAndConfiguration> nodes = new LinkedHashSet<>(targets.size()); + for (BuildConfiguration config : configurations.getTargetConfigurations()) { + for (Target target : targets) { + nodes.add(new TargetAndConfiguration(target, + BuildConfigurationCollection.configureTopLevelTarget(config, target))); + } + } + return ImmutableList.copyOf(nodes); + } + + /** + * Detects when a package root changes between instances of incremental analysis. + * + * <p>This case is currently problematic for incremental analysis because when a package root + * changes, source artifacts with the new root will be created, but we can not be sure that there + * are no references remaining to the corresponding artifacts with the old root. + */ + private boolean buildHasIncompatiblePackageRoots(Map<PackageIdentifier, Path> packageRoots) { + for (Map.Entry<PackageIdentifier, Path> entry : packageRoots.entrySet()) { + Path prevRoot = cumulativePackageRoots.get(entry.getKey()); + if (prevRoot != null && !entry.getValue().equals(prevRoot)) { + return true; + } + } + return false; + } + + /** + * Returns an existing ConfiguredTarget for the specified target and + * configuration, or null if none exists. No validity check is done. + */ + @ThreadSafe + public ConfiguredTarget getExistingConfiguredTarget(Target target, BuildConfiguration config) { + return getExistingConfiguredTarget(target.getLabel(), config); + } + + /** + * Returns an existing ConfiguredTarget for the specified node, or null if none exists. No + * validity check is done. + */ + @ThreadSafe + private ConfiguredTarget getExistingConfiguredTarget( + Label label, BuildConfiguration configuration) { + return Iterables.getFirst( + skyframeExecutor.getConfiguredTargets( + ImmutableList.of(new Dependency(label, configuration))), + null); + } + + @VisibleForTesting + ListMultimap<Attribute, ConfiguredTarget> getPrerequisiteMapForTesting(ConfiguredTarget target) { + DependencyResolver resolver = new DependencyResolver() { + @Override + protected void invalidVisibilityReferenceHook(TargetAndConfiguration node, Label label) { + throw new RuntimeException("bad visibility on " + label + " during testing unexpected"); + } + + @Override + protected void invalidPackageGroupReferenceHook(TargetAndConfiguration node, Label label) { + throw new RuntimeException("bad package group on " + label + " during testing unexpected"); + } + + @Override + protected Target getTarget(Label label) throws NoSuchThingException { + return packageManager.getLoadedTarget(label); + } + }; + TargetAndConfiguration ctNode = new TargetAndConfiguration(target); + ListMultimap<Attribute, Dependency> depNodeNames; + try { + depNodeNames = resolver.dependentNodeMap(ctNode, null, getConfigurableAttributeKeys(ctNode)); + } catch (EvalException e) { + throw new IllegalStateException(e); + } + + final Map<LabelAndConfiguration, ConfiguredTarget> depMap = new HashMap<>(); + for (ConfiguredTarget dep : skyframeExecutor.getConfiguredTargets(depNodeNames.values())) { + depMap.put(LabelAndConfiguration.of(dep.getLabel(), dep.getConfiguration()), dep); + } + + return Multimaps.transformValues(depNodeNames, new Function<Dependency, ConfiguredTarget>() { + @Override + public ConfiguredTarget apply(Dependency depName) { + return depMap.get(LabelAndConfiguration.of(depName.getLabel(), + depName.getConfiguration())); + } + }); + } + + /** + * Sets the possible artifact roots in the artifact factory. This allows the + * factory to resolve paths with unknown roots to artifacts. + * <p> + * <em>Note: This must be called before any call to + * {@link #getConfiguredTarget(Label, BuildConfiguration)} + * </em> + */ + @VisibleForTesting // for BuildViewTestCase + void setArtifactRoots(ImmutableMap<PackageIdentifier, Path> packageRoots) { + Map<Path, Root> rootMap = new HashMap<>(); + Map<PackageIdentifier, Root> realPackageRoots = new HashMap<>(); + for (Map.Entry<PackageIdentifier, Path> entry : packageRoots.entrySet()) { + Root root = rootMap.get(entry.getValue()); + if (root == null) { + root = Root.asSourceRoot(entry.getValue()); + rootMap.put(entry.getValue(), root); + } + realPackageRoots.put(entry.getKey(), root); + } + // Source Artifact roots: + artifactFactory.setPackageRoots(realPackageRoots); + + // Derived Artifact roots: + ImmutableList.Builder<Root> roots = ImmutableList.builder(); + + // build-info.txt and friends; this root is not configuration specific. + roots.add(directories.getBuildDataDirectory()); + + // The roots for each configuration - duplicates are automatically removed in the call below. + for (BuildConfiguration cfg : configurations.getAllConfigurations()) { + roots.addAll(cfg.getRoots()); + } + + artifactFactory.setDerivedArtifactRoots(roots.build()); + } + + /** + * Returns a configured target for the specified target and configuration. + * This should only be called from test cases, and is needed, because + * plain {@link #getConfiguredTarget(Target, BuildConfiguration)} does not + * construct the configured target graph, and would thus fail if called from + * outside an update. + */ + @VisibleForTesting + public ConfiguredTarget getConfiguredTargetForTesting(Label label, BuildConfiguration config) + throws NoSuchPackageException, NoSuchTargetException { + return getConfiguredTargetForTesting(packageManager.getLoadedTarget(label), config); + } + + @VisibleForTesting + public ConfiguredTarget getConfiguredTargetForTesting(Target target, BuildConfiguration config) { + return skyframeExecutor.getConfiguredTargetForTesting(target.getLabel(), config); + } + + /** + * Returns a RuleContext which is the same as the original RuleContext of the target parameter. + */ + @VisibleForTesting + public RuleContext getRuleContextForTesting(ConfiguredTarget target, + StoredEventHandler eventHandler) { + BuildConfiguration config = target.getConfiguration(); + CachingAnalysisEnvironment analysisEnvironment = + new CachingAnalysisEnvironment(artifactFactory, + new ConfiguredTargetKey(target.getLabel(), config), + /*isSystemEnv=*/false, config.extendedSanityChecks(), eventHandler, + /*skyframeEnv=*/null, config.isActionsEnabled(), binTools); + RuleContext ruleContext = new RuleContext.Builder(analysisEnvironment, + (Rule) target.getTarget(), config, ruleClassProvider.getPrerequisiteValidator()) + .setVisibility(NestedSetBuilder.<PackageSpecification>create( + Order.STABLE_ORDER, PackageSpecification.EVERYTHING)) + .setPrerequisites(getPrerequisiteMapForTesting(target)) + .setConfigConditions(ImmutableSet.<ConfigMatchingProvider>of()) + .build(); + return ruleContext; + } + + /** + * Tests and clears the current thread's pending "interrupted" status, and + * throws InterruptedException iff it was set. + */ + protected final void pollInterruptedStatus() throws InterruptedException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + } + + /** + * Drops the analysis cache. If building with Skyframe, targets in {@code topLevelTargets} may + * remain in the cache for use during the execution phase. + * + * @see BuildView.Options#discardAnalysisCache + */ + public void clearAnalysisCache(Collection<ConfiguredTarget> topLevelTargets) { + // TODO(bazel-team): Consider clearing packages too to save more memory. + skyframeAnalysisWasDiscarded = true; + skyframeExecutor.clearAnalysisCache(topLevelTargets); + } + + /******************************************************************** + * * + * 'blaze dump' related functions * + * * + ********************************************************************/ + + /** + * Collects and stores error events while also forwarding them to another eventHandler. + */ + public static class ErrorCollector extends DelegatingEventHandler { + private final List<Event> events; + + public ErrorCollector(EventHandler delegate) { + super(delegate); + this.events = Lists.newArrayList(); + } + + public List<Event> getEvents() { + return events; + } + + @Override + public void handle(Event e) { + super.handle(e); + if (e.getKind() == EventKind.ERROR) { + events.add(e); + } + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java new file mode 100644 index 0000000..bc45ba3 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
@@ -0,0 +1,303 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.ArtifactFactory; +import com.google.devtools.build.lib.actions.ArtifactOwner; +import com.google.devtools.build.lib.actions.MiddlemanFactory; +import com.google.devtools.build.lib.actions.Root; +import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoCollection; +import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory.BuildInfoKey; +import com.google.devtools.build.lib.analysis.config.BinTools; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.events.StoredEventHandler; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.skyframe.BuildInfoCollectionValue; +import com.google.devtools.build.lib.skyframe.WorkspaceStatusValue; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.skyframe.SkyFunction; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import javax.annotation.Nullable; + +/** + * The implementation of AnalysisEnvironment used for analysis. It tracks metadata for each + * configured target, such as the errors and warnings emitted by that target. It is intended that + * a separate instance is used for each configured target, so that these don't mix up. + */ +public class CachingAnalysisEnvironment implements AnalysisEnvironment { + private final ArtifactFactory artifactFactory; + + private final ArtifactOwner owner; + /** + * If this is the system analysis environment, then errors and warnings are directly reported + * to the global reporter, rather than stored, i.e., we don't track here whether there are any + * errors. + */ + private final boolean isSystemEnv; + private final boolean extendedSanityChecks; + + /** + * If false, no actions will be registered, they'll all be just dropped. + * + * <p>Usually, an analysis environment should register all actions. However, in some scenarios we + * analyze some targets twice, but the first one only serves the purpose of collecting information + * for the second analysis. In this case we don't register actions created by the first pass in + * order to avoid action conflicts. + */ + private final boolean allowRegisteringActions; + + private boolean enabled = true; + private MiddlemanFactory middlemanFactory; + private EventHandler errorEventListener; + private SkyFunction.Environment skyframeEnv; + private Map<Artifact, String> artifacts; + private final BinTools binTools; + + /** + * The list of actions registered by the configured target this analysis environment is + * responsible for. May get cleared out at the end of the analysis of said target. + */ + final List<Action> actions = new ArrayList<>(); + + public CachingAnalysisEnvironment(ArtifactFactory artifactFactory, + ArtifactOwner owner, boolean isSystemEnv, boolean extendedSanityChecks, + EventHandler errorEventListener, SkyFunction.Environment env, boolean allowRegisteringActions, + BinTools binTools) { + this.artifactFactory = artifactFactory; + this.owner = Preconditions.checkNotNull(owner); + this.isSystemEnv = isSystemEnv; + this.extendedSanityChecks = extendedSanityChecks; + this.errorEventListener = errorEventListener; + this.skyframeEnv = env; + this.allowRegisteringActions = allowRegisteringActions; + this.binTools = binTools; + middlemanFactory = new MiddlemanFactory(artifactFactory, this); + artifacts = new HashMap<>(); + } + + public void disable(Target target) { + if (!hasErrors() && allowRegisteringActions) { + verifyGeneratedArtifactHaveActions(target); + } + artifacts = null; + middlemanFactory = null; + enabled = false; + errorEventListener = null; + skyframeEnv = null; + } + + private static StringBuilder shortDescription(Action action) { + if (action == null) { + return new StringBuilder("null Action"); + } + return new StringBuilder() + .append(action.getClass().getName()) + .append(' ') + .append(action.getMnemonic()); + } + + /** + * Sanity checks that all generated artifacts have a generating action. + * @param target for error reporting + */ + public void verifyGeneratedArtifactHaveActions(Target target) { + Collection<String> orphanArtifacts = getOrphanArtifactMap().values(); + List<String> checkedActions = null; + if (!orphanArtifacts.isEmpty()) { + checkedActions = Lists.newArrayListWithCapacity(actions.size()); + for (Action action : actions) { + StringBuilder sb = shortDescription(action); + for (Artifact o : action.getOutputs()) { + sb.append("\n "); + sb.append(o.getExecPathString()); + } + checkedActions.add(sb.toString()); + } + throw new IllegalStateException( + String.format( + "%s %s : These artifacts miss a generating action:\n%s\n" + + "These actions we checked:\n%s\n", + target.getTargetKind(), target.getLabel(), + Joiner.on('\n').join(orphanArtifacts), Joiner.on('\n').join(checkedActions))); + } + } + + @Override + public ImmutableSet<Artifact> getOrphanArtifacts() { + return ImmutableSet.copyOf(getOrphanArtifactMap().keySet()); + } + + private Map<Artifact, String> getOrphanArtifactMap() { + // Construct this set to avoid poor performance under large --runs_per_test. + Set<Artifact> artifactsWithActions = new HashSet<>(); + for (Action action : actions) { + // Don't bother checking that every Artifact only appears once; that test is performed + // elsewhere (see #testNonUniqueOutputs in ActionListenerIntegrationTest). + artifactsWithActions.addAll(action.getOutputs()); + } + // The order of the artifacts.entrySet iteration is unspecified - we use a TreeMap here to + // guarantee that the return value of this method is deterministic. + Map<Artifact, String> orphanArtifacts = new TreeMap<>(); + for (Map.Entry<Artifact, String> entry : artifacts.entrySet()) { + Artifact a = entry.getKey(); + if (!a.isSourceArtifact() && !artifactsWithActions.contains(a)) { + orphanArtifacts.put(a, String.format("%s\n%s", + a.getExecPathString(), // uncovered artifact + entry.getValue())); // origin of creation + } + } + return orphanArtifacts; + } + + @Override + public EventHandler getEventHandler() { + return errorEventListener; + } + + @Override + public boolean hasErrors() { + // The system analysis environment never has errors. + if (isSystemEnv) { + return false; + } + Preconditions.checkState(enabled); + return ((StoredEventHandler) errorEventListener).hasErrors(); + } + + @Override + public MiddlemanFactory getMiddlemanFactory() { + Preconditions.checkState(enabled); + return middlemanFactory; + } + + /** + * Keeps track of artifacts. We check that all of them have an owner when the environment is + * sealed (disable()). For performance reasons we only track the originating stacktrace when + * running with --experimental_extended_sanity_checks. + */ + private Artifact trackArtifactAndOrigin(Artifact a, @Nullable Throwable e) { + if ((e != null) && !artifacts.containsKey(a)) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + artifacts.put(a, sw.toString()); + } else { + artifacts.put(a, "No origin, run with --experimental_extended_sanity_checks"); + } + return a; + } + + @Override + public Artifact getDerivedArtifact(PathFragment rootRelativePath, Root root) { + Preconditions.checkState(enabled); + return trackArtifactAndOrigin( + artifactFactory.getDerivedArtifact(rootRelativePath, root, getOwner()), + extendedSanityChecks ? new Throwable() : null); + } + + @Override + public Artifact getFilesetArtifact(PathFragment rootRelativePath, Root root) { + Preconditions.checkState(enabled); + return trackArtifactAndOrigin( + artifactFactory.getFilesetArtifact(rootRelativePath, root, getOwner()), + extendedSanityChecks ? new Throwable() : null); + } + + @Override + public Artifact getConstantMetadataArtifact(PathFragment rootRelativePath, Root root) { + return artifactFactory.getConstantMetadataArtifact(rootRelativePath, root, getOwner()); + } + + @Override + public Artifact getEmbeddedToolArtifact(String embeddedPath) { + Preconditions.checkState(enabled); + return binTools.getEmbeddedArtifact(embeddedPath, artifactFactory); + } + + @Override + public void registerAction(Action... actions) { + Preconditions.checkState(enabled); + if (allowRegisteringActions) { + for (Action action : actions) { + this.actions.add(action); + } + } + } + + @Override + public Action getLocalGeneratingAction(Artifact artifact) { + Preconditions.checkState(allowRegisteringActions); + for (Action action : actions) { + if (action.getOutputs().contains(artifact)) { + return action; + } + } + return null; + } + + @Override + public Collection<Action> getRegisteredActions() { + return Collections.unmodifiableCollection(actions); + } + + @Override + public SkyFunction.Environment getSkyframeEnv() { + return skyframeEnv; + } + + @Override + public Artifact getStableWorkspaceStatusArtifact() { + return ((WorkspaceStatusValue) skyframeEnv.getValue(WorkspaceStatusValue.SKY_KEY)) + .getStableArtifact(); + } + + @Override + public Artifact getVolatileWorkspaceStatusArtifact() { + return ((WorkspaceStatusValue) skyframeEnv.getValue(WorkspaceStatusValue.SKY_KEY)) + .getVolatileArtifact(); + } + + @Override + public ImmutableList<Artifact> getBuildInfo(RuleContext ruleContext, BuildInfoKey key) { + boolean stamp = AnalysisUtils.isStampingEnabled(ruleContext); + BuildInfoCollection collection = + ((BuildInfoCollectionValue) skyframeEnv.getValue(BuildInfoCollectionValue.key( + new BuildInfoCollectionValue.BuildInfoKeyAndConfig(key, ruleContext.getConfiguration())))) + .getCollection(); + return stamp ? collection.getStampedBuildInfo() : collection.getRedactedBuildInfo(); + } + + @Override + public ArtifactOwner getOwner() { + return owner; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CommandHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/CommandHelper.java new file mode 100644 index 0000000..2c05f0f --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/CommandHelper.java
@@ -0,0 +1,307 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.BaseSpawn; +import com.google.devtools.build.lib.analysis.actions.FileWriteAction; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.SkylarkCallable; +import com.google.devtools.build.lib.syntax.SkylarkModule; +import com.google.devtools.build.lib.util.Pair; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Provides shared functionality for parameterized command-line launching + * e.g. {@link com.google.devtools.build.lib.view.genrule.GenRule} + * Also used by {@link com.google.devtools.build.lib.rules.extra.ExtraActionFactory}. + * + * Two largely independent separate sets of functionality are provided: + * 1- string interpolation for {@code $(location[s] ...)} and {@code $(MakeVariable)} + * 2- a utility to build potentially large command lines (presumably made of multiple commands), + * that if presumed too large for the kernel's taste can be dumped into a shell script + * that will contain the same commands, + * at which point the shell script is added to the list of inputs. + */ +@SkylarkModule(name = "command_helper", doc = "A helper class to create shell commands.") +public final class CommandHelper { + + /** + * Maximum total command-line length, in bytes, not counting "/bin/bash -c ". + * If the command is very long, then we write the command to a script file, + * to avoid overflowing any limits on command-line length. + * For short commands, we just use /bin/bash -c command. + */ + @VisibleForTesting + public static int maxCommandLength = 64000; + + /** + * A map of remote path prefixes and corresponding runfiles manifests for tools + * used by this rule. + */ + private final ImmutableMap<PathFragment, Artifact> remoteRunfileManifestMap; + + /** + * Use labelMap for heuristically expanding labels (does not include "outs") + * This is similar to heuristic location expansion in LocationExpander + * and should be kept in sync. + */ + private final ImmutableMap<Label, ImmutableCollection<Artifact>> labelMap; + + /** + * The ruleContext this helper works on + */ + private final RuleContext ruleContext; + + /** + * Output executable files from the 'tools' attribute. + */ + private final ImmutableList<Artifact> resolvedTools; + + /** + * Creates an {@link CommandHelper}. + * + * @param tools - Resolves set of tools into set of executable binaries. Populates manifests, + * remoteRunfiles and label map where required. + * @param labelMap - Adds files to set of known files of label. Used for resolving $(location) + * variables. + */ + public CommandHelper(RuleContext ruleContext, + Iterable<FilesToRunProvider> tools, + ImmutableMap<Label, Iterable<Artifact>> labelMap) { + this.ruleContext = ruleContext; + + ImmutableList.Builder<Artifact> resolvedToolsBuilder = ImmutableList.builder(); + ImmutableMap.Builder<PathFragment, Artifact> remoteRunfileManifestBuilder = + ImmutableMap.builder(); + Map<Label, Collection<Artifact>> tempLabelMap = new HashMap<>(); + + for (Map.Entry<Label, Iterable<Artifact>> entry : labelMap.entrySet()) { + Iterables.addAll(mapGet(tempLabelMap, entry.getKey()), entry.getValue()); + } + + for (FilesToRunProvider tool : tools) { // (Note: host configuration) + Label label = tool.getLabel(); + Collection<Artifact> files = tool.getFilesToRun(); + resolvedToolsBuilder.addAll(files); + Artifact executableArtifact = tool.getExecutable(); + // If the label has an executable artifact add that to the multimaps. + if (executableArtifact != null) { + mapGet(tempLabelMap, label).add(executableArtifact); + // Also send the runfiles when running remotely. + Artifact runfilesManifest = tool.getRunfilesManifest(); + if (runfilesManifest != null) { + remoteRunfileManifestBuilder.put( + BaseSpawn.runfilesForFragment(executableArtifact.getExecPath()), runfilesManifest); + } + } else { + // Map all depArtifacts to the respective label using the multimaps. + Iterables.addAll(mapGet(tempLabelMap, label), files); + } + } + + this.resolvedTools = resolvedToolsBuilder.build(); + this.remoteRunfileManifestMap = remoteRunfileManifestBuilder.build(); + ImmutableMap.Builder<Label, ImmutableCollection<Artifact>> labelMapBuilder = + ImmutableMap.builder(); + for (Entry<Label, Collection<Artifact>> entry : tempLabelMap.entrySet()) { + labelMapBuilder.put(entry.getKey(), ImmutableList.copyOf(entry.getValue())); + } + this.labelMap = labelMapBuilder.build(); + } + + @SkylarkCallable(name = "resolved_tools", doc = "", structField = true) + public List<Artifact> getResolvedTools() { + return resolvedTools; + } + + @SkylarkCallable(name = "runfiles_manifests", doc = "", structField = true) + public ImmutableMap<PathFragment, Artifact> getRemoteRunfileManifestMap() { + return remoteRunfileManifestMap; + } + + // Returns the value in the specified corresponding to 'key', creating and + // inserting an empty container if absent. We use Map not Multimap because + // we need to distinguish the cases of "empty value" and "absent key". + private static Collection<Artifact> mapGet(Map<Label, Collection<Artifact>> map, Label key) { + Collection<Artifact> values = map.get(key); + if (values == null) { + // We use sets not lists, because it's conceivable that the same artifact + // could appear twice, e.g. in "srcs" and "deps". + values = Sets.newHashSet(); + map.put(key, values); + } + return values; + } + + /** + * Resolves the 'cmd' attribute, and expands known locations for $(location) + * variables. + */ + @SkylarkCallable(doc = "") + public String resolveCommandAndExpandLabels(Boolean supportLegacyExpansion, + Boolean allowDataInLabel) { + String command = ruleContext.attributes().get("cmd", Type.STRING); + command = new LocationExpander(ruleContext, allowDataInLabel).expand("cmd", command); + + if (supportLegacyExpansion) { + command = expandLabels(command, labelMap); + } + return command; + } + + /** + * Expands labels occurring in the string "expr" in the rule 'cmd'. + * Each label must be valid, be a declared prerequisite, and expand to a + * unique path. + * + * <p>If the expansion fails, an attribute error is reported and the original + * expression is returned. + */ + private <T extends Iterable<Artifact>> String expandLabels(String expr, Map<Label, T> labelMap) { + try { + return LabelExpander.expand(expr, labelMap, ruleContext.getLabel()); + } catch (LabelExpander.NotUniqueExpansionException nuee) { + ruleContext.attributeError("cmd", nuee.getMessage()); + return expr; + } + } + + private static Pair<List<String>, Artifact> buildCommandLineMaybeWithScriptFile( + RuleContext ruleContext, String command, String scriptPostFix) { + List<String> argv; + Artifact scriptFileArtifact = null; + if (command.length() <= maxCommandLength) { + argv = buildCommandLineSimpleArgv(ruleContext, command); + } else { + // Use script file. + scriptFileArtifact = buildCommandLineArtifact(ruleContext, command, scriptPostFix); + argv = buildCommandLineArgvWithArtifact(ruleContext, scriptFileArtifact); + } + return Pair.of(argv, scriptFileArtifact); + + } + + /** + * Builds the set of command-line arguments. Creates a bash script if the + * command line is longer than the allowed maximum {@link + * #maxCommandLength}. Fixes up the input artifact list with the + * created bash script when required. + * TODO(bazel-team): do away with the side effect on inputs (ugh). + */ + public static List<String> buildCommandLine(RuleContext ruleContext, + String command, NestedSetBuilder<Artifact> inputs, String scriptPostFix) { + Pair<List<String>, Artifact> argvAndScriptFile = + buildCommandLineMaybeWithScriptFile(ruleContext, command, scriptPostFix); + if (argvAndScriptFile.second != null) { + inputs.add(argvAndScriptFile.second); + } + return argvAndScriptFile.first; + } + + /** + * Builds the set of command-line arguments. Creates a bash script if the + * command line is longer than the allowed maximum {@link + * #maxCommandLength}. Fixes up the input artifact list with the + * created bash script when required. + * TODO(bazel-team): do away with the side effect on inputs (ugh). + */ + public static List<String> buildCommandLine(RuleContext ruleContext, + String command, List<Artifact> inputs, String scriptPostFix) { + Pair<List<String>, Artifact> argvAndScriptFile = + buildCommandLineMaybeWithScriptFile(ruleContext, command, scriptPostFix); + if (argvAndScriptFile.second != null) { + inputs.add(argvAndScriptFile.second); + } + return argvAndScriptFile.first; + } + + /** + * Builds the set of command-line arguments. Creates a bash script if the + * command line is longer than the allowed maximum {@link + * #maxCommandLength}. Fixes up the input artifact list with the + * created bash script when required. + * TODO(bazel-team): do away with the side effect on inputs (ugh). + */ + public static List<String> buildCommandLine(RuleContext ruleContext, + String command, ImmutableSet.Builder<Artifact> inputs, String scriptPostFix) { + Pair<List<String>, Artifact> argvAndScriptFile = + buildCommandLineMaybeWithScriptFile(ruleContext, command, scriptPostFix); + if (argvAndScriptFile.second != null) { + inputs.add(argvAndScriptFile.second); + } + return argvAndScriptFile.first; + } + + private static ImmutableList<String> buildCommandLineArgvWithArtifact(RuleContext ruleContext, + Artifact scriptFileArtifact) { + return ImmutableList.of( + ruleContext.getConfiguration().getShExecutable().getPathString(), + scriptFileArtifact.getExecPathString()); + } + + private static Artifact buildCommandLineArtifact(RuleContext ruleContext, String command, + String scriptPostFix) { + String scriptFileName = ruleContext.getTarget().getName() + scriptPostFix; + String scriptFileContents = "#!/bin/bash\n" + command; + Artifact scriptFileArtifact = FileWriteAction.createFile( + ruleContext, scriptFileName, scriptFileContents, /*executable=*/true); + return scriptFileArtifact; + } + + private static ImmutableList<String> buildCommandLineSimpleArgv(RuleContext ruleContext, + String command) { + return ImmutableList.of( + ruleContext.getConfiguration().getShExecutable().getPathString(), "-c", command); + } + + /** + * Builds the set of command-line arguments. Creates a bash script if the + * command line is longer than the allowed maximum {@link + * #maxCommandLength}. Fixes up the input artifact list with the + * created bash script when required. + */ + public List<String> buildCommandLine( + String command, NestedSetBuilder<Artifact> inputs, String scriptPostFix) { + return buildCommandLine(ruleContext, command, inputs, scriptPostFix); + } + + /** + * Builds the set of command-line arguments. Creates a bash script if the + * command line is longer than the allowed maximum {@link + * #maxCommandLength}. Fixes up the input artifact list with the + * created bash script when required. + */ + @SkylarkCallable(doc = "") + public List<String> buildCommandLine( + String command, List<Artifact> inputs, String scriptPostFix) { + return buildCommandLine(ruleContext, command, inputs, scriptPostFix); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CompilationHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/CompilationHelper.java new file mode 100644 index 0000000..23dd8d4 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/CompilationHelper.java
@@ -0,0 +1,92 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.MiddlemanFactory; +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 java.util.List; + +/** + * A helper class for compilation helpers. + */ +public final class CompilationHelper { + /** + * Returns a middleman for all files to build for the given configured target. + * If multiple calls are made, then it returns the same artifact for + * configurations with the same internal directory. + * + * <p>The resulting middleman only aggregates the inputs and must be expanded + * before an action using it can be sent to a distributed execution-system. + */ + public static NestedSet<Artifact> getAggregatingMiddleman( + RuleContext ruleContext, String purpose, NestedSet<Artifact> filesToBuild) { + return NestedSetBuilder.wrap(Order.STABLE_ORDER, getMiddlemanInternal( + ruleContext.getAnalysisEnvironment(), ruleContext, ruleContext.getActionOwner(), purpose, + filesToBuild)); + } + + /** + * Internal implementation for getAggregatingMiddleman / getAggregatingMiddlemanWithSolibSymlinks. + */ + private static List<Artifact> getMiddlemanInternal(AnalysisEnvironment env, + RuleContext ruleContext, ActionOwner actionOwner, String purpose, + NestedSet<Artifact> filesToBuild) { + if (filesToBuild == null) { + return ImmutableList.of(); + } + MiddlemanFactory factory = env.getMiddlemanFactory(); + return ImmutableList.of(factory.createMiddlemanAllowMultiple( + env, actionOwner, purpose, filesToBuild, + ruleContext.getConfiguration().getMiddlemanDirectory())); + } + + // TODO(bazel-team): remove this duplicated code after the ConfiguredTarget migration + /** + * Returns a middleman for all files to build for the given configured target. + * If multiple calls are made, then it returns the same artifact for + * configurations with the same internal directory. + * + * <p>The resulting middleman only aggregates the inputs and must be expanded + * before an action using it can be sent to a distributed execution-system. + */ + public static NestedSet<Artifact> getAggregatingMiddleman( + RuleContext ruleContext, String purpose, TransitiveInfoCollection dep) { + return NestedSetBuilder.wrap(Order.STABLE_ORDER, getMiddlemanInternal( + ruleContext.getAnalysisEnvironment(), ruleContext, ruleContext.getActionOwner(), purpose, + dep)); + } + + /** + * Internal implementation for getAggregatingMiddleman / getAggregatingMiddlemanWithSolibSymlinks. + */ + private static List<Artifact> getMiddlemanInternal(AnalysisEnvironment env, + RuleContext ruleContext, ActionOwner actionOwner, String purpose, + TransitiveInfoCollection dep) { + if (dep == null) { + return ImmutableList.of(); + } + MiddlemanFactory factory = env.getMiddlemanFactory(); + Iterable<Artifact> artifacts = dep.getProvider(FileProvider.class).getFilesToBuild(); + return ImmutableList.of(factory.createMiddlemanAllowMultiple( + env, actionOwner, purpose, artifacts, + ruleContext.getConfiguration().getMiddlemanDirectory())); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CompilationPrerequisitesProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/CompilationPrerequisitesProvider.java new file mode 100644 index 0000000..8c3a6ce --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/CompilationPrerequisitesProvider.java
@@ -0,0 +1,38 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; + +/** + * A provider for compilation prerequisites of a given target. + */ +@Immutable +public final class CompilationPrerequisitesProvider implements TransitiveInfoProvider { + + private final NestedSet<Artifact> compilationPrerequisites; + + public CompilationPrerequisitesProvider(NestedSet<Artifact> compilationPrerequisites) { + this.compilationPrerequisites = compilationPrerequisites; + } + + /** + * Returns compilation prerequisites (e.g., generated sources and headers) for a rule. + */ + public NestedSet<Artifact> getCompilationPrerequisites() { + return compilationPrerequisites; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationCollectionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationCollectionFactory.java new file mode 100644 index 0000000..8ffac43 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationCollectionFactory.java
@@ -0,0 +1,54 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.config.BuildOptions; +import com.google.devtools.build.lib.analysis.config.ConfigurationFactory; +import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; +import com.google.devtools.build.lib.analysis.config.PackageProviderForConfigurations; +import com.google.devtools.build.lib.events.EventHandler; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A factory for configuration collection creation. + */ +public interface ConfigurationCollectionFactory { + /** + * Creates the top-level configuration for a build. + * + * <p>Also it may create a set of BuildConfigurations and define a transition table over them. + * All configurations during a build should be accessible from this top-level configuration + * via configuration transitions. + * @param loadedPackageProvider the package provider + * @param buildOptions top-level build options representing the command-line + * @param clientEnv the system environment + * @param errorEventListener the event listener for errors + * @param performSanityCheck flag to signal about performing sanity check. Can be false only for + * tests in skyframe. Legacy mode uses loadedPackageProvider == null condition for this. + * @return the top-level configuration + * @throws InvalidConfigurationException + */ + @Nullable + public BuildConfiguration createConfigurations( + ConfigurationFactory configurationFactory, + PackageProviderForConfigurations loadedPackageProvider, + BuildOptions buildOptions, + Map<String, String> clientEnv, + EventHandler errorEventListener, + boolean performSanityCheck) throws InvalidConfigurationException; +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationMakeVariableContext.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationMakeVariableContext.java new file mode 100644 index 0000000..8c98f5f --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationMakeVariableContext.java
@@ -0,0 +1,56 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.analysis.MakeVariableExpander.ExpansionException; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.packages.Package; + +import java.util.Map; + +/** + * Implements make variable expansion for make variables that depend on the + * configuration and the target (not on behavior of the + * {@link ConfiguredTarget} implementation) + */ +public class ConfigurationMakeVariableContext implements MakeVariableExpander.Context { + private final Package pkg; + private final Map<String, String> commandLineEnv; + private final Map<String, String> globalEnv; + private final String platform; + + public ConfigurationMakeVariableContext(Package pkg, BuildConfiguration configuration) { + this.pkg = pkg; + commandLineEnv = configuration.getCommandLineDefines(); + globalEnv = configuration.getGlobalMakeEnvironment(); + platform = configuration.getPlatformName(); + } + + @Override + public String lookupMakeVariable(String var) throws ExpansionException { + String value = commandLineEnv.get(var); + if (value == null) { + value = pkg.lookupMakeVariable(var, platform); + } + if (value == null) { + value = globalEnv.get(var); + } + if (value == null) { + throw new MakeVariableExpander.ExpansionException("$(" + var + ") not defined"); + } + + return value; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationsCreatedEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationsCreatedEvent.java new file mode 100644 index 0000000..8b0621a --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationsCreatedEvent.java
@@ -0,0 +1,38 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection; + +/** + * This event is fired when the build configurations are created. + */ +public class ConfigurationsCreatedEvent { + + private final BuildConfigurationCollection configurations; + + /** + * Construct the ConfigurationsCreatedEvent. + * + * @param configurations the build configuration collection + */ + public ConfigurationsCreatedEvent(BuildConfigurationCollection configurations) { + this.configurations = configurations; + } + + public BuildConfigurationCollection getBuildConfigurationCollection() { + return configurations; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspectFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspectFactory.java new file mode 100644 index 0000000..955241a --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspectFactory.java
@@ -0,0 +1,28 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.packages.AspectFactory; + +/** + * Instantiation of {@link AspectFactory} with the actual types. + * + * <p>This is needed because {@link AspectFactory} is needed in the {@code packages} package to + * do loading phase things properly and to be able to specify them on attributes, but the actual + * classes are in the {@code view} package, which is not available there. + */ +public interface ConfiguredAspectFactory + extends AspectFactory<ConfiguredTarget, RuleContext, Aspect> { + +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapper.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapper.java new file mode 100644 index 0000000..84029b2 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapper.java
@@ -0,0 +1,158 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider; +import com.google.devtools.build.lib.packages.AbstractAttributeMapper; +import com.google.devtools.build.lib.packages.AttributeMap; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.Label; + +import java.util.Map; +import java.util.Set; + +/** + * {@link AttributeMap} implementation that binds a rule's attribute as follows: + * + * <ol> + * <li>If the attribute is selectable (i.e. its BUILD declaration is of the form + * "attr = { config1: "value1", "config2: "value2", ... }", returns the subset of values + * chosen by the current configuration in accordance with Bazel's documented policy on + * configurable attribute selection. + * <li>If the attribute is not selectable (i.e. its value is static), returns that value with + * no additional processing. + * </ol> + * + * <p>Example usage: + * <pre> + * Label fooLabel = ConfiguredAttributeMapper.of(ruleConfiguredTarget).get("foo", Type.LABEL); + * </pre> + */ +public class ConfiguredAttributeMapper extends AbstractAttributeMapper { + + private final Map<Label, ConfigMatchingProvider> configConditions; + private Rule rule; + + private ConfiguredAttributeMapper(Rule rule, Set<ConfigMatchingProvider> configConditions) { + super(Preconditions.checkNotNull(rule).getPackage(), rule.getRuleClassObject(), rule.getLabel(), + rule.getAttributeContainer()); + ImmutableMap.Builder<Label, ConfigMatchingProvider> builder = ImmutableMap.builder(); + for (ConfigMatchingProvider configCondition : configConditions) { + builder.put(configCondition.label(), configCondition); + } + this.configConditions = builder.build(); + this.rule = rule; + } + + /** + * "Do-it-all" constructor that just needs a {@link RuleConfiguredTarget}. + */ + public static ConfiguredAttributeMapper of(RuleConfiguredTarget ct) { + return new ConfiguredAttributeMapper(ct.getTarget(), ct.getConfigConditions()); + } + + /** + * "Manual" constructor that requires the caller to pass the set of configurability conditions + * that trigger this rule's configurable attributes. + * + * <p>If you don't know how to do this, you really want to use one of the "do-it-all" + * constructors. + */ + static ConfiguredAttributeMapper of(Rule rule, Set<ConfigMatchingProvider> configConditions) { + return new ConfiguredAttributeMapper(rule, configConditions); + } + + /** + * Checks that all attributes can be mapped to their configured values. This is + * useful for checking that the configuration space in a configured attribute doesn't + * contain unresolvable contradictions. + * + * @throws EvalException if any attribute's value can't be resolved under this mapper + */ + public void validateAttributes() throws EvalException { + for (String attrName : getAttributeNames()) { + getAndValidate(attrName, getAttributeType(attrName)); + } + } + + /** + * Variation of {@link #get} that throws an informative exception if the attribute + * can't be resolved due to intrinsic contradictions in the configuration. + */ + private <T> T getAndValidate(String attributeName, Type<T> type) throws EvalException { + Type.Selector<T> selector = getSelector(attributeName, type); + if (selector == null) { + // This is a normal attribute. + return super.get(attributeName, type); + } + + // We expect exactly one of this attribute's conditions to match (including the default + // condition, if specified). Throw an exception if our expectations aren't met. + Label matchingCondition = null; + T matchingValue = null; + + // Find the matching condition and record its value (checking for duplicates). + for (Map.Entry<Label, T> entry : selector.getEntries().entrySet()) { + Label curCondition = entry.getKey(); + if (Type.Selector.isReservedLabel(curCondition)) { + continue; + } else if (Preconditions.checkNotNull(configConditions.get(curCondition)).matches()) { + if (matchingCondition != null) { + throw new EvalException(rule.getAttributeLocation(attributeName), + "Both " + matchingCondition.toString() + " and " + curCondition.toString() + + " match configurable attribute \"" + attributeName + "\" in " + getLabel() + + ". At most one match is allowed"); + } + matchingCondition = curCondition; + matchingValue = entry.getValue(); + } + } + + // If nothing matched, choose the default condition. + if (matchingCondition == null) { + if (!selector.hasDefault()) { + throw new EvalException(rule.getAttributeLocation(attributeName), + "Configurable attribute \"" + attributeName + "\" doesn't match this " + + "configuration (would a default condition help?)"); + } + matchingValue = selector.getDefault(); + } + + return matchingValue; + } + + @Override + public <T> T get(String attributeName, Type<T> type) { + try { + return getAndValidate(attributeName, type); + } catch (EvalException e) { + // Callers that reach this branch should explicitly validate the attribute through an + // appropriate call and handle the exception directly. This method assumes + // pre-validated attributes. + throw new IllegalStateException( + "lookup failed on attribute " + attributeName + ": " + e.getMessage()); + } + } + + @Override + protected <T> Iterable<T> visitAttribute(String attributeName, Type<T> type) { + T value = get(attributeName, type); + return value == null ? ImmutableList.<T>of() : ImmutableList.of(value); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java new file mode 100644 index 0000000..e84f9aa --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
@@ -0,0 +1,376 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import static com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType.ABSTRACT; +import static com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType.TEST; + +import com.google.common.base.Preconditions; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory; +import com.google.devtools.build.lib.analysis.config.BuildOptions; +import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory; +import com.google.devtools.build.lib.analysis.config.DefaultsPackage; +import com.google.devtools.build.lib.analysis.config.FragmentOptions; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.graph.Digraph; +import com.google.devtools.build.lib.graph.Node; +import com.google.devtools.build.lib.packages.Attribute; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.RuleClassProvider; +import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; +import com.google.devtools.build.lib.rules.SkylarkModules; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.SkylarkEnvironment; +import com.google.devtools.build.lib.syntax.SkylarkModule; +import com.google.devtools.build.lib.syntax.SkylarkType; +import com.google.devtools.build.lib.syntax.ValidationEnvironment; +import com.google.devtools.common.options.OptionsClassProvider; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Knows about every rule Blaze supports and the associated configuration options. + * + * <p>This class is initialized on server startup and the set of rules, build info factories + * and configuration options is guarantees not to change over the life time of the Blaze server. + */ +public class ConfiguredRuleClassProvider implements RuleClassProvider { + /** + * Custom dependency validation logic. + */ + public static interface PrerequisiteValidator { + /** + * Checks whether the rule in {@code contextBuilder} is allowed to depend on + * {@code prerequisite} through the attribute {@code attribute}. + * + * <p>Can be used for enforcing any organization-specific policies about the layout of the + * workspace. + */ + void validate( + RuleContext.Builder contextBuilder, ConfiguredTarget prerequisite, Attribute attribute); + } + + /** + * Builder for {@link ConfiguredRuleClassProvider}. + */ + public static class Builder implements RuleDefinitionEnvironment { + private final List<ConfigurationFragmentFactory> configurationFragments = new ArrayList<>(); + private final List<BuildInfoFactory> buildInfoFactories = new ArrayList<>(); + private final List<Class<? extends FragmentOptions>> configurationOptions = new ArrayList<>(); + + private final Map<String, RuleClass> ruleClassMap = new HashMap<>(); + private final Map<String, Class<? extends RuleDefinition>> ruleDefinitionMap = + new HashMap<>(); + private final Map<Class<? extends RuleDefinition>, RuleClass> ruleMap = new HashMap<>(); + private final Digraph<Class<? extends RuleDefinition>> dependencyGraph = + new Digraph<>(); + private ConfigurationCollectionFactory configurationCollectionFactory; + private PrerequisiteValidator prerequisiteValidator; + private ImmutableMap<String, SkylarkType> skylarkAccessibleJavaClasses = ImmutableMap.of(); + + public Builder setPrerequisiteValidator(PrerequisiteValidator prerequisiteValidator) { + this.prerequisiteValidator = prerequisiteValidator; + return this; + } + + public Builder addBuildInfoFactory(BuildInfoFactory factory) { + buildInfoFactories.add(factory); + return this; + } + + public Builder addRuleDefinition(Class<? extends RuleDefinition> ruleDefinition) { + dependencyGraph.createNode(ruleDefinition); + BlazeRule annotation = ruleDefinition.getAnnotation(BlazeRule.class); + for (Class<? extends RuleDefinition> ancestor : annotation.ancestors()) { + dependencyGraph.addEdge(ancestor, ruleDefinition); + } + + return this; + } + + public Builder addConfigurationOptions(Class<? extends FragmentOptions> configurationOptions) { + this.configurationOptions.add(configurationOptions); + return this; + } + + public Builder addConfigurationFragment(ConfigurationFragmentFactory factory) { + configurationFragments.add(factory); + return this; + } + + public Builder setConfigurationCollectionFactory(ConfigurationCollectionFactory factory) { + this.configurationCollectionFactory = factory; + return this; + } + + public Builder setSkylarkAccessibleJavaClasses(ImmutableMap<String, SkylarkType> objects) { + this.skylarkAccessibleJavaClasses = objects; + return this; + } + + private RuleConfiguredTargetFactory createFactory( + Class<? extends RuleConfiguredTargetFactory> factoryClass) { + try { + Constructor<? extends RuleConfiguredTargetFactory> ctor = factoryClass.getConstructor(); + return ctor.newInstance(); + } catch (NoSuchMethodException | IllegalAccessException | InstantiationException + | InvocationTargetException e) { + throw new IllegalStateException(e); + } + } + + private RuleClass commitRuleDefinition(Class<? extends RuleDefinition> definitionClass) { + BlazeRule annotation = definitionClass.getAnnotation(BlazeRule.class); + Preconditions.checkArgument(ruleClassMap.get(annotation.name()) == null, annotation.name()); + + Preconditions.checkArgument( + annotation.type() == ABSTRACT ^ + annotation.factoryClass() != RuleConfiguredTargetFactory.class); + Preconditions.checkArgument( + (annotation.type() != TEST) || + Arrays.asList(annotation.ancestors()).contains( + BaseRuleClasses.TestBaseRule.class)); + + RuleDefinition instance; + try { + instance = definitionClass.newInstance(); + } catch (IllegalAccessException | InstantiationException e) { + throw new IllegalStateException(e); + } + RuleClass[] ancestorClasses = new RuleClass[annotation.ancestors().length]; + for (int i = 0; i < annotation.ancestors().length; i++) { + ancestorClasses[i] = ruleMap.get(annotation.ancestors()[i]); + if (ancestorClasses[i] == null) { + // Ancestors should have been initialized by now + throw new IllegalStateException("Ancestor " + annotation.ancestors()[i] + " of " + + annotation.name() + " is not initialized"); + } + } + + RuleConfiguredTargetFactory factory = null; + if (annotation.type() != ABSTRACT) { + factory = createFactory(annotation.factoryClass()); + } + + RuleClass.Builder builder = new RuleClass.Builder( + annotation.name(), annotation.type(), false, ancestorClasses); + builder.factory(factory); + RuleClass ruleClass = instance.build(builder, this); + ruleMap.put(definitionClass, ruleClass); + ruleClassMap.put(ruleClass.getName(), ruleClass); + ruleDefinitionMap.put(ruleClass.getName(), definitionClass); + + return ruleClass; + } + + public ConfiguredRuleClassProvider build() { + for (Node<Class<? extends RuleDefinition>> ruleDefinition : + dependencyGraph.getTopologicalOrder()) { + commitRuleDefinition(ruleDefinition.getLabel()); + } + + return new ConfiguredRuleClassProvider( + ImmutableMap.copyOf(ruleClassMap), + ImmutableMap.copyOf(ruleDefinitionMap), + ImmutableList.copyOf(buildInfoFactories), + ImmutableList.copyOf(configurationOptions), + ImmutableList.copyOf(configurationFragments), + configurationCollectionFactory, + prerequisiteValidator, + skylarkAccessibleJavaClasses); + } + + @Override + public Label getLabel(String labelValue) { + return LABELS.getUnchecked(labelValue); + } + } + + /** + * Used to make the label instances unique, so that we don't create a new + * instance for every rule. + */ + private static LoadingCache<String, Label> LABELS = CacheBuilder.newBuilder().build( + new CacheLoader<String, Label>() { + @Override + public Label load(String from) { + try { + return Label.parseAbsolute(from); + } catch (Label.SyntaxException e) { + throw new IllegalArgumentException(from); + } + } + }); + + /** + * Maps rule class name to the metaclass instance for that rule. + */ + private final ImmutableMap<String, RuleClass> ruleClassMap; + + /** + * Maps rule class name to the rule definition metaclasses. + */ + private final ImmutableMap<String, Class<? extends RuleDefinition>> ruleDefinitionMap; + + /** + * The configuration options that affect the behavior of the rules. + */ + private final ImmutableList<Class<? extends FragmentOptions>> configurationOptions; + + /** + * The set of configuration fragment factories. + */ + private final ImmutableList<ConfigurationFragmentFactory> configurationFragments; + + /** + * The factory that creates the configuration collection. + */ + private final ConfigurationCollectionFactory configurationCollectionFactory; + + private final ImmutableList<BuildInfoFactory> buildInfoFactories; + + private final PrerequisiteValidator prerequisiteValidator; + + private final ImmutableMap<String, SkylarkType> skylarkAccessibleJavaClasses; + + private final ValidationEnvironment skylarkValidationEnvironment; + + public ConfiguredRuleClassProvider( + ImmutableMap<String, RuleClass> ruleClassMap, + ImmutableMap<String, Class<? extends RuleDefinition>> ruleDefinitionMap, + ImmutableList<BuildInfoFactory> buildInfoFactories, + ImmutableList<Class<? extends FragmentOptions>> configurationOptions, + ImmutableList<ConfigurationFragmentFactory> configurationFragments, + ConfigurationCollectionFactory configurationCollectionFactory, + PrerequisiteValidator prerequisiteValidator, + ImmutableMap<String, SkylarkType> skylarkAccessibleJavaClasses) { + + this.ruleClassMap = ruleClassMap; + this.ruleDefinitionMap = ruleDefinitionMap; + this.buildInfoFactories = buildInfoFactories; + this.configurationOptions = configurationOptions; + this.configurationFragments = configurationFragments; + this.configurationCollectionFactory = configurationCollectionFactory; + this.prerequisiteValidator = prerequisiteValidator; + this.skylarkAccessibleJavaClasses = skylarkAccessibleJavaClasses; + this.skylarkValidationEnvironment = SkylarkModules.getValidationEnvironment( + ImmutableMap.<String, SkylarkType>builder() + .putAll(skylarkAccessibleJavaClasses) + .put("native", SkylarkType.of(NativeModule.class)) + .build()); + } + + public PrerequisiteValidator getPrerequisiteValidator() { + return prerequisiteValidator; + } + + @Override + public Map<String, RuleClass> getRuleClassMap() { + return ruleClassMap; + } + + /** + * Returns a list of build info factories that are needed for the supported languages. + */ + public ImmutableList<BuildInfoFactory> getBuildInfoFactories() { + return buildInfoFactories; + } + + /** + * Returns the set of configuration fragments provided by this module. + */ + public ImmutableList<ConfigurationFragmentFactory> getConfigurationFragments() { + return configurationFragments; + } + + /** + * Returns the set of configuration options that are supported in this module. + */ + public ImmutableList<Class<? extends FragmentOptions>> getConfigurationOptions() { + return configurationOptions; + } + + /** + * Returns the definition of the rule class definition with the specified name. + */ + public Class<? extends RuleDefinition> getRuleClassDefinition(String ruleClassName) { + return ruleDefinitionMap.get(ruleClassName); + } + + /** + * Returns the configuration collection creator. + */ + public ConfigurationCollectionFactory getConfigurationCollectionFactory() { + return configurationCollectionFactory; + } + + /** + * Returns the defaults package for the default settings. + */ + public String getDefaultsPackageContent() { + return DefaultsPackage.getDefaultsPackageContent(configurationOptions); + } + + /** + * Returns the defaults package for the given options taken from an optionsProvider. + */ + public String getDefaultsPackageContent(OptionsClassProvider optionsProvider) { + return DefaultsPackage.getDefaultsPackageContent( + BuildOptions.of(configurationOptions, optionsProvider)); + } + + /** + * Creates a BuildOptions class for the given options taken from an optionsProvider. + */ + public BuildOptions createBuildOptions(OptionsClassProvider optionsProvider) { + return BuildOptions.of(configurationOptions, optionsProvider); + } + + @SkylarkModule(name = "native", namespace = true, onlyLoadingPhase = true, + doc = "Module for native rules.") + private static final class NativeModule {} + + public static final NativeModule nativeModule = new NativeModule(); + + @Override + public SkylarkEnvironment createSkylarkRuleClassEnvironment( + EventHandler eventHandler, String astFileContentHashCode) { + SkylarkEnvironment env = SkylarkModules.getNewEnvironment(eventHandler, astFileContentHashCode); + for (Map.Entry<String, SkylarkType> entry : skylarkAccessibleJavaClasses.entrySet()) { + env.update(entry.getKey(), entry.getValue().getType()); + } + return env; + } + + @Override + public ValidationEnvironment getSkylarkValidationEnvironment() { + return skylarkValidationEnvironment; + } + + @Override + public Object getNativeModule() { + return nativeModule; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTarget.java new file mode 100644 index 0000000..7aad367 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTarget.java
@@ -0,0 +1,47 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.packages.Target; + +import javax.annotation.Nullable; + +/** + * A {@link ConfiguredTarget} is conceptually a {@link TransitiveInfoCollection} coupled + * with the {@link Target} and {@link BuildConfiguration} objects it was created from. + * + * <p>This interface is supposed to only be used in {@link BuildView} and above. In particular, + * rule implementations should not be able to access the {@link ConfiguredTarget} objects + * associated with their direct dependencies, only the corresponding + * {@link TransitiveInfoCollection}s. Also, {@link ConfiguredTarget} objects should not be + * accessible from the action graph. + */ +public interface ConfiguredTarget extends TransitiveInfoCollection { + /** + * Returns the Target with which this {@link ConfiguredTarget} is associated. + */ + Target getTarget(); + + /** + * <p>Returns the {@link BuildConfiguration} for which this {@link ConfiguredTarget} is + * defined. Configuration is defined for all configured targets with exception + * of the {@link InputFileConfiguredTarget} for which it is always + * <b>null</b>.</p> + */ + @Override + @Nullable + BuildConfiguration getConfiguration(); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java new file mode 100644 index 0000000..d265533 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java
@@ -0,0 +1,302 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.ListMultimap; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.ArtifactFactory; +import com.google.devtools.build.lib.actions.ArtifactOwner; +import com.google.devtools.build.lib.actions.FailAction; +import com.google.devtools.build.lib.actions.Root; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment; +import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider; +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.concurrent.ThreadSafety.ThreadSafe; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.packages.Attribute; +import com.google.devtools.build.lib.packages.ConstantRuleVisibility; +import com.google.devtools.build.lib.packages.InputFile; +import com.google.devtools.build.lib.packages.OutputFile; +import com.google.devtools.build.lib.packages.PackageGroup; +import com.google.devtools.build.lib.packages.PackageGroupsRuleVisibility; +import com.google.devtools.build.lib.packages.PackageSpecification; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.RuleVisibility; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.rules.SkylarkRuleConfiguredTargetBuilder; +import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * This class creates {@link ConfiguredTarget} instances using a given {@link + * ConfiguredRuleClassProvider}. + */ +@ThreadSafe +public final class ConfiguredTargetFactory { + // This class is not meant to be outside of the analysis phase machinery and is only public + // in order to be accessible from the .view.skyframe package. + + private final ConfiguredRuleClassProvider ruleClassProvider; + + public ConfiguredTargetFactory(ConfiguredRuleClassProvider ruleClassProvider) { + this.ruleClassProvider = ruleClassProvider; + } + + /** + * Returns the visibility of the given target. Errors during package group resolution are reported + * to the {@code AnalysisEnvironment}. + */ + private NestedSet<PackageSpecification> convertVisibility( + ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap, EventHandler reporter, + Target target, BuildConfiguration packageGroupConfiguration) { + RuleVisibility ruleVisibility = target.getVisibility(); + if (ruleVisibility instanceof ConstantRuleVisibility) { + return ((ConstantRuleVisibility) ruleVisibility).isPubliclyVisible() + ? NestedSetBuilder.<PackageSpecification>create( + Order.STABLE_ORDER, PackageSpecification.EVERYTHING) + : NestedSetBuilder.<PackageSpecification>emptySet(Order.STABLE_ORDER); + } else if (ruleVisibility instanceof PackageGroupsRuleVisibility) { + PackageGroupsRuleVisibility packageGroupsVisibility = + (PackageGroupsRuleVisibility) ruleVisibility; + + NestedSetBuilder<PackageSpecification> packageSpecifications = + NestedSetBuilder.stableOrder(); + for (Label groupLabel : packageGroupsVisibility.getPackageGroups()) { + // PackageGroupsConfiguredTargets are always in the package-group configuration. + ConfiguredTarget group = + findPrerequisite(prerequisiteMap, groupLabel, packageGroupConfiguration); + PackageSpecificationProvider provider = null; + // group == null can only happen if the package group list comes + // from a default_visibility attribute, because in every other case, + // this missing link is caught during transitive closure visitation or + // if the RuleConfiguredTargetGraph threw out a visibility edge + // because if would have caused a cycle. The filtering should be done + // in a single place, ConfiguredTargetGraph, but for now, this is the + // minimally invasive way of providing a sane error message in case a + // cycle is created by a visibility attribute. + if (group != null) { + provider = group.getProvider(PackageSpecificationProvider.class); + } + if (provider != null) { + packageSpecifications.addTransitive(provider.getPackageSpecifications()); + } else { + reporter.handle(Event.error(target.getLocation(), + String.format("Label '%s' does not refer to a package group", groupLabel))); + } + } + + packageSpecifications.addAll(packageGroupsVisibility.getDirectPackages()); + return packageSpecifications.build(); + } else { + throw new IllegalStateException("unknown visibility"); + } + } + + private ConfiguredTarget findPrerequisite( + ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap, Label label, + BuildConfiguration config) { + for (ConfiguredTarget prerequisite : prerequisiteMap.get(null)) { + if (prerequisite.getLabel().equals(label) && (prerequisite.getConfiguration() == config)) { + return prerequisite; + } + } + return null; + } + + private Artifact getOutputArtifact(OutputFile outputFile, BuildConfiguration configuration, + boolean isFileset, ArtifactFactory artifactFactory) { + Rule rule = outputFile.getAssociatedRule(); + Root root = rule.hasBinaryOutput() + ? configuration.getBinDirectory() + : configuration.getGenfilesDirectory(); + ArtifactOwner owner = + new ConfiguredTargetKey(rule.getLabel(), configuration.getArtifactOwnerConfiguration()); + PathFragment rootRelativePath = Util.getWorkspaceRelativePath(outputFile); + Artifact result = isFileset + ? artifactFactory.getFilesetArtifact(rootRelativePath, root, owner) + : artifactFactory.getDerivedArtifact(rootRelativePath, root, owner); + // The associated rule should have created the artifact. + Preconditions.checkNotNull(result, "no artifact for %s", rootRelativePath); + return result; + } + + /** + * Invokes the appropriate constructor to create a {@link ConfiguredTarget} instance. + * <p>For use in {@code ConfiguredTargetFunction}. + * + * <p>Returns null if Skyframe deps are missing or upon certain errors. + */ + @Nullable + public final ConfiguredTarget createConfiguredTarget(AnalysisEnvironment analysisEnvironment, + ArtifactFactory artifactFactory, Target target, BuildConfiguration config, + ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap, + Set<ConfigMatchingProvider> configConditions) + throws InterruptedException { + if (target instanceof Rule) { + return createRule( + analysisEnvironment, (Rule) target, config, prerequisiteMap, configConditions); + } + + // Visibility, like all package groups, doesn't have a configuration + NestedSet<PackageSpecification> visibility = convertVisibility( + prerequisiteMap, analysisEnvironment.getEventHandler(), target, null); + TargetContext targetContext = new TargetContext(analysisEnvironment, target, config, + prerequisiteMap.get(null), visibility); + if (target instanceof OutputFile) { + OutputFile outputFile = (OutputFile) target; + boolean isFileset = outputFile.getGeneratingRule().getRuleClass().equals("Fileset"); + Artifact artifact = getOutputArtifact(outputFile, config, isFileset, artifactFactory); + TransitiveInfoCollection rule = targetContext.findDirectPrerequisite( + outputFile.getGeneratingRule().getLabel(), config); + if (isFileset) { + return new FilesetOutputConfiguredTarget(targetContext, outputFile, rule, artifact); + } else { + return new OutputFileConfiguredTarget(targetContext, outputFile, rule, artifact); + } + } else if (target instanceof InputFile) { + InputFile inputFile = (InputFile) target; + Artifact artifact = artifactFactory.getSourceArtifact( + inputFile.getExecPath(), + Root.asSourceRoot(inputFile.getPackage().getSourceRoot()), + new ConfiguredTargetKey(target.getLabel(), config)); + + return new InputFileConfiguredTarget(targetContext, inputFile, artifact); + } else if (target instanceof PackageGroup) { + PackageGroup packageGroup = (PackageGroup) target; + return new PackageGroupConfiguredTarget(targetContext, packageGroup); + } else { + throw new AssertionError("Unexpected target class: " + target.getClass().getName()); + } + } + + /** + * Factory method: constructs a RuleConfiguredTarget of the appropriate class, based on the rule + * class. May return null if an error occurred. + */ + @Nullable + private ConfiguredTarget createRule( + AnalysisEnvironment env, Rule rule, BuildConfiguration configuration, + ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap, + Set<ConfigMatchingProvider> configConditions) throws InterruptedException { + // Visibility computation and checking is done for every rule. + RuleContext ruleContext = new RuleContext.Builder(env, rule, configuration, + ruleClassProvider.getPrerequisiteValidator()) + .setVisibility(convertVisibility(prerequisiteMap, env.getEventHandler(), rule, null)) + .setPrerequisites(prerequisiteMap) + .setConfigConditions(configConditions) + .build(); + if (ruleContext.hasErrors()) { + return null; + } + if (!rule.getRuleClassObject().getRequiredConfigurationFragments().isEmpty()) { + if (!configuration.hasAllFragments( + rule.getRuleClassObject().getRequiredConfigurationFragments())) { + if (rule.getRuleClassObject().failIfMissingConfigurationFragment()) { + ruleContext.ruleError(missingFragmentError(ruleContext)); + return null; + } + return createFailConfiguredTarget(ruleContext); + } + } + if (rule.getRuleClassObject().isSkylarkExecutable()) { + // TODO(bazel-team): maybe merge with RuleConfiguredTargetBuilder? + return SkylarkRuleConfiguredTargetBuilder.buildRule( + ruleContext, rule.getRuleClassObject().getConfiguredTargetFunction()); + } else { + RuleClass.ConfiguredTargetFactory<ConfiguredTarget, RuleContext> factory = + rule.getRuleClassObject().<ConfiguredTarget, RuleContext>getConfiguredTargetFactory(); + Preconditions.checkNotNull(factory, rule.getRuleClassObject()); + return factory.create(ruleContext); + } + } + + private String missingFragmentError(RuleContext ruleContext) { + RuleClass ruleClass = ruleContext.getRule().getRuleClassObject(); + Set<Class<?>> missingFragments = new LinkedHashSet<>(); + for (Class<?> fragment : ruleClass.getRequiredConfigurationFragments()) { + if (!ruleContext.getConfiguration().hasFragment(fragment.asSubclass(Fragment.class))) { + missingFragments.add(fragment); + } + } + Preconditions.checkState(!missingFragments.isEmpty()); + StringBuilder result = new StringBuilder(); + result.append("all rules of type " + ruleClass.getName() + " require the presence of "); + List<String> names = new ArrayList<>(); + for (Class<?> fragment : missingFragments) { + // TODO(bazel-team): Using getSimpleName here is sub-optimal, but we don't have anything + // better right now. + names.add(fragment.getSimpleName()); + } + result.append("all of ["); + result.append(Joiner.on(",").join(names)); + result.append("], but these were all disabled"); + return result.toString(); + } + + /** + * Constructs an {@link Aspect}. Returns null if an error occurs; in that case, + * {@code aspectFactory} should call one of the error reporting methods of {@link RuleContext}. + */ + public Aspect createAspect( + AnalysisEnvironment env, RuleConfiguredTarget associatedTarget, + ConfiguredAspectFactory aspectFactory, + ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap, + Set<ConfigMatchingProvider> configConditions) { + RuleContext.Builder builder = new RuleContext.Builder(env, + associatedTarget.getTarget(), + associatedTarget.getConfiguration(), + ruleClassProvider.getPrerequisiteValidator()); + RuleContext ruleContext = builder + .setVisibility(convertVisibility( + prerequisiteMap, env.getEventHandler(), associatedTarget.getTarget(), null)) + .setPrerequisites(prerequisiteMap) + .setConfigConditions(configConditions) + .build(); + if (ruleContext.hasErrors()) { + return null; + } + + return aspectFactory.create(associatedTarget, ruleContext); + } + + /** + * A pseudo-implementation for configured targets that creates fail actions for all declared + * outputs, both implicit and explicit. + */ + private static ConfiguredTarget createFailConfiguredTarget(RuleContext ruleContext) { + RuleConfiguredTargetBuilder builder = new RuleConfiguredTargetBuilder(ruleContext); + if (!ruleContext.getOutputArtifacts().isEmpty()) { + ruleContext.registerAction(new FailAction(ruleContext.getActionOwner(), + ruleContext.getOutputArtifacts(), "Can't build this")); + } + builder.add(RunfilesProvider.class, RunfilesProvider.simple(Runfiles.EMPTY)); + return builder.build(); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java new file mode 100644 index 0000000..64cddb1 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
@@ -0,0 +1,573 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Sets; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider; +import com.google.devtools.build.lib.collect.ImmutableSortedKeyListMultimap; +import com.google.devtools.build.lib.packages.AspectDefinition; +import com.google.devtools.build.lib.packages.AspectFactory; +import com.google.devtools.build.lib.packages.Attribute; +import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition; +import com.google.devtools.build.lib.packages.Attribute.LateBoundDefault; +import com.google.devtools.build.lib.packages.Attribute.SplitTransition; +import com.google.devtools.build.lib.packages.AttributeMap; +import com.google.devtools.build.lib.packages.EnvironmentGroup; +import com.google.devtools.build.lib.packages.InputFile; +import com.google.devtools.build.lib.packages.NoSuchThingException; +import com.google.devtools.build.lib.packages.OutputFile; +import com.google.devtools.build.lib.packages.PackageGroup; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.Label; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import javax.annotation.Nullable; + +/** + * Resolver for dependencies between configured targets. + * + * <p>Includes logic to derive the right configurations depending on transition type. + */ +public abstract class DependencyResolver { + /** + * A dependency of a configured target through a label. + * + * <p>Includes the target and the configuration of the dependency configured target and any + * aspects that may be required. + * + * <p>Note that the presence of an aspect here does not necessarily mean that it will be created. + * They will be filtered based on the {@link TransitiveInfoProvider} instances their associated + * configured targets have (we cannot do that here because the configured targets are not + * available yet). No error or warning is reported in this case, because it is expected that rules + * sometimes over-approximate the providers they supply in their definitions. + */ + public static final class Dependency { + + /** + * Returns the {@link ConfiguredTargetKey} for a direct dependency. + * + * <p>Essentially the same information as {@link Dependency} minus the aspects. + */ + public static final Function<Dependency, ConfiguredTargetKey> + TO_CONFIGURED_TARGET_KEY = new Function<Dependency, ConfiguredTargetKey>() { + @Override + public ConfiguredTargetKey apply(Dependency input) { + return new ConfiguredTargetKey(input.getLabel(), input.getConfiguration()); + } + }; + + private final Label label; + private final BuildConfiguration configuration; + private final ImmutableSet<Class<? extends ConfiguredAspectFactory>> aspects; + + public Dependency(Label label, BuildConfiguration configuration, + ImmutableSet<Class<? extends ConfiguredAspectFactory>> aspects) { + this.label = label; + this.configuration = configuration; + this.aspects = aspects; + } + + public Dependency(Label label, BuildConfiguration configuration) { + this(label, configuration, ImmutableSet.<Class<? extends ConfiguredAspectFactory>>of()); + } + + public Label getLabel() { + return label; + } + + public BuildConfiguration getConfiguration() { + return configuration; + } + + public ImmutableSet<Class<? extends ConfiguredAspectFactory>> getAspects() { + return aspects; + } + } + + protected DependencyResolver() { + } + + /** + * Returns ids for dependent nodes of a given node, sorted by attribute. Note that some + * dependencies do not have a corresponding attribute here, and we use the null attribute to + * represent those edges. Visibility attributes are only visited if {@code visitVisibility} is + * {@code true}. + * + * <p>If {@code aspect} is null, returns the dependent nodes of the configured target node + * representing the given target and configuration, otherwise that of the aspect node accompanying + * the aforementioned configured target node for the specified aspect. + * + * <p>The values are not simply labels because this also implements the first step of applying + * configuration transitions, namely, split transitions. This needs to be done before the labels + * are resolved because late bound attributes depend on the configuration. A good example for this + * is @{code :cc_toolchain}. + * + * <p>The long-term goal is that most configuration transitions be applied here. However, in order + * to do that, we first have to eliminate transitions that depend on the rule class of the + * dependency. + */ + public final ListMultimap<Attribute, Dependency> dependentNodeMap( + TargetAndConfiguration node, AspectDefinition aspect, + Set<ConfigMatchingProvider> configConditions) + throws EvalException { + Target target = node.getTarget(); + BuildConfiguration config = node.getConfiguration(); + ListMultimap<Attribute, Dependency> outgoingEdges = ArrayListMultimap.create(); + if (target instanceof OutputFile) { + Preconditions.checkNotNull(config); + visitTargetVisibility(node, outgoingEdges.get(null)); + Rule rule = ((OutputFile) target).getGeneratingRule(); + outgoingEdges.get(null).add(new Dependency(rule.getLabel(), config)); + } else if (target instanceof InputFile) { + visitTargetVisibility(node, outgoingEdges.get(null)); + } else if (target instanceof EnvironmentGroup) { + visitTargetVisibility(node, outgoingEdges.get(null)); + } else if (target instanceof Rule) { + Preconditions.checkNotNull(config); + visitTargetVisibility(node, outgoingEdges.get(null)); + Rule rule = (Rule) target; + ListMultimap<Attribute, LabelAndConfiguration> labelMap = + resolveAttributes(rule, aspect, config, configConditions); + visitRule(rule, aspect, labelMap, outgoingEdges); + } else if (target instanceof PackageGroup) { + visitPackageGroup(node, (PackageGroup) target, outgoingEdges.get(null)); + } else { + throw new IllegalStateException(target.getLabel().toString()); + } + return outgoingEdges; + } + + private ListMultimap<Attribute, LabelAndConfiguration> resolveAttributes( + Rule rule, AspectDefinition aspect, BuildConfiguration configuration, + Set<ConfigMatchingProvider> configConditions) + throws EvalException { + ConfiguredAttributeMapper attributeMap = ConfiguredAttributeMapper.of(rule, configConditions); + attributeMap.validateAttributes(); + List<Attribute> attributes; + if (aspect == null) { + attributes = rule.getRuleClassObject().getAttributes(); + } else { + attributes = new ArrayList<>(); + attributes.addAll(rule.getRuleClassObject().getAttributes()); + if (aspect != null) { + attributes.addAll(aspect.getAttributes().values()); + } + } + + ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> result = + ImmutableSortedKeyListMultimap.builder(); + + resolveExplicitAttributes(rule, configuration, attributeMap, result); + resolveImplicitAttributes(rule, configuration, attributeMap, attributes, result); + resolveLateBoundAttributes(rule, configuration, attributeMap, attributes, result); + + // Add the rule's visibility labels (which may come from the rule or from package defaults). + addExplicitDeps(result, rule, "visibility", rule.getVisibility().getDependencyLabels(), + configuration); + + // Add package default constraints when the rule doesn't explicitly declare them. + // + // Note that this can have subtle implications for constraint semantics. For example: say that + // package defaults declare compatibility with ':foo' and rule R declares compatibility with + // ':bar'. Does that mean that R is compatible with [':foo', ':bar'] or just [':bar']? In other + // words, did R's author intend to add additional compatibility to the package defaults or to + // override them? More severely, what if package defaults "restrict" support to just [':baz']? + // Should R's declaration signify [':baz'] + ['bar'], [ORIGINAL_DEFAULTS] + ['bar'], or + // something else? + // + // Rather than try to answer these questions with possibly confusing logic, we take the + // simple approach of assigning the rule's "restriction" attribute to the rule-declared value if + // it exists, else the package defaults value (and likewise for "compatibility"). This may not + // always provide what users want, but it makes it easy for them to understand how rule + // declarations and package defaults intermix (and how to refactor them to get what they want). + // + // An alternative model would be to apply the "rule declaration" / "rule class defaults" + // relationship, i.e. the rule class' "compatibility" and "restriction" declarations are merged + // to generate a set of default environments, then the rule's declarations are independently + // processed on top of that. This protects against obscure coupling behavior between + // declarations from wildly different places (e.g. it offers clear answers to the examples posed + // above). But within the scope of a single package it seems better to keep the model simple and + // make the user responsible for resolving ambiguities. + if (!rule.isAttributeValueExplicitlySpecified(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR)) { + addExplicitDeps(result, rule, RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, + rule.getPackage().getDefaultCompatibleWith(), configuration); + } + if (!rule.isAttributeValueExplicitlySpecified(RuleClass.RESTRICTED_ENVIRONMENT_ATTR)) { + addExplicitDeps(result, rule, RuleClass.RESTRICTED_ENVIRONMENT_ATTR, + rule.getPackage().getDefaultRestrictedTo(), configuration); + } + + return result.build(); + } + + /** + * Adds new dependencies to the given rule under the given attribute name + * + * @param result the builder for the attribute --> dependency labels map + * @param rule the rule being evaluated + * @param attrName the name of the attribute to add dependency labels to + * @param labels the dependencies to add + * @param configuration the configuration to apply to those dependencies + */ + private void addExplicitDeps( + ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> result, Rule rule, + String attrName, Iterable<Label> labels, BuildConfiguration configuration) { + if (!rule.isAttrDefined(attrName, Type.LABEL_LIST) + && !rule.isAttrDefined(attrName, Type.NODEP_LABEL_LIST)) { + return; + } + Attribute attribute = rule.getRuleClassObject().getAttributeByName(attrName); + for (Label label : labels) { + // The configuration must be the configuration after the first transition step (applying + // split configurations). The proper configuration (null) for package groups will be set + // later. + result.put(attribute, LabelAndConfiguration.of(label, configuration)); + } + } + + private void resolveExplicitAttributes(Rule rule, final BuildConfiguration configuration, + AttributeMap attributes, + final ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> builder) { + attributes.visitLabels( + new AttributeMap.AcceptsLabelAttribute() { + @Override + public void acceptLabelAttribute(Label label, Attribute attribute) { + String attributeName = attribute.getName(); + if (attributeName.equals("abi_deps")) { + // abi_deps is handled specially: we visit only the branch that + // needs to be taken based on the configuration. + return; + } + + if (attribute.getType() == Type.NODEP_LABEL) { + return; + } + + if (attribute.isImplicit() || attribute.isLateBound()) { + return; + } + + builder.put(attribute, LabelAndConfiguration.of(label, configuration)); + } + }); + + // TODO(bazel-team): Remove this in favor of the new configurable attributes. + if (attributes.getAttributeDefinition("abi_deps") != null) { + Attribute depsAttribute = attributes.getAttributeDefinition("deps"); + MakeVariableExpander.Context context = new ConfigurationMakeVariableContext( + rule.getPackage(), configuration); + String abi = null; + try { + abi = MakeVariableExpander.expand(attributes.get("abi", Type.STRING), context); + } catch (MakeVariableExpander.ExpansionException e) { + // Ignore this. It will be handled during the analysis phase. + } + + if (abi != null) { + for (Map.Entry<String, List<Label>> entry + : attributes.get("abi_deps", Type.LABEL_LIST_DICT).entrySet()) { + try { + if (Pattern.matches(entry.getKey(), abi)) { + for (Label label : entry.getValue()) { + builder.put(depsAttribute, LabelAndConfiguration.of(label, configuration)); + } + } + } catch (PatternSyntaxException e) { + // Ignore this. It will be handled during the analysis phase. + } + } + } + } + } + + private void resolveImplicitAttributes(Rule rule, BuildConfiguration configuration, + AttributeMap attributeMap, Iterable<Attribute> attributes, + ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> builder) { + // Since the attributes that come from aspects do not appear in attributeMap, we have to get + // their values from somewhere else. This incidentally means that aspects attributes are not + // configurable. It would be nice if that wasn't the case, but we'd have to revamp how + // attribute mapping works, which is a large chunk of work. + ImmutableSet<String> mappedAttributes = ImmutableSet.copyOf(attributeMap.getAttributeNames()); + for (Attribute attribute : attributes) { + if (!attribute.isImplicit() || !attribute.getCondition().apply(attributeMap)) { + continue; + } + + if (attribute.getType() == Type.LABEL) { + Label label = mappedAttributes.contains(attribute.getName()) + ? attributeMap.get(attribute.getName(), Type.LABEL) + : Type.LABEL.cast(attribute.getDefaultValue(rule)); + + if (label != null) { + builder.put(attribute, LabelAndConfiguration.of(label, configuration)); + } + } else if (attribute.getType() == Type.LABEL_LIST) { + List<Label> labelList = mappedAttributes.contains(attribute.getName()) + ? attributeMap.get(attribute.getName(), Type.LABEL_LIST) + : Type.LABEL_LIST.cast(attribute.getDefaultValue(rule)); + + for (Label label : labelList) { + builder.put(attribute, LabelAndConfiguration.of(label, configuration)); + } + } + } + } + + private void resolveLateBoundAttributes(Rule rule, BuildConfiguration configuration, + AttributeMap attributeMap, Iterable<Attribute> attributes, + ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> builder) + throws EvalException { + for (Attribute attribute : attributes) { + if (!attribute.isLateBound() || !attribute.getCondition().apply(attributeMap)) { + continue; + } + + List<BuildConfiguration> actualConfigurations = ImmutableList.of(configuration); + if (attribute.getConfigurationTransition() instanceof SplitTransition<?>) { + Preconditions.checkState(attribute.getConfigurator() == null); + // TODO(bazel-team): This ends up applying the split transition twice, both here and in the + // visitRule method below - this is not currently a problem, because the configuration graph + // never contains nested split transitions, so the second application is idempotent. + actualConfigurations = configuration.getSplitConfigurations( + (SplitTransition<?>) attribute.getConfigurationTransition()); + } + + for (BuildConfiguration actualConfig : actualConfigurations) { + @SuppressWarnings("unchecked") + LateBoundDefault<BuildConfiguration> lateBoundDefault = + (LateBoundDefault<BuildConfiguration>) attribute.getLateBoundDefault(); + if (lateBoundDefault.useHostConfiguration()) { + actualConfig = + actualConfig.getConfiguration(ConfigurationTransition.HOST); + } + // TODO(bazel-team): This might be too expensive - can we cache this somehow? + if (!lateBoundDefault.getRequiredConfigurationFragments().isEmpty()) { + if (!actualConfig.hasAllFragments(lateBoundDefault.getRequiredConfigurationFragments())) { + continue; + } + } + + // TODO(bazel-team): We should check if the implementation tries to access an undeclared + // fragment. + Object actualValue = lateBoundDefault.getDefault(rule, actualConfig); + if (attribute.getType() == Type.LABEL) { + Label label; + label = Type.LABEL.cast(actualValue); + if (label != null) { + builder.put(attribute, LabelAndConfiguration.of(label, actualConfig)); + } + } else if (attribute.getType() == Type.LABEL_LIST) { + for (Label label : Type.LABEL_LIST.cast(actualValue)) { + builder.put(attribute, LabelAndConfiguration.of(label, actualConfig)); + } + } else { + throw new IllegalStateException(String.format( + "Late bound attribute '%s' is not a label or a label list", attribute.getName())); + } + } + } + } + + /** + * A variant of {@link #dependentNodeMap} that only returns the values of the resulting map, and + * also converts any internally thrown {@link EvalException} instances into {@link + * IllegalStateException}. + */ + public final Collection<Dependency> dependentNodes( + TargetAndConfiguration node, Set<ConfigMatchingProvider> configConditions) { + try { + return dependentNodeMap(node, null, configConditions).values(); + } catch (EvalException e) { + throw new IllegalStateException(e); + } + } + + /** + * Converts the given multimap of attributes to labels into a multi map of attributes to + * {@link Dependency} objects using the proper configuration transition for each attribute. + * + * @throws IllegalArgumentException if the {@code node} does not refer to a {@link Rule} instance + */ + public final Collection<Dependency> resolveRuleLabels( + TargetAndConfiguration node, AspectDefinition aspect, ListMultimap<Attribute, + LabelAndConfiguration> labelMap) { + Preconditions.checkArgument(node.getTarget() instanceof Rule); + Rule rule = (Rule) node.getTarget(); + ListMultimap<Attribute, Dependency> outgoingEdges = ArrayListMultimap.create(); + visitRule(rule, aspect, labelMap, outgoingEdges); + return outgoingEdges.values(); + } + + private void visitPackageGroup(TargetAndConfiguration node, PackageGroup packageGroup, + Collection<Dependency> outgoingEdges) { + for (Label label : packageGroup.getIncludes()) { + try { + Target target = getTarget(label); + if (target == null) { + return; + } + if (!(target instanceof PackageGroup)) { + // Note that this error could also be caught in PackageGroupConfiguredTarget, but since + // these have the null configuration, visiting the corresponding target would trigger an + // analysis of a rule with a null configuration, which doesn't work. + invalidPackageGroupReferenceHook(node, label); + continue; + } + + outgoingEdges.add(new Dependency(label, node.getConfiguration())); + } catch (NoSuchThingException e) { + // Don't visit targets that don't exist (--keep_going) + } + } + } + + private ImmutableSet<Class<? extends ConfiguredAspectFactory>> requiredAspects( + AspectDefinition aspect, Attribute attribute, Target target) { + if (!(target instanceof Rule)) { + return ImmutableSet.of(); + } + + RuleClass ruleClass = ((Rule) target).getRuleClassObject(); + + // The order of this set will be deterministic. This is necessary because this order eventually + // influences the order in which aspects are merged into the main configured target, which in + // turn influences which aspect takes precedence if two emit the same provider (maybe this + // should be an error) + Set<Class<? extends AspectFactory<?, ?, ?>>> aspectCandidates = new LinkedHashSet<>(); + aspectCandidates.addAll(attribute.getAspects()); + if (aspect != null) { + aspectCandidates.addAll(aspect.getAttributeAspects().get(attribute.getName())); + } + + ImmutableSet.Builder<Class<? extends ConfiguredAspectFactory>> result = ImmutableSet.builder(); + for (Class<? extends AspectFactory<?, ?, ?>> candidateClass : aspectCandidates) { + ConfiguredAspectFactory candidate = + (ConfiguredAspectFactory) AspectFactory.Util.create(candidateClass); + if (Sets.difference( + candidate.getDefinition().getRequiredProviders(), + ruleClass.getAdvertisedProviders()).isEmpty()) { + result.add(candidateClass.asSubclass(ConfiguredAspectFactory.class)); + } + } + + return result.build(); + } + + private void visitRule(Rule rule, AspectDefinition aspect, + ListMultimap<Attribute, LabelAndConfiguration> labelMap, + ListMultimap<Attribute, Dependency> outgoingEdges) { + Preconditions.checkNotNull(labelMap); + for (Map.Entry<Attribute, Collection<LabelAndConfiguration>> entry : + labelMap.asMap().entrySet()) { + Attribute attribute = entry.getKey(); + for (LabelAndConfiguration dep : entry.getValue()) { + Label label = dep.getLabel(); + BuildConfiguration config = dep.getConfiguration(); + + Target toTarget; + try { + toTarget = getTarget(label); + } catch (NoSuchThingException e) { + throw new IllegalStateException("not found: " + label + " from " + rule + " in " + + attribute.getName()); + } + if (toTarget == null) { + continue; + } + Iterable<BuildConfiguration> toConfigurations = config.evaluateTransition( + rule, attribute, toTarget); + for (BuildConfiguration toConfiguration : toConfigurations) { + outgoingEdges.get(entry.getKey()).add(new Dependency( + label, toConfiguration, requiredAspects(aspect, attribute, toTarget))); + } + } + } + } + + private void visitTargetVisibility(TargetAndConfiguration node, + Collection<Dependency> outgoingEdges) { + for (Label label : node.getTarget().getVisibility().getDependencyLabels()) { + try { + Target visibilityTarget = getTarget(label); + if (visibilityTarget == null) { + return; + } + if (!(visibilityTarget instanceof PackageGroup)) { + // Note that this error could also be caught in + // AbstractConfiguredTarget.convertVisibility(), but we have an + // opportunity here to avoid dependency cycles that result from + // the visibility attribute of a rule referring to a rule that + // depends on it (instead of its package) + invalidVisibilityReferenceHook(node, label); + continue; + } + + // Visibility always has null configuration + outgoingEdges.add(new Dependency(label, null)); + } catch (NoSuchThingException e) { + // Don't visit targets that don't exist (--keep_going) + } + } + } + + /** + * Hook for the error case when an invalid visibility reference is found. + * + * @param node the node with the visibility attribute + * @param label the invalid visibility reference + */ + protected abstract void invalidVisibilityReferenceHook(TargetAndConfiguration node, Label label); + + /** + * Hook for the error case when an invalid package group reference is found. + * + * @param node the package group node with the includes attribute + * @param label the invalid reference + */ + protected abstract void invalidPackageGroupReferenceHook(TargetAndConfiguration node, + Label label); + + /** + * Returns the target by the given label. + * + * <p>Throws {@link NoSuchThingException} if the target is known not to exist. + * + * <p>Returns null if the target is not ready to be returned at this moment. If getTarget returns + * null once or more during a {@link #dependentNodeMap} call, the results of that call will be + * incomplete. For use within Skyframe, where several iterations may be needed to discover + * all dependencies. + */ + @Nullable + protected abstract Target getTarget(Label label) throws NoSuchThingException; +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ErrorConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/ErrorConfiguredTarget.java new file mode 100644 index 0000000..aa07a7d --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ErrorConfiguredTarget.java
@@ -0,0 +1,41 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.UnmodifiableIterator; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.packages.Target; + +/** + * A configured target that is used instead of a real configured target if there + * are cyclic dependencies or if any of the prerequisites has errors. This + * avoids accessing state that shouldn't be accessed. + */ +final class ErrorConfiguredTarget extends AbstractConfiguredTarget { + ErrorConfiguredTarget(Target target, BuildConfiguration configuration) { + super(target, configuration); + } + + @Override + public Object get(String providerKey) { + throw new UnsupportedOperationException(); + } + + @Override + public UnmodifiableIterator<TransitiveInfoProvider> iterator() { + throw new IllegalStateException(); + } +} +
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionArtifactsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionArtifactsProvider.java new file mode 100644 index 0000000..05b5f37 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionArtifactsProvider.java
@@ -0,0 +1,103 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +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.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.syntax.Label; + +/** + * A {@link TransitiveInfoProvider} that creates extra actions. + */ +@Immutable +public final class ExtraActionArtifactsProvider implements TransitiveInfoProvider { + public static final ExtraActionArtifactsProvider EMPTY = + new ExtraActionArtifactsProvider( + ImmutableList.<Artifact>of(), + NestedSetBuilder.<ExtraArtifactSet>emptySet(Order.STABLE_ORDER)); + + /** + * The set of extra artifacts provided by a single configured target. + */ + @Immutable + public static final class ExtraArtifactSet { + private final Label label; + private final ImmutableList<Artifact> artifacts; + + private ExtraArtifactSet(Label label, Iterable<Artifact> artifacts) { + this.label = label; + this.artifacts = ImmutableList.copyOf(artifacts); + } + + public Label getLabel() { + return label; + } + + public ImmutableList<Artifact> getArtifacts() { + return artifacts; + } + + public static ExtraArtifactSet of(Label label, Iterable<Artifact> artifacts) { + return new ExtraArtifactSet(label, artifacts); + } + + @Override + public int hashCode() { + return label.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ExtraArtifactSet)) { + return false; + } + + return label.equals(((ExtraArtifactSet) other).getLabel()); + } + } + + /** The outputs of the extra actions associated with this target. */ + private ImmutableList<Artifact> extraActionArtifacts = ImmutableList.of(); + private NestedSet<ExtraArtifactSet> transitiveExtraActionArtifacts = + NestedSetBuilder.emptySet(Order.STABLE_ORDER); + + public ExtraActionArtifactsProvider(ImmutableList<Artifact> extraActionArtifacts, + NestedSet<ExtraArtifactSet> transitiveExtraActionArtifacts) { + this.extraActionArtifacts = extraActionArtifacts; + this.transitiveExtraActionArtifacts = transitiveExtraActionArtifacts; + } + + /** + * The outputs of the extra actions associated with this target. + */ + public ImmutableList<Artifact> getExtraActionArtifacts() { + return extraActionArtifacts; + } + + /** + * The outputs of the extra actions in the whole transitive closure. + */ + public NestedSet<ExtraArtifactSet> getTransitiveExtraActionArtifacts() { + return transitiveExtraActionArtifacts; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionsVisitor.java b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionsVisitor.java new file mode 100644 index 0000000..79ffe80 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionsVisitor.java
@@ -0,0 +1,84 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.ActionGraph; +import com.google.devtools.build.lib.actions.ActionGraphVisitor; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.rules.extra.ExtraActionSpec; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A bipartite graph visitor which accumulates extra actions for a target. + */ +final class ExtraActionsVisitor extends ActionGraphVisitor { + private final RuleContext ruleContext; + private final Multimap<String, ExtraActionSpec> mnemonicToExtraActionMap; + private final List<Artifact> extraArtifacts; + public final Set<Action> actions = Sets.newHashSet(); + + /** Creates a new visitor for the extra actions associated with the given target. */ + public ExtraActionsVisitor(RuleContext ruleContext, + Multimap<String, ExtraActionSpec> mnemonicToExtraActionMap) { + super(getActionGraph(ruleContext)); + this.ruleContext = ruleContext; + this.mnemonicToExtraActionMap = mnemonicToExtraActionMap; + extraArtifacts = Lists.newArrayList(); + } + + public void addExtraAction(Action original) { + Collection<ExtraActionSpec> extraActions = mnemonicToExtraActionMap.get( + original.getMnemonic()); + if (extraActions != null) { + for (ExtraActionSpec extraAction : extraActions) { + extraArtifacts.addAll(extraAction.addExtraAction(ruleContext, original)); + } + } + } + + @Override + protected void visitAction(Action action) { + actions.add(action); + addExtraAction(action); + } + + /** Retrieves the collected artifacts since this method was last called and clears the list. */ + public ImmutableList<Artifact> getAndResetExtraArtifacts() { + ImmutableList<Artifact> collected = ImmutableList.copyOf(extraArtifacts); + extraArtifacts.clear(); + return collected; + } + + /** Gets an action graph wrapper for the given target through its analysis environment. */ + private static ActionGraph getActionGraph(final RuleContext ruleContext) { + return new ActionGraph() { + @Override + @Nullable + public Action getGeneratingAction(Artifact artifact) { + return ruleContext.getAnalysisEnvironment().getLocalGeneratingAction(artifact); + } + }; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/FileConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/FileConfiguredTarget.java new file mode 100644 index 0000000..815eea7 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/FileConfiguredTarget.java
@@ -0,0 +1,93 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.UnmodifiableIterator; +import com.google.devtools.build.lib.actions.Artifact; +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.FileTarget; +import com.google.devtools.build.lib.rules.fileset.FilesetProvider; +import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider; +import com.google.devtools.build.lib.util.FileType; + +/** + * A ConfiguredTarget for a source FileTarget. (Generated files use a + * subclass, OutputFileConfiguredTarget.) + */ +public abstract class FileConfiguredTarget extends AbstractConfiguredTarget + implements FileType.HasFilename, LicensesProvider { + + private final Artifact artifact; + private final ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> + providers; + + FileConfiguredTarget(TargetContext targetContext, Artifact artifact) { + super(targetContext); + NestedSet<Artifact> filesToBuild = NestedSetBuilder.create(Order.STABLE_ORDER, artifact); + this.artifact = artifact; + Builder<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> builder = ImmutableMap + .<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider>builder() + .put(VisibilityProvider.class, this) + .put(LicensesProvider.class, this) + .put(FileProvider.class, new FileProvider(targetContext.getLabel(), filesToBuild)) + .put(FilesToRunProvider.class, new FilesToRunProvider(targetContext.getLabel(), + ImmutableList.copyOf(filesToBuild), null, artifact)); + if (this instanceof FilesetProvider) { + builder.put(FilesetProvider.class, this); + } + if (this instanceof InstrumentedFilesProvider) { + builder.put(InstrumentedFilesProvider.class, this); + } + this.providers = builder.build(); + } + + @Override + public FileTarget getTarget() { + return (FileTarget) super.getTarget(); + } + + public Artifact getArtifact() { + return artifact; + } + + /** + * Returns the file type of this file target. + */ + @Override + public String getFilename() { + return getTarget().getFilename(); + } + + @Override + public <P extends TransitiveInfoProvider> P getProvider(Class<P> provider) { + AnalysisUtils.checkProvider(provider); + return provider.cast(providers.get(provider)); + } + + @Override + public Object get(String providerKey) { + return null; + } + + @Override + public UnmodifiableIterator<TransitiveInfoProvider> iterator() { + return providers.values().iterator(); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/FileProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/FileProvider.java new file mode 100644 index 0000000..893f211 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/FileProvider.java
@@ -0,0 +1,76 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.SkylarkCallable; +import com.google.devtools.build.lib.syntax.SkylarkModule; + +import javax.annotation.Nullable; + +/** + * A representation of the concept "this transitive info provider builds these files". + * + * <p>Every transitive info collection contains at least this provider. + */ +@Immutable +@SkylarkModule(name = "file_provider", doc = "An interface for rules that provide files.") +public final class FileProvider implements TransitiveInfoProvider { + + @Nullable private final Label label; + private final NestedSet<Artifact> filesToBuild; + + public FileProvider(@Nullable Label label, NestedSet<Artifact> filesToBuild) { + this.label = label; + this.filesToBuild = filesToBuild; + } + + /** + * Returns the label that is associated with this piece of information. + * + * <p>This is usually the label of the target that provides the information. + */ + @SkylarkCallable(name = "label", doc = "", structField = true) + public Label getLabel() { + if (label == null) { + throw new UnsupportedOperationException(); + } + return label; + } + + /** + * Returns the set of artifacts that are the "output" of this rule. + * + * <p>The term "output" is somewhat hazily defined; it is vaguely the set of files that are + * passed on to dependent rules that list the rule in their {@code srcs} attribute and the + * set of files that are built when a rule is mentioned on the command line. It does + * <b>not</b> include the runfiles; that is the bailiwick of {@code FilesToRunProvider}. + * + * <p>Note that the above definition is somewhat imprecise; in particular, when a rule is + * mentioned on the command line, some other files are also built + * {@code TopLevelArtifactHelper} and dependent rules are free to filter this set of artifacts + * e.g. based on their extension. + * + * <p>Also, some rules may generate artifacts that are not listed here by way of defining other + * implicit targets, for example, deploy jars. + */ + @SkylarkCallable(name = "files_to_build", doc = "", structField = true) + public NestedSet<Artifact> getFilesToBuild() { + return filesToBuild; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/FilesToCompileProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/FilesToCompileProvider.java new file mode 100644 index 0000000..025392c --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/FilesToCompileProvider.java
@@ -0,0 +1,42 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; + +/** + * A {@link TransitiveInfoProvider} that provides files to be built when the {@code --compile_only} + * command line option is in effect. This is to avoid expensive build steps when the user only + * wants a quick syntax check. + */ +@Immutable +public final class FilesToCompileProvider implements TransitiveInfoProvider { + + private final ImmutableList<Artifact> filesToCompile; + + public FilesToCompileProvider(ImmutableList<Artifact> filesToCompile) { + this.filesToCompile = filesToCompile; + } + + /** + * Returns the list of artifacts to be built when the {@code --compile_only} command line option + * is in effect. + */ + public ImmutableList<Artifact> getFilesToCompile() { + return filesToCompile; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/FilesToRunProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/FilesToRunProvider.java new file mode 100644 index 0000000..0e024b1 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/FilesToRunProvider.java
@@ -0,0 +1,80 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.syntax.Label; + +import javax.annotation.Nullable; + +/** + * Returns information about executables produced by a target and the files needed to run it. + */ +@Immutable +public final class FilesToRunProvider implements TransitiveInfoProvider { + + private final Label label; + private final ImmutableList<Artifact> filesToRun; + @Nullable private final RunfilesSupport runfilesSupport; + @Nullable private final Artifact executable; + + public FilesToRunProvider(Label label, ImmutableList<Artifact> filesToRun, + @Nullable RunfilesSupport runfilesSupport, @Nullable Artifact executable) { + this.label = label; + this.filesToRun = filesToRun; + this.runfilesSupport = runfilesSupport; + this.executable = executable; + } + + /** + * Returns the label that is associated with this piece of information. + * + * <p>This is usually the label of the target that provides the information. + */ + public Label getLabel() { + return label; + } + + /** + * Returns artifacts needed to run the executable for this target. + */ + public ImmutableList<Artifact> getFilesToRun() { + return filesToRun; + } + + /** + * Returns the {@RunfilesSupport} object associated with the target or null if it does not exist. + */ + @Nullable public RunfilesSupport getRunfilesSupport() { + return runfilesSupport; + } + + /** + * Returns the Executable or null if it does not exist. + */ + @Nullable public Artifact getExecutable() { + return executable; + } + + /** + * Returns the RunfilesManifest or null if it does not exist. It is a shortcut to + * getRunfilesSupport().getRunfilesManifest(). + */ + @Nullable public Artifact getRunfilesManifest() { + return runfilesSupport != null ? runfilesSupport.getRunfilesManifest() : null; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/FilesetOutputConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/FilesetOutputConfiguredTarget.java new file mode 100644 index 0000000..860024d --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/FilesetOutputConfiguredTarget.java
@@ -0,0 +1,55 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.packages.OutputFile; +import com.google.devtools.build.lib.rules.fileset.FilesetProvider; +import com.google.devtools.build.lib.vfs.PathFragment; + +/** + * A configured target for output files generated by {@code Fileset} rules. They are almost the + * same thing as output files except that they implement {@link FilesetProvider} so that + * {@code Fileset} can figure out the link tree behind them. + * + * <p>In an ideal world, this would not be needed: Filesets would depend on other Filesets and not + * their output directories. However, sometimes a Fileset depends on the output directory of + * another Fileset. Thus, we need this hack. + */ +public final class FilesetOutputConfiguredTarget extends OutputFileConfiguredTarget + implements FilesetProvider { + private final Artifact filesetInputManifest; + private final PathFragment filesetLinkDir; + + FilesetOutputConfiguredTarget(TargetContext targetContext, OutputFile outputFile, + TransitiveInfoCollection generatingRule, Artifact outputArtifact) { + super(targetContext, outputFile, generatingRule, outputArtifact); + FilesetProvider provider = generatingRule.getProvider(FilesetProvider.class); + Preconditions.checkArgument(provider != null); + filesetInputManifest = provider.getFilesetInputManifest(); + filesetLinkDir = provider.getFilesetLinkDir(); + } + + @Override + public Artifact getFilesetInputManifest() { + return filesetInputManifest; + } + + @Override + public PathFragment getFilesetLinkDir() { + return filesetLinkDir; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/InputFileConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/InputFileConfiguredTarget.java new file mode 100644 index 0000000..9e56033 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/InputFileConfiguredTarget.java
@@ -0,0 +1,68 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.devtools.build.lib.actions.Artifact; +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.InputFile; +import com.google.devtools.build.lib.packages.License; + +/** + * A ConfiguredTarget for an InputFile. + * + * All InputFiles for the same target are equivalent, so configuration does not + * play any role here and is always set to <b>null</b>. + */ +public final class InputFileConfiguredTarget extends FileConfiguredTarget { + private final Artifact artifact; + private final NestedSet<TargetLicense> licenses; + + InputFileConfiguredTarget(TargetContext targetContext, InputFile inputFile, Artifact artifact) { + super(targetContext, artifact); + Preconditions.checkArgument(targetContext.getTarget() == inputFile, getLabel()); + Preconditions.checkArgument(getConfiguration() == null, getLabel()); + this.artifact = artifact; + + if (inputFile.getLicense() != License.NO_LICENSE) { + licenses = NestedSetBuilder.create(Order.LINK_ORDER, + new TargetLicense(getLabel(), inputFile.getLicense())); + } else { + licenses = NestedSetBuilder.emptySet(Order.LINK_ORDER); + } + } + + @Override + public InputFile getTarget() { + return (InputFile) super.getTarget(); + } + + @Override + public Artifact getArtifact() { + return artifact; + } + + @Override + public String toString() { + return "InputFileConfiguredTarget(" + getTarget().getLabel() + ")"; + } + + @Override + public final NestedSet<TargetLicense> getTransitiveLicenses() { + return licenses; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LabelAndConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/LabelAndConfiguration.java new file mode 100644 index 0000000..66efba3 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/LabelAndConfiguration.java
@@ -0,0 +1,76 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.syntax.Label; + +import java.util.Objects; + +import javax.annotation.Nullable; + +/** +* A (label,configuration) pair. +*/ +public final class LabelAndConfiguration { + private final Label label; + @Nullable + private final BuildConfiguration configuration; + + private LabelAndConfiguration(Label label, @Nullable BuildConfiguration configuration) { + this.label = Preconditions.checkNotNull(label); + this.configuration = configuration; + } + + public LabelAndConfiguration(ConfiguredTarget rule) { + this(rule.getTarget().getLabel(), rule.getConfiguration()); + } + + public Label getLabel() { + return label; + } + + @Nullable + public BuildConfiguration getConfiguration() { + return configuration; + } + + @Override + public int hashCode() { + int configVal = configuration == null ? 79 : configuration.hashCode(); + return 31 * label.hashCode() + configVal; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof LabelAndConfiguration)) { + return false; + } + LabelAndConfiguration other = (LabelAndConfiguration) obj; + return Objects.equals(label, other.label) && Objects.equals(configuration, other.configuration); + } + + public static LabelAndConfiguration of( + Label label, @Nullable BuildConfiguration configuration) { + return new LabelAndConfiguration(label, configuration); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LabelExpander.java b/src/main/java/com/google/devtools/build/lib/analysis/LabelExpander.java new file mode 100644 index 0000000..89ce2e7 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/LabelExpander.java
@@ -0,0 +1,181 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Helper class encapsulating string scanning state used during "heuristic" + * expansion of labels embedded within rules. + */ +public final class LabelExpander { + /** + * An exception that is thrown when a label is expanded to zero or multiple + * files during expansion. + */ + public static class NotUniqueExpansionException extends Exception { + public NotUniqueExpansionException(int sizeOfResultSet, String labelText) { + super("heuristic label expansion found '" + labelText + "', which expands to " + + sizeOfResultSet + " files" + + (sizeOfResultSet > 1 + ? ", please use $(locations " + labelText + ") instead" + : "")); + } + } + + // This is a utility class, no need to instantiate. + private LabelExpander() {} + + /** + * CharMatcher to determine if a given character is valid for labels. + * + * <p>The Build Concept Reference additionally allows '=' and ',' to appear in labels, + * but for the purposes of the heuristic, this function does not, as it would cause + * "--foo=:rule1,:rule2" to scan as a single possible label, instead of three + * ("--foo", ":rule1", ":rule2"). + */ + private static final CharMatcher LABEL_CHAR_MATCHER = + CharMatcher.inRange('a', 'z') + .or(CharMatcher.inRange('A', 'Z')) + .or(CharMatcher.inRange('0', '9')) + .or(CharMatcher.anyOf(":/_.-+" + PathFragment.SEPARATOR_CHAR)); + + /** + * Expands all references to labels embedded within a string using the + * provided expansion mapping from labels to artifacts. + * + * <p>Since this pass is heuristic, references to non-existent labels (such + * as arbitrary words) or invalid labels are simply ignored and are unchanged + * in the output. However, if the heuristic discovers a label, which + * identifies an existing target producing zero or multiple files, an error + * is reported. + * + * @param expression the expression to expand. + * @param labelMap the mapping from labels to artifacts, whose relative path + * is to be used as the expansion. + * @param labelResolver the {@code Label} that can resolve label strings + * to {@code Label} objects. The resolved label is either relative to + * {@code labelResolver} or is a global label (i.e. starts with "//"). + * @return the expansion of the string. + * @throws NotUniqueExpansionException if a label that is present in the + * mapping expands to zero or multiple files. + */ + public static <T extends Iterable<Artifact>> String expand(@Nullable String expression, + Map<Label, T> labelMap, Label labelResolver) throws NotUniqueExpansionException { + if (Strings.isNullOrEmpty(expression)) { + return ""; + } + Preconditions.checkNotNull(labelMap); + Preconditions.checkNotNull(labelResolver); + + int offset = 0; + StringBuilder result = new StringBuilder(); + while (offset < expression.length()) { + String labelText = scanLabel(expression, offset); + if (labelText != null) { + offset += labelText.length(); + result.append(tryResolvingLabelTextToArtifactPath(labelText, labelMap, labelResolver)); + } else { + result.append(expression.charAt(offset)); + offset++; + } + } + return result.toString(); + } + + /** + * Tries resolving a label text to a full label for the associated {@code + * Artifact}, using the provided mapping. + * + * <p>The method succeeds if the label text can be resolved to a {@code + * Label} object, which is present in the {@code labelMap} and maps to + * exactly one {@code Artifact}. + * + * @param labelText the text to resolve. + * @param labelMap the mapping from labels to artifacts, whose relative path + * is to be used as the expansion. + * @param labelResolver the {@code Label} that can resolve label strings + * to {@code Label} objects. The resolved label is either relative to + * {@code labelResolver} or is a global label (i.e. starts with "//"). + * @return an absolute label to an {@code Artifact} if the resolving was + * successful or the original label text. + * @throws NotUniqueExpansionException if a label that is present in the + * mapping expands to zero or multiple files. + */ + private static <T extends Iterable<Artifact>> String tryResolvingLabelTextToArtifactPath( + String labelText, Map<Label, T> labelMap, Label labelResolver) + throws NotUniqueExpansionException { + Label resolvedLabel = resolveLabelText(labelText, labelResolver); + if (resolvedLabel != null) { + Iterable<Artifact> artifacts = labelMap.get(resolvedLabel); + if (artifacts != null) { // resolvedLabel identifies an existing target + List<String> locations = new ArrayList<>(); + Artifact.addExecPaths(artifacts, locations); + int resultSetSize = locations.size(); + if (resultSetSize == 1) { + return Iterables.getOnlyElement(locations); // success! + } else { + throw new NotUniqueExpansionException(resultSetSize, labelText); + } + } + } + return labelText; + } + + /** + * Resolves a string to a label text. Uses {@code labelResolver} to do so. + * The result is either relative to {@code labelResolver} or is an absolute + * label. In case of an invalid label text, the return value is null. + */ + private static Label resolveLabelText(String labelText, Label labelResolver) { + try { + return labelResolver.getRelative(labelText); + } catch (Label.SyntaxException e) { + // It's a heuristic, so quietly ignore "errors". Because Label.getRelative never + // returns null, we can use null to indicate an error. + return null; + } + } + + /** + * Scans the argument string from a given start position until the name of a + * potential label has been consumed, then returns the label text. If + * the expression contains no possible label starting at the start position, + * the return value is null. + */ + private static String scanLabel(String expression, int start) { + int offset = start; + while (offset < expression.length() && LABEL_CHAR_MATCHER.matches(expression.charAt(offset))) { + ++offset; + } + if (offset > start) { + return expression.substring(start, offset); + } else { + return null; + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LanguageDependentFragment.java b/src/main/java/com/google/devtools/build/lib/analysis/LanguageDependentFragment.java new file mode 100644 index 0000000..d42d625 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/LanguageDependentFragment.java
@@ -0,0 +1,109 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.syntax.Label; + +import java.util.Set; + +/** + * Transitive info provider for rules that behave differently when used from + * different languages. + * + * <p>Most rules generate code for a particular language or are totally language independent. + * Some rules, however, behave differently when depended upon from different languages. + * They might generate different libraries when used from different languages (and with + * different API versions). This interface allows code sharing between implementations. + * + * <p>This provider is not really a roll-up of transitive information. + */ +@Immutable +public final class LanguageDependentFragment implements TransitiveInfoProvider { + /** + * A language that can be supported by a multi-language configured target. + * + * <p>Note that no {@code hashCode}/{@code equals} methods are provided, because these + * objects are expected to be compared for object identity, which is the default. + */ + public static final class LibraryLanguage { + private final String displayName; + + public LibraryLanguage(String displayName) { + this.displayName = displayName; + } + + @Override + public String toString() { + return displayName; + } + } + + private final Label label; + private final ImmutableSet<LibraryLanguage> languages; + + public LanguageDependentFragment(Label label, Set<LibraryLanguage> languages) { + this.label = label; + this.languages = ImmutableSet.copyOf(languages); + } + + /** + * Returns the label that is associated with this piece of information. + * + * <p>This is usually the label of the target that provides the information. + */ + public Label getLabel() { + return label; + } + + /** + * Returns a set of the languages the ConfiguredTarget generates output for. + * For use only by rules that directly depend on this library via a "deps" attribute. + */ + public ImmutableSet<LibraryLanguage> getSupportedLanguages() { + return languages; + } + + /** + * Routines for verifying that dependency provide the right output. + */ + public static final class Checker { + /** + * Checks that given dep supports the given language. + */ + public static boolean depSupportsLanguage( + RuleContext context, LanguageDependentFragment dep, LibraryLanguage language) { + if (dep.getSupportedLanguages().contains(language)) { + return true; + } else { + context.attributeError( + "deps", String.format("'%s' does not produce output for %s", dep.getLabel(), language)); + return false; + } + } + + /** + * Checks that all LanguageDependentFragment support the given language. + */ + public static void depsSupportsLanguage(RuleContext context, LibraryLanguage language) { + for (LanguageDependentFragment dep : + context.getPrerequisites("deps", Mode.TARGET, LanguageDependentFragment.class)) { + depSupportsLanguage(context, dep, language); + } + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LicensesProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/LicensesProvider.java new file mode 100644 index 0000000..548a1f2 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/LicensesProvider.java
@@ -0,0 +1,88 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.packages.License; +import com.google.devtools.build.lib.syntax.Label; + +import java.util.Objects; + +/** + * A {@link ConfiguredTarget} that has licensed targets in its transitive closure. + */ +public interface LicensesProvider extends TransitiveInfoProvider { + + /** + * The set of label - license associations in the transitive closure. + * + * <p>Always returns an empty set if {@link BuildConfiguration#checkLicenses()} is false. + */ + NestedSet<TargetLicense> getTransitiveLicenses(); + + /** + * License association for a particular target. + */ + public static final class TargetLicense { + + private final Label label; + private final License license; + + public TargetLicense(Label label, License license) { + Preconditions.checkNotNull(label); + Preconditions.checkNotNull(license); + this.label = label; + this.license = license; + } + + /** + * Returns the label of the associated target. + */ + public Label getLabel() { + return label; + } + + /** + * Returns the license for the target. + */ + public License getLicense() { + return license; + } + + @Override + public int hashCode() { + return Objects.hash(label, license); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TargetLicense)) { + return false; + } + TargetLicense other = (TargetLicense) obj; + return label.equals(other.label) && license.equals(other.license); + } + + @Override + public String toString() { + return label + " => " + license; + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LicensesProviderImpl.java b/src/main/java/com/google/devtools/build/lib/analysis/LicensesProviderImpl.java new file mode 100644 index 0000000..ffdf9fd --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/LicensesProviderImpl.java
@@ -0,0 +1,40 @@ +// Copyright 2014 Google Inc. 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.analysis; + +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.concurrent.ThreadSafety.Immutable; + +/** + * A {@link ConfiguredTarget} that has licensed targets in its transitive closure. + */ +@Immutable +public final class LicensesProviderImpl implements LicensesProvider { + public static final LicensesProvider EMPTY = + new LicensesProviderImpl(NestedSetBuilder.<TargetLicense>emptySet(Order.LINK_ORDER)); + + private final NestedSet<TargetLicense> transitiveLicenses; + + public LicensesProviderImpl(NestedSet<TargetLicense> transitiveLicenses) { + this.transitiveLicenses = transitiveLicenses; + } + + @Override + public NestedSet<TargetLicense> getTransitiveLicenses() { + return transitiveLicenses; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java b/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java new file mode 100644 index 0000000..8feb28e --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java
@@ -0,0 +1,260 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Joiner; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.packages.OutputFile; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Expands $(location) tags inside target attributes. + * You can specify something like this in the BUILD file: + * + * somerule(name='some name', + * someopt = [ '$(location //mypackage:myhelper)' ], + * ...) + * + * and location will be substituted with //mypackage:myhelper executable output. + * Note that //mypackage:myhelper should have just one output. + */ +public class LocationExpander { + private static final int MAX_PATHS_SHOWN = 5; + private static final String LOCATION = "$(location"; + private final RuleContext ruleContext; + private Map<Label, Collection<Artifact>> locationMap; + private boolean allowDataAttributeEntriesInLabel = false; + + /** + * Creates location expander helper bound to specific target and with default + * location map. + * + * @param ruleContext BUILD rule + */ + public LocationExpander(RuleContext ruleContext) { + this(ruleContext, false); + } + + public LocationExpander(RuleContext ruleContext, + boolean allowDataAttributeEntriesInLabel) { + this.ruleContext = ruleContext; + this.allowDataAttributeEntriesInLabel = allowDataAttributeEntriesInLabel; + } + + public Map<Label, Collection<Artifact>> getLocationMap() { + if (locationMap == null) { + locationMap = buildLocationMap(ruleContext, allowDataAttributeEntriesInLabel); + } + return locationMap; + } + + /** + * Expands attribute's location and locations tags based on the target and + * location map. + * + * @param attrName name of the attribute + * @param attrValue initial value of the attribute + * @return attribute value with expanded location tags or original value in + * case of errors + */ + public String expand(String attrName, String attrValue) { + int restart = 0; + + int attrLength = attrValue.length(); + StringBuilder result = new StringBuilder(attrValue.length()); + + while (true) { + // (1) find '$(location ' or '$(locations ' + String message = "$(location)"; + boolean multiple = false; + int start = attrValue.indexOf(LOCATION, restart); + int scannedLength = LOCATION.length(); + if (start == -1 || start + scannedLength == attrLength) { + result.append(attrValue.substring(restart)); + break; + } + + if (attrValue.charAt(start + scannedLength) == 's') { + scannedLength++; + if (start + scannedLength == attrLength) { + result.append(attrValue.substring(restart)); + break; + } + message = "$(locations)"; + multiple = true; + } + + if (attrValue.charAt(start + scannedLength) != ' ') { + result.append(attrValue.substring(restart, start + scannedLength)); + restart = start + scannedLength; + continue; + } + scannedLength++; + + int end = attrValue.indexOf(')', start + scannedLength); + if (end == -1) { + ruleContext.attributeError(attrName, "unterminated " + message + " expression"); + return attrValue; + } + + // (2) parse label + String labelText = attrValue.substring(start + scannedLength, end); + Label label; + try { + label = ruleContext.getLabel().getRelative(labelText); + } catch (Label.SyntaxException e) { + ruleContext.attributeError(attrName, + "invalid label in " + message + " expression: " + e.getMessage()); + return attrValue; + } + + // (3) replace with singleton artifact, iff unique. + Collection<Artifact> artifacts = getLocationMap().get(label); + if (artifacts == null) { + ruleContext.attributeError(attrName, + "label '" + label + "' in " + message + " expression is not a " + + "declared prerequisite of this rule"); + return attrValue; + } + List<String> paths = getPaths(artifacts); + if (paths.isEmpty()) { + ruleContext.attributeError(attrName, + "label '" + label + "' in " + message + " expression expands to no " + + "files"); + return attrValue; + } + + result.append(attrValue.substring(restart, start)); + if (multiple) { + Collections.sort(paths); + Joiner.on(' ').appendTo(result, paths); + } else { + if (paths.size() > 1) { + ruleContext.attributeError(attrName, + String.format( + "label '%s' in %s expression expands to more than one file, " + + "please use $(locations %s) instead. Files (at most %d shown) are: %s", + label, message, label, + MAX_PATHS_SHOWN, Iterables.limit(paths, MAX_PATHS_SHOWN))); + return attrValue; + } + result.append(Iterables.getOnlyElement(paths)); + } + restart = end + 1; + } + return result.toString(); + } + + /** + * Extracts all possible target locations from target specification. + * + * @param ruleContext BUILD target object + * @return map of all possible target locations + */ + private static Map<Label, Collection<Artifact>> buildLocationMap(RuleContext ruleContext, + boolean allowDataAttributeEntriesInLabel) { + Map<Label, Collection<Artifact>> locationMap = new HashMap<>(); + + // Add all destination locations. + for (OutputFile out : ruleContext.getRule().getOutputFiles()) { + mapGet(locationMap, out.getLabel()).add(ruleContext.createOutputArtifact(out)); + } + + if (ruleContext.getRule().isAttrDefined("srcs", Type.LABEL_LIST)) { + for (FileProvider src : ruleContext + .getPrerequisites("srcs", Mode.TARGET, FileProvider.class)) { + Iterables.addAll(mapGet(locationMap, src.getLabel()), src.getFilesToBuild()); + } + } + + // Add all locations associated with dependencies and tools + List<FilesToRunProvider> depsDataAndTools = new ArrayList<>(); + if (ruleContext.getRule().isAttrDefined("deps", Type.LABEL_LIST)) { + Iterables.addAll(depsDataAndTools, + ruleContext.getPrerequisites("deps", Mode.DONT_CHECK, FilesToRunProvider.class)); + } + if (allowDataAttributeEntriesInLabel + && ruleContext.getRule().isAttrDefined("data", Type.LABEL_LIST)) { + Iterables.addAll(depsDataAndTools, + ruleContext.getPrerequisites("data", Mode.DATA, FilesToRunProvider.class)); + } + if (ruleContext.getRule().isAttrDefined("tools", Type.LABEL_LIST)) { + Iterables.addAll(depsDataAndTools, + ruleContext.getPrerequisites("tools", Mode.HOST, FilesToRunProvider.class)); + } + + for (FilesToRunProvider dep : depsDataAndTools) { + Label label = dep.getLabel(); + Artifact executableArtifact = dep.getExecutable(); + + // If the label has an executable artifact add that to the multimaps. + if (executableArtifact != null) { + mapGet(locationMap, label).add(executableArtifact); + } else { + mapGet(locationMap, label).addAll(dep.getFilesToRun()); + } + } + return locationMap; + } + + /** + * Extracts list of all executables associated with given collection of label + * artifacts. + * + * @param artifacts to get the paths of + * @return all associated executable paths + */ + private static List<String> getPaths(Collection<Artifact> artifacts) { + List<String> paths = Lists.newArrayListWithCapacity(artifacts.size()); + for (Artifact artifact : artifacts) { + PathFragment execPath = artifact.getExecPath(); + if (execPath != null) { // omit middlemen etc + paths.add(execPath.getPathString()); + } + } + return paths; + } + + /** + * Returns the value in the specified map corresponding to 'key', creating and + * inserting an empty container if absent. We use Map not Multimap because + * we need to distinguish the cases of "empty value" and "absent key". + * + * @return the value in the specified map corresponding to 'key' + */ + private static <K, V> Collection<V> mapGet(Map<K, Collection<V>> map, K key) { + Collection<V> values = map.get(key); + if (values == null) { + // We use sets not lists, because it's conceivable that the same label + // could appear twice, in "srcs" and "deps". + values = Sets.newHashSet(); + map.put(key, values); + } + return values; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/MakeEnvironmentEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/MakeEnvironmentEvent.java new file mode 100644 index 0000000..f4b9ca8 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/MakeEnvironmentEvent.java
@@ -0,0 +1,40 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +/** + * This event is fired once the global make environment is available. + */ +public final class MakeEnvironmentEvent { + + private final Map<String, String> makeEnvMap; + + /** + * Construct the event. + */ + public MakeEnvironmentEvent(Map<String, String> makeEnv) { + makeEnvMap = ImmutableMap.copyOf(makeEnv); + } + + /** + * Returns make environment variable names and values as a map. + */ + public Map<String, String> getMakeEnvMap() { + return makeEnvMap; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/MakeVariableExpander.java b/src/main/java/com/google/devtools/build/lib/analysis/MakeVariableExpander.java new file mode 100644 index 0000000..55366da --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/MakeVariableExpander.java
@@ -0,0 +1,201 @@ +// Copyright 2014 Google Inc. 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.analysis; + +/** + * MakeVariableExpander defines a utility method, <code>expand</code>, for + * expanding references to "Make" variables embedded within a string. The + * caller provides a Context instance which defines the expansion of each + * variable. + * + * <p>Note that neither <code>$(location x)</code> nor Make-isms are treated + * specially in any way by this class. + */ +public class MakeVariableExpander { + + private final char[] buffer; + private final int length; + private int offset; + + private MakeVariableExpander(String expression) { + buffer = expression.toCharArray(); + length = buffer.length; + offset = 0; + } + + /** + * Interface to be implemented by callers of MakeVariableExpander which + * defines the expansion of each "Make" variable. + */ + public interface Context { + + /** + * Returns the expansion of the specified "Make" variable. + * + * @param var the variable to expand. + * @return the expansion of the variable. + * @throws ExpansionException if the variable "var" was not defined or + * there was any other error while expanding "var". + */ + String lookupMakeVariable(String var) throws ExpansionException; + } + + /** + * Exception thrown by MakeVariableExpander.Context.expandVariable when an + * unknown variable is passed. + */ + public static class ExpansionException extends Exception { + public ExpansionException(String message) { + super(message); + } + } + + /** + * Expands all references to "Make" variables embedded within string "expr", + * using the provided Context instance to expand individual variables. + * + * @param expression the string to expand. + * @param context the context which defines the expansion of each individual + * variable. + * @return the expansion of "expr". + * @throws ExpansionException if "expr" contained undefined or ill-formed + * variables references. + */ + public static String expand(String expression, Context context) throws ExpansionException { + if (expression.indexOf('$') < 0) { + return expression; + } + return expand(expression, context, 0); + } + + /** + * If the string contains a single variable, return the expansion of that variable. + * Otherwise, return null. + */ + public static String expandSingleVariable(String expression, Context context) + throws ExpansionException { + String var = new MakeVariableExpander(expression).getSingleVariable(); + return (var != null) ? context.lookupMakeVariable(var) : null; + } + + // Helper method for counting recursion depth. + private static String expand(String expression, Context context, int depth) + throws ExpansionException { + if (depth > 10) { // plenty! + throw new ExpansionException("potentially unbounded recursion during " + + "expansion of '" + expression + "'"); + } + return new MakeVariableExpander(expression).expand(context, depth); + } + + private String expand(Context context, int depth) throws ExpansionException { + StringBuilder result = new StringBuilder(); + while (offset < length) { + char c = buffer[offset]; + if (c == '$') { // variable + offset++; + if (offset >= length) { + throw new ExpansionException("unterminated $"); + } + if (buffer[offset] == '$') { + result.append('$'); + } else { + String var = scanVariable(); + String value = context.lookupMakeVariable(var); + // To prevent infinite recursion for the ignored shell variables + if (!value.equals(var)) { + // recursively expand using Make's ":=" semantics: + value = expand(value, context, depth + 1); + } + result.append(value); + } + } else { + result.append(c); + } + offset++; + } + return result.toString(); + } + + /** + * Starting at the current position, scans forward until the name of a Make + * variable has been consumed. Returns the variable name and advances the + * position. If the variable is a potential shell variable returns the shell + * variable expression itself, so that we can let the shell handle the + * expansion. + * + * @return the name of the variable found at the current point. + * @throws ExpansionException if the variable reference was ill-formed. + */ + private String scanVariable() throws ExpansionException { + char c = buffer[offset]; + switch (c) { + case '(': { // $(SRCS) + offset++; + int start = offset; + while (offset < length && buffer[offset] != ')') { + offset++; + } + if (offset >= length) { + throw new ExpansionException("unterminated variable reference"); + } + return new String(buffer, start, offset - start); + } + case '{': { // ${SRCS} + offset++; + int start = offset; + while (offset < length && buffer[offset] != '}') { + offset++; + } + if (offset >= length) { + throw new ExpansionException("unterminated variable reference"); + } + String expr = new String(buffer, start, offset - start); + throw new ExpansionException("'${" + expr + "}' syntax is not supported; use '$(" + expr + + ")' instead for \"Make\" variables, or escape the '$' as " + + "'$$' if you intended this for the shell"); + } + case '@': + case '<': + case '^': + return String.valueOf(c); + default: { + int start = offset; + while (offset + 1 < length && Character.isJavaIdentifierPart(buffer[offset + 1])) { + offset++; + } + String expr = new String(buffer, start, offset + 1 - start); + throw new ExpansionException("'$" + expr + "' syntax is not supported; use '$(" + expr + + ")' instead for \"Make\" variables, or escape the '$' as " + + "'$$' if you intended this for the shell"); + } + } + } + + /** + * @return the variable name if the variable spans from offset to the end of + * the buffer, otherwise return null. + * @throws ExpansionException if the variable reference was ill-formed. + */ + public String getSingleVariable() throws ExpansionException { + if (buffer[offset] == '$') { + offset++; + String result = scanVariable(); + if (offset + 1 == length) { + return result; + } + } + return null; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/MiddlemanProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/MiddlemanProvider.java new file mode 100644 index 0000000..d8425f2 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/MiddlemanProvider.java
@@ -0,0 +1,38 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; + +/** + * A provider class that supplies an aggregating middleman to the targets that depend on it. + */ +@Immutable +public final class MiddlemanProvider implements TransitiveInfoProvider { + + private final NestedSet<Artifact> middlemanArtifact; + + public MiddlemanProvider(NestedSet<Artifact> middlemanArtifact) { + this.middlemanArtifact = middlemanArtifact; + } + + /** + * Returns the middleman for the files produced by the transitive info collection. + */ + public NestedSet<Artifact> getMiddlemanArtifact() { + return middlemanArtifact; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/NoSuchConfiguredTargetException.java b/src/main/java/com/google/devtools/build/lib/analysis/NoSuchConfiguredTargetException.java new file mode 100644 index 0000000..2e9bf8c --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/NoSuchConfiguredTargetException.java
@@ -0,0 +1,29 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.packages.NoSuchThingException; +import com.google.devtools.build.lib.syntax.Label; + +/** + * Exception indicating that the required configured target is not in the + * analysis cache. + */ +public class NoSuchConfiguredTargetException extends NoSuchThingException { + public NoSuchConfiguredTargetException(Label label, BuildConfiguration configuration) { + super("not in cache: " + label + " " + configuration); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/OutputFileConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/OutputFileConfiguredTarget.java new file mode 100644 index 0000000..51122e2 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/OutputFileConfiguredTarget.java
@@ -0,0 +1,80 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.packages.OutputFile; +import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider; +import com.google.devtools.build.lib.rules.test.InstrumentedFilesProviderImpl; + +/** + * A ConfiguredTarget for an OutputFile. + */ +public class OutputFileConfiguredTarget extends FileConfiguredTarget + implements InstrumentedFilesProvider { + + private final TransitiveInfoCollection generatingRule; + + OutputFileConfiguredTarget( + TargetContext targetContext, OutputFile outputFile, + TransitiveInfoCollection generatingRule, Artifact outputArtifact) { + super(targetContext, outputArtifact); + Preconditions.checkArgument(targetContext.getTarget() == outputFile); + this.generatingRule = generatingRule; + } + + @Override + public OutputFile getTarget() { + return (OutputFile) super.getTarget(); + } + + public TransitiveInfoCollection getGeneratingRule() { + return generatingRule; + } + + @Override + public NestedSet<TargetLicense> getTransitiveLicenses() { + return getProvider(LicensesProvider.class, LicensesProviderImpl.EMPTY) + .getTransitiveLicenses(); + } + + @Override + public NestedSet<Artifact> getInstrumentedFiles() { + return getProvider(InstrumentedFilesProvider.class, InstrumentedFilesProviderImpl.EMPTY) + .getInstrumentedFiles(); + } + + @Override + public NestedSet<Artifact> getInstrumentationMetadataFiles() { + return getProvider(InstrumentedFilesProvider.class, InstrumentedFilesProviderImpl.EMPTY) + .getInstrumentationMetadataFiles(); + } + + /** + * Returns the corresponding provider from the generating rule, if it is non-null, or {@code + * defaultValue} otherwise. + */ + private <T extends TransitiveInfoProvider> T getProvider(Class<T> clazz, T defaultValue) { + if (generatingRule != null) { + T result = generatingRule.getProvider(clazz); + if (result != null) { + return result; + } + } + return defaultValue; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PackageGroupConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/PackageGroupConfiguredTarget.java new file mode 100644 index 0000000..75e2981 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/PackageGroupConfiguredTarget.java
@@ -0,0 +1,77 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.common.collect.UnmodifiableIterator; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.packages.PackageGroup; +import com.google.devtools.build.lib.packages.PackageSpecification; +import com.google.devtools.build.lib.syntax.Label; + +/** + * Dummy ConfiguredTarget for package groups. Contains no functionality, since + * package groups are not really first-class Targets. + */ +public final class PackageGroupConfiguredTarget extends AbstractConfiguredTarget + implements PackageSpecificationProvider { + private final NestedSet<PackageSpecification> packageSpecifications; + + PackageGroupConfiguredTarget(TargetContext targetContext, PackageGroup packageGroup) { + super(targetContext); + Preconditions.checkArgument(targetContext.getConfiguration() == null); + + NestedSetBuilder<PackageSpecification> builder = + NestedSetBuilder.stableOrder(); + for (Label label : packageGroup.getIncludes()) { + TransitiveInfoCollection include = targetContext.findDirectPrerequisite( + label, targetContext.getConfiguration()); + PackageSpecificationProvider provider = include == null ? null : + include.getProvider(PackageSpecificationProvider.class); + if (provider == null) { + targetContext.getAnalysisEnvironment().getEventHandler().handle(Event.error(getTarget().getLocation(), + String.format("label '%s' does not refer to a package group", label))); + continue; + } + + builder.addTransitive(provider.getPackageSpecifications()); + } + + builder.addAll(packageGroup.getPackageSpecifications()); + packageSpecifications = builder.build(); + } + + @Override + public PackageGroup getTarget() { + return (PackageGroup) super.getTarget(); + } + + @Override + public NestedSet<PackageSpecification> getPackageSpecifications() { + return packageSpecifications; + } + + @Override + public Object get(String providerKey) { + throw new UnsupportedOperationException(); + } + + @Override + public UnmodifiableIterator<TransitiveInfoProvider> iterator() { + throw new IllegalStateException(); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PackageSpecificationProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/PackageSpecificationProvider.java new file mode 100644 index 0000000..3f852c7 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/PackageSpecificationProvider.java
@@ -0,0 +1,26 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.packages.PackageSpecification; + +/** + * A {@link TransitiveInfoProvider} that describes a set of transitive package specifications + * used in package groups. + */ +public interface PackageSpecificationProvider extends TransitiveInfoProvider { + NestedSet<PackageSpecification> getPackageSpecifications(); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PrerequisiteArtifacts.java b/src/main/java/com/google/devtools/build/lib/analysis/PrerequisiteArtifacts.java new file mode 100644 index 0000000..8932d5c --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/PrerequisiteArtifacts.java
@@ -0,0 +1,106 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +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.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.util.FileType; +import com.google.devtools.build.lib.util.FileTypeSet; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Contains a sequence of prerequisite artifacts and supplies methods for filtering and reporting + * errors on those artifacts. + */ +public final class PrerequisiteArtifacts { + private final RuleContext ruleContext; + private final String attributeName; + private final ImmutableList<Artifact> artifacts; + + private PrerequisiteArtifacts( + RuleContext ruleContext, String attributeName, ImmutableList<Artifact> artifacts) { + this.ruleContext = Preconditions.checkNotNull(ruleContext); + this.attributeName = Preconditions.checkNotNull(attributeName); + this.artifacts = Preconditions.checkNotNull(artifacts); + } + + static PrerequisiteArtifacts get(RuleContext ruleContext, String attributeName, Mode mode) { + Set<Artifact> result = new LinkedHashSet<>(); + for (FileProvider target : + ruleContext.getPrerequisites(attributeName, mode, FileProvider.class)) { + Iterables.addAll(result, target.getFilesToBuild()); + } + return new PrerequisiteArtifacts(ruleContext, attributeName, ImmutableList.copyOf(result)); + } + + /** + * Returns the artifacts this instance contains in an {@link ImmutableList}. + */ + public ImmutableList<Artifact> list() { + return artifacts; + } + + private PrerequisiteArtifacts filter(Predicate<String> fileType, boolean errorsForNonMatching) { + ImmutableList.Builder<Artifact> filtered = new ImmutableList.Builder<Artifact>(); + + for (Artifact artifact : artifacts) { + if (fileType.apply(artifact.getFilename())) { + filtered.add(artifact); + } else if (errorsForNonMatching) { + ruleContext.attributeError( + attributeName, + String.format("%s does not match expected type: %s", artifact, fileType)); + } + } + + return new PrerequisiteArtifacts(ruleContext, attributeName, filtered.build()); + } + + /** + * Returns an equivalent instance but only containing artifacts of the given type, reporting + * errors for non-matching artifacts. + */ + public PrerequisiteArtifacts errorsForNonMatching(FileType fileType) { + return filter(fileType, /*errorsForNonMatching=*/true); + } + + /** + * Returns an equivalent instance but only containing artifacts of the given types, reporting + * errors for non-matching artifacts. + */ + public PrerequisiteArtifacts errorsForNonMatching(FileTypeSet fileTypeSet) { + return filter(fileTypeSet, /*errorsForNonMatching=*/true); + } + + /** + * Returns an equivalent instance but only containing artifacts of the given type. + */ + public PrerequisiteArtifacts filter(FileType fileType) { + return filter(fileType, /*errorsForNonMatching=*/false); + } + + /** + * Returns an equivalent instance but only containing artifacts of the given types. + */ + public PrerequisiteArtifacts filter(FileTypeSet fileTypeSet) { + return filter(fileTypeSet, /*errorsForNonMatching=*/false); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PrintActionVisitor.java b/src/main/java/com/google/devtools/build/lib/analysis/PrintActionVisitor.java new file mode 100644 index 0000000..c852734 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/PrintActionVisitor.java
@@ -0,0 +1,66 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.ActionGraph; +import com.google.devtools.build.lib.actions.ActionGraphVisitor; +import com.google.devtools.build.lib.actions.ActionOwner; + +import java.util.List; + +/** + * A bipartite graph visitor which accumulates actions with matching mnemonics for a target. + */ +public final class PrintActionVisitor extends ActionGraphVisitor { + private final ConfiguredTarget target; + private final List<Action> actions; + private final Predicate<Action> actionMnemonicMatcher; + private final String targetConfigurationKey; + + /** + * Creates a new visitor for the actions associated with the given target that have a matching + * mnemonic. + */ + public PrintActionVisitor(ActionGraph actionGraph, ConfiguredTarget target, + Predicate<Action> actionMnemonicMatcher) { + super(actionGraph); + this.target = target; + this.actionMnemonicMatcher = actionMnemonicMatcher; + actions = Lists.newArrayList(); + targetConfigurationKey = target.getConfiguration().shortCacheKey(); + } + + @Override + protected boolean shouldVisit(Action action) { + ActionOwner owner = action.getOwner(); + return owner != null && target.getLabel().equals(owner.getLabel()) + && targetConfigurationKey.equals(owner.getConfigurationShortCacheKey()); + } + + @Override + protected void visitAction(Action action) { + if (actionMnemonicMatcher.apply(action)) { + actions.add(action); + } + } + + /** Retrieves the collected actions since this method was last called and clears the list. */ + public ImmutableList<Action> getActions() { + return ImmutableList.copyOf(actions); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PseudoAction.java b/src/main/java/com/google/devtools/build/lib/analysis/PseudoAction.java new file mode 100644 index 0000000..00d43a3 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/PseudoAction.java
@@ -0,0 +1,95 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.actions.AbstractAction; +import com.google.devtools.build.lib.actions.ActionExecutionContext; +import com.google.devtools.build.lib.actions.ActionExecutionException; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.actions.ResourceSet; +import com.google.devtools.build.lib.actions.extra.ExtraActionInfo; +import com.google.devtools.build.lib.util.Fingerprint; +import com.google.protobuf.GeneratedMessage.GeneratedExtension; +import com.google.protobuf.MessageLite; + +import java.util.Collection; +import java.util.UUID; + +/** + * An action that is inserted into the build graph only to provide info + * about rules to extra_actions. + */ +public class PseudoAction<InfoType extends MessageLite> extends AbstractAction { + + private final UUID uuid; + private final String mnemonic; + private final GeneratedExtension<ExtraActionInfo, InfoType> infoExtension; + private final InfoType info; + + public PseudoAction(UUID uuid, ActionOwner owner, + Collection<Artifact> inputs, Collection<Artifact> outputs, + String mnemonic, + GeneratedExtension<ExtraActionInfo, InfoType> infoExtension, InfoType info) { + super(owner, inputs, outputs); + this.uuid = uuid; + this.mnemonic = mnemonic; + this.infoExtension = infoExtension; + this.info = info; + } + + @Override + public String describeStrategy(Executor executor) { + return null; + } + + @Override + public void execute(ActionExecutionContext actionExecutionContext) + throws ActionExecutionException { + throw new ActionExecutionException( + mnemonic + "ExtraAction should not be executed.", this, false); + } + + @Override + public String getMnemonic() { + return mnemonic; + } + + @Override + protected String computeKey() { + return new Fingerprint() + .addUUID(uuid) + .addBytes(info.toByteArray()) + .hexDigestAndReset(); + } + + @Override + public ResourceSet estimateResourceConsumption(Executor executor) { + return ResourceSet.ZERO; + } + + @Override + public ExtraActionInfo.Builder getExtraActionInfo() { + return super.getExtraActionInfo().setExtension(infoExtension, info); + } + + public static Artifact getDummyOutput(RuleContext ruleContext) { + return ruleContext.getAnalysisEnvironment().getDerivedArtifact( + ruleContext.getLabel().toPathFragment().replaceName( + ruleContext.getLabel().getName() + ".extra_action_dummy"), + ruleContext.getConfiguration().getGenfilesDirectory()); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RedirectChaser.java b/src/main/java/com/google/devtools/build/lib/analysis/RedirectChaser.java new file mode 100644 index 0000000..108a577 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/RedirectChaser.java
@@ -0,0 +1,114 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment; +import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; +import com.google.devtools.build.lib.packages.AbstractAttributeMapper; +import com.google.devtools.build.lib.packages.NoSuchPackageException; +import com.google.devtools.build.lib.packages.NoSuchTargetException; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.syntax.Label; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Tool for chasing filegroup redirects. This is mainly intended to be used during + * BuildConfiguration creation. + */ +public final class RedirectChaser { + + /** + * Custom attribute mapper that throws an exception if an attribute's value depends on the + * build configuration. + */ + private static class StaticValuedAttributeMapper extends AbstractAttributeMapper { + public StaticValuedAttributeMapper(Rule rule) { + super(rule.getPackage(), rule.getRuleClassObject(), rule.getLabel(), + rule.getAttributeContainer()); + } + + /** + * Returns the value of the given attribute. + * + * @throws InvalidConfigurationException if the value is configuration-dependent + */ + public <T> T getAndValidate(String attributeName, Type<T> type) + throws InvalidConfigurationException { + if (getSelector(attributeName, type) != null) { + throw new InvalidConfigurationException + ("The value of '" + attributeName + "' cannot be configuration-dependent"); + } + return super.get(attributeName, type); + } + + @Override + protected <T> Iterable<T> visitAttribute(String attributeName, Type<T> type) { + throw new IllegalStateException("Attribute visitation not supported redirect resolution"); + } + } + + /** + * Follows the 'srcs' attribute of the given label recursively. Keeps repeating as long as the + * labels are filegroups with a single srcs entry. + * + * @param env for loading the packages + * @param label the label to start at + * @param name user-meaningful description of the content being resolved + * @return the label which cannot be further resolved + * @throws InvalidConfigurationException if something goes wrong + */ + @Nullable + public static Label followRedirects(ConfigurationEnvironment env, Label label, String name) + throws InvalidConfigurationException { + Set<Label> visitedLabels = new HashSet<>(); + visitedLabels.add(label); + try { + while (true) { + Target possibleRedirect = env.getTarget(label); + if (possibleRedirect == null) { + return null; + } + if ((possibleRedirect instanceof Rule) && + "filegroup".equals(((Rule) possibleRedirect).getRuleClass())) { + List<Label> labels = new StaticValuedAttributeMapper((Rule) possibleRedirect) + .getAndValidate("srcs", Type.LABEL_LIST); + if (labels.size() != 1) { + // We can't distinguish redirects from the final filegroup, so we assume this must be + // the final one. + return label; + } + label = labels.get(0); + if (!visitedLabels.add(label)) { + throw new InvalidConfigurationException("The " + name + " points to a filegroup which " + + "recursively includes itself. The label " + label + " is part of the loop"); + } + } else { + return label; + } + } + } catch (NoSuchPackageException e) { + throw new InvalidConfigurationException(e.getMessage(), e); + } catch (NoSuchTargetException e) { + return label; + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTarget.java new file mode 100644 index 0000000..602e949 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTarget.java
@@ -0,0 +1,226 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.UnmodifiableIterator; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider; +import com.google.devtools.build.lib.analysis.config.RunUnder; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.packages.OutputFile; +import com.google.devtools.build.lib.packages.Rule; + +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * A generic implementation of RuleConfiguredTarget. Do not use directly. Use {@link + * RuleConfiguredTargetBuilder} instead. + */ +public final class RuleConfiguredTarget extends AbstractConfiguredTarget { + /** + * The configuration transition for an attribute through which a prerequisite + * is requested. + */ + public enum Mode { + TARGET, + HOST, + DATA, + SPLIT, + DONT_CHECK + } + + private final ImmutableMap<Class<? extends TransitiveInfoProvider>, Object> providers; + private final ImmutableList<Artifact> mandatoryStampFiles; + private final Set<ConfigMatchingProvider> configConditions; + private final ImmutableList<Aspect> aspects; + + RuleConfiguredTarget(RuleContext ruleContext, + ImmutableList<Artifact> mandatoryStampFiles, + ImmutableMap<String, Object> skylarkProviders, + Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers) { + super(ruleContext); + // We don't use ImmutableMap.Builder here to allow augmenting the initial list of 'default' + // providers by passing them in. + Map<Class<? extends TransitiveInfoProvider>, Object> providerBuilder = new LinkedHashMap<>(); + providerBuilder.putAll(providers); + Preconditions.checkState(providerBuilder.containsKey(RunfilesProvider.class)); + Preconditions.checkState(providerBuilder.containsKey(FileProvider.class)); + Preconditions.checkState(providerBuilder.containsKey(FilesToRunProvider.class)); + + providerBuilder.put(SkylarkProviders.class, new SkylarkProviders(skylarkProviders)); + + this.providers = ImmutableMap.copyOf(providerBuilder); + this.mandatoryStampFiles = mandatoryStampFiles; + this.configConditions = ruleContext.getConfigConditions(); + this.aspects = ImmutableList.of(); + + // If this rule is the run_under target, then check that we have an executable; note that + // run_under is only set in the target configuration, and the target must also be analyzed for + // the target configuration. + RunUnder runUnder = getConfiguration().getRunUnder(); + if (runUnder != null && getLabel().equals(runUnder.getLabel())) { + if (getProvider(FilesToRunProvider.class).getExecutable() == null) { + ruleContext.ruleError("run_under target " + runUnder.getLabel() + " is not executable"); + } + } + + // Make sure that all declared output files are also created as artifacts. The + // CachingAnalysisEnvironment makes sure that they all have generating actions. + if (!ruleContext.hasErrors()) { + for (OutputFile out : ruleContext.getRule().getOutputFiles()) { + ruleContext.createOutputArtifact(out); + } + } + } + + /** + * Merge a configured target with its associated aspects. + * + * <p>If aspects are present, the configured target must be created from a rule (instead of e.g. + * an input or an output file). + */ + public static ConfiguredTarget mergeAspects( + ConfiguredTarget base, Iterable<Aspect> aspects) { + if (Iterables.isEmpty(aspects)) { + // If there are no aspects, don't bother with creating a proxy object + return base; + } else { + // Aspects can only be attached to rules for now. This invariant is upheld by + // DependencyResolver#requiredAspects() + return new RuleConfiguredTarget((RuleConfiguredTarget) base, aspects); + } + } + + /** + * Creates an instance based on a configured target and a set of aspects. + */ + private RuleConfiguredTarget(RuleConfiguredTarget base, Iterable<Aspect> aspects) { + super(base.getTarget(), base.getConfiguration()); + + Set<Class<? extends TransitiveInfoProvider>> providers = new HashSet<>(); + + providers.addAll(base.providers.keySet()); + for (Aspect aspect : aspects) { + for (TransitiveInfoProvider aspectProvider : aspect) { + if (!providers.add(aspectProvider.getClass())) { + throw new IllegalStateException( + "Provider " + aspectProvider.getClass() + " provided twice"); + } + } + } + this.providers = base.providers; + this.mandatoryStampFiles = base.mandatoryStampFiles; + this.configConditions = base.configConditions; + this.aspects = ImmutableList.copyOf(aspects); + } + + /** + * The configuration conditions that trigger this rule's configurable attributes. + */ + Set<ConfigMatchingProvider> getConfigConditions() { + return configConditions; + } + + @Override + public <P extends TransitiveInfoProvider> P getProvider(Class<P> providerClass) { + AnalysisUtils.checkProvider(providerClass); + // TODO(bazel-team): Should aspects be allowed to override providers on the configured target + // class? + Object provider = providers.get(providerClass); + if (provider == null) { + for (Aspect aspect : aspects) { + provider = aspect.getProviders().get(providerClass); + if (provider != null) { + break; + } + } + } + + return providerClass.cast(provider); + } + + /** + * Returns a value provided by this target. Only meant to use from Skylark. + */ + @Override + public Object get(String providerKey) { + return getProvider(SkylarkProviders.class).skylarkProviders.get(providerKey); + } + + public ImmutableList<Artifact> getMandatoryStampFiles() { + return mandatoryStampFiles; + } + + @Override + public final Rule getTarget() { + return (Rule) super.getTarget(); + } + + /** + * A helper class for transitive infos provided by Skylark rule implementations. + */ + @Immutable + public static final class SkylarkProviders implements TransitiveInfoProvider { + private final ImmutableMap<String, Object> skylarkProviders; + + private SkylarkProviders(ImmutableMap<String, Object> skylarkProviders) { + Preconditions.checkNotNull(skylarkProviders); + this.skylarkProviders = skylarkProviders; + } + + /** + * Returns the keys for the Skylark providers. + */ + public ImmutableCollection<String> getKeys() { + return skylarkProviders.keySet(); + } + } + + @Override + public UnmodifiableIterator<TransitiveInfoProvider> iterator() { + Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> allProviders = + new LinkedHashMap<>(); + for (int i = aspects.size() - 1; i >= 0; i++) { + for (TransitiveInfoProvider tip : aspects.get(i)) { + allProviders.put(tip.getClass(), tip); + } + } + + for (Map.Entry<Class<? extends TransitiveInfoProvider>, Object> entry : providers.entrySet()) { + allProviders.put(entry.getKey(), entry.getKey().cast(entry.getValue())); + } + + return ImmutableList.copyOf(allProviders.values()).iterator(); + } + + @Override + public String errorMessage(String name) { + return String.format("target (rule class of '%s') doesn't have provider '%s'.", + getTarget().getRuleClass(), name); + } + + @Override + public ImmutableCollection<String> getKeys() { + return ImmutableList.<String>builder().addAll(super.getKeys()) + .addAll(getProvider(SkylarkProviders.class).skylarkProviders.keySet()).build(); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java new file mode 100644 index 0000000..b82713f --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
@@ -0,0 +1,423 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.ExtraActionArtifactsProvider.ExtraArtifactSet; +import com.google.devtools.build.lib.analysis.LicensesProvider.TargetLicense; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics; +import com.google.devtools.build.lib.analysis.constraints.EnvironmentCollection; +import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironments; +import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironmentsProvider; +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.events.Location; +import com.google.devtools.build.lib.packages.License; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.TargetUtils; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.rules.extra.ExtraActionMapProvider; +import com.google.devtools.build.lib.rules.extra.ExtraActionSpec; +import com.google.devtools.build.lib.rules.test.ExecutionInfoProvider; +import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider; +import com.google.devtools.build.lib.rules.test.TestActionBuilder; +import com.google.devtools.build.lib.rules.test.TestProvider; +import com.google.devtools.build.lib.rules.test.TestProvider.TestParams; +import com.google.devtools.build.lib.syntax.ClassObject; +import com.google.devtools.build.lib.syntax.Environment; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.EvalUtils; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.SkylarkList; +import com.google.devtools.build.lib.syntax.SkylarkNestedSet; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Builder class for analyzed rule instances (i.e., instances of {@link ConfiguredTarget}). + */ +public final class RuleConfiguredTargetBuilder { + private final RuleContext ruleContext; + private final Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers = + new LinkedHashMap<>(); + private final ImmutableMap.Builder<String, Object> skylarkProviders = ImmutableMap.builder(); + + /** These are supported by all configured targets and need to be specially handled. */ + private NestedSet<Artifact> filesToBuild = NestedSetBuilder.emptySet(Order.STABLE_ORDER); + private RunfilesSupport runfilesSupport; + private Artifact executable; + private ImmutableList<Artifact> mandatoryStampFiles; + private ImmutableSet<Action> actionsWithoutExtraAction = ImmutableSet.of(); + + public RuleConfiguredTargetBuilder(RuleContext ruleContext) { + this.ruleContext = ruleContext; + add(LicensesProvider.class, initializeLicensesProvider()); + add(VisibilityProvider.class, new VisibilityProviderImpl(ruleContext.getVisibility())); + } + + /** + * Constructs the RuleConfiguredTarget instance based on the values set for this Builder. + */ + public ConfiguredTarget build() { + if (ruleContext.getConfiguration().enforceConstraints()) { + checkConstraints(); + } + if (ruleContext.hasErrors()) { + return null; + } + + FilesToRunProvider filesToRunProvider = new FilesToRunProvider(ruleContext.getLabel(), + RuleContext.getFilesToRun(runfilesSupport, filesToBuild), runfilesSupport, executable); + add(FileProvider.class, new FileProvider(ruleContext.getLabel(), filesToBuild)); + add(FilesToRunProvider.class, filesToRunProvider); + + // Create test action and artifacts if target was successfully initialized + // and is a test. + if (TargetUtils.isTestRule(ruleContext.getTarget())) { + Preconditions.checkState(runfilesSupport != null); + add(TestProvider.class, initializeTestProvider(filesToRunProvider)); + } + add(ExtraActionArtifactsProvider.class, initializeExtraActions()); + return new RuleConfiguredTarget( + ruleContext, mandatoryStampFiles, skylarkProviders.build(), providers); + } + + /** + * Invokes Blaze's constraint enforcement system: checks that this rule's dependencies + * support its environments and reports appropriate errors if violations are found. Also + * publishes this rule's supported environments for the rules that depend on it. + */ + private void checkConstraints() { + if (providers.get(SupportedEnvironmentsProvider.class) == null) { + // Note the "environment" rule sets its own SupportedEnvironmentProvider instance, so this + // logic is for "normal" rules that just want to apply default semantics. + EnvironmentCollection supportedEnvironments = + ConstraintSemantics.getSupportedEnvironments(ruleContext); + if (supportedEnvironments != null) { + add(SupportedEnvironmentsProvider.class, new SupportedEnvironments(supportedEnvironments)); + ConstraintSemantics.checkConstraints(ruleContext, supportedEnvironments); + } + } + } + + private TestProvider initializeTestProvider(FilesToRunProvider filesToRunProvider) { + int explicitShardCount = ruleContext.attributes().get("shard_count", Type.INTEGER); + if (explicitShardCount < 0 + && ruleContext.getRule().isAttributeValueExplicitlySpecified("shard_count")) { + ruleContext.attributeError("shard_count", "Must not be negative."); + } + if (explicitShardCount > 50) { + ruleContext.attributeError("shard_count", + "Having more than 50 shards is indicative of poor test organization. " + + "Please reduce the number of shards."); + } + final TestParams testParams = new TestActionBuilder(ruleContext) + .setFilesToRunProvider(filesToRunProvider) + .setInstrumentedFiles(findProvider(InstrumentedFilesProvider.class)) + .setExecutionRequirements(findProvider(ExecutionInfoProvider.class)) + .setShardCount(explicitShardCount) + .build(); + final ImmutableList<String> testTags = + ImmutableList.copyOf(ruleContext.getRule().getRuleTags()); + return new TestProvider(testParams, testTags); + } + + private LicensesProvider initializeLicensesProvider() { + if (!ruleContext.getConfiguration().checkLicenses()) { + return LicensesProviderImpl.EMPTY; + } + + NestedSetBuilder<TargetLicense> builder = NestedSetBuilder.linkOrder(); + BuildConfiguration configuration = ruleContext.getConfiguration(); + Rule rule = ruleContext.getRule(); + License toolOutputLicense = rule.getToolOutputLicense(ruleContext.attributes()); + if (configuration.isHostConfiguration() && toolOutputLicense != null) { + if (toolOutputLicense != License.NO_LICENSE) { + builder.add(new TargetLicense(rule.getLabel(), toolOutputLicense)); + } + } else { + if (rule.getLicense() != License.NO_LICENSE) { + builder.add(new TargetLicense(rule.getLabel(), rule.getLicense())); + } + + for (TransitiveInfoCollection dep : ruleContext.getConfiguredTargetMap().values()) { + LicensesProvider provider = dep.getProvider(LicensesProvider.class); + if (provider != null) { + builder.addTransitive(provider.getTransitiveLicenses()); + } + } + } + + return new LicensesProviderImpl(builder.build()); + } + + /** + * Scans {@code action_listeners} associated with this build to see if any + * {@code extra_actions} should be added to this configured target. If any + * action_listeners are present, a partial visit of the artifact/action graph + * is performed (for as long as actions found are owned by this {@link + * ConfiguredTarget}). Any actions that match the {@code action_listener} + * get an {@code extra_action} associated. The output artifacts of the + * extra_action are reported to the {@link AnalysisEnvironment} for + * bookkeeping. + */ + private ExtraActionArtifactsProvider initializeExtraActions() { + BuildConfiguration configuration = ruleContext.getConfiguration(); + if (configuration.isHostConfiguration()) { + return ExtraActionArtifactsProvider.EMPTY; + } + + ImmutableList<Artifact> extraActionArtifacts = ImmutableList.of(); + NestedSetBuilder<ExtraArtifactSet> builder = NestedSetBuilder.stableOrder(); + + List<Label> actionListenerLabels = configuration.getActionListeners(); + if (!actionListenerLabels.isEmpty() + && ruleContext.getRule().getAttributeDefinition(":action_listener") != null) { + ExtraActionsVisitor visitor = new ExtraActionsVisitor(ruleContext, + computeMnemonicsToExtraActionMap()); + + // The action list is modified within the body of the loop by the addExtraAction() call, + // thus the copy + for (Action action : ImmutableList.copyOf( + ruleContext.getAnalysisEnvironment().getRegisteredActions())) { + if (!actionsWithoutExtraAction.contains(action)) { + visitor.addExtraAction(action); + } + } + + extraActionArtifacts = visitor.getAndResetExtraArtifacts(); + if (!extraActionArtifacts.isEmpty()) { + builder.add(ExtraArtifactSet.of(ruleContext.getLabel(), extraActionArtifacts)); + } + } + + // Add extra action artifacts from dependencies + for (TransitiveInfoCollection dep : ruleContext.getConfiguredTargetMap().values()) { + ExtraActionArtifactsProvider provider = + dep.getProvider(ExtraActionArtifactsProvider.class); + if (provider != null) { + builder.addTransitive(provider.getTransitiveExtraActionArtifacts()); + } + } + + if (mandatoryStampFiles != null && !mandatoryStampFiles.isEmpty()) { + builder.add(ExtraArtifactSet.of(ruleContext.getLabel(), mandatoryStampFiles)); + } + + if (extraActionArtifacts.isEmpty() && builder.isEmpty()) { + return ExtraActionArtifactsProvider.EMPTY; + } + return new ExtraActionArtifactsProvider(extraActionArtifacts, builder.build()); + } + + /** + * Populates the configuration specific mnemonicToExtraActionMap + * based on all action_listers selected by the user (via the blaze option + * --experimental_action_listener=<target>). + */ + private Multimap<String, ExtraActionSpec> computeMnemonicsToExtraActionMap() { + // We copy the multimap here every time. This could be expensive. + Multimap<String, ExtraActionSpec> mnemonicToExtraActionMap = HashMultimap.create(); + for (TransitiveInfoCollection actionListener : + ruleContext.getPrerequisites(":action_listener", Mode.HOST)) { + ExtraActionMapProvider provider = actionListener.getProvider(ExtraActionMapProvider.class); + if (provider == null) { + ruleContext.ruleError(String.format( + "Unable to match experimental_action_listeners to this rule. " + + "Specified target %s is not an action_listener rule", + actionListener.getLabel().toString())); + } else { + mnemonicToExtraActionMap.putAll(provider.getExtraActionMap()); + } + } + return mnemonicToExtraActionMap; + } + + private <T extends TransitiveInfoProvider> T findProvider(Class<T> clazz) { + return clazz.cast(providers.get(clazz)); + } + + /** + * Add a specific provider with a given value. + */ + public <T extends TransitiveInfoProvider> RuleConfiguredTargetBuilder add(Class<T> key, T value) { + return addProvider(key, value); + } + + /** + * Add a specific provider with a given value. + */ + public RuleConfiguredTargetBuilder addProvider( + Class<? extends TransitiveInfoProvider> key, TransitiveInfoProvider value) { + Preconditions.checkNotNull(key); + Preconditions.checkNotNull(value); + AnalysisUtils.checkProvider(key); + providers.put(key, value); + return this; + } + + /** + * Add multiple providers with given values. + */ + public RuleConfiguredTargetBuilder addProviders( + Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers) { + for (Entry<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> provider : + providers.entrySet()) { + addProvider(provider.getKey(), provider.getValue()); + } + return this; + } + + /** + * Add a Skylark transitive info. The provider value must be safe (i.e. a String, a Boolean, + * an Integer, an Artifact, a Label, None, a Java TransitiveInfoProvider or something composed + * from these in Skylark using lists, sets, structs or dicts). Otherwise an EvalException is + * thrown. + */ + public RuleConfiguredTargetBuilder addSkylarkTransitiveInfo( + String name, Object value, Location loc) throws EvalException { + try { + checkSkylarkObjectSafe(value); + } catch (IllegalArgumentException e) { + throw new EvalException(loc, String.format("Value of provider '%s' is of an illegal type: %s", + name, e.getMessage())); + } + skylarkProviders.put(name, value); + return this; + } + + /** + * Add a Skylark transitive info. The provider value must be safe. + */ + public RuleConfiguredTargetBuilder addSkylarkTransitiveInfo( + String name, Object value) { + checkSkylarkObjectSafe(value); + skylarkProviders.put(name, value); + return this; + } + + /** + * Check if the value provided by a Skylark provider is safe (i.e. can be a + * TransitiveInfoProvider value). + */ + private void checkSkylarkObjectSafe(Object value) { + if (!isSimpleSkylarkObjectSafe(value.getClass()) + // Java transitive Info Providers are accessible from Skylark. + || value instanceof TransitiveInfoProvider) { + checkCompositeSkylarkObjectSafe(value); + } + } + + private void checkCompositeSkylarkObjectSafe(Object object) { + if (object instanceof SkylarkList) { + SkylarkList list = (SkylarkList) object; + if (list == SkylarkList.EMPTY_LIST || isSimpleSkylarkObjectSafe(list.getGenericType())) { + // Try not to iterate over the list if avoidable. + return; + } + // The list can be a tuple or a list of composite items. + for (Object listItem : list) { + checkSkylarkObjectSafe(listItem); + } + return; + } else if (object instanceof SkylarkNestedSet) { + // SkylarkNestedSets cannot have composite items. + Class<?> genericType = ((SkylarkNestedSet) object).getGenericType(); + if (!genericType.equals(Object.class) && !isSimpleSkylarkObjectSafe(genericType)) { + throw new IllegalArgumentException(EvalUtils.getDatatypeName(genericType)); + } + return; + } else if (object instanceof Map<?, ?>) { + for (Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) { + checkSkylarkObjectSafe(entry.getKey()); + checkSkylarkObjectSafe(entry.getValue()); + } + return; + } else if (object instanceof ClassObject) { + ClassObject struct = (ClassObject) object; + for (String key : struct.getKeys()) { + checkSkylarkObjectSafe(struct.getValue(key)); + } + return; + } + throw new IllegalArgumentException(EvalUtils.getDatatypeName(object)); + } + + private boolean isSimpleSkylarkObjectSafe(Class<?> type) { + return type.equals(String.class) + || type.equals(Integer.class) + || type.equals(Boolean.class) + || Artifact.class.isAssignableFrom(type) + || type.equals(Label.class) + || type.equals(Environment.NoneType.class); + } + + /** + * Set the runfiles support for executable targets. + */ + public RuleConfiguredTargetBuilder setRunfilesSupport( + RunfilesSupport runfilesSupport, Artifact executable) { + this.runfilesSupport = runfilesSupport; + this.executable = executable; + return this; + } + + /** + * Set the files to build. + */ + public RuleConfiguredTargetBuilder setFilesToBuild(NestedSet<Artifact> filesToBuild) { + this.filesToBuild = filesToBuild; + return this; + } + + /** + * Set the baseline coverage Artifacts. + */ + public RuleConfiguredTargetBuilder setBaselineCoverageArtifacts( + Collection<Artifact> artifacts) { + return add(BaselineCoverageArtifactsProvider.class, + new BaselineCoverageArtifactsProvider(ImmutableList.copyOf(artifacts))); + } + + /** + * Set the mandatory stamp files. + */ + public RuleConfiguredTargetBuilder setMandatoryStampFiles(ImmutableList<Artifact> files) { + this.mandatoryStampFiles = files; + return this; + } + + /** + * Set the extra action pseudo actions. + */ + public RuleConfiguredTargetBuilder setActionsWithoutExtraAction( + ImmutableSet<Action> actions) { + this.actionsWithoutExtraAction = actions; + return this; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java new file mode 100644 index 0000000..9ad7c70 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -0,0 +1,1391 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimaps; +import com.google.common.collect.Sets; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.ActionRegistry; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.ArtifactOwner; +import com.google.devtools.build.lib.actions.Root; +import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.PrerequisiteValidator; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment; +import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider; +import com.google.devtools.build.lib.collect.ImmutableSortedKeyListMultimap; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.events.Location; +import com.google.devtools.build.lib.packages.Attribute; +import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition; +import com.google.devtools.build.lib.packages.Attribute.SplitTransition; +import com.google.devtools.build.lib.packages.AttributeMap; +import com.google.devtools.build.lib.packages.FileTarget; +import com.google.devtools.build.lib.packages.ImplicitOutputsFunction; +import com.google.devtools.build.lib.packages.InputFile; +import com.google.devtools.build.lib.packages.OutputFile; +import com.google.devtools.build.lib.packages.PackageSpecification; +import com.google.devtools.build.lib.packages.RawAttributeMapper; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.RuleErrorConsumer; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.packages.TargetUtils; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.rules.fileset.FilesetProvider; +import com.google.devtools.build.lib.shell.ShellUtils; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.FilesetEntry; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.util.FileTypeSet; +import com.google.devtools.build.lib.vfs.FileSystemUtils; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A helper class for rule implementations building and initialization. Objects of this + * class are intended to be passed to the builder for the configured target, which then creates the + * configured target. + */ +public final class RuleContext extends TargetContext + implements ActionConstructionContext, ActionRegistry, RuleErrorConsumer { + + /** + * The configured version of FilesetEntry. + */ + @Immutable + public static final class ConfiguredFilesetEntry { + private final FilesetEntry entry; + private final TransitiveInfoCollection src; + private final ImmutableList<TransitiveInfoCollection> files; + + ConfiguredFilesetEntry(FilesetEntry entry, TransitiveInfoCollection src) { + this.entry = entry; + this.src = src; + this.files = null; + } + + ConfiguredFilesetEntry(FilesetEntry entry, ImmutableList<TransitiveInfoCollection> files) { + this.entry = entry; + this.src = null; + this.files = files; + } + + public FilesetEntry getEntry() { + return entry; + } + + public TransitiveInfoCollection getSrc() { + return src; + } + + /** + * Targets from FilesetEntry.files, or null if the user omitted it. + */ + @Nullable + public List<TransitiveInfoCollection> getFiles() { + return files; + } + } + + static final String HOST_CONFIGURATION_PROGRESS_TAG = "for host"; + + private final Rule rule; + private final ListMultimap<String, ConfiguredTarget> targetMap; + private final ListMultimap<String, ConfiguredFilesetEntry> filesetEntryMap; + private final Set<ConfigMatchingProvider> configConditions; + private final AttributeMap attributes; + private final ImmutableSet<String> features; + + private ActionOwner actionOwner; + + /* lazily computed cache for Make variables, computed from the above. See get... method */ + private transient ConfigurationMakeVariableContext configurationMakeVariableContext = null; + + private RuleContext(Builder builder, ListMultimap<String, ConfiguredTarget> targetMap, + ListMultimap<String, ConfiguredFilesetEntry> filesetEntryMap, + Set<ConfigMatchingProvider> configConditions, ImmutableSet<String> features) { + super(builder.env, builder.rule, builder.configuration, builder.prerequisiteMap.get(null), + builder.visibility); + this.rule = builder.rule; + this.targetMap = targetMap; + this.filesetEntryMap = filesetEntryMap; + this.configConditions = configConditions; + this.attributes = + ConfiguredAttributeMapper.of(builder.rule, configConditions); + this.features = features; + } + + @Override + public Rule getRule() { + return rule; + } + + /** + * The configuration conditions that trigger this rule's configurable attributes. + */ + Set<ConfigMatchingProvider> getConfigConditions() { + return configConditions; + } + + /** + * Returns the host configuration for this rule; keep in mind that there may be multiple different + * host configurations, even during a single build. + */ + public BuildConfiguration getHostConfiguration() { + BuildConfiguration configuration = getConfiguration(); + // Note: the Builder checks that the configuration is non-null. + return configuration.getConfiguration(ConfigurationTransition.HOST); + } + + /** + * Accessor for the Rule's attribute values. + */ + public AttributeMap attributes() { + return attributes; + } + + /** + * Returns whether this instance is known to have errors at this point during analysis. Do not + * call this method after the initializationHook has returned. + */ + public boolean hasErrors() { + return getAnalysisEnvironment().hasErrors(); + } + + /** + * Returns an immutable map from attribute name to list of configured targets for that attribute. + */ + public ListMultimap<String, ? extends TransitiveInfoCollection> getConfiguredTargetMap() { + return targetMap; + } + + /** + * Returns an immutable map from attribute name to list of fileset entries. + */ + public ListMultimap<String, ConfiguredFilesetEntry> getFilesetEntryMap() { + return filesetEntryMap; + } + + @Override + public ActionOwner getActionOwner() { + if (actionOwner == null) { + actionOwner = new RuleActionOwner(rule, getConfiguration()); + } + return actionOwner; + } + + /** + * Returns a configuration fragment for this this target. + */ + @Nullable + public <T extends Fragment> T getFragment(Class<T> fragment) { + // TODO(bazel-team): The fragments can also be accessed directly through BuildConfiguration. + // Can we lock that down somehow? + Preconditions.checkArgument( + rule.getRuleClassObject().isLegalConfigurationFragment(fragment), + "%s does not have access to %s", rule.getRuleClass(), fragment); + return getConfiguration().getFragment(fragment); + } + + @Override + public ArtifactOwner getOwner() { + return getAnalysisEnvironment().getOwner(); + } + + // TODO(bazel-team): This class could be simpler if Rule and BuildConfiguration classes + // were immutable. Then we would need to store only references those two. + @Immutable + private static final class RuleActionOwner implements ActionOwner { + private final Label label; + private final Location location; + private final String configurationName; + private final String mnemonic; + private final String targetKind; + private final String shortCacheKey; + private final boolean hostConfiguration; + + private RuleActionOwner(Rule rule, BuildConfiguration configuration) { + this.label = rule.getLabel(); + this.location = rule.getLocation(); + this.targetKind = rule.getTargetKind(); + this.configurationName = configuration.getShortName(); + this.mnemonic = configuration.getMnemonic(); + this.shortCacheKey = configuration.shortCacheKey(); + this.hostConfiguration = configuration.isHostConfiguration(); + } + + @Override + public Location getLocation() { + return location; + } + + @Override + public Label getLabel() { + return label; + } + + @Override + public String getConfigurationName() { + return configurationName; + } + + @Override + public String getConfigurationMnemonic() { + return mnemonic; + } + + @Override + public String getConfigurationShortCacheKey() { + return shortCacheKey; + } + + @Override + public String getTargetKind() { + return targetKind; + } + + @Override + public String getAdditionalProgressInfo() { + return hostConfiguration ? HOST_CONFIGURATION_PROGRESS_TAG : null; + } + } + + @Override + public void registerAction(Action... action) { + getAnalysisEnvironment().registerAction(action); + } + + /** + * Convenience function for subclasses to report non-attribute-specific + * errors in the current rule. + */ + @Override + public void ruleError(String message) { + reportError(rule.getLocation(), prefixRuleMessage(message)); + } + + /** + * Convenience function for subclasses to report non-attribute-specific + * warnings in the current rule. + */ + @Override + public void ruleWarning(String message) { + reportWarning(rule.getLocation(), prefixRuleMessage(message)); + } + + /** + * Convenience function for subclasses to report attribute-specific errors in + * the current rule. + * + * <p>If the name of the attribute starts with <code>$</code> + * it is replaced with a string <code>(an implicit dependency)</code>. + */ + @Override + public void attributeError(String attrName, String message) { + reportError(rule.getAttributeLocation(attrName), + prefixAttributeMessage(Attribute.isImplicit(attrName) + ? "(an implicit dependency)" + : attrName, + message)); + } + + /** + * Like attributeError, but does not mark the configured target as errored. + * + * <p>If the name of the attribute starts with <code>$</code> + * it is replaced with a string <code>(an implicit dependency)</code>. + */ + @Override + public void attributeWarning(String attrName, String message) { + reportWarning(rule.getAttributeLocation(attrName), + prefixAttributeMessage(Attribute.isImplicit(attrName) + ? "(an implicit dependency)" + : attrName, + message)); + } + + private String prefixAttributeMessage(String attrName, String message) { + return "in " + attrName + " attribute of " + + rule.getRuleClass() + " rule " + + getLabel() + ": " + message; + } + + private String prefixRuleMessage(String message) { + return "in " + rule.getRuleClass() + " rule " + + getLabel() + ": " + message; + } + + private void reportError(Location location, String message) { + getAnalysisEnvironment().getEventHandler().handle(Event.error(location, message)); + } + + private void reportWarning(Location location, String message) { + getAnalysisEnvironment().getEventHandler().handle(Event.warn(location, message)); + } + + /** + * Returns an artifact beneath the root of either the "bin" or "genfiles" + * tree, whose path is based on the name of this target and the current + * configuration. The choice of which tree to use is based on the rule with + * which this target (which must be an OutputFile or a Rule) is associated. + */ + public Artifact createOutputArtifact() { + return internalCreateOutputArtifact(getTarget()); + } + + /** + * Returns the output artifact of an {@link OutputFile} of this target. + * + * @see #createOutputArtifact() + */ + public Artifact createOutputArtifact(OutputFile out) { + return internalCreateOutputArtifact(out); + } + + /** + * Implementation for {@link #createOutputArtifact()} and + * {@link #createOutputArtifact(OutputFile)}. This is private so that + * {@link #createOutputArtifact(OutputFile)} can have a more specific + * signature. + */ + private Artifact internalCreateOutputArtifact(Target target) { + Root root = getBinOrGenfilesDirectory(); + return getAnalysisEnvironment().getDerivedArtifact(Util.getWorkspaceRelativePath(target), root); + } + + /** + * Returns the root of either the "bin" or "genfiles" + * tree, based on this target and the current configuration. + * The choice of which tree to use is based on the rule with + * which this target (which must be an OutputFile or a Rule) is associated. + */ + public Root getBinOrGenfilesDirectory() { + return rule.hasBinaryOutput() + ? getConfiguration().getBinDirectory() + : getConfiguration().getGenfilesDirectory(); + } + + /** + * Returns the list of transitive info collections that feed into this target through the + * specified attribute. Note that you need to specify the correct mode for the attribute, + * otherwise an assertion will be raised. + */ + public List<? extends TransitiveInfoCollection> getPrerequisites(String attributeName, + Mode mode) { + Attribute attributeDefinition = getRule().getAttributeDefinition(attributeName); + if ((mode == Mode.TARGET) + && (attributeDefinition.getConfigurationTransition() instanceof SplitTransition)) { + // TODO(bazel-team): If you request a split-configured attribute in the target configuration, + // we return only the list of configured targets for the first architecture; this is for + // backwards compatibility with existing code in cases where the call to getPrerequisites is + // deeply nested and we can't easily inject the behavior we want. However, we should fix all + // such call sites. + checkAttribute(attributeName, Mode.SPLIT); + Map<String, ? extends List<? extends TransitiveInfoCollection>> map = + getSplitPrerequisites(attributeName, /*requireSplit=*/false); + return map.isEmpty() + ? ImmutableList.<TransitiveInfoCollection>of() + : map.entrySet().iterator().next().getValue(); + } + + checkAttribute(attributeName, mode); + return targetMap.get(attributeName); + } + + /** + * Returns the a prerequisites keyed by the CPU of their configurations; this method throws an + * exception if the split transition is not active. + */ + public Map<String, ? extends List<? extends TransitiveInfoCollection>> + getSplitPrerequisites(String attributeName) { + return getSplitPrerequisites(attributeName, /*requireSplit*/true); + } + + private Map<String, ? extends List<? extends TransitiveInfoCollection>> + getSplitPrerequisites(String attributeName, boolean requireSplit) { + checkAttribute(attributeName, Mode.SPLIT); + + Attribute attributeDefinition = getRule().getAttributeDefinition(attributeName); + SplitTransition<?> transition = + (SplitTransition<?>) attributeDefinition.getConfigurationTransition(); + List<BuildConfiguration> configurations = + getConfiguration().getTransitions().getSplitConfigurations(transition); + if (configurations.size() == 1) { + // There are two cases here: + // 1. Splitting is enabled, but only one target cpu. + // 2. Splitting is disabled, and no --cpu value was provided on the command line. + // In the first case, the cpu value is non-null, but in the second case it is null. We only + // allow that to proceed if the caller specified that he is going to ignore the cpu value + // anyway. + String cpu = configurations.get(0).getCpu(); + if (cpu == null) { + Preconditions.checkState(!requireSplit); + cpu = "DO_NOT_USE"; + } + return ImmutableMap.of(cpu, targetMap.get(attributeName)); + } + + Set<String> cpus = new HashSet<>(); + for (BuildConfiguration config : configurations) { + // This method should only be called when the split config is enabled on the command line, in + // which case this cpu can't be null. + Preconditions.checkNotNull(config.getCpu()); + cpus.add(config.getCpu()); + } + + // Use an ImmutableListMultimap.Builder here to preserve ordering. + ImmutableListMultimap.Builder<String, TransitiveInfoCollection> result = + ImmutableListMultimap.builder(); + for (TransitiveInfoCollection t : targetMap.get(attributeName)) { + if (t.getConfiguration() != null) { + result.put(t.getConfiguration().getCpu(), t); + } else { + // Source files don't have a configuration, so we add them to all architecture entries. + for (String cpu : cpus) { + result.put(cpu, t); + } + } + } + return Multimaps.asMap(result.build()); + } + + /** + * Returns the specified provider of the prerequisite referenced by the attribute in the + * argument. Note that you need to specify the correct mode for the attribute, otherwise an + * assertion will be raised. If the attribute is empty of it does not support the specified + * provider, returns null. + */ + public <C extends TransitiveInfoProvider> C getPrerequisite( + String attributeName, Mode mode, Class<C> provider) { + TransitiveInfoCollection prerequisite = getPrerequisite(attributeName, mode); + return prerequisite == null ? null : prerequisite.getProvider(provider); + } + + /** + * Returns the transitive info collection that feeds into this target through the specified + * attribute. Note that you need to specify the correct mode for the attribute, otherwise an + * assertion will be raised. Returns null if the attribute is empty. + */ + public TransitiveInfoCollection getPrerequisite(String attributeName, Mode mode) { + checkAttribute(attributeName, mode); + List<? extends TransitiveInfoCollection> elements = targetMap.get(attributeName); + if (elements.size() > 1) { + throw new IllegalStateException(rule.getRuleClass() + " attribute " + attributeName + + " produces more then one prerequisites"); + } + return elements.isEmpty() ? null : elements.get(0); + } + + /** + * Returns all the providers of the specified type that are listed under the specified attribute + * of this target in the BUILD file. + */ + public <C extends TransitiveInfoProvider> Iterable<C> getPrerequisites(String attributeName, + Mode mode, final Class<C> classType) { + AnalysisUtils.checkProvider(classType); + return AnalysisUtils.getProviders(getPrerequisites(attributeName, mode), classType); + } + + /** + * Returns all the providers of the specified type that are listed under the specified attribute + * of this target in the BUILD file, and that contain the specified provider. + */ + public <C extends TransitiveInfoProvider> Iterable<? extends TransitiveInfoCollection> + getPrerequisitesIf(String attributeName, Mode mode, final Class<C> classType) { + AnalysisUtils.checkProvider(classType); + return AnalysisUtils.filterByProvider(getPrerequisites(attributeName, mode), classType); + } + + /** + * Returns the prerequisite referred to by the specified attribute. Also checks whether + * the attribute is marked as executable and that the target referred to can actually be + * executed. + * + * <p>The {@code mode} argument must match the configuration transition specified in the + * definition of the attribute. + * + * @param attributeName the name of the attribute + * @param mode the configuration transition of the attribute + * + * @return the {@link FilesToRunProvider} interface of the prerequisite. + */ + public FilesToRunProvider getExecutablePrerequisite(String attributeName, Mode mode) { + Attribute ruleDefinition = getRule().getAttributeDefinition(attributeName); + + if (ruleDefinition == null) { + throw new IllegalStateException(getRule().getRuleClass() + " attribute " + attributeName + + " is not defined"); + } + if (!ruleDefinition.isExecutable()) { + throw new IllegalStateException(getRule().getRuleClass() + " attribute " + attributeName + + " is not configured to be executable"); + } + + TransitiveInfoCollection prerequisite = getPrerequisite(attributeName, mode); + if (prerequisite == null) { + return null; + } + + FilesToRunProvider result = prerequisite.getProvider(FilesToRunProvider.class); + if (result == null || result.getExecutable() == null) { + attributeError( + attributeName, prerequisite.getLabel() + " does not refer to a valid executable target"); + } + return result; + } + + /** + * Gets an attribute of type STRING_LIST expanding Make variables and + * tokenizes the result. + * + * @param attributeName the name of the attribute to process + * @return a list of strings containing the expanded and tokenized values for the + * attribute + */ + public List<String> getTokenizedStringListAttr(String attributeName) { + if (!getRule().isAttrDefined(attributeName, Type.STRING_LIST)) { + // TODO(bazel-team): This should be an error. + return ImmutableList.of(); + } + List<String> original = attributes().get(attributeName, Type.STRING_LIST); + if (original.isEmpty()) { + return ImmutableList.of(); + } + List<String> tokens = new ArrayList<>(); + for (String token : original) { + tokenizeAndExpandMakeVars(tokens, attributeName, token); + } + return ImmutableList.copyOf(tokens); + } + + /** + * Expands make variables in value and tokenizes the result into tokens. + * + * <p>This methods should be called only during initialization. + */ + public void tokenizeAndExpandMakeVars(List<String> tokens, String attributeName, + String value) { + try { + ShellUtils.tokenize(tokens, expandMakeVariables(attributeName, value)); + } catch (ShellUtils.TokenizationException e) { + attributeError(attributeName, e.getMessage()); + } + } + + /** + * Return a context that maps Make variable names (string) to values (string). + * + * @return a ConfigurationMakeVariableContext. + **/ + public ConfigurationMakeVariableContext getConfigurationMakeVariableContext() { + if (configurationMakeVariableContext == null) { + configurationMakeVariableContext = new ConfigurationMakeVariableContext( + getRule().getPackage(), getConfiguration()); + } + return configurationMakeVariableContext; + } + + /** + * Returns the string "expression" after expanding all embedded references to + * "Make" variables. If any errors are encountered, they are reported, and + * "expression" is returned unchanged. + * + * @param attributeName the name of the attribute from which "expression" comes; + * used for error reporting. + * @param expression the string to expand. + * @return the expansion of "expression". + */ + public String expandMakeVariables(String attributeName, String expression) { + return expandMakeVariables(attributeName, expression, getConfigurationMakeVariableContext()); + } + + /** + * Returns the string "expression" after expanding all embedded references to + * "Make" variables. If any errors are encountered, they are reported, and + * "expression" is returned unchanged. + * + * @param attributeName the name of the attribute from which "expression" comes; + * used for error reporting. + * @param expression the string to expand. + * @param context the ConfigurationMakeVariableContext which can have a customized + * lookupMakeVariable(String) method. + * @return the expansion of "expression". + */ + public String expandMakeVariables(String attributeName, String expression, + ConfigurationMakeVariableContext context) { + try { + return MakeVariableExpander.expand(expression, context); + } catch (MakeVariableExpander.ExpansionException e) { + attributeError(attributeName, e.getMessage()); + return expression; + } + } + + /** + * Gets the value of the STRING_LIST attribute expanding all make variables. + */ + public List<String> expandedMakeVariablesList(String attrName) { + List<String> variables = new ArrayList<>(); + for (String variable : attributes().get(attrName, Type.STRING_LIST)) { + variables.add(expandMakeVariables(attrName, variable)); + } + return variables; + } + + /** + * If the string consists of a single variable, returns the expansion of + * that variable. Otherwise, returns null. Syntax errors are reported. + * + * @param attrName the name of the attribute from which "expression" comes; + * used for error reporting. + * @param expression the string to expand. + * @return the expansion of "expression", or null. + */ + public String expandSingleMakeVariable(String attrName, String expression) { + try { + return MakeVariableExpander.expandSingleVariable(expression, + new ConfigurationMakeVariableContext(getRule().getPackage(), getConfiguration())); + } catch (MakeVariableExpander.ExpansionException e) { + attributeError(attrName, e.getMessage()); + return expression; + } + } + + private void checkAttribute(String attributeName, Mode mode) { + Attribute attributeDefinition = getRule().getAttributeDefinition(attributeName); + if (attributeDefinition == null) { + throw new IllegalStateException(getRule().getLocation() + ": " + getRule().getRuleClass() + + " attribute " + attributeName + " is not defined"); + } + if (!(attributeDefinition.getType() == Type.LABEL + || attributeDefinition.getType() == Type.LABEL_LIST)) { + throw new IllegalStateException(rule.getRuleClass() + " attribute " + attributeName + + " is not a label type attribute"); + } + if (mode == Mode.HOST) { + if (attributeDefinition.getConfigurationTransition() != ConfigurationTransition.HOST) { + throw new IllegalStateException(getRule().getLocation() + ": " + + getRule().getRuleClass() + " attribute " + attributeName + + " is not configured for the host configuration"); + } + } else if (mode == Mode.TARGET) { + if (attributeDefinition.getConfigurationTransition() != ConfigurationTransition.NONE) { + throw new IllegalStateException(getRule().getLocation() + ": " + + getRule().getRuleClass() + " attribute " + attributeName + + " is not configured for the target configuration"); + } + } else if (mode == Mode.DATA) { + if (attributeDefinition.getConfigurationTransition() != ConfigurationTransition.DATA) { + throw new IllegalStateException(getRule().getLocation() + ": " + + getRule().getRuleClass() + " attribute " + attributeName + + " is not configured for the data configuration"); + } + } else if (mode == Mode.SPLIT) { + if (!(attributeDefinition.getConfigurationTransition() instanceof SplitTransition)) { + throw new IllegalStateException(getRule().getLocation() + ": " + + getRule().getRuleClass() + " attribute " + attributeName + + " is not configured for a split transition"); + } + } + } + + /** + * Returns the Mode for which the attribute is configured. + * This is intended for Skylark, where the Mode is implicitly chosen. + */ + public Mode getAttributeMode(String attributeName) { + Attribute attributeDefinition = getRule().getAttributeDefinition(attributeName); + if (attributeDefinition == null) { + throw new IllegalStateException(getRule().getLocation() + ": " + getRule().getRuleClass() + + " attribute " + attributeName + " is not defined"); + } + if (!(attributeDefinition.getType() == Type.LABEL + || attributeDefinition.getType() == Type.LABEL_LIST)) { + throw new IllegalStateException(rule.getRuleClass() + " attribute " + attributeName + + " is not a label type attribute"); + } + if (attributeDefinition.getConfigurationTransition() == ConfigurationTransition.HOST) { + return Mode.HOST; + } else if (attributeDefinition.getConfigurationTransition() == ConfigurationTransition.NONE) { + return Mode.TARGET; + } else if (attributeDefinition.getConfigurationTransition() == ConfigurationTransition.DATA) { + return Mode.DATA; + } else if (attributeDefinition.getConfigurationTransition() instanceof SplitTransition) { + return Mode.SPLIT; + } + throw new IllegalStateException(getRule().getLocation() + ": " + + getRule().getRuleClass() + " attribute " + attributeName + " is not configured"); + } + + /** + * For the specified attribute "attributeName" (which must be of type + * list(label)), resolve all the labels into ConfiguredTargets (for the + * configuration appropriate to the attribute) and return their build + * artifacts as a {@link PrerequisiteArtifacts} instance. + * + * @param attributeName the name of the attribute to traverse + */ + public PrerequisiteArtifacts getPrerequisiteArtifacts(String attributeName, Mode mode) { + return PrerequisiteArtifacts.get(this, attributeName, mode); + } + + /** + * For the specified attribute "attributeName" (which must be of type label), + * resolves the ConfiguredTarget and returns its single build artifact. + * + * <p>If the attribute is optional, has no default and was not specified, then + * null will be returned. Note also that null is returned (and an attribute + * error is raised) if there wasn't exactly one build artifact for the target. + */ + public Artifact getPrerequisiteArtifact(String attributeName, Mode mode) { + TransitiveInfoCollection target = getPrerequisite(attributeName, mode); + return transitiveInfoCollectionToArtifact(attributeName, target); + } + + /** + * Equivalent to getPrerequisiteArtifact(), but also asserts that + * host-configuration is appropriate for the specified attribute. + */ + public Artifact getHostPrerequisiteArtifact(String attributeName) { + TransitiveInfoCollection target = getPrerequisite(attributeName, Mode.HOST); + return transitiveInfoCollectionToArtifact(attributeName, target); + } + + private Artifact transitiveInfoCollectionToArtifact( + String attributeName, TransitiveInfoCollection target) { + if (target != null) { + Iterable<Artifact> artifacts = target.getProvider(FileProvider.class).getFilesToBuild(); + if (Iterables.size(artifacts) == 1) { + return Iterables.getOnlyElement(artifacts); + } else { + attributeError(attributeName, target.getLabel() + " expected a single artifact"); + } + } + return null; + } + + /** + * Returns the sole file in the "srcs" attribute. Reports an error and + * (possibly) returns null if "srcs" does not identify a single file of the + * expected type. + */ + public Artifact getSingleSource(String fileTypeName) { + List<Artifact> srcs = PrerequisiteArtifacts.get(this, "srcs", Mode.TARGET).list(); + switch (srcs.size()) { + case 0 : // error already issued by getSrc() + return null; + case 1 : // ok + return Iterables.getOnlyElement(srcs); + default : + attributeError("srcs", "only a single " + fileTypeName + " is allowed here"); + return srcs.get(0); + } + } + + public Artifact getSingleSource() { + return getSingleSource(getRule().getRuleClass() + " source file"); + } + + /** + * Returns a path fragment qualified by the rule name and unique fragment to + * disambiguate artifacts produced from the source file appearing in + * multiple rules. + * + * <p>For example "pkg/dir/name" -> "pkg/<fragment>/rule/dir/name. + */ + public final PathFragment getUniqueDirectory(String fragment) { + return AnalysisUtils.getUniqueDirectory(getLabel(), new PathFragment(fragment)); + } + + /** + * Check that all targets that were specified as sources are from the same + * package as this rule. Output a warning or an error for every target that is + * imported from a different package. + */ + public void checkSrcsSamePackage(boolean onlyWarn) { + PathFragment packageName = getLabel().getPackageFragment(); + for (Artifact srcItem : PrerequisiteArtifacts.get(this, "srcs", Mode.TARGET).list()) { + if (!srcItem.isSourceArtifact()) { + // In theory, we should not do this check. However, in practice, we + // have a couple of rules that do not obey the "srcs must contain + // files and only files" rule. Thus, we are stuck with this hack here :( + continue; + } + Label associatedLabel = srcItem.getOwner(); + PathFragment itemPackageName = associatedLabel.getPackageFragment(); + if (!itemPackageName.equals(packageName)) { + String message = "please do not import '" + associatedLabel + "' directly. " + + "You should either move the file to this package or depend on " + + "an appropriate rule there"; + if (onlyWarn) { + attributeWarning("srcs", message); + } else { + attributeError("srcs", message); + } + } + } + } + + + /** + * Returns the label to which the {@code NODEP_LABEL} attribute + * {@code attrName} refers, checking that it is a valid label, and that it is + * referring to a local target. Reports a warning otherwise. + */ + public Label getLocalNodepLabelAttribute(String attrName) { + Label label = attributes().get(attrName, Type.NODEP_LABEL); + if (label == null) { + return null; + } + + if (!getTarget().getLabel().getPackageFragment().equals(label.getPackageFragment())) { + attributeWarning(attrName, "does not reference a local rule"); + } + + return label; + } + + /** + * Returns the implicit output artifact for a given template function. If multiple or no artifacts + * can be found as a result of the template, an exception is thrown. + */ + public Artifact getImplicitOutputArtifact(ImplicitOutputsFunction function) { + Iterable<String> result; + try { + result = function.getImplicitOutputs(RawAttributeMapper.of(rule)); + } catch (EvalException e) { + // It's ok as long as we don't use this method from Skylark. + throw new IllegalStateException(e); + } + return getImplicitOutputArtifact(Iterables.getOnlyElement(result)); + } + + /** + * Only use from Skylark. Returns the implicit output artifact for a given output path. + */ + public Artifact getImplicitOutputArtifact(String path) { + Root root = getBinOrGenfilesDirectory(); + PathFragment packageFragment = getLabel().getPackageFragment(); + return getAnalysisEnvironment().getDerivedArtifact(packageFragment.getRelative(path), root); + } + + /** + * Convenience method to return a host configured target for the "compiler" + * attribute. Allows caller to decide whether a warning should be printed if + * the "compiler" attribute is not set to the default value. + * + * @param warnIfNotDefault if true, print a warning if the value for the + * "compiler" attribute is set to something other than the default + * @return a ConfiguredTarget using the host configuration for the "compiler" + * attribute + */ + public final FilesToRunProvider getCompiler(boolean warnIfNotDefault) { + Label label = attributes().get("compiler", Type.LABEL); + if (warnIfNotDefault && !label.equals(getRule().getAttrDefaultValue("compiler"))) { + attributeWarning("compiler", "setting the compiler is strongly discouraged"); + } + return getExecutablePrerequisite("compiler", Mode.HOST); + } + + /** + * Returns the (unmodifiable, ordered) list of artifacts which are the outputs + * of this target. + * + * <p>Each element in this list is associated with a single output, either + * declared implicitly (via setImplicitOutputsFunction()) or explicitly + * (listed in the 'outs' attribute of our rule). + */ + public final ImmutableList<Artifact> getOutputArtifacts() { + ImmutableList.Builder<Artifact> artifacts = ImmutableList.builder(); + for (OutputFile out : getRule().getOutputFiles()) { + artifacts.add(createOutputArtifact(out)); + } + return artifacts.build(); + } + + /** + * Like getFilesToBuild(), except that it also includes the runfiles middleman, if any. + * Middlemen are expanded in the SpawnStrategy or by the Distributor. + */ + public static ImmutableList<Artifact> getFilesToRun( + RunfilesSupport runfilesSupport, NestedSet<Artifact> filesToBuild) { + if (runfilesSupport == null) { + return ImmutableList.copyOf(filesToBuild); + } else { + ImmutableList.Builder<Artifact> allFilesToBuild = ImmutableList.builder(); + allFilesToBuild.addAll(filesToBuild); + allFilesToBuild.add(runfilesSupport.getRunfilesMiddleman()); + return allFilesToBuild.build(); + } + } + + /** + * Like {@link #getOutputArtifacts()} but for a singular output item. + * Reports an error if the "out" attribute is not a singleton. + * + * @return null if the output list is empty, the artifact for the first item + * of the output list otherwise + */ + public Artifact getOutputArtifact() { + List<Artifact> outs = getOutputArtifacts(); + if (outs.size() != 1) { + attributeError("out", "exactly one output file required"); + if (outs.isEmpty()) { + return null; + } + } + return outs.get(0); + } + + /** + * Returns an artifact with a given file extension. All other path components + * are the same as in {@code pathFragment}. + */ + public final Artifact getRelatedArtifact(PathFragment pathFragment, String extension) { + PathFragment file = FileSystemUtils.replaceExtension(pathFragment, extension); + return getAnalysisEnvironment().getDerivedArtifact(file, getConfiguration().getBinDirectory()); + } + + /** + * Returns true if runfiles support should create the runfiles tree, or + * false if it should just create the manifest. + */ + public boolean shouldCreateRunfilesSymlinks() { + // TODO(bazel-team): Ideally we wouldn't need such logic, and we'd + // always use the BuildConfiguration#buildRunfiles() to determine + // whether to build the runfiles. The problem is that certain build + // steps actually consume their runfiles. These include: + // a. par files consumes the runfiles directory + // We should modify autopar to take a list of files instead. + // of the runfiles directory. + // b. host tools could potentially use data files, but currently don't + // (they're run from the execution root, not a runfiles tree). + // Currently hostConfiguration.buildRunfiles() returns true. + if (TargetUtils.isTestRule(getTarget())) { + // Tests are only executed during testing (duh), + // and their runfiles are generated lazily on local + // execution (see LocalTestStrategy). Therefore, it + // is safe not to build their runfiles. + return getConfiguration().buildRunfiles(); + } else { + return true; + } + } + + /** + * @return true if {@code rule} is visible from {@code prerequisite}. + * + * <p>This only computes the logic as implemented by the visibility system. The final decision + * whether a dependency is allowed is made by + * {@link ConfiguredRuleClassProvider.PrerequisiteValidator}. + */ + public static boolean isVisible(Rule rule, TransitiveInfoCollection prerequisite) { + // Check visibility attribute + for (PackageSpecification specification : + prerequisite.getProvider(VisibilityProvider.class).getVisibility()) { + if (specification.containsPackage(rule.getLabel().getPackageFragment())) { + return true; + } + } + + return false; + } + + /** + * @return the set of features applicable for the current rule's package. + */ + public ImmutableSet<String> getFeatures() { + return features; + } + + /** + * Builder class for a RuleContext. + */ + public static final class Builder { + private final AnalysisEnvironment env; + private final Rule rule; + private final BuildConfiguration configuration; + private final PrerequisiteValidator prerequisiteValidator; + private ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap; + private Set<ConfigMatchingProvider> configConditions; + private NestedSet<PackageSpecification> visibility; + + Builder(AnalysisEnvironment env, Rule rule, BuildConfiguration configuration, + PrerequisiteValidator prerequisiteValidator) { + this.env = Preconditions.checkNotNull(env); + this.rule = Preconditions.checkNotNull(rule); + this.configuration = Preconditions.checkNotNull(configuration); + this.prerequisiteValidator = prerequisiteValidator; + } + + RuleContext build() { + Preconditions.checkNotNull(prerequisiteMap); + Preconditions.checkNotNull(configConditions); + Preconditions.checkNotNull(visibility); + ListMultimap<String, ConfiguredTarget> targetMap = createTargetMap(); + ListMultimap<String, ConfiguredFilesetEntry> filesetEntryMap = + createFilesetEntryMap(rule, configConditions); + return new RuleContext(this, targetMap, filesetEntryMap, configConditions, + getEnabledFeatures()); + } + + private ImmutableSet<String> getEnabledFeatures() { + Set<String> enabled = new HashSet<>(); + Set<String> disabled = new HashSet<>(); + for (String feature : Iterables.concat(getConfiguration().getDefaultFeatures(), + getRule().getPackage().getFeatures())) { + if (feature.startsWith("-")) { + disabled.add(feature.substring(1)); + } else if (feature.equals("no_layering_check")) { + // TODO(bazel-team): Remove once we do not have BUILD files left that contain + // 'no_layering_check'. + disabled.add(feature.substring(3)); + } else { + enabled.add(feature); + } + } + return Sets.difference(enabled, disabled).immutableCopy(); + } + + Builder setVisibility(NestedSet<PackageSpecification> visibility) { + this.visibility = visibility; + return this; + } + + /** + * Sets the prerequisites and checks their visibility. It also generates appropriate error or + * warning messages and sets the error flag as appropriate. + */ + Builder setPrerequisites(ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap) { + this.prerequisiteMap = Preconditions.checkNotNull(prerequisiteMap); + return this; + } + + /** + * Sets the configuration conditions needed to determine which paths to follow for this + * rule's configurable attributes. + */ + Builder setConfigConditions(Set<ConfigMatchingProvider> configConditions) { + this.configConditions = Preconditions.checkNotNull(configConditions); + return this; + } + + private boolean validateFilesetEntry(FilesetEntry filesetEntry, ConfiguredTarget src) { + if (src.getProvider(FilesetProvider.class) != null) { + return true; + } + if (filesetEntry.isSourceFileset()) { + return true; + } + + Target srcTarget = src.getTarget(); + if (!(srcTarget instanceof FileTarget)) { + attributeError("entries", String.format( + "Invalid 'srcdir' target '%s'. Must be another Fileset or package", + srcTarget.getLabel())); + return false; + } + + if (srcTarget instanceof OutputFile) { + attributeWarning("entries", String.format("'srcdir' target '%s' is not an input file. " + + "This forces the Fileset to be executed unconditionally", + srcTarget.getLabel())); + } + + return true; + } + + /** + * Determines and returns a map from attribute name to list of configured fileset entries, based + * on a PrerequisiteMap instance. + */ + private ListMultimap<String, ConfiguredFilesetEntry> createFilesetEntryMap( + final Rule rule, Set<ConfigMatchingProvider> configConditions) { + final ImmutableSortedKeyListMultimap.Builder<String, ConfiguredFilesetEntry> mapBuilder = + ImmutableSortedKeyListMultimap.builder(); + for (Attribute attr : rule.getAttributes()) { + if (attr.getType() != Type.FILESET_ENTRY_LIST) { + continue; + } + String attributeName = attr.getName(); + Map<Label, ConfiguredTarget> ctMap = new HashMap<>(); + for (ConfiguredTarget prerequisite : prerequisiteMap.get(attr)) { + ctMap.put(prerequisite.getLabel(), prerequisite); + } + List<FilesetEntry> entries = ConfiguredAttributeMapper.of(rule, configConditions) + .get(attributeName, Type.FILESET_ENTRY_LIST); + for (FilesetEntry entry : entries) { + if (entry.getFiles() == null) { + Label label = entry.getSrcLabel(); + ConfiguredTarget src = ctMap.get(label); + if (!validateFilesetEntry(entry, src)) { + continue; + } + + mapBuilder.put(attributeName, new ConfiguredFilesetEntry(entry, src)); + } else { + ImmutableList.Builder<TransitiveInfoCollection> files = ImmutableList.builder(); + for (Label file : entry.getFiles()) { + files.add(ctMap.get(file)); + } + mapBuilder.put(attributeName, new ConfiguredFilesetEntry(entry, files.build())); + } + } + } + return mapBuilder.build(); + } + + /** + * Determines and returns a map from attribute name to list of configured targets. + */ + private ImmutableSortedKeyListMultimap<String, ConfiguredTarget> createTargetMap() { + ImmutableSortedKeyListMultimap.Builder<String, ConfiguredTarget> mapBuilder = + ImmutableSortedKeyListMultimap.builder(); + + for (Map.Entry<Attribute, Collection<ConfiguredTarget>> entry : + prerequisiteMap.asMap().entrySet()) { + Attribute attribute = entry.getKey(); + if (attribute == null) { + continue; + } + if (attribute.isSilentRuleClassFilter()) { + Predicate<RuleClass> filter = attribute.getAllowedRuleClassesPredicate(); + for (ConfiguredTarget configuredTarget : entry.getValue()) { + Target prerequisiteTarget = configuredTarget.getTarget(); + if ((prerequisiteTarget instanceof Rule) + && filter.apply(((Rule) prerequisiteTarget).getRuleClassObject())) { + validateDirectPrerequisite(attribute, configuredTarget); + mapBuilder.put(attribute.getName(), configuredTarget); + } + } + } else { + for (ConfiguredTarget configuredTarget : entry.getValue()) { + validateDirectPrerequisite(attribute, configuredTarget); + mapBuilder.put(attribute.getName(), configuredTarget); + } + } + } + + // Handle abi_deps+deps error. + Attribute abiDepsAttr = rule.getAttributeDefinition("abi_deps"); + if ((abiDepsAttr != null) && rule.isAttributeValueExplicitlySpecified("abi_deps") + && rule.isAttributeValueExplicitlySpecified("deps")) { + attributeError("deps", "Only one of deps and abi_deps should be provided"); + } + return mapBuilder.build(); + } + + private String prefixRuleMessage(String message) { + return String.format("in %s rule %s: %s", rule.getRuleClass(), rule.getLabel(), message); + } + + private String maskInternalAttributeNames(String name) { + return Attribute.isImplicit(name) ? "(an implicit dependency)" : name; + } + + private String prefixAttributeMessage(String attrName, String message) { + return String.format("in %s attribute of %s rule %s: %s", + maskInternalAttributeNames(attrName), rule.getRuleClass(), rule.getLabel(), message); + } + + public void reportError(Location location, String message) { + env.getEventHandler().handle(Event.error(location, message)); + } + + public void ruleError(String message) { + reportError(rule.getLocation(), prefixRuleMessage(message)); + } + + public void attributeError(String attrName, String message) { + reportError(rule.getAttributeLocation(attrName), prefixAttributeMessage(attrName, message)); + } + + public void reportWarning(Location location, String message) { + env.getEventHandler().handle(Event.warn(location, message)); + } + + public void ruleWarning(String message) { + env.getEventHandler().handle(Event.warn(rule.getLocation(), prefixRuleMessage(message))); + } + + public void attributeWarning(String attrName, String message) { + reportWarning(rule.getAttributeLocation(attrName), prefixAttributeMessage(attrName, message)); + } + + private void reportBadPrerequisite(Attribute attribute, String targetKind, + Label prerequisiteLabel, String reason, boolean isWarning) { + String msgPrefix = targetKind != null ? targetKind + " " : ""; + String msgReason = reason != null ? " (" + reason + ")" : ""; + if (isWarning) { + attributeWarning(attribute.getName(), String.format( + "%s'%s' is unexpected here%s; continuing anyway", + msgPrefix, prerequisiteLabel, msgReason)); + } else { + attributeError(attribute.getName(), String.format( + "%s'%s' is misplaced here%s", msgPrefix, prerequisiteLabel, msgReason)); + } + } + + private void validateDirectPrerequisiteType(ConfiguredTarget prerequisite, + Attribute attribute) { + Target prerequisiteTarget = prerequisite.getTarget(); + Label prerequisiteLabel = prerequisiteTarget.getLabel(); + + if (prerequisiteTarget instanceof Rule) { + Rule prerequisiteRule = (Rule) prerequisiteTarget; + + String reason = attribute.getValidityPredicate().checkValid(rule, prerequisiteRule); + if (reason != null) { + reportBadPrerequisite(attribute, prerequisiteTarget.getTargetKind(), + prerequisiteLabel, reason, false); + } + } + + if (attribute.isStrictLabelCheckingEnabled()) { + if (prerequisiteTarget instanceof Rule) { + RuleClass ruleClass = ((Rule) prerequisiteTarget).getRuleClassObject(); + if (!attribute.getAllowedRuleClassesPredicate().apply(ruleClass)) { + boolean allowedWithWarning = attribute.getAllowedRuleClassesWarningPredicate() + .apply(ruleClass); + reportBadPrerequisite(attribute, prerequisiteTarget.getTargetKind(), prerequisiteLabel, + "expected " + attribute.getAllowedRuleClassesPredicate().toString(), + allowedWithWarning); + } + } else if (prerequisiteTarget instanceof FileTarget) { + if (!attribute.getAllowedFileTypesPredicate() + .apply(((FileTarget) prerequisiteTarget).getFilename())) { + if (prerequisiteTarget instanceof InputFile + && !((InputFile) prerequisiteTarget).getPath().exists()) { + // Misplaced labels, no corresponding target exists + if (attribute.getAllowedFileTypesPredicate().isNone() + && !((InputFile) prerequisiteTarget).getFilename().contains(".")) { + // There are no allowed files in the attribute but it's not a valid rule, + // and the filename doesn't contain a dot --> probably a misspelled rule + attributeError(attribute.getName(), + "rule '" + prerequisiteLabel + "' does not exist"); + } else { + attributeError(attribute.getName(), + "target '" + prerequisiteLabel + "' does not exist"); + } + } else { + // The file exists but has a bad extension + reportBadPrerequisite(attribute, "file", prerequisiteLabel, + "expected " + attribute.getAllowedFileTypesPredicate().toString(), false); + } + } + } + } + } + + public Rule getRule() { + return rule; + } + + public BuildConfiguration getConfiguration() { + return configuration; + } + + /** + * @return true if {@code rule} is visible from {@code prerequisite}. + * + * <p>This only computes the logic as implemented by the visibility system. The final decision + * whether a dependency is allowed is made by + * {@link ConfiguredRuleClassProvider.PrerequisiteValidator}, who is supposed to call this + * method to determine whether a dependency is allowed as per visibility rules. + */ + public boolean isVisible(TransitiveInfoCollection prerequisite) { + return RuleContext.isVisible(rule, prerequisite); + } + + private void validateDirectPrerequisiteFileTypes(ConfiguredTarget prerequisite, + Attribute attribute) { + if (attribute.isSkipAnalysisTimeFileTypeCheck()) { + return; + } + FileTypeSet allowedFileTypes = attribute.getAllowedFileTypesPredicate(); + if (allowedFileTypes == FileTypeSet.ANY_FILE && !attribute.isNonEmpty() + && !attribute.isSingleArtifact()) { + return; + } + + // If we allow any file we still need to check if there are actually files generated + // Note that this check only runs for ANY_FILE predicates if the attribute is NON_EMPTY + // or SINGLE_ARTIFACT + // If we performed this check when allowedFileTypes == NO_FILE this would + // always throw an error in those cases + if (allowedFileTypes != FileTypeSet.NO_FILE) { + Iterable<Artifact> artifacts = prerequisite.getProvider(FileProvider.class) + .getFilesToBuild(); + if (attribute.isSingleArtifact() && Iterables.size(artifacts) != 1) { + attributeError(attribute.getName(), + "'" + prerequisite.getLabel() + "' must produce a single file"); + return; + } + for (Artifact sourceArtifact : artifacts) { + if (allowedFileTypes.apply(sourceArtifact.getFilename())) { + return; + } + } + attributeError(attribute.getName(), "'" + prerequisite.getLabel() + + "' does not produce any " + rule.getRuleClass() + " " + attribute.getName() + + " files (expected " + allowedFileTypes + ")"); + } + } + + private void validateMandatoryProviders(ConfiguredTarget prerequisite, Attribute attribute) { + for (String provider : attribute.getMandatoryProviders()) { + if (prerequisite.get(provider) == null) { + attributeError(attribute.getName(), "'" + prerequisite.getLabel() + + "' does not have mandatory provider '" + provider + "'"); + } + } + } + + private void validateDirectPrerequisite(Attribute attribute, ConfiguredTarget prerequisite) { + validateDirectPrerequisiteType(prerequisite, attribute); + validateDirectPrerequisiteFileTypes(prerequisite, attribute); + validateMandatoryProviders(prerequisite, attribute); + prerequisiteValidator.validate(this, prerequisite, attribute); + } + } + + @Override + public String toString() { + return "RuleContext(" + getLabel() + ", " + getConfiguration() + ")"; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleDefinition.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleDefinition.java new file mode 100644 index 0000000..c5e32e3 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleDefinition.java
@@ -0,0 +1,39 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.packages.RuleClass; + +/** + * This class is a common ancestor for every rule object. + * + * <p>Implementors are also required to have the {@link BlazeRule} annotation + * set. + */ +public interface RuleDefinition { + /** + * This method should return a RuleClass object that represents the rule. The usual pattern is + * that various setter methods are called on the builder object passed in as the argument, then + * the object that is built by the builder is returned. + * + * @param builder A {@link com.google.devtools.build.lib.packages.RuleClass.Builder} object + * already preloaded with the attributes of the ancestors specified in the {@link + * BlazeRule} annotation. + * @param environment The services Blaze provides to rule definitions. + * + * @return the {@link RuleClass} representing the rule. + */ + RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleDefinitionEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleDefinitionEnvironment.java new file mode 100644 index 0000000..4dcd080 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleDefinitionEnvironment.java
@@ -0,0 +1,31 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.syntax.Label; + +/** + * Encapsulates the services available for implementors of the {@link RuleDefinition} + * interface. + */ +public interface RuleDefinitionEnvironment { + /** + * Parses the given string as a label and returns the label, by calling {@link + * Label#parseAbsolute}. Instead of throwing a {@link + * com.google.devtools.build.lib.syntax.Label.SyntaxException}, it throws an {@link + * IllegalArgumentException}, if the parsing fails. + */ + Label getLabel(String labelValue); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java b/src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java new file mode 100644 index 0000000..a4da4b4 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java
@@ -0,0 +1,756 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +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.events.Event; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.events.Location; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.util.Pair; +import com.google.devtools.build.lib.vfs.Path; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * An object that encapsulates runfiles. Conceptually, the runfiles are a map of paths to files, + * forming a symlink tree. + * + * <p>In order to reduce memory consumption, this map is not explicitly stored here, but instead as + * a combination of four parts: artifacts placed at their root-relative paths, source tree symlinks, + * root symlinks (outside of the source tree), and artifacts included as parts of "pruning + * manifests" (see {@link PruningManifest}). + */ +@Immutable +public final class Runfiles { + private static final Function<Map.Entry<PathFragment, Artifact>, Artifact> TO_ARTIFACT = + new Function<Map.Entry<PathFragment, Artifact>, Artifact>() { + @Override + public Artifact apply(Map.Entry<PathFragment, Artifact> input) { + return input.getValue(); + } + }; + + private static final Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> + DUMMY_SYMLINK_EXPANDER = + new Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>>() { + @Override + public Map<PathFragment, Artifact> apply(Map<PathFragment, Artifact> input) { + return ImmutableMap.of(); + } + }; + + // It is important to declare this *after* the DUMMY_SYMLINK_EXPANDER to avoid NPEs + public static final Runfiles EMPTY = new Builder().build(); + + + /** + * The artifacts that should *always* be present in the runfiles directory. These are + * differentiated from the artifacts that may or may not be included by a pruning manifest + * (see {@link PruningManifest} below). + * + * <p>This collection may not include any middlemen. These artifacts will be placed at a location + * that corresponds to the root-relative path of each artifact. It's possible for several + * artifacts to have the same root-relative path, in which case the last one will win. + */ + private final NestedSet<Artifact> unconditionalArtifacts; + + /** + * A map of symlinks that should be present in the runfiles directory. In general, the symlink can + * be determined from the artifact by using the root-relative path, so this should only be used + * for cases where that isn't possible. + * + * <p>This may include runfiles symlinks from the root of the runfiles tree. + */ + private final NestedSet<Map.Entry<PathFragment, Artifact>> symlinks; + + /** + * A map of symlinks that should be present above the runfiles directory. These are useful for + * certain rule types like AppEngine apps which have root level config files outside of the + * regular source tree. + */ + private final NestedSet<Map.Entry<PathFragment, Artifact>> rootSymlinks; + + /** + * A function to generate extra manifest entries. + */ + private final Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> + manifestExpander; + + /** + * Defines a set of artifacts that may or may not be included in the runfiles directory and + * a manifest file that makes that determination. These are applied on top of any artifacts + * specified in {@link #unconditionalArtifacts}. + * + * <p>The incentive behind this is to enable execution-phase "pruning" of runfiles. Anything + * set in unconditionalArtifacts is hard-set in Blaze's analysis phase, and thus unchangeable in + * response to execution phase results. This isn't always convenient. For example, say we have an + * action that consumes a set of "possible" runtime dependencies for a source file, parses that + * file for "import a.b.c" statements, and outputs a manifest of the actual dependencies that are + * referenced and thus really needed. This can reduce the size of the runfiles set, but we can't + * use this information until the manifest output is available. + * + * <p>Only artifacts present in the candidate set AND the manifest output make it into the + * runfiles tree. The candidate set requirement guarantees that analysis-time dependencies are a + * superset of the pruned dependencies, so undeclared inclusions (which can break build + * correctness) aren't possible. + */ + public static class PruningManifest { + private final NestedSet<Artifact> candidateRunfiles; + private final Artifact manifestFile; + + /** + * Creates a new pruning manifest. + * + * @param candidateRunfiles set of possible artifacts that the manifest file may reference + * @param manifestFile the manifest file, expected to be a newline-separated list of + * source tree root-relative paths (i.e. "my/package/myfile.txt"). Anything that can't be + * resolved back to an entry in candidateRunfiles is ignored and will *not* make it into + * the runfiles tree. + */ + public PruningManifest(NestedSet<Artifact> candidateRunfiles, Artifact manifestFile) { + this.candidateRunfiles = candidateRunfiles; + this.manifestFile = manifestFile; + } + + public NestedSet<Artifact> getCandidateRunfiles() { + return candidateRunfiles; + } + + public Artifact getManifestFile() { + return manifestFile; + } + } + + /** + * The pruning manifests that should be applied to these runfiles. + */ + private final NestedSet<PruningManifest> pruningManifests; + + private Runfiles(NestedSet<Artifact> artifacts, + NestedSet<Map.Entry<PathFragment, Artifact>> symlinks, + NestedSet<Map.Entry<PathFragment, Artifact>> rootSymlinks, + NestedSet<PruningManifest> pruningManifests, + Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> expander) { + this.unconditionalArtifacts = Preconditions.checkNotNull(artifacts); + this.symlinks = Preconditions.checkNotNull(symlinks); + this.rootSymlinks = Preconditions.checkNotNull(rootSymlinks); + this.pruningManifests = Preconditions.checkNotNull(pruningManifests); + this.manifestExpander = Preconditions.checkNotNull(expander); + } + + /** + * Returns the artifacts that are unconditionally included in the runfiles (as opposed to + * pruning manifest candidates, which may or may not be included). + */ + @VisibleForTesting + public NestedSet<Artifact> getUnconditionalArtifacts() { + return unconditionalArtifacts; + } + + /** + * Returns the artifacts that are unconditionally included in the runfiles (as opposed to + * pruning manifest candidates, which may or may not be included). Middleman artifacts are + * excluded. + */ + public Iterable<Artifact> getUnconditionalArtifactsWithoutMiddlemen() { + return Iterables.filter(unconditionalArtifacts, Artifact.MIDDLEMAN_FILTER); + } + + /** + * Returns the collection of runfiles as artifacts, including both unconditional artifacts + * and pruning manifest candidates. + */ + @VisibleForTesting + public NestedSet<Artifact> getArtifacts() { + NestedSetBuilder<Artifact> allArtifacts = NestedSetBuilder.stableOrder(); + allArtifacts.addAll(unconditionalArtifacts.toCollection()); + for (PruningManifest manifest : getPruningManifests()) { + allArtifacts.addTransitive(manifest.getCandidateRunfiles()); + } + return allArtifacts.build(); + } + + /** + * Returns the collection of runfiles as artifacts, including both unconditional artifacts + * and pruning manifest candidates. Middleman artifacts are excluded. + */ + public Iterable<Artifact> getArtifactsWithoutMiddlemen() { + return Iterables.filter(getArtifacts(), Artifact.MIDDLEMAN_FILTER); + } + + /** + * Returns the symlinks. + */ + public NestedSet<Map.Entry<PathFragment, Artifact>> getSymlinks() { + return symlinks; + } + + /** + * Returns the symlinks as a map from path fragment to artifact. + */ + public Map<PathFragment, Artifact> getSymlinksAsMap() { + return entriesToMap(symlinks); + } + + /** + * @param eventHandler Used for throwing an error if we have an obscuring runlink. + * May be null, in which case obscuring symlinks are silently discarded. + * @param location Location for reporter. Ignored if reporter is null. + * @param workingManifest Manifest to be checked for obscuring symlinks. + * @return map of source file names mapped to their location on disk. + */ + public static Map<PathFragment, Artifact> filterListForObscuringSymlinks( + EventHandler eventHandler, Location location, Map<PathFragment, Artifact> workingManifest) { + Map<PathFragment, Artifact> newManifest = new HashMap<>(); + + outer: + for (Iterator<Entry<PathFragment, Artifact>> i = workingManifest.entrySet().iterator(); + i.hasNext(); ) { + Entry<PathFragment, Artifact> entry = i.next(); + PathFragment source = entry.getKey(); + Artifact symlink = entry.getValue(); + // drop nested entries; warn if this changes anything + int n = source.segmentCount(); + for (int j = 1; j < n; ++j) { + PathFragment prefix = source.subFragment(0, n - j); + Artifact ancestor = workingManifest.get(prefix); + if (ancestor != null) { + // This is an obscuring symlink, so just drop it and move on if there's no reporter. + if (eventHandler == null) { + continue outer; + } + PathFragment suffix = source.subFragment(n - j, n); + Path viaAncestor = ancestor.getPath().getRelative(suffix); + Path expected = symlink.getPath(); + if (!viaAncestor.equals(expected)) { + eventHandler.handle(Event.warn(location, "runfiles symlink " + source + " -> " + + expected + " obscured by " + prefix + " -> " + ancestor.getPath())); + } + continue outer; + } + } + newManifest.put(entry.getKey(), entry.getValue()); + } + return newManifest; + } + + /** + * Returns the symlinks as a map from PathFragment to Artifact, with PathFragments relativized + * and rooted at the specified points. + * @param root The root the PathFragment is computed relative to (before it is + * rooted again). May be null. + * @param eventHandler Used for throwing an error if we have an obscuring runlink. + * May be null, in which case obscuring symlinks are silently discarded. + * @param location Location for eventHandler warnings. Ignored if eventHandler is null. + * @return Pair of Maps from remote path fragment to artifact, the first of normal source tree + * entries, the second of any elements that live outside the source tree. + */ + public Pair<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> getRunfilesInputs( + PathFragment root, String workspaceSuffix, EventHandler eventHandler, Location location) + throws IOException { + Map<PathFragment, Artifact> manifest = getSymlinksAsMap(); + // Add unconditional artifacts (committed to inclusion on construction of runfiles). + for (Artifact artifact : getUnconditionalArtifactsWithoutMiddlemen()) { + addToManifest(manifest, artifact, root); + } + + // Add conditional artifacts (only included if they appear in a pruning manifest). + for (Runfiles.PruningManifest pruningManifest : getPruningManifests()) { + // This map helps us convert from source tree root-relative paths back to artifacts. + Map<String, Artifact> allowedRunfiles = new HashMap<>(); + for (Artifact artifact : pruningManifest.getCandidateRunfiles()) { + allowedRunfiles.put(artifact.getRootRelativePath().getPathString(), artifact); + } + BufferedReader reader = new BufferedReader( + new InputStreamReader(pruningManifest.getManifestFile().getPath().getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + Artifact artifact = allowedRunfiles.get(line); + if (artifact != null) { + addToManifest(manifest, artifact, root); + } + } + } + + manifest = filterListForObscuringSymlinks(eventHandler, location, manifest); + manifest.putAll(manifestExpander.apply(manifest)); + PathFragment path = new PathFragment(workspaceSuffix); + Map<PathFragment, Artifact> result = new HashMap<>(); + for (Map.Entry<PathFragment, Artifact> entry : manifest.entrySet()) { + result.put(path.getRelative(entry.getKey()), entry.getValue()); + } + return Pair.of(result, (Map<PathFragment, Artifact>) new HashMap<>(getRootSymlinksAsMap())); + } + + @VisibleForTesting + protected static void addToManifest(Map<PathFragment, Artifact> manifest, Artifact artifact, + PathFragment root) { + PathFragment rootRelativePath = root != null + ? artifact.getRootRelativePath().relativeTo(root) + : artifact.getRootRelativePath(); + manifest.put(rootRelativePath, artifact); + } + + /** + * Returns the root symlinks. + */ + public NestedSet<Map.Entry<PathFragment, Artifact>> getRootSymlinks() { + return rootSymlinks; + } + + /** + * Returns the root symlinks. + */ + public Map<PathFragment, Artifact> getRootSymlinksAsMap() { + return entriesToMap(rootSymlinks); + } + + /** + * Returns the unified map of path fragments to artifacts, taking both artifacts and symlinks into + * account. + */ + public Map<PathFragment, Artifact> asMapWithoutRootSymlinks() { + Map<PathFragment, Artifact> result = entriesToMap(symlinks); + // If multiple artifacts have the same root-relative path, the last one in the list will win. + // That is because the runfiles tree cannot contain the same artifact for different + // configurations, because it only uses root-relative paths. + for (Artifact artifact : Iterables.filter(unconditionalArtifacts, Artifact.MIDDLEMAN_FILTER)) { + result.put(artifact.getRootRelativePath(), artifact); + } + return result; + } + + /** + * Returns the pruning manifests specified for this runfiles tree. + */ + public NestedSet<PruningManifest> getPruningManifests() { + return pruningManifests; + } + + /** + * Returns the symlinks expander specified for this runfiles tree. + */ + public Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> getSymlinkExpander() { + return manifestExpander; + } + + /** + * Returns the unified map of path fragments to artifacts, taking into account artifacts, + * symlinks, and pruning manifest candidates. The returned set is guaranteed to be a (not + * necessarily strict) superset of the actual runfiles tree created at execution time. + */ + public NestedSet<Artifact> getAllArtifacts() { + if (isEmpty()) { + return NestedSetBuilder.emptySet(Order.STABLE_ORDER); + } + NestedSetBuilder<Artifact> allArtifacts = NestedSetBuilder.stableOrder(); + allArtifacts + .addTransitive(unconditionalArtifacts) + .addAll(Iterables.transform(symlinks, TO_ARTIFACT)) + .addAll(Iterables.transform(rootSymlinks, TO_ARTIFACT)); + for (PruningManifest manifest : getPruningManifests()) { + allArtifacts.addTransitive(manifest.getCandidateRunfiles()); + } + return allArtifacts.build(); + } + + /** + * Returns if there are no runfiles. + */ + public boolean isEmpty() { + return unconditionalArtifacts.isEmpty() && symlinks.isEmpty() && rootSymlinks.isEmpty() && + pruningManifests.isEmpty(); + } + + private static <K, V> Map<K, V> entriesToMap(Iterable<Map.Entry<K, V>> entrySet) { + Map<K, V> map = new LinkedHashMap<>(); + for (Map.Entry<K, V> entry : entrySet) { + map.put(entry.getKey(), entry.getValue()); + } + return map; + } + + /** + * Builder for Runfiles objects. + */ + public static final class Builder { + /** + * This must be COMPILE_ORDER because {@link #asMapWithoutRootSymlinks} overwrites earlier + * entries with later ones, so we want a post-order iteration. + */ + private NestedSetBuilder<Artifact> artifactsBuilder = + NestedSetBuilder.compileOrder(); + private NestedSetBuilder<Map.Entry<PathFragment, Artifact>> symlinksBuilder = + NestedSetBuilder.stableOrder(); + private NestedSetBuilder<Map.Entry<PathFragment, Artifact>> rootSymlinksBuilder = + NestedSetBuilder.stableOrder(); + private NestedSetBuilder<PruningManifest> pruningManifestsBuilder = + NestedSetBuilder.stableOrder(); + private Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> + manifestExpander = DUMMY_SYMLINK_EXPANDER; + + /** + * Builds a new Runfiles object. + */ + public Runfiles build() { + return new Runfiles(artifactsBuilder.build(), symlinksBuilder.build(), + rootSymlinksBuilder.build(), pruningManifestsBuilder.build(), + manifestExpander); + } + + /** + * Adds an artifact to the internal collection of artifacts. + */ + public Builder addArtifact(Artifact artifact) { + Preconditions.checkNotNull(artifact); + artifactsBuilder.add(artifact); + return this; + } + + /** + * Adds several artifacts to the internal collection. + */ + public Builder addArtifacts(Iterable<Artifact> artifacts) { + for (Artifact artifact : artifacts) { + addArtifact(artifact); + } + return this; + } + + + /** + * Use {@link #addTransitiveArtifacts} instead, to prevent increased memory use. + */ + @Deprecated + public Builder addArtifacts(NestedSet<Artifact> artifacts) { + // Do not delete this method, or else addArtifacts(Iterable) calls with a NestedSet argument + // will not be flagged. + Iterable<Artifact> it = artifacts; + addArtifacts(it); + return this; + } + /** + * Adds a nested set to the internal collection. + */ + public Builder addTransitiveArtifacts(NestedSet<Artifact> artifacts) { + artifactsBuilder.addTransitive(artifacts); + return this; + } + + /** + * Adds a symlink. + */ + public Builder addSymlink(PathFragment link, Artifact target) { + Preconditions.checkNotNull(link); + Preconditions.checkNotNull(target); + symlinksBuilder.add(Maps.immutableEntry(link, target)); + return this; + } + + /** + * Adds several symlinks. + */ + public Builder addSymlinks(Map<PathFragment, Artifact> symlinks) { + symlinksBuilder.addAll(symlinks.entrySet()); + return this; + } + + /** + * Adds several symlinks as a NestedSet. + */ + public Builder addSymlinks(NestedSet<Map.Entry<PathFragment, Artifact>> symlinks) { + symlinksBuilder.addTransitive(symlinks); + return this; + } + + /** + * Adds several root symlinks. + */ + public Builder addRootSymlinks(Map<PathFragment, Artifact> symlinks) { + rootSymlinksBuilder.addAll(symlinks.entrySet()); + return this; + } + + /** + * Adds several root symlinks as a NestedSet. + */ + public Builder addRootSymlinks(NestedSet<Map.Entry<PathFragment, Artifact>> symlinks) { + rootSymlinksBuilder.addTransitive(symlinks); + return this; + } + + /** + * Adds a pruning manifest. See {@link PruningManifest} for an explanation. + */ + public Builder addPruningManifest(PruningManifest manifest) { + pruningManifestsBuilder.add(manifest); + return this; + } + + /** + * Adds several pruning manifests as a NestedSet. See {@link PruningManifest} for an + * explanation. + */ + public Builder addPruningManifests(NestedSet<PruningManifest> manifests) { + pruningManifestsBuilder.addTransitive(manifests); + return this; + } + + /** + * Specify a function that can create additional manifest entries based on the input entries. + */ + public Builder setManifestExpander( + Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> expander) { + manifestExpander = Preconditions.checkNotNull(expander); + return this; + } + + /** + * Merges runfiles from a given runfiles support. + * + * @param runfilesSupport the runfiles support to be merged in + */ + public Builder merge(@Nullable RunfilesSupport runfilesSupport) { + return merge(runfilesSupport, null); + } + + /** + * Merges runfiles from a given runfiles support. + * + * <p>Sometimes a particular symlink from the runfiles support must not be included in runfiles. + * In such cases the path fragment denoting the symlink should be passed in as {@code + * ommittedAdditionalSymlink}. The symlink will then be filtered away from the set of additional + * symlinks of the target. + * + * @param runfilesSupport the runfiles support to be merged in + * @param omittedAdditionalSymlink the symlink to be omitted, or null if no filtering is needed + */ + public Builder merge(@Nullable RunfilesSupport runfilesSupport, + @Nullable final PathFragment omittedAdditionalSymlink) { + if (runfilesSupport == null) { + return this; + } + // TODO(bazel-team): We may be able to remove this now. + addArtifact(runfilesSupport.getRunfilesMiddleman()); + Runfiles runfiles = runfilesSupport.getRunfiles(); + if (omittedAdditionalSymlink == null) { + merge(runfiles); + } else { + artifactsBuilder.addTransitive(runfiles.getUnconditionalArtifacts()); + symlinksBuilder.addAll(Maps.filterKeys(entriesToMap(runfiles.getSymlinks()), + Predicates.not(Predicates.equalTo(omittedAdditionalSymlink))).entrySet()); + rootSymlinksBuilder.addTransitive(runfiles.getRootSymlinks()); + pruningManifestsBuilder.addTransitive(runfiles.getPruningManifests()); + if (manifestExpander == DUMMY_SYMLINK_EXPANDER) { + manifestExpander = runfiles.getSymlinkExpander(); + } else { + Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> otherExpander = + runfiles.getSymlinkExpander(); + Preconditions.checkState((otherExpander == DUMMY_SYMLINK_EXPANDER) + || manifestExpander.equals(otherExpander)); + } + } + return this; + } + + /** + * Adds the runfiles for a particular target and visits the transitive closure of "srcs", + * "deps" and "data", collecting all of their respective runfiles. + */ + public Builder addRunfiles(RuleContext ruleContext, + Function<TransitiveInfoCollection, Runfiles> mapping) { + Preconditions.checkNotNull(mapping); + Preconditions.checkNotNull(ruleContext); + addDataDeps(ruleContext); + addNonDataDeps(ruleContext, mapping); + return this; + } + + /** + * Adds the files specified by a mapping from the transitive info collection to the runfiles. + * + * <p>Dependencies in {@code srcs} and {@code deps} are considered. + */ + public Builder add(RuleContext ruleContext, + Function<TransitiveInfoCollection, Runfiles> mapping) { + Preconditions.checkNotNull(ruleContext); + Preconditions.checkNotNull(mapping); + for (TransitiveInfoCollection dep : getNonDataDeps(ruleContext)) { + Runfiles runfiles = mapping.apply(dep); + if (runfiles != null) { + merge(runfiles); + } + } + + return this; + } + + /** + * Collects runfiles from data dependencies of a target. + */ + public Builder addDataDeps(RuleContext ruleContext) { + addTargets(getPrerequisites(ruleContext, "data", Mode.DATA), + RunfilesProvider.DATA_RUNFILES); + return this; + } + + /** + * Collects runfiles from "srcs" and "deps" of a target. + */ + public Builder addNonDataDeps(RuleContext ruleContext, + Function<TransitiveInfoCollection, Runfiles> mapping) { + for (TransitiveInfoCollection target : getNonDataDeps(ruleContext)) { + addTargetExceptFileTargets(target, mapping); + } + return this; + } + + public Builder addTargets(Iterable<? extends TransitiveInfoCollection> targets, + Function<TransitiveInfoCollection, Runfiles> mapping) { + for (TransitiveInfoCollection target : targets) { + addTarget(target, mapping); + } + return this; + } + + public Builder addTarget(TransitiveInfoCollection target, + Function<TransitiveInfoCollection, Runfiles> mapping) { + return addTargetIncludingFileTargets(target, mapping); + } + + private Builder addTargetExceptFileTargets(TransitiveInfoCollection target, + Function<TransitiveInfoCollection, Runfiles> mapping) { + Runfiles runfiles = mapping.apply(target); + if (runfiles != null) { + merge(runfiles); + } + + return this; + } + + private Builder addTargetIncludingFileTargets(TransitiveInfoCollection target, + Function<TransitiveInfoCollection, Runfiles> mapping) { + if (target.getProvider(RunfilesProvider.class) == null + && mapping == RunfilesProvider.DATA_RUNFILES) { + // RuleConfiguredTarget implements RunfilesProvider, so this will only be called on + // FileConfiguredTarget instances. + // TODO(bazel-team): This is a terrible hack. We should be able to make this go away + // by implementing RunfilesProvider on FileConfiguredTarget. We'd need to be mindful + // of the memory use, though, since we have a whole lot of FileConfiguredTarget instances. + addTransitiveArtifacts(target.getProvider(FileProvider.class).getFilesToBuild()); + return this; + } + + return addTargetExceptFileTargets(target, mapping); + } + + /** + * Adds symlinks to given artifacts at their exec paths. + */ + public Builder addSymlinksToArtifacts(Iterable<Artifact> artifacts) { + for (Artifact artifact : artifacts) { + addSymlink(artifact.getExecPath(), artifact); + } + return this; + } + + /** + * Add the other {@link Runfiles} object transitively. + */ + public Builder merge(Runfiles runfiles) { + return merge(runfiles, true); + } + + /** + * Add the other {@link Runfiles} object transitively, but don't merge + * pruning manifests. + */ + public Builder mergeExceptPruningManifests(Runfiles runfiles) { + return merge(runfiles, false); + } + + /** + * Add the other {@link Runfiles} object transitively, with the option to include or exclude + * pruning manifests in the merge. + */ + private Builder merge(Runfiles runfiles, boolean includePruningManifests) { + artifactsBuilder.addTransitive(runfiles.getUnconditionalArtifacts()); + symlinksBuilder.addTransitive(runfiles.getSymlinks()); + rootSymlinksBuilder.addTransitive(runfiles.getRootSymlinks()); + if (includePruningManifests) { + pruningManifestsBuilder.addTransitive(runfiles.getPruningManifests()); + } + if (manifestExpander == DUMMY_SYMLINK_EXPANDER) { + manifestExpander = runfiles.getSymlinkExpander(); + } else { + Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> otherExpander = + runfiles.getSymlinkExpander(); + Preconditions.checkState((otherExpander == DUMMY_SYMLINK_EXPANDER) + || manifestExpander.equals(otherExpander)); + } + return this; + } + + private static Iterable<TransitiveInfoCollection> getNonDataDeps(RuleContext ruleContext) { + return Iterables.concat( + // TODO(bazel-team): This line shouldn't be here. Removing it requires that no rules have + // dependent rules in srcs (except for filegroups and such), but always in deps. + // TODO(bazel-team): DONT_CHECK is not optimal here. Rules that use split configs need to + // be changed not to call into here. + getPrerequisites(ruleContext, "srcs", Mode.DONT_CHECK), + getPrerequisites(ruleContext, "deps", Mode.DONT_CHECK)); + } + + /** + * For the specified attribute "attributeName" (which must be of type list(label)), resolves all + * the labels into ConfiguredTargets (for the same configuration as this one) and returns them + * as a list. + * + * <p>If the rule does not have the specified attribute, returns the empty list. + */ + private static Iterable<? extends TransitiveInfoCollection> getPrerequisites( + RuleContext ruleContext, String attributeName, Mode mode) { + if (ruleContext.getRule().isAttrDefined(attributeName, Type.LABEL_LIST)) { + return ruleContext.getPrerequisites(attributeName, mode); + } else { + return Collections.emptyList(); + } + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RunfilesProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/RunfilesProvider.java new file mode 100644 index 0000000..2e26bbc --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/RunfilesProvider.java
@@ -0,0 +1,91 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Function; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; + +/** + * Runfiles a target contributes to targets that depend on it. + * + * <p>The set of runfiles contributed can be different if the dependency is through a + * <code>data</code> attribute (note that this is just a rough approximation of the reality -- + * rule implementations are free to request the data runfiles at any time) + */ +@Immutable +public final class RunfilesProvider implements TransitiveInfoProvider { + private final Runfiles defaultRunfiles; + private final Runfiles dataRunfiles; + + private RunfilesProvider(Runfiles defaultRunfiles, Runfiles dataRunfiles) { + this.defaultRunfiles = defaultRunfiles; + this.dataRunfiles = dataRunfiles; + } + + public Runfiles getDefaultRunfiles() { + return defaultRunfiles; + } + + public Runfiles getDataRunfiles() { + return dataRunfiles; + } + + /** + * Returns a function that gets the default runfiles from a {@link TransitiveInfoCollection} or + * the empty runfiles instance if it does not contain that provider. + */ + public static final Function<TransitiveInfoCollection, Runfiles> DEFAULT_RUNFILES = + new Function<TransitiveInfoCollection, Runfiles>() { + @Override + public Runfiles apply(TransitiveInfoCollection input) { + RunfilesProvider provider = input.getProvider(RunfilesProvider.class); + if (provider != null) { + return provider.getDefaultRunfiles(); + } + + return Runfiles.EMPTY; + } + }; + + /** + * Returns a function that gets the data runfiles from a {@link TransitiveInfoCollection} or the + * empty runfiles instance if it does not contain that provider. + * + * <p>These are usually used if the target is depended on through a {@code data} attribute. + */ + public static final Function<TransitiveInfoCollection, Runfiles> DATA_RUNFILES = + new Function<TransitiveInfoCollection, Runfiles>() { + @Override + public Runfiles apply(TransitiveInfoCollection input) { + RunfilesProvider provider = input.getProvider(RunfilesProvider.class); + if (provider != null) { + return provider.getDataRunfiles(); + } + + return Runfiles.EMPTY; + } + }; + + public static RunfilesProvider simple(Runfiles defaultRunfiles) { + return new RunfilesProvider(defaultRunfiles, defaultRunfiles); + } + + public static RunfilesProvider withData( + Runfiles defaultRunfiles, Runfiles dataRunfiles) { + return new RunfilesProvider(defaultRunfiles, dataRunfiles); + } + + public static final RunfilesProvider EMPTY = new RunfilesProvider( + Runfiles.EMPTY, Runfiles.EMPTY); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RunfilesSupport.java b/src/main/java/com/google/devtools/build/lib/analysis/RunfilesSupport.java new file mode 100644 index 0000000..aa7f429 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/RunfilesSupport.java
@@ -0,0 +1,382 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.analysis.SourceManifestAction.ManifestType; +import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.config.RunUnder; +import com.google.devtools.build.lib.collect.IterablesChain; +import com.google.devtools.build.lib.packages.TargetUtils; +import com.google.devtools.build.lib.vfs.FileSystemUtils; +import com.google.devtools.build.lib.vfs.Path; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * This class manages the creation of the runfiles symlink farms. + * + * <p>For executables that might depend on the existence of files at run-time, we create a symlink + * farm: a directory which contains symlinks to the right locations for those runfiles. + * + * <p>The runfiles symlink farm serves two purposes. The first is to allow programs (and + * programmers) to refer to files using their workspace-relative paths, regardless of whether the + * files were source files or generated files, and regardless of which part of the package path they + * came from. The second purpose is to ensure that all run-time dependencies are explicitly declared + * in the BUILD files; programs may only use files which the build system knows that they depend on. + * + * <p>The symlink farm contains a MANIFEST file which describes its contents. The MANIFEST file + * lists the names and contents of all of the symlinks in the symlink farm. For efficiency, Blaze's + * dependency analysis ignores the actual symlinks and just looks at the MANIFEST file. It is an + * invariant that the MANIFEST file should accurately represent the contents of the symlinks + * whenever the MANIFEST file is present. build_runfile_links.py preserves this invariant (modulo + * bugs - currently it has a bug where it may fail to preserve that invariant if it gets + * interrupted). So the Blaze dependency analysis looks only at the MANIFEST file, rather than at + * the individual symlinks. + * + * <p>We create an Artifact for the MANIFEST file and a RunfilesAction Action to create it. This + * action does not depend on any other Artifacts. + * + * <p>When building an executable and running it, there are three things which must be built: the + * executable itself, the runfiles symlink farm (represented in the action graph by the Artifact for + * its MANIFEST), and the files pointed to by the symlinks in the symlink farm. To avoid redundancy + * in the dependency analysis, we create a Middleman Artifact which depends on all of these. Actions + * which will run an executable should depend on this Middleman Artifact. + */ +public class RunfilesSupport { + private static final String RUNFILES_DIR_EXT = ".runfiles"; + + private final Runfiles runfiles; + + private final Artifact runfilesInputManifest; + private final Artifact runfilesManifest; + private final Artifact runfilesMiddleman; + private final Artifact sourcesManifest; + private final Artifact owningExecutable; + private final boolean createSymlinks; + private final ImmutableList<String> args; + + /** + * Creates the RunfilesSupport helper with the given executable and runfiles. + * + * @param ruleContext the rule context to create the runfiles support for + * @param executable the executable for whose runfiles this runfiles support is responsible, may + * be null + * @param runfiles the runfiles + * @param appendingArgs to be added after the rule's args + */ + private RunfilesSupport(RuleContext ruleContext, Artifact executable, Runfiles runfiles, + List<String> appendingArgs, boolean createSymlinks) { + owningExecutable = executable; + this.createSymlinks = createSymlinks; + + // Adding run_under target to the runfiles manifest so it would become part + // of runfiles tree and would be executable everywhere. + RunUnder runUnder = ruleContext.getConfiguration().getRunUnder(); + if (runUnder != null && runUnder.getLabel() != null + && TargetUtils.isTestRule(ruleContext.getRule())) { + TransitiveInfoCollection runUnderTarget = + ruleContext.getPrerequisite(":run_under", Mode.DATA); + runfiles = new Runfiles.Builder() + .merge(getRunfiles(runUnderTarget)) + .merge(runfiles) + .build(); + } + this.runfiles = runfiles; + + Preconditions.checkState(!runfiles.isEmpty()); + + Map<PathFragment, Artifact> symlinks = getRunfilesSymlinks(); + if (executable != null && !symlinks.values().contains(executable)) { + throw new IllegalStateException("main program " + executable + " not included in runfiles"); + } + + runfilesInputManifest = createRunfilesInputManifestArtifact(ruleContext); + this.runfilesManifest = createRunfilesAction(ruleContext, runfiles); + this.runfilesMiddleman = createRunfilesMiddleman(ruleContext, runfiles.getAllArtifacts()); + sourcesManifest = createSourceManifest(ruleContext, runfiles); + args = ImmutableList.<String>builder() + .addAll(ruleContext.getTokenizedStringListAttr("args")) + .addAll(appendingArgs) + .build(); + } + + private RunfilesSupport(Runfiles runfiles, Artifact runfilesInputManifest, + Artifact runfilesManifest, Artifact runfilesMiddleman, Artifact sourcesManifest, + Artifact owningExecutable, boolean createSymlinks, ImmutableList<String> args) { + this.runfiles = runfiles; + this.runfilesInputManifest = runfilesInputManifest; + this.runfilesManifest = runfilesManifest; + this.runfilesMiddleman = runfilesMiddleman; + this.sourcesManifest = sourcesManifest; + this.owningExecutable = owningExecutable; + this.createSymlinks = createSymlinks; + this.args = args; + } + + /** + * Returns the executable owning this RunfilesSupport. Only use from Skylark. + */ + public Artifact getExecutable() { + return owningExecutable; + } + + /** + * Returns the exec path of the directory where the runfiles contained in this + * RunfilesSupport are generated. When the owning rule has no executable, + * returns null. + */ + public PathFragment getRunfilesDirectoryExecPath() { + if (owningExecutable == null) { + return null; + } + + PathFragment executablePath = owningExecutable.getExecPath(); + return executablePath.getParentDirectory().getChild( + executablePath.getBaseName() + RUNFILES_DIR_EXT); + } + + public Runfiles getRunfiles() { + return runfiles; + } + + /** + * For executable programs, the .runfiles_manifest file outside of the + * runfiles symlink farm; otherwise, returns null. + * + * <p>The MANIFEST file represents the contents of all of the symlinks in the + * symlink farm. For efficiency, Blaze's dependency analysis ignores the + * actual symlinks and just looks at the MANIFEST file. It is an invariant + * that the MANIFEST file should accurately represent the contents of the + * symlinks whenever the MANIFEST file is present. + */ + public Artifact getRunfilesInputManifest() { + return runfilesInputManifest; + } + + private Artifact createRunfilesInputManifestArtifact(ActionConstructionContext context) { + // The executable may be null for emptyRunfiles + PathFragment relativePath = (owningExecutable != null) + ? owningExecutable.getRootRelativePath() + : Util.getWorkspaceRelativePath(context.getRule()); + String basename = relativePath.getBaseName(); + PathFragment inputManifestPath = relativePath.replaceName(basename + ".runfiles_manifest"); + return context.getAnalysisEnvironment().getDerivedArtifact(inputManifestPath, + context.getConfiguration().getBinDirectory()); + } + + /** + * For executable programs, returns the MANIFEST file in the runfiles + * symlink farm, if blaze is run with --build_runfile_links; returns + * the .runfiles_manifest file outside of the symlink farm, if blaze + * is run with --nobuild_runfile_links. + * <p> + * Beware: In most cases {@link #getRunfilesInputManifest} is the more + * appropriate function. + */ + public Artifact getRunfilesManifest() { + return runfilesManifest; + } + + /** + * For executable programs, the root directory of the runfiles symlink farm; + * otherwise, returns null. + */ + public Path getRunfilesDirectory() { + return FileSystemUtils.replaceExtension(getRunfilesInputManifest().getPath(), RUNFILES_DIR_EXT); + } + + /** + * Returns the files pointed to by the symlinks in the runfiles symlink farm. This method is slow. + */ + @VisibleForTesting + public Collection<Artifact> getRunfilesSymlinkTargets() { + return getRunfilesSymlinks().values(); + } + + /** + * Returns the names of the symlinks in the runfiles symlink farm as a Set of PathFragments. This + * method is slow. + */ + // We should make this VisibleForTesting, but it is still used by TestHelper + public Set<PathFragment> getRunfilesSymlinkNames() { + return getRunfilesSymlinks().keySet(); + } + + /** + * Returns the names of the symlinks in the runfiles symlink farm as a Set of PathFragments. This + * method is slow. + */ + @VisibleForTesting + public Map<PathFragment, Artifact> getRunfilesSymlinks() { + return runfiles.asMapWithoutRootSymlinks(); + } + + /** + * Returns both runfiles artifacts and "conditional" artifacts that may be part of a + * Runfiles PruningManifest. This means the returned set may be an overapproximation of the + * actual set of runfiles (see {@link Runfiles.PruningManifest}). + */ + public Iterable<Artifact> getRunfilesArtifactsWithoutMiddlemen() { + return runfiles.getArtifactsWithoutMiddlemen(); + } + + /** + * Returns the middleman artifact that depends on getExecutable(), + * getRunfilesManifest(), and getRunfilesSymlinkTargets(). Anything which + * needs to actually run the executable should depend on this. + */ + public Artifact getRunfilesMiddleman() { + return runfilesMiddleman; + } + + /** + * Returns the Sources manifest. + * This may be null if the owningRule has no executable. + */ + public Artifact getSourceManifest() { + return sourcesManifest; + } + + private Artifact createRunfilesMiddleman(ActionConstructionContext context, + Iterable<Artifact> allRunfilesArtifacts) { + Iterable<Artifact> inputs = IterablesChain.<Artifact>builder() + .add(allRunfilesArtifacts) + .addElement(runfilesManifest) + .build(); + return context.getAnalysisEnvironment().getMiddlemanFactory().createRunfilesMiddleman( + context.getActionOwner(), owningExecutable, inputs, + context.getConfiguration().getMiddlemanDirectory()); + } + + /** + * Creates a runfiles action for all of the specified files, and returns the + * output artifact (the artifact for the MANIFEST file). + * + * <p>The "runfiles" action creates a symlink farm that links all the runfiles + * (which may come from different places, e.g. different package paths, + * generated files, etc.) into a single tree, so that programs can access them + * using the workspace-relative name. + */ + private Artifact createRunfilesAction(ActionConstructionContext context, Runfiles runfiles) { + // Compute the names of the runfiles directory and its MANIFEST file. + Artifact inputManifest = getRunfilesInputManifest(); + context.getAnalysisEnvironment().registerAction( + SourceManifestAction.forRunfiles( + ManifestType.SOURCE_SYMLINKS, context.getActionOwner(), inputManifest, runfiles)); + + if (!createSymlinks) { + // Just return the manifest if that's all the build calls for. + return inputManifest; + } + + PathFragment runfilesDir = FileSystemUtils.replaceExtension(inputManifest.getRootRelativePath(), + RUNFILES_DIR_EXT); + PathFragment outputManifestPath = runfilesDir.getRelative("MANIFEST"); + + BuildConfiguration config = context.getConfiguration(); + Artifact outputManifest = context.getAnalysisEnvironment().getDerivedArtifact( + outputManifestPath, config.getBinDirectory()); + context.getAnalysisEnvironment().registerAction(new SymlinkTreeAction( + context.getActionOwner(), inputManifest, outputManifest, /*filesetTree=*/false)); + return outputManifest; + } + + /** + * Creates an Artifact which writes the "sources only" manifest file. + * + * @param context the owner for the manifest action + * @param runfiles the runfiles + * @return the Artifact representing the file write action. + */ + private Artifact createSourceManifest(ActionConstructionContext context, Runfiles runfiles) { + // Put the sources only manifest next to the MANIFEST file but call it SOURCES. + PathFragment runfilesDir = getRunfilesDirectoryExecPath(); + if (runfilesDir != null) { + PathFragment sourcesManifestPath = runfilesDir.getRelative("SOURCES"); + Artifact sourceOnlyManifest = context.getAnalysisEnvironment().getDerivedArtifact( + sourcesManifestPath, context.getConfiguration().getBinDirectory()); + context.getAnalysisEnvironment().registerAction( + SourceManifestAction.forRunfiles( + ManifestType.SOURCES_ONLY, context.getActionOwner(), sourceOnlyManifest, runfiles)); + return sourceOnlyManifest; + } else { + return null; + } + } + + /** + * Helper method that returns a collection of artifacts that are necessary for the runfiles of the + * given target. Note that the runfile symlink tree is never built, so this may include artifacts + * that end up not being used (see {@link Runfiles}). + * + * @return the Runfiles object + */ + + private static Runfiles getRunfiles(TransitiveInfoCollection target) { + RunfilesProvider runfilesProvider = target.getProvider(RunfilesProvider.class); + if (runfilesProvider != null) { + return runfilesProvider.getDefaultRunfiles(); + } else { + return Runfiles.EMPTY; + } + } + + /** + * Returns the unmodifiable list of expanded and tokenized 'args' attribute + * values. + */ + public List<String> getArgs() { + return args; + } + + /** + * Creates and returns a RunfilesSupport object for the given rule and executable. Note that this + * method calls back into the passed in rule to obtain the runfiles. + */ + public static RunfilesSupport withExecutable(RuleContext ruleContext, Runfiles runfiles, + Artifact executable) { + return new RunfilesSupport(ruleContext, executable, runfiles, ImmutableList.<String>of(), + ruleContext.shouldCreateRunfilesSymlinks()); + } + + /** + * Creates and returns a RunfilesSupport object for the given rule and executable. Note that this + * method calls back into the passed in rule to obtain the runfiles. + */ + public static RunfilesSupport withExecutable(RuleContext ruleContext, Runfiles runfiles, + Artifact executable, boolean createSymlinks) { + return new RunfilesSupport(ruleContext, executable, runfiles, ImmutableList.<String>of(), + createSymlinks); + } + + /** + * Creates and returns a RunfilesSupport object for the given rule, executable, runfiles and args. + */ + public static RunfilesSupport withExecutable(RuleContext ruleContext, Runfiles runfiles, + Artifact executable, List<String> appendingArgs) { + return new RunfilesSupport(ruleContext, executable, runfiles, + ImmutableList.copyOf(appendingArgs), ruleContext.shouldCreateRunfilesSymlinks()); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/SourceManifestAction.java b/src/main/java/com/google/devtools/build/lib/analysis/SourceManifestAction.java new file mode 100644 index 0000000..5aa3bdc --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/SourceManifestAction.java
@@ -0,0 +1,404 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import static java.nio.charset.StandardCharsets.ISO_8859_1; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.actions.Executor.ActionContext; +import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.util.Fingerprint; +import com.google.devtools.build.lib.util.Pair; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Action to create a manifest of input files for processing by a subsequent + * build step (e.g. runfiles symlinking or archive building). + * + * <p>The manifest's format is specifiable by {@link ManifestType}, in + * accordance with the needs of the calling functionality. + * + * <p>Note that this action carefully avoids building the manifest content in + * memory. + */ +public class SourceManifestAction extends AbstractFileWriteAction { + /** + * Action context that tells what workspace suffix we should use. + */ + public interface Context extends ActionContext { + PathFragment getRunfilesPrefix(); + } + + private static final String GUID = "07459553-a3d0-4d37-9d78-18ed942470f4"; + + /** + * Interface for defining manifest formatting and reporting specifics. + */ + @VisibleForTesting + interface ManifestWriter { + + /** + * Writes a single line of manifest output. + * + * @param manifestWriter the output stream + * @param rootRelativePath path of an entry relative to the manifest's root + * @param symlink (optional) symlink that resolves the above path + */ + void writeEntry(Writer manifestWriter, PathFragment rootRelativePath, + @Nullable Artifact symlink) throws IOException; + + /** + * Fulfills {@link #ActionMetadata.getMnemonic()} + */ + String getMnemonic(); + + /** + * Fulfills {@link #AbstractAction.getRawProgressMessage()} + */ + String getRawProgressMessage(); + } + + /** + * The strategy we use to write manifest entries. + */ + private final ManifestWriter manifestWriter; + + /** + * The runfiles for which to create the symlink tree. + */ + private final Runfiles runfiles; + + /** + * If non-null, the paths should be computed relative to this path fragment. + */ + private final PathFragment root; + + /** + * Creates a new AbstractSourceManifestAction instance using latin1 encoding + * to write the manifest file and with a specified root path for manifest entries. + * + * @param manifestWriter the strategy to use to write manifest entries + * @param owner the action owner + * @param output the file to which to write the manifest + * @param runfiles runfiles + * @param root the artifacts' root-relative path is relativized to this before writing it out + */ + private SourceManifestAction(ManifestWriter manifestWriter, ActionOwner owner, Artifact output, + Runfiles runfiles, PathFragment root) { + super(owner, getDependencies(runfiles), output, false); + this.manifestWriter = manifestWriter; + this.runfiles = runfiles; + this.root = root; + } + + @VisibleForTesting + public void writeOutputFile(OutputStream out, EventHandler eventHandler, String workspaceSuffix) + throws IOException { + writeFile(out, runfiles.getRunfilesInputs( + root, workspaceSuffix, eventHandler, getOwner().getLocation())); + } + + @Override + public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, Executor executor) + throws IOException { + final Pair<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> runfilesInputs = + runfiles.getRunfilesInputs(root, + executor.getContext(Context.class).getRunfilesPrefix().toString(), eventHandler, + getOwner().getLocation()); + return new DeterministicWriter() { + @Override + public void writeOutputFile(OutputStream out) throws IOException { + writeFile(out, runfilesInputs); + } + }; + } + + /** + * Returns the input dependencies for this action. Note we don't need to create the symlink + * target Artifacts before we write the output manifest, so this Action does not have to + * depend on them. The only necessary dependencies are pruning manifests, which must be read + * to properly prune the tree. + */ + private static Collection<Artifact> getDependencies(Runfiles runfiles) { + ImmutableList.Builder<Artifact> builder = ImmutableList.builder(); + for (Runfiles.PruningManifest manifest : runfiles.getPruningManifests()) { + builder.add(manifest.getManifestFile()); + } + return builder.build(); + } + + /** + * Sort the entries in both the normal and root manifests and write the output + * file. + * + * @param out is the message stream to write errors to. + * @param output The actual mapping of the output manifest. + * @throws IOException + */ + private void writeFile(OutputStream out, + Pair<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> output) + throws IOException { + Writer manifestFile = new BufferedWriter(new OutputStreamWriter(out, ISO_8859_1)); + + Comparator<Map.Entry<PathFragment, Artifact>> fragmentComparator = + new Comparator<Map.Entry<PathFragment, Artifact>>() { + @Override + public int compare(Map.Entry<PathFragment, Artifact> path1, + Map.Entry<PathFragment, Artifact> path2) { + return path1.getKey().compareTo(path2.getKey()); + } + }; + + List<Map.Entry<PathFragment, Artifact>> sortedRootLinks = + new ArrayList<>(output.second.entrySet()); + Collections.sort(sortedRootLinks, fragmentComparator); + + List<Map.Entry<PathFragment, Artifact>> sortedManifest = + new ArrayList<>(output.first.entrySet()); + Collections.sort(sortedManifest, fragmentComparator); + + for (Map.Entry<PathFragment, Artifact> line : sortedRootLinks) { + manifestWriter.writeEntry(manifestFile, line.getKey(), line.getValue()); + } + + for (Map.Entry<PathFragment, Artifact> line : sortedManifest) { + manifestWriter.writeEntry(manifestFile, line.getKey(), line.getValue()); + } + manifestFile.flush(); + } + + @Override + public String getMnemonic() { + return manifestWriter.getMnemonic(); + } + + @Override + protected String getRawProgressMessage() { + return manifestWriter.getRawProgressMessage() + " for " + getOwner().getLabel(); + } + + @Override + protected String computeKey() { + Fingerprint f = new Fingerprint(); + f.addString(GUID); + Map<PathFragment, Artifact> symlinks = runfiles.getSymlinksAsMap(); + f.addInt(symlinks.size()); + for (Map.Entry<PathFragment, Artifact> symlink : symlinks.entrySet()) { + f.addPath(symlink.getKey()); + f.addPath(symlink.getValue().getPath()); + } + Map<PathFragment, Artifact> rootSymlinks = runfiles.getRootSymlinksAsMap(); + f.addInt(rootSymlinks.size()); + for (Map.Entry<PathFragment, Artifact> rootSymlink : rootSymlinks.entrySet()) { + f.addPath(rootSymlink.getKey()); + f.addPath(rootSymlink.getValue().getPath()); + } + + if (root != null) { + for (Artifact artifact : runfiles.getArtifactsWithoutMiddlemen()) { + f.addPath(artifact.getRootRelativePath().relativeTo(root)); + f.addPath(artifact.getPath()); + } + } else { + for (Artifact artifact : runfiles.getArtifactsWithoutMiddlemen()) { + f.addPath(artifact.getRootRelativePath()); + f.addPath(artifact.getPath()); + } + } + return f.hexDigestAndReset(); + } + + /** + * Supported manifest writing strategies. + */ + public static enum ManifestType implements ManifestWriter { + + /** + * Writes each line as: + * + * [rootRelativePath] [resolvingSymlink] + * + * <p>This strategy is suitable for creating an input manifest to a source view tree. Its + * output is a valid input to {@link com.google.devtools.build.lib.analysis.SymlinkTreeAction}. + */ + SOURCE_SYMLINKS { + @Override + public void writeEntry(Writer manifestWriter, PathFragment rootRelativePath, Artifact symlink) + throws IOException { + manifestWriter.append(rootRelativePath.getPathString()); + // This trailing whitespace is REQUIRED to process the single entry line correctly. + manifestWriter.append(' '); + if (symlink != null) { + manifestWriter.append(symlink.getPath().getPathString()); + } + manifestWriter.append('\n'); + } + + @Override + public String getMnemonic() { + return "SourceSymlinkManifest"; + } + + @Override + public String getRawProgressMessage() { + return "Creating source manifest"; + } + }, + + /** + * Writes each line as: + * + * [rootRelativePath] + * + * <p>This strategy is suitable for an input into a packaging system (notably .par) that + * consumes a list of all source files but needs that list to be constant with respect to + * how the user has their client laid out on local disk. + */ + SOURCES_ONLY { + @Override + public void writeEntry(Writer manifestWriter, PathFragment rootRelativePath, Artifact symlink) + throws IOException { + manifestWriter.append(rootRelativePath.getPathString()); + manifestWriter.append('\n'); + manifestWriter.flush(); + } + + @Override + public String getMnemonic() { + return "PackagingSourcesManifest"; + } + + @Override + public String getRawProgressMessage() { + return "Creating file sources list"; + } + } + } + + /** Creates an action for the given runfiles. */ + public static SourceManifestAction forRunfiles(ManifestType manifestType, ActionOwner owner, + Artifact output, Runfiles runfiles) { + return new SourceManifestAction(manifestType, owner, output, runfiles, null); + } + + /** + * Builder class to construct {@link SourceManifestAction} instances. + */ + public static final class Builder { + private final ManifestWriter manifestWriter; + private final ActionOwner owner; + private final Artifact output; + private PathFragment top; + private final Runfiles.Builder runfilesBuilder = new Runfiles.Builder(); + + public Builder(ManifestType manifestType, ActionOwner owner, Artifact output) { + manifestWriter = manifestType; + this.owner = owner; + this.output = output; + } + + @VisibleForTesting + Builder(ManifestWriter manifestWriter, ActionOwner owner, Artifact output) { + this.manifestWriter = manifestWriter; + this.owner = owner; + this.output = output; + } + + public SourceManifestAction build() { + return new SourceManifestAction(manifestWriter, owner, output, runfilesBuilder.build(), top); + } + + /** + * Sets the path fragment which is used to relativize the artifacts' root + * relative paths further. Most likely, you don't need this. + */ + public Builder setTopLevel(PathFragment top) { + this.top = top; + return this; + } + + /** + * Adds a set of symlinks from the artifacts' root-relative paths to the + * artifacts themselves. + */ + public Builder addSymlinks(Iterable<Artifact> artifacts) { + runfilesBuilder.addArtifacts(artifacts); + return this; + } + + /** + * Adds a map of symlinks. + */ + public Builder addSymlinks(Map<PathFragment, Artifact> symlinks) { + runfilesBuilder.addSymlinks(symlinks); + return this; + } + + /** + * Adds a single symlink. + */ + public Builder addSymlink(PathFragment link, Artifact target) { + runfilesBuilder.addSymlink(link, target); + return this; + } + + /** + * <p>Adds a mapping of Artifacts to the directory above the normal symlink + * forest base. + */ + public Builder addRootSymlinks(Map<PathFragment, Artifact> rootSymlinks) { + runfilesBuilder.addRootSymlinks(rootSymlinks); + return this; + } + + /** + * Set an expander function for the symlinks. + */ + @VisibleForTesting + Builder setSymlinksExpander( + Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> expander) { + runfilesBuilder.setManifestExpander(expander); + return this; + } + + /** + * Adds a runfiles pruning manifest. + */ + @VisibleForTesting + Builder addPruningManifest(Runfiles.PruningManifest manifest) { + runfilesBuilder.addPruningManifest(manifest); + return this; + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeAction.java b/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeAction.java new file mode 100644 index 0000000..2dc0d4a --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeAction.java
@@ -0,0 +1,109 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.AbstractAction; +import com.google.devtools.build.lib.actions.ActionExecutionContext; +import com.google.devtools.build.lib.actions.ActionExecutionException; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.actions.ResourceSet; +import com.google.devtools.build.lib.util.Fingerprint; + +/** + * Action responsible for the symlink tree creation. + * Used to generate runfiles and fileset symlink farms. + */ +public class SymlinkTreeAction extends AbstractAction { + + private static final String GUID = "63412bda-4026-4c8e-a3ad-7deb397728d4"; + + private final Artifact inputManifest; + private final Artifact outputManifest; + private final boolean filesetTree; + + /** + * Creates SymlinkTreeAction instance. + * + * @param owner action owner + * @param inputManifest exec path to the input runfiles manifest + * @param outputManifest exec path to the generated symlink tree manifest + * (must have "MANIFEST" base name). Symlink tree root + * will be set to the artifact's parent directory. + * @param filesetTree true if this is fileset symlink tree, + * false if this is a runfiles symlink tree. + */ + public SymlinkTreeAction(ActionOwner owner, Artifact inputManifest, Artifact outputManifest, + boolean filesetTree) { + super(owner, ImmutableList.of(inputManifest), ImmutableList.of(outputManifest)); + Preconditions.checkArgument(outputManifest.getPath().getBaseName().equals("MANIFEST")); + this.inputManifest = inputManifest; + this.outputManifest = outputManifest; + this.filesetTree = filesetTree; + } + + public Artifact getInputManifest() { + return inputManifest; + } + + public Artifact getOutputManifest() { + return outputManifest; + } + + public boolean isFilesetTree() { + return filesetTree; + } + + @Override + public String getMnemonic() { + return "SymlinkTree"; + } + + @Override + protected String getRawProgressMessage() { + return (filesetTree ? "Creating Fileset tree " : "Creating runfiles tree ") + + outputManifest.getExecPath().getParentDirectory().getPathString(); + } + + @Override + protected String computeKey() { + Fingerprint f = new Fingerprint(); + f.addString(GUID); + f.addInt(filesetTree ? 1 : 0); + return f.hexDigestAndReset(); + } + + @Override + public ResourceSet estimateResourceConsumption(Executor executor) { + // Return null here to indicate that resources would be managed manually + // during action execution. + return null; + } + + @Override + public String describeStrategy(Executor executor) { + return "local"; // Symlink tree is always generated locally. + } + + @Override + public void execute( + ActionExecutionContext actionExecutionContext) + throws ActionExecutionException, InterruptedException { + actionExecutionContext.getExecutor().getContext(SymlinkTreeActionContext.class) + .createSymlinks(this, actionExecutionContext); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeActionContext.java b/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeActionContext.java new file mode 100644 index 0000000..fe61056 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeActionContext.java
@@ -0,0 +1,31 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.actions.ActionExecutionContext; +import com.google.devtools.build.lib.actions.ActionExecutionException; +import com.google.devtools.build.lib.actions.Executor.ActionContext; + +/** + * Action context for symlink tree actions (an action that creates a tree of symlinks). + */ +public interface SymlinkTreeActionContext extends ActionContext { + + /** + * Creates the symlink tree. + */ + void createSymlinks(SymlinkTreeAction action, + ActionExecutionContext actionExecutionContext) + throws ActionExecutionException, InterruptedException; +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TargetAndConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/TargetAndConfiguration.java new file mode 100644 index 0000000..cc0feb6 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/TargetAndConfiguration.java
@@ -0,0 +1,104 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; +import com.google.devtools.build.lib.syntax.Label; + +import java.util.Objects; + +import javax.annotation.Nullable; + +/** + * Refers to the pair of a target and a configuration and certain additional information. Not the + * same as {@link ConfiguredTarget} -- that also contains the result of the analysis phase. + */ +public class TargetAndConfiguration { + private final Target target; + @Nullable private final BuildConfiguration configuration; + + public TargetAndConfiguration(Target target, @Nullable BuildConfiguration configuration) { + this.target = Preconditions.checkNotNull(target); + this.configuration = configuration; + } + + public TargetAndConfiguration(ConfiguredTarget configuredTarget) { + this.target = Preconditions.checkNotNull(configuredTarget).getTarget(); + this.configuration = configuredTarget.getConfiguration(); + } + + // The node name in the graph. The name should be unique. + // It is not suitable for user display. + public String getName() { + return target.getLabel() + " " + + (configuration == null ? "null" : configuration.shortCacheKey()); + } + + public static final Function<TargetAndConfiguration, String> NAME_FUNCTION = + new Function<TargetAndConfiguration, String>() { + @Override + public String apply(TargetAndConfiguration node) { + return node.getName(); + } + }; + + public static final Function<TargetAndConfiguration, ConfiguredTargetKey> + TO_LABEL_AND_CONFIGURATION = new Function<TargetAndConfiguration, ConfiguredTargetKey>() { + @Override + public ConfiguredTargetKey apply(TargetAndConfiguration input) { + return new ConfiguredTargetKey(input.getLabel(), input.getConfiguration()); + } + }; + + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + if (!(that instanceof TargetAndConfiguration)) { + return false; + } + + TargetAndConfiguration thatNode = (TargetAndConfiguration) that; + return thatNode.target.getLabel().equals(this.target.getLabel()) && + thatNode.configuration == this.configuration; + } + + @Override + public int hashCode() { + return Objects.hash(target.getLabel(), configuration); + } + + @Override + public String toString() { + return target.getLabel() + " (" + configuration + ")"; + } + + public Target getTarget() { + return target; + } + + public Label getLabel() { + return target.getLabel(); + } + + @Nullable + public BuildConfiguration getConfiguration() { + return configuration; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java new file mode 100644 index 0000000..fcfec55 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java
@@ -0,0 +1,75 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; +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.syntax.Label; +import com.google.devtools.build.skyframe.SkyValue; + +/** + * This event is fired as soon as a target is either built or fails. + */ +public final class TargetCompleteEvent implements SkyValue { + + private final ConfiguredTarget target; + private final NestedSet<Label> rootCauses; + + private TargetCompleteEvent(ConfiguredTarget target, NestedSet<Label> rootCauses) { + this.target = target; + this.rootCauses = (rootCauses == null) + ? NestedSetBuilder.<Label>emptySet(Order.STABLE_ORDER) + : rootCauses; + } + + /** + * Construct a successful target completion event. + */ + public static TargetCompleteEvent createSuccessful(ConfiguredTarget ct) { + return new TargetCompleteEvent(ct, null); + } + + /** + * Construct a target completion event for a failed target, with the given non-empty root causes. + */ + public static TargetCompleteEvent createFailed(ConfiguredTarget ct, NestedSet<Label> rootCauses) { + Preconditions.checkArgument(!Iterables.isEmpty(rootCauses)); + return new TargetCompleteEvent(ct, rootCauses); + } + + /** + * Returns the target associated with the event. + */ + public ConfiguredTarget getTarget() { + return target; + } + + /** + * Determines whether the target has failed or succeeded. + */ + public boolean failed() { + return !rootCauses.isEmpty(); + } + + /** + * Get the root causes of the target. May be empty. + */ + public Iterable<Label> getRootCauses() { + return rootCauses; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TargetContext.java b/src/main/java/com/google/devtools/build/lib/analysis/TargetContext.java new file mode 100644 index 0000000..9c95db3 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/TargetContext.java
@@ -0,0 +1,96 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.packages.PackageSpecification; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.syntax.Label; + +import java.util.List; + +import javax.annotation.Nullable; + +/** + * A helper class for building {@link ConfiguredTarget} instances, in particular for non-rule ones. + * For {@link RuleConfiguredTarget} instances, use {@link RuleContext} instead, + * which is a subclass of this class. + * + * <p>The class is intended to be sub-classed by RuleContext, in order to share the code. However, + * it's not intended for sub-classing beyond that, and the constructor is intentionally package + * private to enforce that. + */ +public class TargetContext { + + private final AnalysisEnvironment env; + private final Target target; + private final BuildConfiguration configuration; + /** + * This list only contains prerequisites that are not declared in rule attributes, with the + * exception of visibility (i.e., visibility is represented here, even though it is a rule + * attribute in case of a rule). Rule attributes are handled by the {@link RuleContext} subclass. + */ + private final List<ConfiguredTarget> directPrerequisites; + private final NestedSet<PackageSpecification> visibility; + + /** + * The constructor is intentionally package private. + */ + TargetContext(AnalysisEnvironment env, Target target, BuildConfiguration configuration, + List<ConfiguredTarget> directPrerequisites, + NestedSet<PackageSpecification> visibility) { + this.env = env; + this.target = target; + this.configuration = configuration; + this.directPrerequisites = directPrerequisites; + this.visibility = visibility; + } + + public AnalysisEnvironment getAnalysisEnvironment() { + return env; + } + + public Target getTarget() { + return target; + } + + public Label getLabel() { + return target.getLabel(); + } + + /** + * Returns the configuration for this target. This may return null if the target is supposed to be + * configuration-independent (like an input file, or a visibility rule). However, this is + * guaranteed to be non-null for rules and for output files. + */ + @Nullable + public BuildConfiguration getConfiguration() { + return configuration; + } + + public NestedSet<PackageSpecification> getVisibility() { + return visibility; + } + + TransitiveInfoCollection findDirectPrerequisite(Label label, BuildConfiguration config) { + for (ConfiguredTarget prerequisite : directPrerequisites) { + if (prerequisite.getLabel().equals(label) && (prerequisite.getConfiguration() == config)) { + return prerequisite; + } + } + return null; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TempsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/TempsProvider.java new file mode 100644 index 0000000..109992e --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/TempsProvider.java
@@ -0,0 +1,42 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; + +import java.util.Collection; + +/** + * A {@link TransitiveInfoProvider} for rule classes that save extra files when + * {@code --save_temps} is in effect. + */ +@Immutable +public final class TempsProvider implements TransitiveInfoProvider { + + private final ImmutableList<Artifact> temps; + + public TempsProvider(ImmutableList<Artifact> temps) { + this.temps = temps; + } + + /** + * Return the extra artifacts to save when {@code --save_temps} is in effect. + */ + public Collection<Artifact> getTemps() { + return temps; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactContext.java b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactContext.java new file mode 100644 index 0000000..c9b8e51 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactContext.java
@@ -0,0 +1,104 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; + +import java.util.Objects; +import java.util.Set; + +/** + * Contains options which control the set of artifacts to build for top-level targets. + */ +@Immutable +public final class TopLevelArtifactContext { + + public static final TopLevelArtifactContext DEFAULT = new TopLevelArtifactContext( + "", /*compileOnly=*/false, /*compilationPrerequisitesOnly*/false, + /*runTestsExclusively=*/false, /*outputGroups=*/ImmutableSet.<String>of(), + /*shouldRunTests=*/false); + + private final String buildCommand; + private final boolean compileOnly; + private final boolean compilationPrerequisitesOnly; + private final boolean runTestsExclusively; + private final ImmutableSet<String> outputGroups; + private final boolean shouldRunTests; + + public TopLevelArtifactContext(String buildCommand, boolean compileOnly, + boolean compilationPrerequisitesOnly, boolean runTestsExclusively, + ImmutableSet<String> outputGroups, boolean shouldRunTests) { + this.buildCommand = buildCommand; + this.compileOnly = compileOnly; + this.compilationPrerequisitesOnly = compilationPrerequisitesOnly; + this.runTestsExclusively = runTestsExclusively; + this.outputGroups = outputGroups; + this.shouldRunTests = shouldRunTests; + } + + /** Returns the build command as a string. */ + public String buildCommand() { + return buildCommand; + } + + /** Returns the value of the --compile_only flag. */ + public boolean compileOnly() { + return compileOnly; + } + + /** Returns the value of the --compilation_prerequisites_only flag. */ + public boolean compilationPrerequisitesOnly() { + return compilationPrerequisitesOnly; + } + + /** Whether to run tests in exclusive mode. */ + public boolean runTestsExclusively() { + return runTestsExclusively; + } + + /** Returns the value of the --output_groups flag. */ + public Set<String> outputGroups() { + return outputGroups; + } + + /** Whether the top-level request command may run tests. */ + public boolean shouldRunTests() { + return shouldRunTests; + } + + // TopLevelArtifactContexts are stored in maps in BuildView, + // so equals() and hashCode() need to work. + @Override + public boolean equals(Object other) { + if (other instanceof TopLevelArtifactContext) { + TopLevelArtifactContext otherContext = (TopLevelArtifactContext) other; + return buildCommand.equals(otherContext.buildCommand) + && compileOnly == otherContext.compileOnly + && compilationPrerequisitesOnly == otherContext.compilationPrerequisitesOnly + && runTestsExclusively == otherContext.runTestsExclusively + && outputGroups.equals(otherContext.outputGroups) + && shouldRunTests == otherContext.shouldRunTests; + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(buildCommand, compileOnly, compilationPrerequisitesOnly, + runTestsExclusively, outputGroups, shouldRunTests); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java new file mode 100644 index 0000000..3c025f4 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
@@ -0,0 +1,158 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.rules.test.TestProvider; + +/** + * A small static class containing utility methods for handling the inclusion of + * extra top-level artifacts into the build. + */ +public final class TopLevelArtifactHelper { + + private TopLevelArtifactHelper() { + // Prevent instantiation. + } + + /** Returns command-specific artifacts which may exist for a given target and build command. */ + public static final Iterable<Artifact> getCommandArtifacts(TransitiveInfoCollection target, + String buildCommand) { + TopLevelArtifactProvider provider = target.getProvider(TopLevelArtifactProvider.class); + if (provider != null + && provider.getCommandsForExtraArtifacts().contains(buildCommand.toLowerCase())) { + return provider.getArtifactsForCommand(); + } else { + return ImmutableList.of(); + } + } + + /** + * Utility function to form a list of all test output Artifacts of the given targets to test. + */ + public static ImmutableCollection<Artifact> getAllArtifactsToTest( + Iterable<? extends TransitiveInfoCollection> targets) { + if (targets == null) { + return ImmutableList.of(); + } + ImmutableList.Builder<Artifact> allTestArtifacts = ImmutableList.builder(); + for (TransitiveInfoCollection target : targets) { + allTestArtifacts.addAll(TestProvider.getTestStatusArtifacts(target)); + } + return allTestArtifacts.build(); + } + + /** + * Utility function to form a NestedSet of all top-level Artifacts of the given targets. + */ + public static NestedSet<Artifact> getAllArtifactsToBuild( + Iterable<? extends TransitiveInfoCollection> targets, TopLevelArtifactContext options) { + NestedSetBuilder<Artifact> allArtifacts = NestedSetBuilder.stableOrder(); + for (TransitiveInfoCollection target : targets) { + allArtifacts.addTransitive(getAllArtifactsToBuild(target, options)); + } + return allArtifacts.build(); + } + + /** + * Returns all artifacts to build if this target is requested as a top-level target. The resulting + * set includes the temps and either the files to compile, if + * {@code options.compileOnly() == true}, or the files to run. + * + * <p>Calls to this method should generally return quickly; however, the runfiles computation can + * be lazy, in which case it can be expensive on the first call. Subsequent calls may or may not + * return the same {@code Iterable} instance. + */ + public static NestedSet<Artifact> getAllArtifactsToBuild(TransitiveInfoCollection target, + TopLevelArtifactContext options) { + NestedSetBuilder<Artifact> allArtifacts = NestedSetBuilder.stableOrder(); + TempsProvider tempsProvider = target.getProvider(TempsProvider.class); + if (tempsProvider != null) { + allArtifacts.addAll(tempsProvider.getTemps()); + } + + TopLevelArtifactProvider topLevelArtifactProvider = + target.getProvider(TopLevelArtifactProvider.class); + if (topLevelArtifactProvider != null) { + for (String outputGroup : options.outputGroups()) { + NestedSet<Artifact> results = topLevelArtifactProvider.getOutputGroup(outputGroup); + if (results != null) { + allArtifacts.addTransitive(results); + } + } + } + + if (options.compileOnly()) { + FilesToCompileProvider provider = target.getProvider(FilesToCompileProvider.class); + if (provider != null) { + allArtifacts.addAll(provider.getFilesToCompile()); + } + } else if (options.compilationPrerequisitesOnly()) { + CompilationPrerequisitesProvider provider = + target.getProvider(CompilationPrerequisitesProvider.class); + if (provider != null) { + allArtifacts.addTransitive(provider.getCompilationPrerequisites()); + } + } else { + FilesToRunProvider filesToRunProvider = target.getProvider(FilesToRunProvider.class); + boolean hasRunfilesSupport = false; + if (filesToRunProvider != null) { + allArtifacts.addAll(filesToRunProvider.getFilesToRun()); + hasRunfilesSupport = filesToRunProvider.getRunfilesSupport() != null; + } + + if (!hasRunfilesSupport) { + RunfilesProvider runfilesProvider = + target.getProvider(RunfilesProvider.class); + if (runfilesProvider != null) { + allArtifacts.addTransitive(runfilesProvider.getDefaultRunfiles().getAllArtifacts()); + } + } + + AlwaysBuiltArtifactsProvider forcedArtifacts = target.getProvider( + AlwaysBuiltArtifactsProvider.class); + if (forcedArtifacts != null) { + allArtifacts.addTransitive(forcedArtifacts.getArtifactsToAlwaysBuild()); + } + } + + allArtifacts.addAll(getCommandArtifacts(target, options.buildCommand())); + allArtifacts.addAll(getCoverageArtifacts(target, options)); + return allArtifacts.build(); + } + + private static Iterable<Artifact> getCoverageArtifacts(TransitiveInfoCollection target, + TopLevelArtifactContext topLevelOptions) { + if (!topLevelOptions.compileOnly() && !topLevelOptions.compilationPrerequisitesOnly() + && topLevelOptions.shouldRunTests()) { + // Add baseline code coverage artifacts if we are collecting code coverage. We do that only + // when running tests. + // It might be slightly faster to first check if any configuration has coverage enabled. + if (target.getConfiguration() != null + && target.getConfiguration().isCodeCoverageEnabled()) { + BaselineCoverageArtifactsProvider provider = + target.getProvider(BaselineCoverageArtifactsProvider.class); + if (provider != null) { + return provider.getBaselineCoverageArtifacts(); + } + } + } + return ImmutableList.of(); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactProvider.java new file mode 100644 index 0000000..e2a2d57 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactProvider.java
@@ -0,0 +1,61 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; + +/** + * ConfiguredTargets implementing this interface can provide command-specific + * and unconditional extra artifacts to the build. + */ +@Immutable +public final class TopLevelArtifactProvider implements TransitiveInfoProvider { + + private final ImmutableList<String> commandsForExtraArtifacts; + private final ImmutableList<Artifact> artifactsForCommand; + private final ImmutableMap<String, NestedSet<Artifact>> outputGroups; + + public TopLevelArtifactProvider(ImmutableList<String> commandsForExtraArtifacts, + ImmutableList<Artifact> artifactsForCommand) { + this.commandsForExtraArtifacts = commandsForExtraArtifacts; + this.artifactsForCommand = artifactsForCommand; + this.outputGroups = ImmutableMap.<String, NestedSet<Artifact>>of(); + } + + public TopLevelArtifactProvider(String key, NestedSet<Artifact> artifactsToBuild) { + this.commandsForExtraArtifacts = ImmutableList.of(); + this.artifactsForCommand = ImmutableList.of(); + this.outputGroups = ImmutableMap.<String, NestedSet<Artifact>>of(key, artifactsToBuild); + } + + /** Returns the commands (in lowercase) that this provider should provide artifacts for. */ + public ImmutableList<String> getCommandsForExtraArtifacts() { + return commandsForExtraArtifacts; + } + + /** Returns the extra artifacts for the commands. */ + public ImmutableList<Artifact> getArtifactsForCommand() { + return artifactsForCommand; + } + + /** Returns artifacts that are to be built for every command. */ + public NestedSet<Artifact> getOutputGroup(String outputGroupName) { + return outputGroups.get(outputGroupName); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoCollection.java new file mode 100644 index 0000000..82396ee --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoCollection.java
@@ -0,0 +1,118 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.collect.UnmodifiableIterator; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.SkylarkModule; + +import javax.annotation.Nullable; + +/** + * Objects that implement this interface bundle multiple {@link TransitiveInfoProvider} interfaces. + * + * <p>This interface (together with {@link TransitiveInfoProvider} is the cornerstone of the data + * model of the analysis phase. + * + * <p>The computation a configured target does is allowed to depend on the following things: + * <ul> + * <li>The associated Target (which will usually be a Rule) + * <li>Its own configuration (the configured target does not have access to other configurations, + * e.g. the host configuration, though) + * <li>The transitive info providers and labels of its direct dependencies. + * </ul> + * + * <p>And these are the only inputs. Notably, a configured target is not supposed to access + * other configured targets, the transitive info collections of configured targets it does not + * directly depend on, the actions created by anyone else or the contents of any input file. We + * strive to make it impossible for configured targets to do these things. + * + * <p>A configured target is expected to produce the following data during its analysis: + * <ul> + * <li>A number of Artifacts and Actions generating them + * <li>A set of {@link TransitiveInfoProvider}s that it passes on to the targets directly dependent + * on it + * </ul> + * + * <p>The information that can be passed on to dependent targets by way of + * {@link TransitiveInfoProvider} is subject to constraints (which are detailed in the + * documentation of that class). + * + * <p>Configured targets are currently allowed to create artifacts at any exec path. It would be + * better if they could be constrained to a subtree based on the label of the configured target, + * but this is currently not feasible because multiple rules violate this constraint and the + * output format is part of its interface. + * + * <p>In principle, multiple configured targets should not create actions with conflicting + * outputs. There are still a few exceptions to this rule that are slated to be eventually + * removed, we have provisions to handle this case (Action instances that share at least one + * output file are required to be exactly the same), but this does put some pressure on the design + * and we are eventually planning to eliminate this option. + * + * <p>These restrictions together make it possible to: + * <ul> + * <li>Correctly cache the analysis phase; by tightly constraining what a configured target is + * allowed to access and what it is not, we can know when it needs to invalidate a particular + * one and when it can reuse an already existing one. + * <li>Serialize / deserialize individual configured targets at will, making it possible for + * example to swap out part of the analysis state if there is memory pressure or to move them in + * persistent storage so that the state can be reconstructed at a different time or in a + * different process. The stretch goal is to eventually facilitate cross-uses caching of this + * information. + * </ul> + * + * <p>Implementations of build rules should <b>not</b> hold on to references to the + * {@link TransitiveInfoCollection}s representing their direct prerequisites in order to reduce + * their memory footprint (otherwise, the referenced object could refer one of its direct + * dependencies in turn, thereby making the size of the objects reachable from a single instance + * unbounded). + * + * @see TransitiveInfoProvider + */ +@SkylarkModule(name = "target", doc = "A BUILD target.") +public interface TransitiveInfoCollection extends Iterable<TransitiveInfoProvider> { + + /** + * Returns the transitive information provider requested, or null if the provider is not found. + * The provider has to be a TransitiveInfoProvider Java class. + */ + @Nullable <P extends TransitiveInfoProvider> P getProvider(Class<P> provider); + + /** + * Returns the label associated with this prerequisite. + */ + Label getLabel(); + + /** + * <p>Returns the {@link BuildConfiguration} for which this transitive info collection is defined. + * Configuration is defined for all configured targets with exception of {@link + * InputFileConfiguredTarget} and {@link PackageGroupConfiguredTarget} for which it is always + * <b>null</b>.</p> + */ + @Nullable BuildConfiguration getConfiguration(); + + /** + * Returns the transitive information requested or null, if the information is not found. + * The transitive information has to have been added using the Skylark framework. + */ + @Nullable Object get(String providerKey); + + /** + * Returns an unmodifiable iterator over the transitive info providers in the collections. + */ + @Override + UnmodifiableIterator<TransitiveInfoProvider> iterator(); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProvider.java new file mode 100644 index 0000000..37ec191 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProvider.java
@@ -0,0 +1,60 @@ +// Copyright 2014 Google Inc. 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.analysis; + +/** + * This marker interface must be extended by every interface that represents + * rolled-up data about the transitive closure of a configured target. + * + * TransitiveInfoProviders need to be serializable, and for that reason they must conform to + * the following restrictions: + * + * <ul> + * <li>The provider interface must directly extend {@code TransitiveInfoProvider}. + * <li>Every method must return immutable data.</li> + * <li>Every method must return the same object if called multiple times with the same + * arguments.</li> + * <li>Overloading a method name multiple times is forbidden.</li> + * <li>The return type of a method must satisfy one of the following conditions: + * <ul> + * <li>It must be from the set of {String, Integer, int, Boolean, bool, Label, PathFragment, + * Artifact}, OR</li> + * <li>it must be an ImmutableList/List/Collection/Iterable of T, where T is either + * one of the types above with a default serializer or T implements ValueSerializer), OR</li> + * <li>it must be serializable (TBD)</li> + * </ul> + * <li>If the method takes arguments, it must declare a custom serializer (TBD).</li> + * </ul> + * + * <p>Some typical uses of this interface are: + * <ul> + * <li>The set of Python source files in the transitive closure of this rule + * <li>The set of declared C++ header files in the transitive closure + * <li>The files that need to be built when the target is mentioned on the command line + * </ul> + * + * <p>Note that if implemented naively, this would result in the memory requirements + * being O(n^2): in a long dependency chain, if every target adds one single artifact, storing the + * transitive closures of every rule would take 1+2+3+...+n-1+n = O(n^2) memory. + * + * <p>In order to avoid this, we introduce the concept of nested sets. A nested set is an immutable + * data structure that can contain direct members and other nested sets (recursively). Nested sets + * are iterable and can be flattened into ordered sets, where the order depends on which + * implementation of NestedSet you pick. + * + * @see TransitiveInfoCollection + */ +public interface TransitiveInfoProvider { +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/Util.java b/src/main/java/com/google/devtools/build/lib/analysis/Util.java new file mode 100644 index 0000000..ee10bf0 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/Util.java
@@ -0,0 +1,64 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.vfs.PathFragment; + +/** + * Utility methods for use by ConfiguredTarget implementations. + */ +public abstract class Util { + + private Util() {} + + //---------- Label and Target related methods + + /** + * Returns the workspace-relative path of the specified target (file or rule). + * + * <p>For example, "//foo/bar:wiz" and "//foo:bar/wiz" both result in "foo/bar/wiz". + */ + public static PathFragment getWorkspaceRelativePath(Target target) { + return getWorkspaceRelativePath(target.getLabel()); + } + + /** + * Returns the workspace-relative path of the specified target (file or rule). + * + * <p>For example, "//foo/bar:wiz" and "//foo:bar/wiz" both result in "foo/bar/wiz". + */ + public static PathFragment getWorkspaceRelativePath(Label label) { + return label.getPackageFragment().getRelative(label.getName()); + } + + /** + * Returns the workspace-relative path of the specified target (file or rule), + * prepending a prefix and appending a suffix. + * + * <p>For example, "//foo/bar:wiz" and "//foo:bar/wiz" both result in "foo/bar/wiz". + */ + public static PathFragment getWorkspaceRelativePath(Target target, String prefix, String suffix) { + return target.getLabel().getPackageFragment().getRelative(prefix + target.getName() + suffix); + } + + /** + * Checks if a PathFragment contains a '-'. + */ + public static boolean containsHyphen(PathFragment path) { + return path.getPathString().indexOf('-') >= 0; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ViewCreationFailedException.java b/src/main/java/com/google/devtools/build/lib/analysis/ViewCreationFailedException.java new file mode 100644 index 0000000..ae7dfc5 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ViewCreationFailedException.java
@@ -0,0 +1,31 @@ +// Copyright 2014 Google Inc. 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.analysis; + +/** + * An exception indicating that there was a problem during the view + * construction (loading and analysis phases) for one or more targets, that the + * configured target graph could not be successfully constructed, and that + * a build cannot be started. + */ +public class ViewCreationFailedException extends Exception { + + public ViewCreationFailedException(String message) { + super(message); + } + + public ViewCreationFailedException(String message, Throwable cause) { + super(message + ": " + cause.getMessage(), cause); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/VisibilityProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/VisibilityProvider.java new file mode 100644 index 0000000..a438145 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/VisibilityProvider.java
@@ -0,0 +1,29 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.packages.PackageSpecification; + +/** + * Provider class for configured targets that have a visibility. + */ +public interface VisibilityProvider extends TransitiveInfoProvider { + + /** + * Returns the visibility specification. + */ + NestedSet<PackageSpecification> getVisibility(); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/VisibilityProviderImpl.java b/src/main/java/com/google/devtools/build/lib/analysis/VisibilityProviderImpl.java new file mode 100644 index 0000000..01dd06a --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/VisibilityProviderImpl.java
@@ -0,0 +1,36 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.packages.PackageSpecification; + +/** + * Visibility provider implementation. + */ +@Immutable +public final class VisibilityProviderImpl implements VisibilityProvider { + private final NestedSet<PackageSpecification> visibility; + + public VisibilityProviderImpl(NestedSet<PackageSpecification> visibility) { + this.visibility = visibility; + } + + @Override + public NestedSet<PackageSpecification> getVisibility() { + return visibility; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java b/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java new file mode 100644 index 0000000..5b3bd27 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java
@@ -0,0 +1,185 @@ +// Copyright 2014 Google Inc. 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.analysis; + +import com.google.common.base.Splitter; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.actions.AbstractAction; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.ArtifactFactory; +import com.google.devtools.build.lib.actions.ArtifactOwner; +import com.google.devtools.build.lib.actions.Executor.ActionContext; +import com.google.devtools.build.lib.vfs.FileSystemUtils; +import com.google.devtools.build.lib.vfs.Path; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * An action writing the workspace status files. + * + * <p>These files represent information about the environment the build was run in. They are used + * by language-specific build info factories to make the data in them available for individual + * languages (e.g. by turning them into .h files for C++) + * + * <p>The format of these files a list of key-value pairs, one for each line. The key and the value + * are separated by a space. + * + * <p>There are two of these files: volatile and stable. Changes in the volatile file do not + * cause rebuilds if no other file is changed. This is useful for frequently-changing information + * that does not significantly affect the build, e.g. the current time. + */ +public abstract class WorkspaceStatusAction extends AbstractAction { + + /** + * The type of a workspace status action key. + */ + public enum KeyType { + INTEGER, + STRING, + VERBATIM, + } + + /** + * Language for keys that should be present in the build info for every language. + */ + // TODO(bazel-team): Once this is released, migrate the only place in the depot to use + // the BUILD_USERNAME, BUILD_HOSTNAME and BUILD_DIRECTORY keys instead of BUILD_INFO. Then + // language-specific build info keys can be removed. + public static final String ALL_LANGUAGES = "*"; + + /** + * Action context required by the actions that write language-specific workspace status artifacts. + */ + public static interface Context extends ActionContext { + ImmutableMap<String, Key> getStableKeys(); + ImmutableMap<String, Key> getVolatileKeys(); + } + + /** + * A key in the workspace status info file. + */ + public static class Key { + private final KeyType type; + + /** + * Should be set to ALL_LANGUAGES if the key should be present in the build info of every + * language. + */ + private final String language; + private final String defaultValue; + private final String redactedValue; + + private Key(KeyType type, String language, String defaultValue, String redactedValue) { + this.type = type; + this.language = language; + this.defaultValue = defaultValue; + this.redactedValue = redactedValue; + } + + public KeyType getType() { + return type; + } + + public boolean isInLanguage(String language) { + return this.language.equals(ALL_LANGUAGES) || this.language.equals(language); + } + + public String getDefaultValue() { + return defaultValue; + } + + public String getRedactedValue() { + return redactedValue; + } + + public static Key forLanguage( + String language, KeyType type, String defaultValue, String redactedValue) { + return new Key(type, language, defaultValue, redactedValue); + } + + public static Key of(KeyType type, String defaultValue, String redactedValue) { + return new Key(type, ALL_LANGUAGES, defaultValue, redactedValue); + } + } + + /** + * Parses the output of the workspace status action. + * + * <p>The output is a text file with each line representing a workspace status info key. + * The key is the part of the line before the first space and should consist of the characters + * [A-Z_] (although this is not checked). Everything after the first space is the value. + */ + public static Map<String, String> parseValues(Path file) throws IOException { + HashMap<String, String> result = new HashMap<>(); + Splitter lineSplitter = Splitter.on(" ").limit(2); + for (String line : Splitter.on("\n").split( + new String(FileSystemUtils.readContentAsLatin1(file)))) { + List<String> items = ImmutableList.copyOf(lineSplitter.split(line)); + if (items.size() != 2) { + continue; + } + + result.put(items.get(0), items.get(1)); + } + + return ImmutableMap.copyOf(result); + } + + /** + * Factory for {@link WorkspaceStatusAction}. + */ + public interface Factory { + /** + * Creates the workspace status action. + * + * <p>If the objects returned for two builds are equals, the workspace status action can be + * be reused between them. Note that this only applies to the action object itself (the action + * will be unconditionally re-executed on every build) + */ + WorkspaceStatusAction createWorkspaceStatusAction( + ArtifactFactory artifactFactory, ArtifactOwner artifactOwner, Supplier<UUID> buildId); + + /** + * Creates a dummy workspace status map. Used in cases where the build failed, so that part of + * the workspace status is nevertheless available. + */ + Map<String, String> createDummyWorkspaceStatus(); + } + + protected WorkspaceStatusAction(ActionOwner owner, + Iterable<Artifact> inputs, + Iterable<Artifact> outputs) { + super(owner, inputs, outputs); + } + + /** + * The volatile status artifact containing items that may change even if nothing changed + * between the two builds, e.g. current time. + */ + public abstract Artifact getVolatileStatus(); + + /** + * The stable status artifact containing items that change only if information relevant to the + * build changes, e.g. the name of the user running the build or the hostname. + */ + public abstract Artifact getStableStatus(); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/AbstractFileWriteAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/AbstractFileWriteAction.java new file mode 100644 index 0000000..187fc48 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/AbstractFileWriteAction.java
@@ -0,0 +1,142 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.AbstractAction; +import com.google.devtools.build.lib.actions.ActionExecutionContext; +import com.google.devtools.build.lib.actions.ActionExecutionException; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.ExecException; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.actions.ResourceSet; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.syntax.Label; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Abstract Action to write to a file. + */ +public abstract class AbstractFileWriteAction extends AbstractAction { + + protected final boolean makeExecutable; + + /** + * Creates a new AbstractFileWriteAction instance. + * + * @param owner the action owner. + * @param inputs the Artifacts that this Action depends on + * @param output the Artifact that will be created by executing this Action. + * @param makeExecutable iff true will change the output file to be + * executable. + */ + public AbstractFileWriteAction(ActionOwner owner, + Iterable<Artifact> inputs, Artifact output, boolean makeExecutable) { + // There is only one output, and it is primary. + super(owner, inputs, ImmutableList.of(output)); + this.makeExecutable = makeExecutable; + } + + public boolean makeExecutable() { + return makeExecutable; + } + + @Override + public final void execute(ActionExecutionContext actionExecutionContext) + throws ActionExecutionException, InterruptedException { + try { + getStrategy(actionExecutionContext.getExecutor()).exec(actionExecutionContext.getExecutor(), + this, actionExecutionContext.getFileOutErr(), actionExecutionContext); + } catch (ExecException e) { + throw e.toActionExecutionException( + "Writing file for rule '" + Label.print(getOwner().getLabel()) + "'", + actionExecutionContext.getExecutor().getVerboseFailures(), this); + } + afterWrite(actionExecutionContext.getExecutor()); + } + + /** + * Produce a DeterministicWriter that can write the file to an OutputStream deterministically. + * + * @param eventHandler destination for warning messages. (Note that errors should + * still be indicated by throwing an exception; reporter.error() will + * not cause action execution to fail.) + * @param executor the Executor. + * @throws IOException if the content cannot be written to the output stream + */ + public abstract DeterministicWriter newDeterministicWriter(EventHandler eventHandler, + Executor executor) throws IOException, InterruptedException, ExecException; + + /** + * This hook is called after the File has been successfully written to disk. + * + * @param executor the Executor. + */ + protected void afterWrite(Executor executor) { + } + + // We're mainly doing I/O, so estimate very low CPU usage, e.g. 1%. Just a guess. + private static final ResourceSet DEFAULT_FILEWRITE_LOCAL_ACTION_RESOURCE_SET = + new ResourceSet(/*memoryMb=*/0.0, /*cpuUsage=*/0.01, /*ioUsage=*/0.2); + + @Override + public ResourceSet estimateResourceConsumption(Executor executor) { + return executor.getContext(FileWriteActionContext.class).estimateResourceConsumption(this); + } + + public ResourceSet estimateResourceConsumptionLocal() { + return DEFAULT_FILEWRITE_LOCAL_ACTION_RESOURCE_SET; + } + + @Override + public String getMnemonic() { + return "FileWrite"; + } + + @Override + protected String getRawProgressMessage() { + return "Writing " + (makeExecutable ? "script " : "file ") + + Iterables.getOnlyElement(getOutputs()).prettyPrint(); + } + + /** + * Whether the file write can be generated remotely. If the file is consumed in Blaze + * unconditionally, it doesn't make sense to run remotely. + */ + public boolean isRemotable() { + return true; + } + + @Override + public final String describeStrategy(Executor executor) { + return executor.getContext(FileWriteActionContext.class).strategyLocality(this); + } + + private FileWriteActionContext getStrategy(Executor executor) { + return executor.getContext(FileWriteActionContext.class); + } + + /** + * A deterministic writer writes bytes to an output stream. The same byte stream is written + * on every invocation of writeOutputFile(). + */ + public interface DeterministicWriter { + public void writeOutputFile(OutputStream out) throws IOException; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java new file mode 100644 index 0000000..b7461e5 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java
@@ -0,0 +1,37 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.analysis.AnalysisEnvironment; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.packages.Rule; + +/** + * A temporary interface to allow migration from RuleConfiguredTarget to RuleContext. It bundles + * the items commonly needed to construct action instances. + */ +public interface ActionConstructionContext { + /** The rule for which the actions are constructed. */ + Rule getRule(); + + /** Returns the action owner that should be used for actions. */ + ActionOwner getActionOwner(); + + /** Returns the {@link BuildConfiguration} for which the given rule is analyzed. */ + BuildConfiguration getConfiguration(); + + /** The current analysis environment. */ + AnalysisEnvironment getAnalysisEnvironment(); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/BinaryFileWriteAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/BinaryFileWriteAction.java new file mode 100644 index 0000000..b9a2ea5 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/BinaryFileWriteAction.java
@@ -0,0 +1,90 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.io.ByteSource; +import com.google.common.io.ByteStreams; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.util.Fingerprint; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Action to write a binary file. + */ +public final class BinaryFileWriteAction extends AbstractFileWriteAction { + + private static final String GUID = "eeee07fe-4b40-11e4-82d6-eba0b4f713e2"; + + private final ByteSource source; + + /** + * Creates a new BinaryFileWriteAction instance without inputs. + * + * @param owner the action owner. + * @param output the Artifact that will be created by executing this Action. + * @param source a source of bytes that will be written to the file. + * @param makeExecutable iff true will change the output file to be executable. + */ + public BinaryFileWriteAction( + ActionOwner owner, Artifact output, ByteSource source, boolean makeExecutable) { + super(owner, /*inputs=*/Artifact.NO_ARTIFACTS, output, makeExecutable); + this.source = Preconditions.checkNotNull(source); + } + + @VisibleForTesting + public ByteSource getSource() { + return source; + } + + @Override + public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, Executor executor) { + return new DeterministicWriter() { + @Override + public void writeOutputFile(OutputStream out) throws IOException { + try (InputStream in = source.openStream()) { + ByteStreams.copy(in, out); + } + out.flush(); + } + }; + } + + @Override + protected String computeKey() { + Fingerprint f = new Fingerprint(); + f.addString(GUID); + f.addString(String.valueOf(makeExecutable)); + + try (InputStream in = source.openStream()) { + byte[] buffer = new byte[512]; + int amountRead; + while ((amountRead = in.read(buffer)) != -1) { + f.addBytes(buffer, 0, amountRead); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + return f.hexDigestAndReset(); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/CommandLine.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/CommandLine.java new file mode 100644 index 0000000..ddadc25 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/CommandLine.java
@@ -0,0 +1,123 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.collect.CollectionUtils; + +/** + * A representation of a command line to be executed by a SpawnAction. + */ +public abstract class CommandLine { + /** + * Returns the command line. + */ + public abstract Iterable<String> arguments(); + + /** + * Returns whether the command line represents a shell command with the given shell executable. + * This is used to give better error messages. + * + * <p>By default, this method returns false. + */ + public boolean isShellCommand() { + return false; + } + + /** + * A default implementation of a command line backed by a copy of the given list of arguments. + */ + static CommandLine ofInternal(Iterable<String> arguments, final boolean isShellCommand) { + final Iterable<String> immutableArguments = CollectionUtils.makeImmutable(arguments); + return new CommandLine() { + @Override + public Iterable<String> arguments() { + return immutableArguments; + } + + @Override + public boolean isShellCommand() { + return isShellCommand; + } + }; + } + + /** + * Returns a {@link CommandLine} backed by a copy of the given list of arguments. + */ + public static CommandLine of(Iterable<String> arguments, final boolean isShellCommand) { + final Iterable<String> immutableArguments = CollectionUtils.makeImmutable(arguments); + return new CommandLine() { + @Override + public Iterable<String> arguments() { + return immutableArguments; + } + + @Override + public boolean isShellCommand() { + return isShellCommand; + } + }; + } + + /** + * Returns a {@link CommandLine} that is constructed by prepending the {@code executableArgs} to + * {@code commandLine}. + */ + static CommandLine ofMixed(final ImmutableList<String> executableArgs, + final CommandLine commandLine, final boolean isShellCommand) { + Preconditions.checkState(!executableArgs.isEmpty()); + return new CommandLine() { + @Override + public Iterable<String> arguments() { + return Iterables.concat(executableArgs, commandLine.arguments()); + } + + @Override + public boolean isShellCommand() { + return isShellCommand; + } + }; + } + + /** + * Returns a {@link CommandLine} with {@link CharSequence} arguments. This can be useful to create + * memory efficient command lines with {@link com.google.devtools.build.lib.util.LazyString}s. + */ + public static CommandLine ofCharSequences(final ImmutableList<CharSequence> arguments) { + return new CommandLine() { + @Override + public Iterable<String> arguments() { + ImmutableList.Builder<String> builder = ImmutableList.builder(); + for (CharSequence arg : arguments) { + builder.add(arg.toString()); + } + return builder.build(); + } + }; + } + + /** + * This helps when debugging Blaze code that uses {@link CommandLine}s, as you can see their + * content directly in the variable inspector. + */ + @Override + public String toString() { + return Joiner.on(' ').join(arguments()); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java new file mode 100644 index 0000000..d358f0b --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java
@@ -0,0 +1,358 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.collect.CollectionUtils; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.util.ArrayList; +import java.util.List; + +/** + * A customizable, serializable class for building memory efficient command lines. + */ +@Immutable +public final class CustomCommandLine extends CommandLine { + + private abstract static class ArgvFragment { + abstract void eval(ImmutableList.Builder<String> builder); + } + + // It's better to avoid anonymous classes if we want to serialize command lines + + private static final class ObjectArg extends ArgvFragment { + private final Object arg; + + private ObjectArg(Object arg) { + this.arg = arg; + } + + @Override + void eval(ImmutableList.Builder<String> builder) { + builder.add(arg.toString()); + } + } + + private static final class JoinExecPathsArg extends ArgvFragment { + + private final String delimiter; + private final Iterable<Artifact> artifacts; + + private JoinExecPathsArg(String delimiter, Iterable<Artifact> artifacts) { + this.delimiter = delimiter; + this.artifacts = CollectionUtils.makeImmutable(artifacts); + } + + @Override + void eval(ImmutableList.Builder<String> builder) { + builder.add(Artifact.joinExecPaths(delimiter, artifacts)); + } + } + + private static final class PathWithTemplateArg extends ArgvFragment { + + private final String template; + private final PathFragment[] paths; + + private PathWithTemplateArg(String template, PathFragment... paths) { + this.template = template; + this.paths = paths; + } + + @Override + void eval(ImmutableList.Builder<String> builder) { + // PathFragment.toString() uses getPathString() + builder.add(String.format(template, (Object[]) paths)); + } + } + + // TODO(bazel-team): CustomArgv and CustomMultiArgv is going to be difficult to expose + // in Skylark. Maybe we can get rid of them by refactoring JavaCompileAction. It also + // raises immutability / serialization issues. + /** + * Custom Java code producing a String argument. Usage of this class is discouraged. + */ + public abstract static class CustomArgv extends ArgvFragment { + + @Override + void eval(ImmutableList.Builder<String> builder) { + builder.add(argv()); + } + + public abstract String argv(); + } + + /** + * Custom Java code producing a List of String arguments. Usage of this class is discouraged. + */ + public abstract static class CustomMultiArgv extends ArgvFragment { + + @Override + void eval(ImmutableList.Builder<String> builder) { + builder.addAll(argv()); + } + + public abstract Iterable<String> argv(); + } + + private static final class JoinPathsArg extends ArgvFragment { + + private final String delimiter; + private final Iterable<PathFragment> paths; + + private JoinPathsArg(String delimiter, Iterable<PathFragment> paths) { + this.delimiter = delimiter; + this.paths = CollectionUtils.makeImmutable(paths); + } + + @Override + void eval(ImmutableList.Builder<String> builder) { + builder.add(Joiner.on(delimiter).join(paths)); + } + } + + /** + * Arguments that intersperse strings between the items in a sequence. There are two forms of + * interspersing, and either may be used by this implementation: + * <ul> + * <li>before each - a string is added before each item in a sequence. e.g. + * {@code -f foo -f bar -f baz} + * <li>format each - a format string is used to format each item in a sequence. e.g. + * {@code -I/foo -I/bar -I/baz} for the format {@code "-I%s"} + * </ul> + * + * <p>This class could be used both with both the "before" and "format" features at the same + * time, but this is probably more confusion than it is worth. If you need this functionality, + * consider using "before" only but storing the strings pre-formated in a {@link NestedSet}. + */ + private static final class InterspersingArgs extends ArgvFragment { + private final Iterable<?> sequence; + private final String beforeEach; + private final String formatEach; + + /** + * Do not call from outside this class because this does not guarantee that {@code sequence} is + * immutable. + */ + private InterspersingArgs(Iterable<?> sequence, String beforeEach, String formatEach) { + this.sequence = sequence; + this.beforeEach = beforeEach; + this.formatEach = formatEach; + } + + static InterspersingArgs fromStrings( + Iterable<?> sequence, String beforeEach, String formatEach) { + return new InterspersingArgs( + CollectionUtils.makeImmutable(sequence), beforeEach, formatEach); + } + + static InterspersingArgs fromExecPaths( + Iterable<Artifact> sequence, String beforeEach, String formatEach) { + return new InterspersingArgs( + Artifact.toExecPaths(CollectionUtils.makeImmutable(sequence)), beforeEach, formatEach); + } + + @Override + void eval(ImmutableList.Builder<String> builder) { + for (Object item : sequence) { + if (item == null) { + continue; + } + + if (beforeEach != null) { + builder.add(beforeEach); + } + String arg = item.toString(); + if (formatEach != null) { + arg = String.format(formatEach, arg); + } + builder.add(arg); + } + } + } + + /** + * A Builder class for CustomCommandLine with the appropriate methods. + * + * <p>{@link Iterable} instances passed to {@code add*} methods will be stored internally as + * collections that are known to be immutable copies. This means that any {@link Iterable} that is + * not a {@link NestedSet} or {@link ImmutableList} may be copied. + * + * <p>{@code addFormatEach*} methods take an {@link Iterable} but use these as arguments to + * {@link String#format(String, Object...)} with a certain constant format string. For instance, + * if {@code format} is {@code "-I%s"}, then the final arguments may be + * {@code -Ifoo -Ibar -Ibaz} + * + * <p>{@code addBeforeEach*} methods take an {@link Iterable} but insert a certain {@link String} + * once before each element in the string, meaning the total number of elements added is twice the + * length of the {@link Iterable}. For instance: {@code -f foo -f bar -f baz} + */ + public static final class Builder { + + private final List<ArgvFragment> arguments = new ArrayList<>(); + + public Builder add(CharSequence arg) { + if (arg != null) { + arguments.add(new ObjectArg(arg)); + } + return this; + } + + public Builder add(Label arg) { + if (arg != null) { + arguments.add(new ObjectArg(arg)); + } + return this; + } + + public Builder add(String arg, Iterable<String> args) { + if (arg != null && args != null) { + arguments.add(new ObjectArg(arg)); + arguments.add(InterspersingArgs.fromStrings(args, /*beforeEach=*/null, "%s")); + } + return this; + } + + public Builder add(Iterable<String> args) { + if (args != null) { + arguments.add(InterspersingArgs.fromStrings(args, /*beforeEach=*/null, "%s")); + } + return this; + } + + public Builder addExecPath(String arg, Artifact artifact) { + if (arg != null && artifact != null) { + arguments.add(new ObjectArg(arg)); + arguments.add(new ObjectArg(artifact.getExecPath())); + } + return this; + } + + public Builder addExecPaths(String arg, Iterable<Artifact> artifacts) { + if (arg != null && artifacts != null) { + arguments.add(new ObjectArg(arg)); + arguments.add(InterspersingArgs.fromExecPaths(artifacts, /*beforeEach=*/null, "%s")); + } + return this; + } + + public Builder addExecPaths(Iterable<Artifact> artifacts) { + if (artifacts != null) { + arguments.add(InterspersingArgs.fromExecPaths(artifacts, /*beforeEach=*/null, "%s")); + } + return this; + } + + public Builder addJoinExecPaths(String arg, String delimiter, Iterable<Artifact> artifacts) { + if (arg != null && artifacts != null) { + arguments.add(new ObjectArg(arg)); + arguments.add(new JoinExecPathsArg(delimiter, artifacts)); + } + return this; + } + + public Builder addPath(PathFragment path) { + if (path != null) { + arguments.add(new ObjectArg(path)); + } + return this; + } + + public Builder addPaths(String template, PathFragment... path) { + if (template != null && path != null) { + arguments.add(new PathWithTemplateArg(template, path)); + } + return this; + } + + public Builder addJoinPaths(String delimiter, Iterable<PathFragment> paths) { + if (delimiter != null && paths != null) { + arguments.add(new JoinPathsArg(delimiter, paths)); + } + return this; + } + + public Builder addBeforeEachPath(String repeated, Iterable<PathFragment> paths) { + if (repeated != null && paths != null) { + arguments.add(InterspersingArgs.fromStrings(paths, repeated, "%s")); + } + return this; + } + + public Builder addBeforeEach(String repeated, Iterable<String> strings) { + if (repeated != null && strings != null) { + arguments.add(InterspersingArgs.fromStrings(strings, repeated, "%s")); + } + return this; + } + + public Builder addBeforeEachExecPath(String repeated, Iterable<Artifact> artifacts) { + if (repeated != null && artifacts != null) { + arguments.add(InterspersingArgs.fromExecPaths(artifacts, repeated, "%s")); + } + return this; + } + + public Builder addFormatEach(String format, Iterable<String> strings) { + if (format != null && strings != null) { + arguments.add(InterspersingArgs.fromStrings(strings, /*beforeEach=*/null, format)); + } + return this; + } + + public Builder add(CustomArgv arg) { + if (arg != null) { + arguments.add(arg); + } + return this; + } + + public Builder add(CustomMultiArgv arg) { + if (arg != null) { + arguments.add(arg); + } + return this; + } + + public CustomCommandLine build() { + return new CustomCommandLine(arguments); + } + } + + public static Builder builder() { + return new Builder(); + } + + private final ImmutableList<ArgvFragment> arguments; + + private CustomCommandLine(List<ArgvFragment> arguments) { + this.arguments = ImmutableList.copyOf(arguments); + } + + @Override + public Iterable<String> arguments() { + ImmutableList.Builder<String> builder = ImmutableList.builder(); + for (ArgvFragment arg : arguments) { + arg.eval(builder); + } + return builder.build(); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ExecutableSymlinkAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ExecutableSymlinkAction.java new file mode 100644 index 0000000..376d9b8 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ExecutableSymlinkAction.java
@@ -0,0 +1,66 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.ActionExecutionContext; +import com.google.devtools.build.lib.actions.ActionExecutionException; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.vfs.Path; + +import java.io.IOException; + +/** + * Action to create an executable symbolic link. It includes additional + * validation that symlink target is indeed an executable file. + */ +public final class ExecutableSymlinkAction extends SymlinkAction { + + public ExecutableSymlinkAction(ActionOwner owner, Artifact input, Artifact output) { + super(owner, input, output, "Symlinking " + owner.getLabel()); + } + + @Override + public void execute(ActionExecutionContext actionExecutionContext) + throws ActionExecutionException { + Path inputPath = actionExecutionContext.getExecutor().getExecRoot().getRelative( + getInputPath()); + try { + // Validate that input path is a file with the executable bit is set. + if (!inputPath.isFile()) { + throw new ActionExecutionException( + "'" + Iterables.getOnlyElement(getInputs()).prettyPrint() + "' is not a file", this, + false); + } + if (!inputPath.isExecutable()) { + throw new ActionExecutionException("failed to create symbolic link '" + + Iterables.getOnlyElement(getOutputs()).prettyPrint() + + "': file '" + Iterables.getOnlyElement(getInputs()).prettyPrint() + + "' is not executable", this, false); + } + } catch (IOException e) { + throw new ActionExecutionException("failed to create symbolic link '" + + Iterables.getOnlyElement(getOutputs()).prettyPrint() + + "' to the '" + Iterables.getOnlyElement(getInputs()).prettyPrint() + + "' due to I/O error: " + e.getMessage(), e, this, false); + } + + super.execute(actionExecutionContext); + } + + @Override + public String getMnemonic() { return "ExecutableSymlink"; } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/FileWriteAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/FileWriteAction.java new file mode 100644 index 0000000..1128617 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/FileWriteAction.java
@@ -0,0 +1,145 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.util.Fingerprint; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; + +/** + * Action to write to a file. + * <p>TODO(bazel-team): Choose a better name to distinguish this class from + * {@link BinaryFileWriteAction}. + */ +public class FileWriteAction extends AbstractFileWriteAction { + + private static final String GUID = "332877c7-ca9f-4731-b387-54f620408522"; + + /** + * We keep it as a CharSequence for memory-efficiency reasons. The toString() + * method of the object represents the content of the file. + * + * <p>For example, this allows us to keep a {@code List<Artifact>} wrapped + * in a {@code LazyString} instead of the string representation of the concatenation. + * This saves memory because the Artifacts are shared objects but the + * resulting String is not. + */ + private final CharSequence fileContents; + + /** + * Creates a new FileWriteAction instance without inputs using UTF8 encoding. + * + * @param owner the action owner. + * @param output the Artifact that will be created by executing this Action. + * @param fileContents the contents to be written to the file. + * @param makeExecutable iff true will change the output file to be + * executable. + */ + public FileWriteAction(ActionOwner owner, Artifact output, CharSequence fileContents, + boolean makeExecutable) { + this(owner, Artifact.NO_ARTIFACTS, output, fileContents, makeExecutable); + } + + /** + * Creates a new FileWriteAction instance using UTF8 encoding. + * + * @param owner the action owner. + * @param inputs the Artifacts that this Action depends on + * @param output the Artifact that will be created by executing this Action. + * @param fileContents the contents to be written to the file. + * @param makeExecutable iff true will change the output file to be + * executable. + */ + public FileWriteAction(ActionOwner owner, Collection<Artifact> inputs, + Artifact output, CharSequence fileContents, boolean makeExecutable) { + super(owner, inputs, output, makeExecutable); + this.fileContents = fileContents; + } + + /** + * Creates a new FileWriteAction instance using UTF8 encoding. + * + * @param owner the action owner. + * @param inputs the Artifacts that this Action depends on + * @param output the Artifact that will be created by executing this Action. + * @param makeExecutable iff true will change the output file to be + * executable. + */ + protected FileWriteAction(ActionOwner owner, Collection<Artifact> inputs, + Artifact output, boolean makeExecutable) { + this(owner, inputs, output, "", makeExecutable); + } + + public String getFileContents() { + return fileContents.toString(); + } + + /** + * Create a DeterministicWriter for the content of the output file as provided by + * {@link #getFileContents()}. + */ + @Override + public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, + Executor executor) { + return new DeterministicWriter() { + @Override + public void writeOutputFile(OutputStream out) throws IOException { + out.write(getFileContents().getBytes(UTF_8)); + } + }; + } + + /** + * Computes the Action key for this action by computing the fingerprint for + * the file contents. + */ + @Override + protected String computeKey() { + Fingerprint f = new Fingerprint(); + f.addString(GUID); + f.addString(String.valueOf(makeExecutable)); + f.addString(getFileContents()); + return f.hexDigestAndReset(); + } + + /** + * Creates a FileWriteAction to write contents to the resulting artifact + * fileName in the genfiles root underneath the package path. + * + * @param ruleContext the ruleContext that will own the action of creating this file. + * @param fileName name of the file to create. + * @param contents data to write to file. + * @param executable flags that file should be marked executable. + * @return Artifact describing the file to create. + */ + public static Artifact createFile(RuleContext ruleContext, + String fileName, CharSequence contents, boolean executable) { + Artifact scriptFileArtifact = ruleContext.getAnalysisEnvironment().getDerivedArtifact( + ruleContext.getTarget().getLabel().getPackageFragment().getRelative(fileName), + ruleContext.getConfiguration().getGenfilesDirectory()); + ruleContext.registerAction(new FileWriteAction( + ruleContext.getActionOwner(), scriptFileArtifact, contents, executable)); + return scriptFileArtifact; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionContext.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionContext.java new file mode 100644 index 0000000..7e10331 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionContext.java
@@ -0,0 +1,44 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import com.google.devtools.build.lib.actions.ActionExecutionContext; +import com.google.devtools.build.lib.actions.ExecException; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.actions.Executor.ActionContext; +import com.google.devtools.build.lib.actions.ResourceSet; +import com.google.devtools.build.lib.util.io.FileOutErr; + +/** + * The action context for {@link AbstractFileWriteAction} instances (technically instances of + * subclasses). + */ +public interface FileWriteActionContext extends ActionContext { + + /** + * Performs all the setup and then calls back into the action to write the data. + */ + void exec(Executor executor, AbstractFileWriteAction action, FileOutErr outErr, + ActionExecutionContext actionExecutionContext) throws ExecException, InterruptedException; + + /** + * Returns the estimated resource consumption of the action. + */ + ResourceSet estimateResourceConsumption(AbstractFileWriteAction action); + + /** + * Returns where the action actually runs. + */ + String strategyLocality(AbstractFileWriteAction action); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileHelper.java new file mode 100644 index 0000000..e7869e4 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileHelper.java
@@ -0,0 +1,158 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.ParameterFile; +import com.google.devtools.build.lib.analysis.AnalysisEnvironment; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.util.List; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * A command-line implementation that wraps another command line and puts the arguments in a + * parameter file if necessary + * + * <p>The Linux kernel has a limit for the command line length, and that can be easily reached + * if, for example, a command is listing all its inputs on the command line. + */ +@Immutable +public final class ParamFileHelper { + + /** + * Returns a params file artifact or null for a given command description. + * + * <p>Returns null if parameter files are not to be used according to paramFileInfo, or if the + * command line is short enough that a parameter file is not needed. + * + * <p>Make sure to add the returned artifact (if not null) as an input of the corresponding + * action. + * + * @param executableArgs leading arguments that should never be wrapped in a parameter file + * @param arguments arguments to the command (in addition to executableArgs), OR + * @param commandLine a {@link CommandLine} that provides the arguments (in addition to + * executableArgs) + * @param paramFileInfo parameter file information + * @param configuration the configuration + * @param analysisEnvironment the analysis environment + * @param outputs outputs of the action (used to construct a filename for the params file) + */ + public static Artifact getParamsFile( + List<String> executableArgs, + @Nullable Iterable<String> arguments, + @Nullable CommandLine commandLine, + @Nullable ParamFileInfo paramFileInfo, + BuildConfiguration configuration, + AnalysisEnvironment analysisEnvironment, + Iterable<Artifact> outputs) { + if (paramFileInfo == null || + getParamFileSize(executableArgs, arguments, commandLine) + < configuration.getMinParamFileSize()) { + return null; + } + + PathFragment paramFilePath = ParameterFile.derivePath( + Iterables.getFirst(outputs, null).getRootRelativePath()); + return analysisEnvironment.getDerivedArtifact(paramFilePath, configuration.getBinDirectory()); + } + + /** + * Creates a command line using an external params file. + * + * <p>Call this with the result of {@link #getParamsFile} if it is not null. + * + * @param executableArgs leading arguments that should never be wrapped in a parameter file + * @param arguments arguments to the command (in addition to executableArgs), OR + * @param commandLine a {@link CommandLine} that provides the arguments (in addition to + * executableArgs) + * @param isShellCommand true if this is a shell command + * @param owner owner of the action + * @param paramFileInfo parameter file information + */ + public static CommandLine createWithParamsFile( + List<String> executableArgs, + @Nullable Iterable<String> arguments, + @Nullable CommandLine commandLine, + boolean isShellCommand, + ActionOwner owner, + List<Action> requiredActions, + ParamFileInfo paramFileInfo, + Artifact parameterFile) { + Preconditions.checkNotNull(parameterFile); + if (commandLine != null && arguments != null && !Iterables.isEmpty(arguments)) { + throw new IllegalStateException("must provide either commandLine or arguments: " + arguments); + } + + CommandLine paramFileContents = + (commandLine != null) ? commandLine : CommandLine.ofInternal(arguments, false); + requiredActions.add(new ParameterFileWriteAction(owner, parameterFile, paramFileContents, + paramFileInfo.getFileType(), paramFileInfo.getCharset())); + + String pathWithFlag = paramFileInfo.getFlag() + parameterFile.getExecPathString(); + Iterable<String> commandArgv = Iterables.concat(executableArgs, ImmutableList.of(pathWithFlag)); + return CommandLine.ofInternal(commandArgv, isShellCommand); + } + + /** + * Creates a command line without using a params file. + * + * <p>Call this if {@link #getParamsFile} returns null. + * + * @param executableArgs leading arguments that should never be wrapped in a parameter file + * @param arguments arguments to the command (in addition to executableArgs), OR + * @param commandLine a {@link CommandLine} that provides the arguments (in addition to + * executableArgs) + * @param isShellCommand true if this is a shell command + */ + public static CommandLine createWithoutParamsFile(List<String> executableArgs, + Iterable<String> arguments, CommandLine commandLine, boolean isShellCommand) { + if (commandLine == null) { + Iterable<String> commandArgv = Iterables.concat(executableArgs, arguments); + return CommandLine.ofInternal(commandArgv, isShellCommand); + } + + if (executableArgs.isEmpty()) { + return commandLine; + } + + return CommandLine.ofMixed(ImmutableList.copyOf(executableArgs), commandLine, isShellCommand); + } + + /** + * Estimates the params file size for the given arguments. + */ + private static int getParamFileSize( + List<String> executableArgs, Iterable<String> arguments, CommandLine commandLine) { + Iterable<String> actualArguments = (commandLine != null) ? commandLine.arguments() : arguments; + return getParamFileSize(executableArgs) + getParamFileSize(actualArguments); + } + + private static int getParamFileSize(Iterable<String> args) { + int size = 0; + for (String s : args) { + size += s.length() + 1; + } + return size; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileInfo.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileInfo.java new file mode 100644 index 0000000..ae00181 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileInfo.java
@@ -0,0 +1,79 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import com.google.common.base.Preconditions; +import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType; + +import java.nio.charset.Charset; +import java.util.Objects; + +import javax.annotation.concurrent.Immutable; + +/** + * An object that encapsulates how a params file should be constructed: what is the filetype, + * what charset to use and what prefix (typically "@") to use. + */ +@Immutable +public final class ParamFileInfo { + private final ParameterFileType fileType; + private final Charset charset; + private final String flag; + + public ParamFileInfo(ParameterFileType fileType, Charset charset, String flag) { + this.fileType = Preconditions.checkNotNull(fileType); + this.charset = Preconditions.checkNotNull(charset); + this.flag = Preconditions.checkNotNull(flag); + } + + /** + * Returns the file type. + */ + public ParameterFileType getFileType() { + return fileType; + } + + /** + * Returns the charset. + */ + public Charset getCharset() { + return charset; + } + + /** + * Returns the prefix for the params filename on the command line (typically "@"). + */ + public String getFlag() { + return flag; + } + + @Override + public int hashCode() { + return Objects.hash(charset, flag, fileType); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ParamFileInfo)) { + return false; + } + ParamFileInfo other = (ParamFileInfo) obj; + return fileType.equals(other.fileType) && charset.equals(other.charset) + && flag.equals(other.flag); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ParameterFileWriteAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParameterFileWriteAction.java new file mode 100644 index 0000000..fd10e2b --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParameterFileWriteAction.java
@@ -0,0 +1,122 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.util.Fingerprint; +import com.google.devtools.build.lib.util.ShellEscaper; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; + +/** + * Action to write a parameter file for a {@link CommandLine}. + */ +public final class ParameterFileWriteAction extends AbstractFileWriteAction { + + private static final String GUID = "45f678d8-e395-401e-8446-e795ccc6361f"; + + private final CommandLine commandLine; + private final ParameterFileType type; + private final Charset charset; + + /** + * Creates a new instance. + * + * @param owner the action owner + * @param output the Artifact that will be created by executing this Action + * @param commandLine the contents to be written to the file + * @param type the type of the file + * @param charset the charset of the file + */ + public ParameterFileWriteAction(ActionOwner owner, Artifact output, CommandLine commandLine, + ParameterFileType type, Charset charset) { + super(owner, ImmutableList.<Artifact>of(), output, false); + this.commandLine = commandLine; + this.type = type; + this.charset = charset; + } + + /** + * Returns the list of options written to the parameter file. Don't use this + * method outside tests - the list is often huge, resulting in significant + * garbage collection overhead. + */ + @VisibleForTesting + public Iterable<String> getContents() { + return commandLine.arguments(); + } + + @Override + public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, Executor executor) { + return new DeterministicWriter() { + @Override + public void writeOutputFile(OutputStream out) throws IOException { + switch (type) { + case SHELL_QUOTED : + writeContentQuoted(out); + break; + case UNQUOTED : + writeContentUnquoted(out); + break; + default : + throw new AssertionError(); + } + } + }; + } + + /** + * Writes the arguments from the list into the parameter file. + */ + private void writeContentUnquoted(OutputStream outputStream) throws IOException { + OutputStreamWriter out = new OutputStreamWriter(outputStream, charset); + for (String line : commandLine.arguments()) { + out.write(line); + out.write('\n'); + } + out.flush(); + } + + /** + * Writes the arguments from the list into the parameter file with shell + * quoting (if required). + */ + private void writeContentQuoted(OutputStream outputStream) throws IOException { + OutputStreamWriter out = new OutputStreamWriter(outputStream, charset); + for (String line : ShellEscaper.escapeAll(commandLine.arguments())) { + out.write(line); + out.write('\n'); + } + out.flush(); + } + + @Override + protected String computeKey() { + Fingerprint f = new Fingerprint(); + f.addString(GUID); + f.addString(String.valueOf(makeExecutable)); + f.addStrings(commandLine.arguments()); + return f.hexDigestAndReset(); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java new file mode 100644 index 0000000..f51c917 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
@@ -0,0 +1,874 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import static java.nio.charset.StandardCharsets.ISO_8859_1; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.CharMatcher; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.devtools.build.lib.actions.AbstractAction; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.ActionExecutionContext; +import com.google.devtools.build.lib.actions.ActionExecutionException; +import com.google.devtools.build.lib.actions.ActionInput; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.BaseSpawn; +import com.google.devtools.build.lib.actions.ExecException; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType; +import com.google.devtools.build.lib.actions.ResourceSet; +import com.google.devtools.build.lib.actions.Spawn; +import com.google.devtools.build.lib.actions.SpawnActionContext; +import com.google.devtools.build.lib.actions.extra.ExtraActionInfo; +import com.google.devtools.build.lib.actions.extra.SpawnInfo; +import com.google.devtools.build.lib.analysis.AnalysisEnvironment; +import com.google.devtools.build.lib.analysis.FilesToRunProvider; +import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.collect.CollectionUtils; +import com.google.devtools.build.lib.collect.IterablesChain; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.util.Fingerprint; +import com.google.devtools.build.lib.util.ShellEscaper; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.protobuf.GeneratedMessage.GeneratedExtension; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.CheckReturnValue; + +/** + * An Action representing an arbitrary subprocess to be forked and exec'd. + */ +public class SpawnAction extends AbstractAction { + private static class ExtraActionInfoSupplier<T> { + private final GeneratedExtension<ExtraActionInfo, T> extension; + private final T value; + + private ExtraActionInfoSupplier( + GeneratedExtension<ExtraActionInfo, T> extension, + T value) { + this.extension = extension; + this.value = value; + } + + void extend(ExtraActionInfo.Builder builder) { + builder.setExtension(extension, value); + } + } + + private static final String GUID = "ebd6fce3-093e-45ee-adb6-bf513b602f0d"; + + private final CommandLine argv; + + private final String progressMessage; + private final String mnemonic; + // entries are (directory for remote execution, Artifact) + private final Map<PathFragment, Artifact> inputManifests; + + private final ResourceSet resourceSet; + private final ImmutableMap<String, String> environment; + private final ImmutableMap<String, String> executionInfo; + + private final ExtraActionInfoSupplier<?> extraActionInfoSupplier; + + /** + * Constructs a SpawnAction using direct initialization arguments. + * <p> + * All collections provided must not be subsequently modified. + * + * @param owner the owner of the Action. + * @param inputs the set of all files potentially read by this action; must + * not be subsequently modified. + * @param outputs the set of all files written by this action; must not be + * subsequently modified. + * @param resourceSet the resources consumed by executing this Action + * @param environment the map of environment variables. + * @param argv the command line to execute. This is merely a list of options + * to the executable, and is uninterpreted by the build tool for the + * purposes of dependency checking; typically it may include the names + * of input and output files, but this is not necessary. + * @param progressMessage the message printed during the progression of the build + * @param mnemonic the mnemonic that is reported in the master log. + */ + public SpawnAction(ActionOwner owner, + Iterable<Artifact> inputs, Iterable<Artifact> outputs, + ResourceSet resourceSet, + CommandLine argv, + Map<String, String> environment, + String progressMessage, + String mnemonic) { + this(owner, inputs, outputs, + resourceSet, argv, ImmutableMap.copyOf(environment), + ImmutableMap.<String, String>of(), progressMessage, + ImmutableMap.<PathFragment, Artifact>of(), mnemonic, null); + } + + /** + * Constructs a SpawnAction using direct initialization arguments. + * + * <p>All collections provided must not be subsequently modified. + * + * @param owner the owner of the Action. + * @param inputs the set of all files potentially read by this action; must + * not be subsequently modified. + * @param outputs the set of all files written by this action; must not be + * subsequently modified. + * @param resourceSet the resources consumed by executing this Action + * @param environment the map of environment variables. + * @param executionInfo out-of-band information for scheduling the spawn. + * @param argv the argv array (including argv[0]) of arguments to pass. This + * is merely a list of options to the executable, and is uninterpreted + * by the build tool for the purposes of dependency checking; typically + * it may include the names of input and output files, but this is not + * necessary. + * @param progressMessage the message printed during the progression of the build + * @param inputManifests entries in inputs that are symlink manifest files. + * These are passed to remote execution in the environment rather than as inputs. + * @param mnemonic the mnemonic that is reported in the master log. + */ + public SpawnAction(ActionOwner owner, + Iterable<Artifact> inputs, Iterable<Artifact> outputs, + ResourceSet resourceSet, + CommandLine argv, + ImmutableMap<String, String> environment, + ImmutableMap<String, String> executionInfo, + String progressMessage, + ImmutableMap<PathFragment, Artifact> inputManifests, + String mnemonic, + ExtraActionInfoSupplier<?> extraActionInfoSupplier) { + super(owner, inputs, outputs); + this.resourceSet = resourceSet; + this.executionInfo = executionInfo; + this.environment = environment; + this.argv = argv; + this.progressMessage = progressMessage; + this.inputManifests = inputManifests; + this.mnemonic = mnemonic; + this.extraActionInfoSupplier = extraActionInfoSupplier; + } + + /** + * Returns the (immutable) list of all arguments, including the command name, argv[0]. + */ + @VisibleForTesting + public List<String> getArguments() { + return ImmutableList.copyOf(argv.arguments()); + } + + /** + * Returns command argument, argv[0]. + */ + @VisibleForTesting + public String getCommandFilename() { + return Iterables.getFirst(argv.arguments(), null); + } + + /** + * Returns the (immutable) list of arguments, excluding the command name, + * argv[0]. + */ + @VisibleForTesting + public List<String> getRemainingArguments() { + return ImmutableList.copyOf(Iterables.skip(argv.arguments(), 1)); + } + + @VisibleForTesting + public boolean isShellCommand() { + return argv.isShellCommand(); + } + + /** + * Executes the action without handling ExecException errors. + * + * <p>Called by {@link #execute}. + */ + protected void internalExecute( + ActionExecutionContext actionExecutionContext) throws ExecException, InterruptedException { + getContext(actionExecutionContext.getExecutor()).exec(getSpawn(), actionExecutionContext); + } + + @Override + public void execute(ActionExecutionContext actionExecutionContext) + throws ActionExecutionException, InterruptedException { + Executor executor = actionExecutionContext.getExecutor(); + try { + internalExecute(actionExecutionContext); + } catch (ExecException e) { + String failMessage = progressMessage; + if (isShellCommand()) { + // The possible reasons it could fail are: shell executable not found, shell + // exited non-zero, or shell died from signal. The first is impossible + // and the second two aren't very interesting, so in the interests of + // keeping the noise-level down, we don't print a reason why, just the + // command that failed. + // + // 0=shell executable, 1=shell command switch, 2=command + failMessage = "error executing shell command: " + "'" + + truncate(Iterables.get(argv.arguments(), 2), 200) + "'"; + } + throw e.toActionExecutionException(failMessage, executor.getVerboseFailures(), this); + } + } + + /** + * Returns s, truncated to no more than maxLen characters, appending an + * ellipsis if truncation occurred. + */ + private static String truncate(String s, int maxLen) { + return s.length() > maxLen + ? s.substring(0, maxLen - "...".length()) + "..." + : s; + } + + /** + * Returns a Spawn that is representative of the command that this Action + * will execute. This function must not modify any state. + */ + public Spawn getSpawn() { + return new ActionSpawn(); + } + + @Override + protected String computeKey() { + Fingerprint f = new Fingerprint(); + f.addString(GUID); + f.addStrings(argv.arguments()); + f.addString(getMnemonic()); + f.addInt(inputManifests.size()); + for (Map.Entry<PathFragment, Artifact> input : inputManifests.entrySet()) { + f.addString(input.getKey().getPathString() + "/"); + f.addPath(input.getValue().getExecPath()); + } + f.addStringMap(getEnvironment()); + f.addStringMap(getExecutionInfo()); + return f.hexDigestAndReset(); + } + + @Override + public String describeKey() { + StringBuilder message = new StringBuilder(); + message.append(getProgressMessage()); + message.append('\n'); + for (Map.Entry<String, String> entry : getEnvironment().entrySet()) { + message.append(" Environment variable: "); + message.append(ShellEscaper.escapeString(entry.getKey())); + message.append('='); + message.append(ShellEscaper.escapeString(entry.getValue())); + message.append('\n'); + } + for (String argument : ShellEscaper.escapeAll(argv.arguments())) { + message.append(" Argument: "); + message.append(argument); + message.append('\n'); + } + return message.toString(); + } + + @Override + public final String getMnemonic() { + return mnemonic; + } + + @Override + protected String getRawProgressMessage() { + return progressMessage; + } + + @Override + public ExtraActionInfo.Builder getExtraActionInfo() { + ExtraActionInfo.Builder builder = super.getExtraActionInfo(); + if (extraActionInfoSupplier == null) { + Spawn spawn = getSpawn(); + SpawnInfo spawnInfo = spawn.getExtraActionInfo(); + + return builder + .setExtension(SpawnInfo.spawnInfo, spawnInfo); + } else { + extraActionInfoSupplier.extend(builder); + return builder; + } + } + + /** + * Returns the environment in which to run this action. + */ + public Map<String, String> getEnvironment() { + return environment; + } + + /** + * Returns the out-of-band execution data for this action. + */ + public Map<String, String> getExecutionInfo() { + return executionInfo; + } + + @Override + public String describeStrategy(Executor executor) { + return getContext(executor).strategyLocality(getMnemonic(), isRemotable()); + } + + protected SpawnActionContext getContext(Executor executor) { + return executor.getSpawnActionContext(getMnemonic()); + } + + @Override + public ResourceSet estimateResourceConsumption(Executor executor) { + SpawnActionContext context = getContext(executor); + if (context.isRemotable(getMnemonic(), isRemotable())) { + return ResourceSet.ZERO; + } + return resourceSet; + } + + /** + * Returns true if this can be run remotely. + */ + public final boolean isRemotable() { + // TODO(bazel-team): get rid of this method. + return !executionInfo.containsKey("local"); + } + + /** + * The Spawn which this SpawnAction will execute. + */ + private class ActionSpawn extends BaseSpawn { + + private final List<Artifact> filesets = new ArrayList<>(); + + public ActionSpawn() { + super(ImmutableList.copyOf(argv.arguments()), + ImmutableMap.<String, String>of(), + executionInfo, + inputManifests, + SpawnAction.this, + resourceSet); + for (Artifact input : getInputs()) { + if (input.isFileset()) { + filesets.add(input); + } + } + } + + @Override + public ImmutableMap<String, String> getEnvironment() { + return ImmutableMap.copyOf(SpawnAction.this.getEnvironment()); + } + + @Override + public ImmutableList<Artifact> getFilesetManifests() { + return ImmutableList.copyOf(filesets); + } + + @Override + public Iterable<? extends ActionInput> getInputFiles() { + // Remove Fileset directories in inputs list. Instead, these are + // included as manifests in getEnvironment(). + List<Artifact> inputs = Lists.newArrayList(getInputs()); + inputs.removeAll(filesets); + inputs.removeAll(inputManifests.values()); + return inputs; + // Also expand middleman artifacts. + } + } + + /** + * Builder class to construct {@link SpawnAction} instances. + */ + public static class Builder { + + private final NestedSetBuilder<Artifact> inputsBuilder = + NestedSetBuilder.stableOrder(); + private final List<Artifact> outputs = new ArrayList<>(); + private final Map<PathFragment, Artifact> inputManifests = new LinkedHashMap<>(); + private ResourceSet resourceSet = AbstractAction.DEFAULT_RESOURCE_SET; + private ImmutableMap<String, String> environment = ImmutableMap.of(); + private ImmutableMap<String, String> executionInfo = ImmutableMap.of(); + private boolean isShellCommand = false; + private boolean useDefaultShellEnvironment = false; + private PathFragment executable; + // executableArgs does not include the executable itself. + private List<String> executableArgs; + private final IterablesChain.Builder<String> argumentsBuilder = IterablesChain.builder(); + private CommandLine commandLine; + + private String progressMessage; + private ParamFileInfo paramFileInfo = null; + private String mnemonic = "Unknown"; + private ExtraActionInfoSupplier<?> extraActionInfoSupplier = null; + + /** + * Creates a SpawnAction builder. + */ + public Builder() {} + + /** + * Creates a builder that is a copy of another builder. + */ + public Builder(Builder other) { + this.inputsBuilder.addTransitive(other.inputsBuilder.build()); + this.outputs.addAll(other.outputs); + this.inputManifests.putAll(other.inputManifests); + this.resourceSet = other.resourceSet; + this.environment = other.environment; + this.executionInfo = other.executionInfo; + this.isShellCommand = other.isShellCommand; + this.useDefaultShellEnvironment = other.useDefaultShellEnvironment; + this.executable = other.executable; + this.executableArgs = (other.executableArgs != null) + ? Lists.newArrayList(other.executableArgs) + : null; + this.argumentsBuilder.add(other.argumentsBuilder.build()); + this.commandLine = other.commandLine; + this.progressMessage = other.progressMessage; + this.paramFileInfo = other.paramFileInfo; + this.mnemonic = other.mnemonic; + } + + /** + * Builds the SpawnAction using the passed in action configuration and returns it and all + * dependent actions. The first item of the returned array is always the SpawnAction itself. + * + * <p>This method makes a copy of all the collections, so it is safe to reuse the builder after + * this method returns. + * + * <p>This is annotated with @CheckReturnValue, which causes a compiler error when you call this + * method and ignore its return value. This is because some time ago, calling .build() had the + * side-effect of registering it with the RuleContext that was passed in to the constructor. + * This logic was removed, but if people don't notice and still rely on the side-effect, things + * may break. + * + * @return the SpawnAction and any actions required by it, with the first item always being the + * SpawnAction itself. + */ + @CheckReturnValue + public Action[] build(ActionConstructionContext context) { + return build(context.getActionOwner(), context.getAnalysisEnvironment(), + context.getConfiguration()); + } + + @VisibleForTesting @CheckReturnValue + public Action[] build(ActionOwner owner, AnalysisEnvironment analysisEnvironment, + BuildConfiguration configuration) { + if (isShellCommand && executable == null) { + executable = configuration.getShExecutable(); + } + Preconditions.checkNotNull(executable); + Preconditions.checkNotNull(executableArgs); + + if (useDefaultShellEnvironment) { + this.environment = configuration.getDefaultShellEnvironment(); + } + + ImmutableList<String> argv = ImmutableList.<String>builder() + .add(executable.getPathString()) + .addAll(executableArgs) + .build(); + + Iterable<String> arguments = argumentsBuilder.build(); + + Artifact paramsFile = ParamFileHelper.getParamsFile(argv, arguments, commandLine, + paramFileInfo, configuration, analysisEnvironment, outputs); + + List<Action> actions = new ArrayList<>(); + CommandLine actualCommandLine; + if (paramsFile != null) { + actualCommandLine = ParamFileHelper.createWithParamsFile(argv, arguments, commandLine, + isShellCommand, owner, actions, paramFileInfo, paramsFile); + } else { + actualCommandLine = ParamFileHelper.createWithoutParamsFile(argv, arguments, commandLine, + isShellCommand); + } + + Iterable<Artifact> actualInputs = collectActualInputs(paramsFile); + + actions.add(0, new SpawnAction(owner, actualInputs, ImmutableList.copyOf(outputs), + resourceSet, actualCommandLine, environment, executionInfo, progressMessage, + ImmutableMap.copyOf(inputManifests), mnemonic, extraActionInfoSupplier)); + return actions.toArray(new Action[actions.size()]); + } + + private Iterable<Artifact> collectActualInputs(Artifact parameterFile) { + if (parameterFile != null) { + inputsBuilder.add(parameterFile); + } + return inputsBuilder.build(); + } + + /** + * Adds an input to this action. + */ + public Builder addInput(Artifact artifact) { + inputsBuilder.add(artifact); + return this; + } + + /** + * Adds inputs to this action. + */ + public Builder addInputs(Iterable<Artifact> artifacts) { + inputsBuilder.addAll(artifacts); + return this; + } + + /** + * Adds transitive inputs to this action. + */ + public Builder addTransitiveInputs(NestedSet<Artifact> artifacts) { + inputsBuilder.addTransitive(artifacts); + return this; + } + + public Builder addInputManifest(Artifact artifact, PathFragment remote) { + inputManifests.put(remote, artifact); + return this; + } + + public Builder addOutput(Artifact artifact) { + outputs.add(artifact); + return this; + } + + public Builder addOutputs(Iterable<Artifact> artifacts) { + Iterables.addAll(outputs, artifacts); + return this; + } + + public Builder setResources(ResourceSet resourceSet) { + this.resourceSet = resourceSet; + return this; + } + + /** + * Sets the map of environment variables. + */ + public Builder setEnvironment(Map<String, String> environment) { + this.environment = ImmutableMap.copyOf(environment); + this.useDefaultShellEnvironment = false; + return this; + } + + /** + * Sets the map of execution info. + */ + public Builder setExecutionInfo(Map<String, String> info) { + this.executionInfo = ImmutableMap.copyOf(info); + return this; + } + + /** + * Sets the environment to the configurations default shell environment, + * see {@link BuildConfiguration#getDefaultShellEnvironment}. + */ + public Builder useDefaultShellEnvironment() { + this.environment = null; + this.useDefaultShellEnvironment = true; + return this; + } + + /** + * Sets the executable path; the path is interpreted relative to the + * execution root. + * + * <p>Calling this method overrides any previous values set via calls to + * {@link #setExecutable(Artifact)}, {@link #setJavaExecutable}, or + * {@link #setShellCommand(String)}. + */ + public Builder setExecutable(PathFragment executable) { + this.executable = executable; + this.executableArgs = Lists.newArrayList(); + this.isShellCommand = false; + return this; + } + + /** + * Sets the executable as an artifact. + * + * <p>Calling this method overrides any previous values set via calls to + * {@link #setExecutable(Artifact)}, {@link #setJavaExecutable}, or + * {@link #setShellCommand(String)}. + */ + public Builder setExecutable(Artifact executable) { + return setExecutable(executable.getExecPath()); + } + + /** + * Sets the executable as a configured target. Automatically adds the files + * to run to the inputs and uses the executable of the target as the + * executable. + * + * <p>Calling this method overrides any previous values set via calls to + * {@link #setExecutable(Artifact)}, {@link #setJavaExecutable}, or + * {@link #setShellCommand(String)}. + */ + public Builder setExecutable(TransitiveInfoCollection executable) { + FilesToRunProvider provider = executable.getProvider(FilesToRunProvider.class); + Preconditions.checkArgument(provider != null); + return setExecutable(provider); + } + + /** + * Sets the executable as a configured target. Automatically adds the files + * to run to the inputs and uses the executable of the target as the + * executable. + * + * <p>Calling this method overrides any previous values set via calls to + * {@link #setExecutable}, {@link #setJavaExecutable}, or + * {@link #setShellCommand(String)}. + */ + public Builder setExecutable(FilesToRunProvider executableProvider) { + Preconditions.checkArgument(executableProvider.getExecutable() != null, + "The target does not have an executable"); + setExecutable(executableProvider.getExecutable().getExecPath()); + return addTool(executableProvider); + } + + private Builder setJavaExecutable(PathFragment javaExecutable, Artifact deployJar, + List<String> jvmArgs, String... launchArgs) { + this.executable = javaExecutable; + this.executableArgs = Lists.newArrayList(); + executableArgs.add("-Xverify:none"); + executableArgs.addAll(jvmArgs); + for (String arg : launchArgs) { + executableArgs.add(arg); + } + inputsBuilder.add(deployJar); + this.isShellCommand = false; + return this; + } + + /** + * Sets the executable to be a java class executed from the given deploy + * jar. The deploy jar is automatically added to the action inputs. + * + * <p>Calling this method overrides any previous values set via calls to + * {@link #setExecutable}, {@link #setJavaExecutable}, or + * {@link #setShellCommand(String)}. + */ + public Builder setJavaExecutable(PathFragment javaExecutable, + Artifact deployJar, String javaMainClass, List<String> jvmArgs) { + return setJavaExecutable(javaExecutable, deployJar, jvmArgs, "-cp", + deployJar.getExecPathString(), javaMainClass); + } + + /** + * Sets the executable to be a jar executed from the given deploy jar. The deploy jar is + * automatically added to the action inputs. + * + * <p>This method is similar to {@link #setJavaExecutable} but it assumes that the Jar artifact + * declares a main class. + * + * <p>Calling this method overrides any previous values set via calls to {@link #setExecutable}, + * {@link #setJavaExecutable}, or {@link #setShellCommand(String)}. + */ + public Builder setJarExecutable(PathFragment javaExecutable, + Artifact deployJar, List<String> jvmArgs) { + return setJavaExecutable(javaExecutable, deployJar, jvmArgs, "-jar", + deployJar.getExecPathString()); + } + + /** + * Sets the executable to be the shell and adds the given command as the + * command to be executed. + * + * <p>Note that this will not clear the arguments, so any arguments will + * be passed in addition to the command given here. + * + * <p>Calling this method overrides any previous values set via calls to + * {@link #setExecutable(Artifact)}, {@link #setJavaExecutable}, or + * {@link #setShellCommand(String)}. + */ + public Builder setShellCommand(String command) { + this.executable = null; + // 0=shell command switch, 1=command + this.executableArgs = Lists.newArrayList("-c", command); + this.isShellCommand = true; + return this; + } + + /** + * Sets the executable to be the shell and adds the given interned commands as the + * commands to be executed. + */ + public Builder setShellCommand(Iterable<String> command) { + this.executable = new PathFragment(Iterables.getFirst(command, null)); + // The first item of the commands is the shell executable that should be used. + this.executableArgs = ImmutableList.copyOf(Iterables.skip(command, 1)); + this.isShellCommand = true; + return this; + } + + /** + * Adds an executable and its runfiles, so it can be called from a shell command. + */ + public Builder addTool(FilesToRunProvider tool) { + addInputs(tool.getFilesToRun()); + if (tool.getRunfilesManifest() != null) { + addInputManifest(tool.getRunfilesManifest(), + BaseSpawn.runfilesForFragment(tool.getExecutable().getExecPath())); + } + return this; + } + + /** + * Appends the arguments to the list of executable arguments. + */ + public Builder addExecutableArguments(String... arguments) { + Preconditions.checkState(executableArgs != null); + executableArgs.addAll(Arrays.asList(arguments)); + return this; + } + + /** + * Add multiple arguments in the order they are returned by the collection + * to the list of executable arguments. + */ + public Builder addExecutableArguments(Iterable<String> arguments) { + Preconditions.checkState(executableArgs != null); + Iterables.addAll(executableArgs, arguments); + return this; + } + + /** + * Appends the argument to the list of command-line arguments. + */ + public Builder addArgument(String argument) { + Preconditions.checkState(commandLine == null); + argumentsBuilder.addElement(argument); + return this; + } + + /** + * Appends the arguments to the list of command-line arguments. + */ + public Builder addArguments(String... arguments) { + Preconditions.checkState(commandLine == null); + argumentsBuilder.add(ImmutableList.copyOf(arguments)); + return this; + } + + /** + * Add multiple arguments in the order they are returned by the collection. + */ + public Builder addArguments(Iterable<String> arguments) { + Preconditions.checkState(commandLine == null); + argumentsBuilder.add(CollectionUtils.makeImmutable(arguments)); + return this; + } + + /** + * Appends the argument both to the inputs and to the list of command-line + * arguments. + */ + public Builder addInputArgument(Artifact argument) { + Preconditions.checkState(commandLine == null); + addInput(argument); + addArgument(argument.getExecPathString()); + return this; + } + + /** + * Appends the arguments both to the inputs and to the list of command-line + * arguments. + */ + public Builder addInputArguments(Iterable<Artifact> arguments) { + for (Artifact argument : arguments) { + addInputArgument(argument); + } + return this; + } + + /** + * Appends the argument both to the ouputs and to the list of command-line + * arguments. + */ + public Builder addOutputArgument(Artifact argument) { + Preconditions.checkState(commandLine == null); + outputs.add(argument); + argumentsBuilder.addElement(argument.getExecPathString()); + return this; + } + + /** + * Sets a delegate to compute the command line at a later time. This method + * cannot be used in conjunction with the {@link #addArgument} or {@link + * #addArguments} methods. + * + * <p>The main intention of this method is to save memory by allowing + * client-controlled sharing between actions and configured targets. + * Objects passed to this method MUST be immutable. + */ + public Builder setCommandLine(CommandLine commandLine) { + Preconditions.checkState(argumentsBuilder.isEmpty()); + this.commandLine = commandLine; + return this; + } + + public Builder setProgressMessage(String progressMessage) { + this.progressMessage = progressMessage; + return this; + } + + public Builder setMnemonic(String mnemonic) { + Preconditions.checkArgument( + !mnemonic.isEmpty() && CharMatcher.JAVA_LETTER_OR_DIGIT.matchesAllOf(mnemonic), + "mnemonic must only contain letters and/or digits, and have non-zero length, was: \"%s\"", + mnemonic); + this.mnemonic = mnemonic; + return this; + } + + public <T> Builder setExtraActionInfo( + GeneratedExtension<ExtraActionInfo, T> extension, T value) { + this.extraActionInfoSupplier = new ExtraActionInfoSupplier<T>(extension, value); + return this; + } + + /** + * Enable use of a parameter file and set the encoding to ISO-8859-1 (latin1). + * + * <p>In order to use parameter files, at least one output artifact must be specified. + */ + public Builder useParameterFile(ParameterFileType parameterFileType) { + return useParameterFile(parameterFileType, ISO_8859_1, "@"); + } + + /** + * Enable or disable the use of a parameter file, set the encoding to the given value, and + * specify the argument prefix to use in passing the parameter file name to the tool. + * + * <p>The default argument prefix is "@". In order to use parameter files, at least one output + * artifact must be specified. + */ + public Builder useParameterFile( + ParameterFileType parameterFileType, Charset charset, String flagPrefix) { + paramFileInfo = new ParamFileInfo(parameterFileType, charset, flagPrefix); + return this; + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java new file mode 100644 index 0000000..2bbb3e2 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java
@@ -0,0 +1,130 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.AbstractAction; +import com.google.devtools.build.lib.actions.ActionExecutionContext; +import com.google.devtools.build.lib.actions.ActionExecutionException; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.actions.ResourceSet; +import com.google.devtools.build.lib.util.Fingerprint; +import com.google.devtools.build.lib.vfs.Path; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.io.IOException; + +/** + * Action to create a symbolic link. + */ +public class SymlinkAction extends AbstractAction { + + private static final String GUID = "349675b5-437c-4da8-891a-7fb98fba6ab5"; + + private final PathFragment inputPath; + private final Artifact output; + private final String progressMessage; + + /** + * Creates a new SymlinkAction instance. + * + * @param owner the action owner. + * @param input the Artifact that will be the src of the symbolic link. + * @param output the Artifact that will be created by executing this Action. + * @param progressMessage the progress message. + */ + public SymlinkAction(ActionOwner owner, Artifact input, Artifact output, + String progressMessage) { + // These actions typically have only one input and one output, which + // become the sole and primary in their respective lists. + this(owner, input.getExecPath(), input, output, progressMessage); + } + + /** + * Creates a new SymlinkAction instance, where the inputPath + * may be different than that input artifact's path. This is + * only useful when dealing with runfiles trees where + * link target is a directory. + * + * @param owner the action owner. + * @param inputPath the Path that will be the src of the symbolic link. + * @param input the Artifact that is required to build the inputPath. + * @param output the Artifact that will be created by executing this Action. + * @param progressMessage the progress message. + */ + public SymlinkAction(ActionOwner owner, PathFragment inputPath, Artifact input, + Artifact output, String progressMessage) { + super(owner, ImmutableList.of(input), ImmutableList.of(output)); + this.inputPath = Preconditions.checkNotNull(inputPath); + this.output = Preconditions.checkNotNull(output); + this.progressMessage = progressMessage; + } + + public PathFragment getInputPath() { + return inputPath; + } + + public Path getOutputPath() { + return output.getPath(); + } + + @Override + public void execute(ActionExecutionContext actionExecutionContext) + throws ActionExecutionException { + try { + getOutputPath().createSymbolicLink( + actionExecutionContext.getExecutor().getExecRoot().getRelative(inputPath)); + } catch (IOException e) { + throw new ActionExecutionException("failed to create symbolic link '" + + Iterables.getOnlyElement(getOutputs()).prettyPrint() + + "' to the '" + Iterables.getOnlyElement(getInputs()).prettyPrint() + + "' due to I/O error: " + e.getMessage(), e, this, false); + } + } + + @Override + public ResourceSet estimateResourceConsumption(Executor executor) { + return new ResourceSet(/*memoryMb=*/0, /*cpuUsage=*/0, /*ioUsage=*/0.0); + } + + @Override + protected String computeKey() { + Fingerprint f = new Fingerprint(); + f.addString(GUID); + // We don't normally need to add inputs to the key. In this case, however, the inputPath can be + // different from the actual input artifact. + f.addPath(inputPath); + return f.hexDigestAndReset(); + } + + @Override + public String getMnemonic() { + return "Symlink"; + } + + @Override + protected String getRawProgressMessage() { + return progressMessage; + } + + @Override + public String describeStrategy(Executor executor) { + return "local"; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java new file mode 100644 index 0000000..b2c83fb --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java
@@ -0,0 +1,335 @@ +// Copyright 2014 Google Inc. 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.analysis.actions; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.util.Fingerprint; +import com.google.devtools.build.lib.util.ResourceFileLoader; +import com.google.devtools.build.lib.util.StringUtilities; +import com.google.devtools.build.lib.vfs.FileSystemUtils; +import com.google.devtools.build.lib.vfs.Path; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.List; + +/** + * Action to expand a template and write the expanded content to a file. + */ +public class TemplateExpansionAction extends AbstractFileWriteAction { + + private static final String GUID = "786c1fe0-dca8-407a-b108-e1ecd6d1bc7f"; + + /** + * A pair of a string to be substituted and a string to substitute it with. + * For simplicity, these are called key and value. All implementations must + * be immutable, and always return the identical key. The returned values + * must be the same, though they need not be the same object. + * + * <p>It should be assumed that the {@link #getKey} invocation is cheap, and + * that the {@link #getValue} invocation is expensive. + */ + public abstract static class Substitution { + private Substitution() { + } + + public abstract String getKey(); + public abstract String getValue(); + + /** + * Returns an immutable Substitution instance for the given key and value. + */ + public static Substitution of(final String key, final String value) { + return new Substitution() { + @Override + public String getKey() { + return key; + } + + @Override + public String getValue() { + return value; + } + }; + } + + /** + * Returns an immutable Substitution instance for the key and list of values. The + * values will be joined by spaces before substitution. + */ + public static Substitution ofSpaceSeparatedList(final String key, final List<?> value) { + return new Substitution() { + @Override + public String getKey() { + return key; + } + + @Override + public String getValue() { + return Joiner.on(" ").join(value); + } + }; + } + } + + /** + * A substitution with a fixed key, and a computed value. The computed value + * must not change over the lifetime of an instance, though the {@link + * #getValue} method may return different String objects. + * + * <p>It should be assumed that the {@link #getKey} invocation is cheap, and + * that the {@link #getValue} invocation is expensive. + */ + public abstract static class ComputedSubstitution extends Substitution { + private final String key; + + public ComputedSubstitution(String key) { + this.key = key; + } + + @Override + public String getKey() { + return key; + } + } + + /** + * A template that contains text content, or alternatively throws an {@link + * IOException}. + */ + public abstract static class Template { + + /** + * We only allow subclasses in this file. + */ + private Template() { + } + + /** + * Returns the text content of the template. + */ + protected abstract String getContent() throws IOException; + + /** + * Returns a string that is used for the action key. This must change if + * the getContent method returns something different, but is not allowed to + * throw an exception. + */ + protected abstract String getKey(); + + /** + * Loads a template from the given resource. The resource is looked up + * relative to the given class. If the resource cannot be loaded, the returned + * template throws an {@link IOException} when {@link #getContent} is + * called. This makes it safe to use this method in a constant initializer. + */ + public static Template forResource(final Class<?> relativeToClass, final String templateName) { + try { + String content = ResourceFileLoader.loadResource(relativeToClass, templateName); + return forString(content); + } catch (final IOException e) { + return new Template() { + @Override + protected String getContent() throws IOException { + throw new IOException("failed to load resource file '" + templateName + + "' due to I/O error: " + e.getMessage(), e); + } + + @Override + protected String getKey() { + return "ERROR: " + e.getMessage(); + } + }; + } + } + + /** + * Returns a template for the given text string. + */ + public static Template forString(final String templateText) { + return new Template() { + @Override + protected String getContent() { + return templateText; + } + + @Override + protected String getKey() { + return templateText; + } + }; + } + + /** + * Returns a template that loads the given artifact. It is important that + * the artifact is also an input for the action, or this won't work. + * Therefore this method is private, and you should use the corresponding + * {@link TemplateExpansionAction} constructor. + */ + private static Template forArtifact(final Artifact templateArtifact) { + return new Template() { + @Override + protected String getContent() throws IOException { + Path templatePath = templateArtifact.getPath(); + try { + return new String(FileSystemUtils.readContentAsLatin1(templatePath)); + } catch (IOException e) { + throw new IOException("failed to load template file '" + templatePath.getPathString() + + "' due to I/O error: " + e.getMessage(), e); + } + } + + @Override + protected String getKey() { + // This isn't strictly necessary, because the action inputs are automatically considered. + return "ARTIFACT: " + templateArtifact.getExecPathString(); + } + }; + } + } + + private final Template template; + private final List<Substitution> substitutions; + + /** + * Creates a new TemplateExpansionAction instance. + * + * @param owner the action owner. + * @param inputs the Artifacts that this Action depends on + * @param output the Artifact that will be created by executing this Action. + * @param template the template that will be expanded by this Action. + * @param substitutions the substitutions that will be applied to the + * template. All substitutions will be applied in order. + * @param makeExecutable iff true will change the output file to be + * executable. + */ + private TemplateExpansionAction(ActionOwner owner, + Collection<Artifact> inputs, + Artifact output, + Template template, + List<Substitution> substitutions, + boolean makeExecutable) { + super(owner, inputs, output, makeExecutable); + this.template = template; + this.substitutions = ImmutableList.copyOf(substitutions); + } + + /** + * Creates a new TemplateExpansionAction instance for an artifact template. + * + * @param owner the action owner. + * @param templateArtifact the Artifact that will be read as the text template + * file + * @param output the Artifact that will be created by executing this Action. + * @param substitutions the substitutions that will be applied to the + * template. All substitutions will be applied in order. + * @param makeExecutable iff true will change the output file to be + * executable. + */ + public TemplateExpansionAction(ActionOwner owner, + Artifact templateArtifact, + Artifact output, + List<Substitution> substitutions, + boolean makeExecutable) { + this(owner, ImmutableList.of(templateArtifact), output, Template.forArtifact(templateArtifact), + substitutions, makeExecutable); + } + + /** + * Creates a new TemplateExpansionAction instance without inputs. + * + * @param owner the action owner. + * @param output the Artifact that will be created by executing this Action. + * @param template the template + * @param substitutions the substitutions that will be applied to the + * template. All substitutions will be applied in order. + * @param makeExecutable iff true will change the output file to be + * executable. + */ + public TemplateExpansionAction(ActionOwner owner, + Artifact output, + Template template, + List<Substitution> substitutions, + boolean makeExecutable) { + this(owner, Artifact.NO_ARTIFACTS, output, template, substitutions, makeExecutable); + } + + /** + * Expands the template by applying all substitutions. + * @param template + * @return the expanded text. + */ + private String expandTemplate(String template) { + for (Substitution entry : substitutions) { + template = StringUtilities.replaceAllLiteral(template, entry.getKey(), entry.getValue()); + } + return template; + } + + @VisibleForTesting + public String getFileContents() throws IOException { + return expandTemplate(template.getContent()); + } + + @Override + public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, + Executor executor) throws IOException { + final byte[] bytes = getFileContents().getBytes(UTF_8); + return new DeterministicWriter() { + @Override + public void writeOutputFile(OutputStream out) throws IOException { + out.write(bytes); + } + }; + } + + @Override + protected String computeKey() { + Fingerprint f = new Fingerprint(); + f.addString(GUID); + f.addString(String.valueOf(makeExecutable)); + f.addString(template.getKey()); + f.addInt(substitutions.size()); + for (Substitution entry : substitutions) { + f.addString(entry.getKey()); + f.addString(entry.getValue()); + } + return f.hexDigestAndReset(); + } + + @Override + public String getMnemonic() { + return "TemplateExpand"; + } + + @Override + protected String getRawProgressMessage() { + return "Expanding template " + Iterables.getOnlyElement(getOutputs()).prettyPrint(); + } + + public List<Substitution> getSubstitutions() { + return substitutions; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/buildinfo/BuildInfoCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/buildinfo/BuildInfoCollection.java new file mode 100644 index 0000000..54067a0 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/buildinfo/BuildInfoCollection.java
@@ -0,0 +1,48 @@ +// Copyright 2014 Google Inc. 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.analysis.buildinfo; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; + +import java.util.List; + +/** + * A collection of build-info files for both stamped and unstamped modes. + */ +public final class BuildInfoCollection { + private final ImmutableList<Action> actions; + private final ImmutableList<Artifact> stampedBuildInfo; + private final ImmutableList<Artifact> redactedBuildInfo; + + public BuildInfoCollection(List<? extends Action> actions, List<Artifact> stampedBuildInfo, + List<Artifact> redactedBuildInfo) { + this.actions = ImmutableList.copyOf(actions); + this.stampedBuildInfo = ImmutableList.copyOf(stampedBuildInfo); + this.redactedBuildInfo = ImmutableList.copyOf(redactedBuildInfo); + } + + public ImmutableList<Action> getActions() { + return actions; + } + + public ImmutableList<Artifact> getStampedBuildInfo() { + return stampedBuildInfo; + } + + public ImmutableList<Artifact> getRedactedBuildInfo() { + return redactedBuildInfo; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/buildinfo/BuildInfoFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/buildinfo/BuildInfoFactory.java new file mode 100644 index 0000000..c6ec4d7 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/buildinfo/BuildInfoFactory.java
@@ -0,0 +1,99 @@ +// Copyright 2014 Google Inc. 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.analysis.buildinfo; + +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.Root; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.io.Serializable; + +/** + * A factory for language-specific build-info files. Use this to translate the build-info into + * target-independent language-specific files. The generated actions are registered into the action + * graph on every build, but only executed if anything depends on them. + */ +public interface BuildInfoFactory extends Serializable { + /** + * Type of the build-data artifact. + */ + public enum BuildInfoType { + /** + * Ignore changes to this file for the purposes of determining whether an action needs to be + * re-executed. I.e., the action is only re-executed if at least one other input has changed. + */ + NO_REBUILD, + + /** + * Changes to this file trigger re-execution of actions, similar to source file changes. + */ + FORCE_REBUILD_IF_CHANGED; + } + + /** + * Context for the creation of build-info artifacts. + */ + public interface BuildInfoContext { + Artifact getBuildInfoArtifact(PathFragment rootRelativePath, Root root, BuildInfoType type); + Root getBuildDataDirectory(); + } + + /** + * Build-info key for lookup from the {@link + * com.google.devtools.build.lib.analysis.AnalysisEnvironment}. + */ + public static final class BuildInfoKey implements Serializable { + private final String name; + + public BuildInfoKey(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof BuildInfoKey)) { + return false; + } + return name.equals(((BuildInfoKey) o).name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + } + + /** + * Create actions and artifacts for language-specific build-info files. + */ + BuildInfoCollection create(BuildInfoContext context, BuildConfiguration config, + Artifact buildInfo, Artifact buildChangelist); + + /** + * Returns the key for the information created by this factory. + */ + BuildInfoKey getKey(); + + /** + * Returns false if this build info factory is disabled based on the configuration (usually by + * checking if all required configuration fragments are present). + */ + boolean isEnabled(BuildConfiguration config); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BinTools.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BinTools.java new file mode 100644 index 0000000..6d2477d --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BinTools.java
@@ -0,0 +1,191 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.ArtifactFactory; +import com.google.devtools.build.lib.actions.EnvironmentalExecException; +import com.google.devtools.build.lib.actions.ExecException; +import com.google.devtools.build.lib.analysis.BlazeDirectories; +import com.google.devtools.build.lib.vfs.Dirent; +import com.google.devtools.build.lib.vfs.FileSystemUtils; +import com.google.devtools.build.lib.vfs.Path; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.lib.vfs.Symlinks; + +import java.io.IOException; + +/** + * Initializes the <execRoot>/_bin/ directory that contains auxiliary tools used during action + * execution (alarm, etc). The main purpose of this is to make sure that those tools are accessible + * using relative paths from the execution root. + */ +public final class BinTools { + private final BlazeDirectories directories; + private final Path binDir; // the working bin directory under execRoot + private final ImmutableList<String> embeddedTools; + + private BinTools(BlazeDirectories directories, ImmutableList<String> tools) { + this.directories = directories; + this.binDir = directories.getExecRoot().getRelative("_bin"); + this.embeddedTools = tools; + } + + /** + * Creates an instance with the list of embedded tools obtained from scanning the directory + * into which said binaries were extracted by the launcher. + */ + public static BinTools forProduction(BlazeDirectories directories) throws IOException { + ImmutableList.Builder<String> builder = ImmutableList.builder(); + scanDirectoryRecursively(builder, directories.getEmbeddedBinariesRoot(), ""); + return new BinTools(directories, builder.build()); + } + + /** + * Creates an empty instance for testing. + */ + @VisibleForTesting + public static BinTools empty(BlazeDirectories directories) { + return new BinTools(directories, ImmutableList.<String>of()); + } + + /** + * Creates an instance for testing without actually symlinking the tools. + * + * <p>Used for tests that need a set of embedded tools to be present, but not the actual files. + */ + @VisibleForTesting + public static BinTools forUnitTesting(BlazeDirectories directories, Iterable<String> tools) { + return new BinTools(directories, ImmutableList.copyOf(tools)); + } + + /** + * Populates the _bin directory by symlinking the necessary files from the given + * srcDir, and returns the corresponding BinTools. + */ + @VisibleForTesting + public static BinTools forIntegrationTesting( + BlazeDirectories directories, String srcDir, Iterable<String> tools) + throws IOException { + Path srcPath = directories.getOutputBase().getFileSystem().getPath(srcDir); + for (String embedded : tools) { + Path runfilesPath = srcPath.getRelative(embedded); + if (!runfilesPath.isFile()) { + // The file isn't there - nothing to symlink! + // + // Note: This path is usually taken by the tests using the in-memory + // file system. They can't run the embedded scripts anyhow, so there isn't + // much point in creating a symlink to a non-existent binary here. + continue; + } + Path outputPath = directories.getExecRoot().getChild("_bin").getChild(embedded); + if (outputPath.exists()) { + outputPath.delete(); + } + FileSystemUtils.createDirectoryAndParents(outputPath.getParentDirectory()); + outputPath.createSymbolicLink(runfilesPath); + } + + return new BinTools(directories, ImmutableList.copyOf(tools)); + } + + private static void scanDirectoryRecursively( + ImmutableList.Builder<String> result, Path root, String relative) throws IOException { + for (Dirent dirent : root.readdir(Symlinks.NOFOLLOW)) { + String childRelative = relative.isEmpty() + ? dirent.getName() + : relative + "/" + dirent.getName(); + switch (dirent.getType()) { + case FILE: + result.add(childRelative); + break; + + case DIRECTORY: + scanDirectoryRecursively(result, root.getChild(dirent.getName()), childRelative); + break; + + default: + // Nothing to do here -- we ignore symlinks, since they should not be present in the + // embedded binaries tree. + break; + } + } + } + + public PathFragment getExecPath(String embedPath) { + Preconditions.checkState(embeddedTools.contains(embedPath), "%s not in %s", embedPath, + embeddedTools); + return new PathFragment("_bin").getRelative(new PathFragment(embedPath).getBaseName()); + } + + public Artifact getEmbeddedArtifact(String embedPath, ArtifactFactory artifactFactory) { + return artifactFactory.getDerivedArtifact(getExecPath(embedPath)); + } + + public ImmutableList<Artifact> getAllEmbeddedArtifacts(ArtifactFactory artifactFactory) { + ImmutableList.Builder<Artifact> builder = ImmutableList.builder(); + for (String embeddedTool : embeddedTools) { + builder.add(getEmbeddedArtifact(embeddedTool, artifactFactory)); + } + return builder.build(); + } + + /** + * Initializes the build tools not available at absolute paths. Note that + * these must be constant across all configurations. + */ + public void setupBuildTools() throws ExecException { + try { + FileSystemUtils.createDirectoryAndParents(binDir); + } catch (IOException e) { + throw new EnvironmentalExecException("could not create directory '" + binDir + "'", e); + } + + for (String embeddedPath : embeddedTools) { + setupTool(embeddedPath); + } + } + + private void setupTool(String embeddedPath) throws ExecException { + Path sourcePath = directories.getEmbeddedBinariesRoot().getRelative(embeddedPath); + Path linkPath = binDir.getRelative(new PathFragment(embeddedPath).getBaseName()); + linkTool(sourcePath, linkPath); + } + + private void linkTool(Path sourcePath, Path linkPath) throws ExecException { + if (linkPath.getFileSystem().supportsSymbolicLinks()) { + try { + if (!linkPath.isSymbolicLink()) { + // ensureSymbolicLink() does not handle the case where there is already + // a file with the same name, so we need to handle it here. + linkPath.delete(); + } + FileSystemUtils.ensureSymbolicLink(linkPath, sourcePath); + } catch (IOException e) { + throw new EnvironmentalExecException("failed to link '" + sourcePath + "'", e); + } + } else { + // For file systems that do not support linking, copy. + try { + FileSystemUtils.copyTool(sourcePath, linkPath); + } catch (IOException e) { + throw new EnvironmentalExecException("failed to copy '" + sourcePath + "'" , e); + } + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java new file mode 100644 index 0000000..8e80211 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
@@ -0,0 +1,1944 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.devtools.build.lib.actions.ArtifactFactory; +import com.google.devtools.build.lib.actions.PackageRootResolver; +import com.google.devtools.build.lib.actions.Root; +import com.google.devtools.build.lib.analysis.BlazeDirectories; +import com.google.devtools.build.lib.analysis.ViewCreationFailedException; +import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection.Transitions; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.packages.Attribute; +import com.google.devtools.build.lib.packages.Attribute.Configurator; +import com.google.devtools.build.lib.packages.Attribute.SplitTransition; +import com.google.devtools.build.lib.packages.Attribute.Transition; +import com.google.devtools.build.lib.packages.InputFile; +import com.google.devtools.build.lib.packages.PackageGroup; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.rules.test.TestActionBuilder; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.Label.SyntaxException; +import com.google.devtools.build.lib.syntax.SkylarkCallable; +import com.google.devtools.build.lib.syntax.SkylarkModule; +import com.google.devtools.build.lib.util.Fingerprint; +import com.google.devtools.build.lib.util.OS; +import com.google.devtools.build.lib.util.RegexFilter; +import com.google.devtools.build.lib.util.StringUtilities; +import com.google.devtools.build.lib.vfs.Path; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.skyframe.SkyFunction; +import com.google.devtools.build.skyframe.SkyFunction.Environment; +import com.google.devtools.common.options.Converter; +import com.google.devtools.common.options.Converters; +import com.google.devtools.common.options.EnumConverter; +import com.google.devtools.common.options.Option; +import com.google.devtools.common.options.OptionsBase; +import com.google.devtools.common.options.OptionsParsingException; +import com.google.devtools.common.options.TriState; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Instances of BuildConfiguration represent a collection of context + * information which may affect a build (for example: the target platform for + * compilation, or whether or not debug tables are required). In fact, all + * "environmental" information (e.g. from the tool's command-line, as opposed + * to the BUILD file) that can affect the output of any build tool should be + * explicitly represented in the BuildConfiguration instance. + * + * <p>A single build may require building tools to run on a variety of + * platforms: when compiling a server application for production, we must build + * the build tools (like compilers) to run on the host platform, but cross-compile + * the application for the production environment. + * + * <p>There is always at least one BuildConfiguration instance in any build: + * the one representing the host platform. Additional instances may be created, + * in a cross-compilation build, for example. + * + * <p>Instances of BuildConfiguration are canonical: + * <pre>c1.equals(c2) <=> c1==c2.</pre> + */ +@SkylarkModule(name = "configuration", + doc = "Data required for the analysis of a target that comes from targets that " + + "depend on it and not targets that it depends on.") +public final class BuildConfiguration implements Serializable { + + /** + * An interface for language-specific configurations. + */ + public abstract static class Fragment implements Serializable { + /** + * Returns a human-readable name of the configuration fragment. + */ + public abstract String getName(); + + /** + * Validates the options for this Fragment. Issues warnings for the + * use of deprecated options, and warnings or errors for any option settings + * that conflict. + */ + @SuppressWarnings("unused") + public void reportInvalidOptions(EventHandler reporter, BuildOptions buildOptions) { + } + + /** + * Adds mapping of names to values of "Make" variables defined by this configuration. + */ + @SuppressWarnings("unused") + public void addGlobalMakeVariables(ImmutableMap.Builder<String, String> globalMakeEnvBuilder) { + } + + /** + * Collects all labels that should be implicitly loaded from labels that were specified as + * options, keyed by the name to be displayed to the user if something goes wrong. + * The resulting set only contains labels that were derived from command-line options; the + * intention is that it can be used to sanity-check that the command-line options actually + * contain these in their transitive closure. + */ + @SuppressWarnings("unused") + public void addImplicitLabels(Multimap<String, Label> implicitLabels) { + } + + /** + * Returns a string that identifies the configuration fragment. + */ + public abstract String cacheKey(); + + /** + * The fragment may use this hook to perform I/O and read data into memory that is used during + * analysis. During the analysis phase disk I/O operations are disallowed. + * + * <p>This hook is only called for the top-level configuration after the loading phase is + * complete. + */ + @SuppressWarnings("unused") + public void prepareHook(Path execPath, ArtifactFactory artifactFactory, + PathFragment genfilesPath, PackageRootResolver resolver) + throws ViewCreationFailedException { + } + + /** + * Adds all the roots from this fragment. + */ + @SuppressWarnings("unused") + public void addRoots(List<Root> roots) { + } + + /** + * Returns a (key, value) mapping to insert into the subcommand environment for coverage. + */ + public Map<String, String> getCoverageEnvironment() { + return ImmutableMap.<String, String>of(); + } + + /* + * Returns the command-line "Make" variable overrides. + */ + public ImmutableMap<String, String> getCommandLineDefines() { + return ImmutableMap.of(); + } + + /** + * Returns all the coverage labels for the fragment. + */ + public ImmutableList<Label> getCoverageLabels() { + return ImmutableList.of(); + } + + /** + * Returns the coverage report generator tool labels. + */ + public ImmutableList<Label> getCoverageReportGeneratorLabels() { + return ImmutableList.of(); + } + + /** + * Returns a fragment of the output directory name for this configuration. The output + * directory for the whole configuration contains all the short names by all fragments. + */ + @Nullable + public String getOutputDirectoryName() { + return null; + } + + /** + * This will be added to the name of the configuration, but not to the output directory name. + */ + @Nullable + public String getConfigurationNameSuffix() { + return null; + } + + /** + * The platform name is a concatenation of fragment platform names. + */ + public String getPlatformName() { + return ""; + } + + /** + * Return false if incremental build is not possible for some reason. + */ + public boolean supportsIncrementalBuild() { + return true; + } + + /** + * Return true if the fragment performs static linking. This information is needed for + * lincence checking. + */ + public boolean performsStaticLink() { + return false; + } + + /** + * Fragments should delete temporary directories they create for their inner mechanisms. + * This is only called for target configuration. + */ + @SuppressWarnings("unused") + public void prepareForExecutionPhase() throws IOException { + } + + /** + * Add items to the shell environment. + */ + @SuppressWarnings("unused") + public void setupShellEnvironment(ImmutableMap.Builder<String, String> builder) { + } + + /** + * Add mappings from generally available tool names (like "sh") to their paths + * that actions can access. + */ + @SuppressWarnings("unused") + public void defineExecutables(ImmutableMap.Builder<String, PathFragment> builder) { + } + + /** + * Returns { 'option name': 'alternative default' } entries for options where the + * "real default" should be something besides the default specified in the {@link Option} + * declaration. + */ + public Map<String, Object> lateBoundOptionDefaults() { + return ImmutableMap.of(); + } + + /** + * Declares dependencies on any relevant Skyframe values (for example, relevant FileValues). + * + * @param env the skyframe environment + */ + public void declareSkyframeDependencies(Environment env) { + } + } + + /** + * A converter from strings to Labels. + */ + public static class LabelConverter implements Converter<Label> { + @Override + public Label convert(String input) throws OptionsParsingException { + try { + // Check if the input starts with '/'. We don't check for "//" so that + // we get a better error message if the user accidentally tries to use + // an absolute path (starting with '/') for a label. + if (!input.startsWith("/")) { + input = "//" + input; + } + return Label.parseAbsolute(input); + } catch (SyntaxException e) { + throw new OptionsParsingException(e.getMessage()); + } + } + + @Override + public String getTypeDescription() { + return "a build target label"; + } + } + + public static class PluginOptionConverter implements Converter<Map.Entry<String, String>> { + @Override + public Map.Entry<String, String> convert(String input) throws OptionsParsingException { + int index = input.indexOf('='); + if (index == -1) { + throw new OptionsParsingException("Plugin option not in the plugin=option format"); + } + String option = input.substring(0, index); + String value = input.substring(index + 1); + return Maps.immutableEntry(option, value); + } + + @Override + public String getTypeDescription() { + return "An option for a plugin"; + } + } + + public static class RunsPerTestConverter extends PerLabelOptions.PerLabelOptionsConverter { + @Override + public PerLabelOptions convert(String input) throws OptionsParsingException { + try { + return parseAsInteger(input); + } catch (NumberFormatException ignored) { + return parseAsRegex(input); + } + } + + private PerLabelOptions parseAsInteger(String input) + throws NumberFormatException, OptionsParsingException { + int numericValue = Integer.parseInt(input); + if (numericValue <= 0) { + throw new OptionsParsingException("'" + input + "' should be >= 1"); + } else { + RegexFilter catchAll = new RegexFilter(Collections.singletonList(".*"), + Collections.<String>emptyList()); + return new PerLabelOptions(catchAll, Collections.singletonList(input)); + } + } + + private PerLabelOptions parseAsRegex(String input) throws OptionsParsingException { + PerLabelOptions testRegexps = super.convert(input); + if (testRegexps.getOptions().size() != 1) { + throw new OptionsParsingException( + "'" + input + "' has multiple runs for a single pattern"); + } + String runsPerTest = Iterables.getOnlyElement(testRegexps.getOptions()); + try { + int numericRunsPerTest = Integer.parseInt(runsPerTest); + if (numericRunsPerTest <= 0) { + throw new OptionsParsingException("'" + input + "' has a value < 1"); + } + } catch (NumberFormatException e) { + throw new OptionsParsingException("'" + input + "' has a non-numeric value", e); + } + return testRegexps; + } + + @Override + public String getTypeDescription() { + return "a positive integer or test_regex@runs. This flag may be passed more than once"; + } + } + + /** + * Values for the --strict_*_deps option + */ + public static enum StrictDepsMode { + /** Silently allow referencing transitive dependencies. */ + OFF, + /** Warn about transitive dependencies being used directly. */ + WARN, + /** Fail the build when transitive dependencies are used directly. */ + ERROR, + /** Transition to strict by default. */ + STRICT, + /** When no flag value is specified on the command line. */ + DEFAULT + } + + /** + * Converter for the --strict_*_deps option. + */ + public static class StrictDepsConverter extends EnumConverter<StrictDepsMode> { + public StrictDepsConverter() { + super(StrictDepsMode.class, "strict dependency checking level"); + } + } + + /** + * Options that affect the value of a BuildConfiguration instance. + * + * <p>(Note: any client that creates a view will also need to declare + * BuildView.Options, which affect the <i>mechanism</i> of view construction, + * even if they don't affect the value of the BuildConfiguration instances.) + * + * <p>IMPORTANT: when adding new options, be sure to consider whether those + * values should be propagated to the host configuration or not (see + * {@link ConfigurationFactory#getConfiguration}. + * + * <p>ALSO IMPORTANT: all option types MUST define a toString method that + * gives identical results for semantically identical option values. The + * simplest way to ensure that is to return the input string. + */ + public static class Options extends FragmentOptions implements Cloneable { + public String getCpu() { + return cpu; + } + + @Option(name = "cpu", + defaultValue = "null", + category = "semantics", + help = "The target CPU.") + public String cpu; + + @Option(name = "min_param_file_size", + defaultValue = "32768", + category = "undocumented", + help = "Minimum command line length before creating a parameter file.") + public int minParamFileSize; + + @Option(name = "experimental_extended_sanity_checks", + defaultValue = "false", + category = "undocumented", + help = "Enables internal validation checks to make sure that configured target " + + "implementations only access things they should. Causes a performance hit.") + public boolean extendedSanityChecks; + + @Option(name = "experimental_allow_runtime_deps_on_neverlink", + defaultValue = "true", + category = "undocumented", + help = "Flag to help transition from allowing to disallowing runtime_deps on neverlink" + + " Java archives. The depot needs to be cleaned up to roll this out by default.") + public boolean allowRuntimeDepsOnNeverLink; + + @Option(name = "strict_filesets", + defaultValue = "false", + category = "semantics", + help = "If this option is enabled, filesets crossing package boundaries are reported " + + "as errors. It does not work when check_fileset_dependencies_recursively is " + + "disabled.") + public boolean strictFilesets; + + // Plugins are build using the host config. To avoid cycles we just don't propagate + // this option to the host config. If one day we decide to use plugins when building + // host tools, we can improve this by (for example) creating a compiler configuration that is + // used only for building plugins. + @Option(name = "plugin", + converter = LabelConverter.class, + allowMultiple = true, + defaultValue = "", + category = "flags", + help = "Plugins to use in the build. Currently works with java_plugin.") + public List<Label> pluginList; + + @Option(name = "plugin_copt", + converter = PluginOptionConverter.class, + allowMultiple = true, + category = "flags", + defaultValue = ":", + help = "Plugin options") + public List<Map.Entry<String, String>> pluginCoptList; + + @Option(name = "stamp", + defaultValue = "true", + category = "semantics", + help = "Stamp binaries with the date, username, hostname, workspace information, etc.") + public boolean stampBinaries; + + // TODO(bazel-team): delete from OSS tree + @Option(name = "instrumentation_filter", + converter = RegexFilter.RegexFilterConverter.class, + defaultValue = "-javatests,-_test$", + category = "semantics", + help = "When coverage is enabled, only rules with names included by the " + + "specified regex-based filter will be instrumented. Rules prefixed " + + "with '-' are excluded instead. By default, rules containing " + + "'javatests' or ending with '_test' will not be instrumented.") + public RegexFilter instrumentationFilter; + + @Option(name = "show_cached_analysis_results", + defaultValue = "true", + category = "undocumented", + help = "Bazel reruns a static analysis only if it detects changes in the analysis " + + "or its dependencies. If this option is enabled, Bazel will show the analysis' " + + "results, even if it did not rerun the analysis. If this option is disabled, " + + "Bazel will show analysis results only if it reran the analysis.") + public boolean showCachedAnalysisResults; + + @Option(name = "host_cpu", + defaultValue = "null", + category = "semantics", + help = "The host CPU.") + public String hostCpu; + + @Option(name = "compilation_mode", + abbrev = 'c', + converter = CompilationMode.Converter.class, + defaultValue = "fastbuild", + category = "semantics", // Should this be "flags"? + help = "Specify the mode the binary will be built in. " + + "Values: 'fastbuild', 'dbg', 'opt'.") + public CompilationMode compilationMode; + + /** + * This option is used internally to set the short name (see {@link + * #getShortName()}) of the <i>host</i> configuration to a constant, so + * that the output files for the host are completely independent of those + * for the target, no matter what options are in force (k8/piii, opt/dbg, + * etc). + */ + @Option(name = "configuration short name", // (Spaces => can't be specified on command line.) + defaultValue = "null", + category = "undocumented") + public String shortName; + + @Option(name = "platform_suffix", + defaultValue = "null", + category = "misc", + help = "Specifies a suffix to be added to the configuration directory.") + public String platformSuffix; + + @Option(name = "test_env", + converter = Converters.OptionalAssignmentConverter.class, + allowMultiple = true, + defaultValue = "", + category = "testing", + help = "Specifies additional environment variables to be injected into the test runner " + + "environment. Variables can be either specified by name, in which case its value " + + "will be read from the Bazel client environment, or by the name=value pair. " + + "This option can be used multiple times to specify several variables. " + + "Used only by the 'bazel test' command." + ) + public List<Map.Entry<String, String>> testEnvironment; + + @Option(name = "collect_code_coverage", + defaultValue = "false", + category = "testing", + help = "If specified, Bazel will instrument code (using offline instrumentation where " + + "possible) and will collect coverage information during tests. Only targets that " + + " match --instrumentation_filter will be affected. Usually this option should " + + " not be specified directly - 'bazel coverage' command should be used instead." + ) + public boolean collectCodeCoverage; + + @Option(name = "microcoverage", + defaultValue = "false", + category = "testing", + help = "If specified with coverage, Blaze will collect microcoverage (per test method " + + "coverage) information during tests. Only targets that match " + + "--instrumentation_filter will be affected. Usually this option should not be " + + "specified directly - 'blaze coverage --microcoverage' command should be used " + + "instead." + ) + public boolean collectMicroCoverage; + + @Option(name = "cache_test_results", + defaultValue = "auto", + category = "testing", + abbrev = 't', // it's useful to toggle this on/off quickly + help = "If 'auto', Bazel will only rerun a test if any of the following conditions apply: " + + "(1) Bazel detects changes in the test or its dependencies " + + "(2) the test is marked as external " + + "(3) multiple test runs were requested with --runs_per_test" + + "(4) the test failed" + + "If 'yes', the caching behavior will be the same as 'auto' except that " + + "it may cache test failures and test runs with --runs_per_test." + + "If 'no', all tests will be always executed.") + public TriState cacheTestResults; + + @Deprecated + @Option(name = "test_result_expiration", + defaultValue = "-1", // No expiration by defualt. + category = "testing", + help = "This option is deprecated and has no effect.") + public int testResultExpiration; + + @Option(name = "test_sharding_strategy", + defaultValue = "explicit", + category = "testing", + converter = TestActionBuilder.ShardingStrategyConverter.class, + help = "Specify strategy for test sharding: " + + "'explicit' to only use sharding if the 'shard_count' BUILD attribute is present. " + + "'disabled' to never use test sharding. " + + "'experimental_heuristic' to enable sharding on remotely executed tests without an " + + "explicit 'shard_count' attribute which link in a supported framework. Considered " + + "experimental.") + public TestActionBuilder.TestShardingStrategy testShardingStrategy; + + @Option(name = "runs_per_test", + allowMultiple = true, + defaultValue = "1", + category = "testing", + converter = RunsPerTestConverter.class, + help = "Specifies number of times to run each test. If any of those attempts " + + "fail for any reason, the whole test would be considered failed. " + + "Normally the value specified is just an integer. Example: --runs_per_test=3 " + + "will run all tests 3 times. " + + "Alternate syntax: regex_filter@runs_per_test. Where runs_per_test stands for " + + "an integer value and regex_filter stands " + + "for a list of include and exclude regular expression patterns (Also see " + + "--instrumentation_filter). Example: " + + "--runs_per_test=//foo/.*,-//foo/bar/.*@3 runs all tests in //foo/ " + + "except those under foo/bar three times. " + + "This option can be passed multiple times. ") + public List<PerLabelOptions> runsPerTest; + + @Option(name = "build_runfile_links", + defaultValue = "true", + category = "strategy", + help = "If true, build runfiles symlink forests for all targets. " + + "If false, write only manifests when possible.") + public boolean buildRunfiles; + + @Option(name = "test_arg", + allowMultiple = true, + defaultValue = "", + category = "testing", + help = "Specifies additional options and arguments that should be passed to the test " + + "executable. Can be used multiple times to specify several arguments. " + + "If multiple tests are executed, each of them will receive identical arguments. " + + "Used only by the 'bazel test' command." + ) + public List<String> testArguments; + + @Option(name = "test_filter", + allowMultiple = false, + defaultValue = "null", + category = "testing", + help = "Specifies a filter to forward to the test framework. Used to limit " + + "the tests run. Note that this does not affect which targets are built.") + public String testFilter; + + @Option(name = "check_fileset_dependencies_recursively", + defaultValue = "true", + category = "semantics", + help = "If false, fileset targets will, whenever possible, create " + + "symlinks to directories instead of creating one symlink for each " + + "file inside the directory. Disabling this will significantly " + + "speed up fileset builds, but targets that depend on filesets will " + + "not be rebuilt if files are added, removed or modified in a " + + "subdirectory which has not been traversed.") + public boolean checkFilesetDependenciesRecursively; + + @Option(name = "run_under", + category = "run", + defaultValue = "null", + converter = RunUnderConverter.class, + help = "Prefix to insert in front of command before running. " + + "Examples:\n" + + "\t--run_under=valgrind\n" + + "\t--run_under=strace\n" + + "\t--run_under='strace -c'\n" + + "\t--run_under='valgrind --quiet --num-callers=20'\n" + + "\t--run_under=//package:target\n" + + "\t--run_under='//package:target --options'\n") + public RunUnder runUnder; + + @Option(name = "distinct_host_configuration", + defaultValue = "true", + category = "strategy", + help = "Build all the tools used during the build for a distinct configuration from " + + "that used for the target program. By default, the same configuration is used " + + "for host and target programs, but this may cause undesirable rebuilds of tool " + + "such as the protocol compiler (and then everything downstream) whenever a minor " + + "change is made to the target configuration, such as setting the linker options. " + + "When this flag is specified, a distinct configuration will be used to build the " + + "tools, preventing undesired rebuilds. However, certain libraries will then " + + "need to be compiled twice, once for each configuration, which may cause some " + + "builds to be slower. As a rule of thumb, this option is likely to benefit " + + "users that make frequent changes in configuration (e.g. opt/dbg). " + + "Please read the user manual for the full explanation.") + public boolean useDistinctHostConfiguration; + + @Option(name = "check_visibility", + defaultValue = "true", + category = "checking", + help = "If disabled, visibility errors are demoted to warnings.") + public boolean checkVisibility; + + // Moved from viewOptions to here because license information is very expensive to serialize. + // Having it here allows us to skip computation of transitive license information completely + // when the setting is disabled. + @Option(name = "check_licenses", + defaultValue = "false", + category = "checking", + help = "Check that licensing constraints imposed by dependent packages " + + "do not conflict with distribution modes of the targets being built. " + + "By default, licenses are not checked.") + public boolean checkLicenses; + + @Option(name = "experimental_enforce_constraints", + defaultValue = "true", + category = "undocumented", + help = "Checks the environments each target is compatible with and reports errors if any " + + "target has dependencies that don't support the same environments") + public boolean enforceConstraints; + + @Option(name = "experimental_action_listener", + allowMultiple = true, + defaultValue = "", + category = "experimental", + converter = LabelConverter.class, + help = "Use action_listener to attach an extra_action to existing build actions.") + public List<Label> actionListeners; + + @Option(name = "is host configuration", + defaultValue = "false", + category = "undocumented", + help = "Shows whether these options are set for host configuration.") + public boolean isHost; + + @Option(name = "experimental_proto_header_modules", + defaultValue = "false", + category = "undocumented", + help = "Enables compilation of C++ header modules for proto libraries.") + public boolean protoHeaderModules; + + @Option(name = "features", + allowMultiple = true, + defaultValue = "", + category = "flags", + help = "The given features will be enabled or disabled by default for all packages. " + + "Specifying -<feature> will disable the feature globally. " + + "Negative features always override positive ones. " + + "This flag is used to enable rolling out default feature changes without a " + + "Blaze release.") + public List<String> defaultFeatures; + + @Override + public FragmentOptions getHost(boolean fallback) { + Options host = (Options) getDefault(); + + host.shortName = "host"; + host.compilationMode = CompilationMode.OPT; + host.isHost = true; + + if (fallback) { + // In the fallback case, we have already tried the target options and they didn't work, so + // now we try the default options; the hostCpu field has the default value, because we use + // getDefault() above. + host.cpu = computeHostCpu(host.hostCpu); + } else { + host.cpu = computeHostCpu(hostCpu); + } + + // === Runfiles === + // Ideally we could force this the other way, and skip runfiles construction + // for host tools which are never run locally, but that's probably a very + // small optimization. + host.buildRunfiles = true; + + // === Linkstamping === + // Disable all link stamping for the host configuration, to improve action + // cache hit rates for tools. + host.stampBinaries = false; + + // === Visibility === + host.checkVisibility = checkVisibility; + + // === Licenses === + host.checkLicenses = checkLicenses; + + // === Allow runtime_deps to depend on neverlink Java libraries. + host.allowRuntimeDepsOnNeverLink = allowRuntimeDepsOnNeverLink; + + // === Pass on C++ compiler features. + host.defaultFeatures = ImmutableList.copyOf(defaultFeatures); + + return host; + } + + private static String computeHostCpu(String explicitHostCpu) { + if (explicitHostCpu != null) { + return explicitHostCpu; + } + switch (OS.getCurrent()) { + case DARWIN: + return "darwin"; + default: + return "k8"; + } + } + + @Override + public void addAllLabels(Multimap<String, Label> labelMap) { + labelMap.putAll("action_listener", actionListeners); + labelMap.putAll("plugins", pluginList); + if ((runUnder != null) && (runUnder.getLabel() != null)) { + labelMap.put("RunUnder", runUnder.getLabel()); + } + } + } + + /** + * A list of build configurations that only contains the null element. + */ + private static final List<BuildConfiguration> NULL_LIST = + Collections.unmodifiableList(Arrays.asList(new BuildConfiguration[] { null })); + + private final String cacheKey; + private final String shortCacheKey; + + private Transitions transitions; + private Set<BuildConfiguration> allReachableConfigurations; + + private final ImmutableMap<Class<? extends Fragment>, Fragment> fragments; + + // Directories in the output tree + private final Root outputDirectory; // the configuration-specific output directory. + private final Root binDirectory; + private final Root genfilesDirectory; + private final Root coverageMetadataDirectory; // for coverage-related metadata, artifacts, etc. + private final Root testLogsDirectory; + private final Root includeDirectory; + private final Root middlemanDirectory; + + private final PathFragment binFragment; + private final PathFragment genfilesFragment; + + // If false, AnalysisEnviroment doesn't register any actions created by the ConfiguredTarget. + private final boolean actionsEnabled; + + private final ImmutableSet<Label> coverageLabels; + private final ImmutableSet<Label> coverageReportGeneratorLabels; + + // Executables like "perl" or "sh" + private final ImmutableMap<String, PathFragment> executables; + + // All the "defglobals" in //tools:GLOBALS for this platform/configuration: + private final ImmutableMap<String, String> globalMakeEnv; + + private final ImmutableMap<String, String> defaultShellEnvironment; + private final BuildOptions buildOptions; + private final Options options; + + private final String shortName; + private final String mnemonic; + private final String platformName; + + /** + * It is not fingerprinted because it should only be used to access + * variables that do not break the hermetism of build rules. + */ + private final ImmutableMap<String, String> clientEnvironment; + + /** + * Helper container for {@link #transitiveOptionsMap} below. + */ + private static class OptionDetails implements Serializable { + private OptionDetails(Class<? extends OptionsBase> optionsClass, Object value, + boolean allowsMultiple) { + this.optionsClass = optionsClass; + this.value = value; + this.allowsMultiple = allowsMultiple; + } + + /** The {@link FragmentOptions} class that defines this option. */ + private final Class<? extends OptionsBase> optionsClass; + + /** + * The value of the given option (either explicitly defined or default). May be null. + */ + private final Object value; + + /** Whether or not this option supports multiple values. */ + private final boolean allowsMultiple; + } + + /** + * Maps option names to the {@link OptionDetails} the option takes for this configuration. + * + * <p>This can be used to: + * <ol> + * <li>Find an option's (parsed) value given its command-line name</li> + * <li>Parse alternative values for the option.</li> + * </ol> + * + * <p>This map is "transitive" in that it includes *all* options recognizable by this + * configuration, including those defined in child fragments. + */ + private final Map<String, OptionDetails> transitiveOptionsMap; + + + /** + * Validates the options for this BuildConfiguration. Issues warnings for the + * use of deprecated options, and warnings or errors for any option settings + * that conflict. + */ + public void reportInvalidOptions(EventHandler reporter) { + for (Fragment fragment : fragments.values()) { + fragment.reportInvalidOptions(reporter, this.buildOptions); + } + + Set<String> plugins = new HashSet<>(); + for (Label plugin : options.pluginList) { + String name = plugin.getName(); + if (plugins.contains(name)) { + reporter.handle(Event.error("A build cannot have two plugins with the same name")); + } + plugins.add(name); + } + for (Map.Entry<String, String> opt : options.pluginCoptList) { + if (!plugins.contains(opt.getKey())) { + reporter.handle(Event.error("A plugin_copt must refer to an existing plugin")); + } + } + + if (options.shortName != null) { + reporter.handle(Event.error( + "The internal '--configuration short name' option cannot be used on the command line")); + } + + if (options.testShardingStrategy + == TestActionBuilder.TestShardingStrategy.EXPERIMENTAL_HEURISTIC) { + reporter.handle(Event.warn( + "Heuristic sharding is intended as a one-off experimentation tool for determing the " + + "benefit from sharding certain tests. Please don't keep this option in your " + + ".blazerc or continuous build")); + } + } + + private ImmutableMap<String, String> setupShellEnvironment() { + ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>(); + for (Fragment fragment : fragments.values()) { + fragment.setupShellEnvironment(builder); + } + return builder.build(); + } + + BuildConfiguration(BlazeDirectories directories, + Map<Class<? extends Fragment>, Fragment> fragmentsMap, + BuildOptions buildOptions, + Map<String, String> clientEnv, + boolean actionsDisabled) { + this.actionsEnabled = !actionsDisabled; + fragments = ImmutableMap.copyOf(fragmentsMap); + + // This is a view that will be updated upon each client command. + this.clientEnvironment = ImmutableMap.copyOf(clientEnv); + + this.buildOptions = buildOptions; + this.options = buildOptions.get(Options.class); + + this.mnemonic = buildMnemonic(); + String outputDirName = (options.shortName != null) ? options.shortName : mnemonic; + this.shortName = buildShortName(outputDirName); + + this.executables = collectExecutables(); + + Path execRoot = directories.getExecRoot(); + // configuration-specific output tree + Path outputDir = directories.getOutputPath().getRelative(outputDirName); + this.outputDirectory = Root.asDerivedRoot(execRoot, outputDir); + + // specific subdirs under outputDirectory + this.binDirectory = Root.asDerivedRoot(execRoot, outputDir.getRelative("bin")); + this.genfilesDirectory = Root.asDerivedRoot(execRoot, outputDir.getRelative("genfiles")); + this.coverageMetadataDirectory = Root.asDerivedRoot(execRoot, + outputDir.getRelative("coverage-metadata")); + this.testLogsDirectory = Root.asDerivedRoot(execRoot, outputDir.getRelative("testlogs")); + this.includeDirectory = Root.asDerivedRoot(execRoot, + outputDir.getRelative(BlazeDirectories.RELATIVE_INCLUDE_DIR)); + this.middlemanDirectory = Root.middlemanRoot(execRoot, outputDir); + + // precompute some frequently-used relative paths + this.binFragment = getBinDirectory().getExecPath(); + this.genfilesFragment = getGenfilesDirectory().getExecPath(); + + ImmutableSet.Builder<Label> coverageLabelsBuilder = ImmutableSet.builder(); + ImmutableSet.Builder<Label> coverageReportGeneratorLabelsBuilder = ImmutableSet.builder(); + for (Fragment fragment : fragments.values()) { + coverageLabelsBuilder.addAll(fragment.getCoverageLabels()); + coverageReportGeneratorLabelsBuilder.addAll(fragment.getCoverageReportGeneratorLabels()); + } + this.coverageLabels = coverageLabelsBuilder.build(); + this.coverageReportGeneratorLabels = coverageReportGeneratorLabelsBuilder.build(); + + // Platform name + StringBuilder platformNameBuilder = new StringBuilder(); + for (Fragment fragment : fragments.values()) { + platformNameBuilder.append(fragment.getPlatformName()); + } + this.platformName = platformNameBuilder.toString(); + + this.defaultShellEnvironment = setupShellEnvironment(); + + this.transitiveOptionsMap = computeOptionsMap(buildOptions, fragments.values()); + + ImmutableMap.Builder<String, String> globalMakeEnvBuilder = ImmutableMap.builder(); + for (Fragment fragment : fragments.values()) { + fragment.addGlobalMakeVariables(globalMakeEnvBuilder); + } + + // Lots of packages in third_party assume that BINMODE expands to either "-dbg", or "-opt". So + // for backwards compatibility we preserve that invariant, setting BINMODE to "-dbg" rather than + // "-fastbuild" if the compilation mode is "fastbuild". + // We put the real compilation mode in a new variable COMPILATION_MODE. + globalMakeEnvBuilder.put("COMPILATION_MODE", options.compilationMode.toString()); + globalMakeEnvBuilder.put("BINMODE", "-" + + ((options.compilationMode == CompilationMode.FASTBUILD) + ? "dbg" + : options.compilationMode.toString())); + /* + * Attention! Document these in the build-encyclopedia + */ + // the bin directory and the genfiles directory + // These variables will be used on Windows as well, so we need to make sure + // that paths use the correct system file-separator. + globalMakeEnvBuilder.put("BINDIR", binFragment.getPathString()); + globalMakeEnvBuilder.put("INCDIR", + getIncludeDirectory().getExecPath().getPathString()); + globalMakeEnvBuilder.put("GENDIR", genfilesFragment.getPathString()); + globalMakeEnv = globalMakeEnvBuilder.build(); + + cacheKey = computeCacheKey( + directories, fragmentsMap, this.buildOptions, this.clientEnvironment); + shortCacheKey = shortName + "-" + Fingerprint.md5Digest(cacheKey); + } + + + /** + * Computes and returns the transitive optionName -> "option info" map for + * this configuration. + */ + private static Map<String, OptionDetails> computeOptionsMap(BuildOptions buildOptions, + Iterable<Fragment> fragments) { + // Collect from our fragments "alternative defaults" for options where the default + // should be something other than what's specified in Option.defaultValue. + Map<String, Object> lateBoundDefaults = Maps.newHashMap(); + for (Fragment fragment : fragments) { + lateBoundDefaults.putAll(fragment.lateBoundOptionDefaults()); + } + + ImmutableMap.Builder<String, OptionDetails> map = ImmutableMap.builder(); + try { + for (FragmentOptions options : buildOptions.getOptions()) { + for (Field field : options.getClass().getFields()) { + if (field.isAnnotationPresent(Option.class)) { + Option option = field.getAnnotation(Option.class); + Object value = field.get(options); + if (value == null) { + if (lateBoundDefaults.containsKey(option.name())) { + value = lateBoundDefaults.get(option.name()); + } else if (!option.defaultValue().equals("null")) { + // See {@link Option#defaultValue} for an explanation of default "null" strings. + value = option.defaultValue(); + } + } + map.put(option.name(), + new OptionDetails(options.getClass(), value, option.allowMultiple())); + } + } + } + } catch (IllegalAccessException e) { + throw new IllegalStateException( + "Unexpected illegal access trying to create this configuration's options map: ", e); + } + return map.build(); + } + + private String buildShortName(String outputDirName) { + ArrayList<String> nameParts = new ArrayList<>(ImmutableList.of(outputDirName)); + for (Fragment fragment : fragments.values()) { + nameParts.add(fragment.getConfigurationNameSuffix()); + } + return Joiner.on('-').skipNulls().join(nameParts); + } + + private String buildMnemonic() { + // See explanation at getShortName(). + String platformSuffix = (options.platformSuffix != null) ? options.platformSuffix : ""; + ArrayList<String> nameParts = new ArrayList<String>(); + for (Fragment fragment : fragments.values()) { + nameParts.add(fragment.getOutputDirectoryName()); + } + nameParts.add(getCompilationMode() + platformSuffix); + return Joiner.on('-').join(Iterables.filter(nameParts, Predicates.notNull())); + } + + /** + * Set the outgoing configuration transitions. During the lifetime of a given build configuration, + * this must happen exactly once, shortly after the configuration is created. + * TODO(bazel-team): this makes the object mutable, get rid of it. + */ + public void setConfigurationTransitions(Transitions transitions) { + Preconditions.checkNotNull(transitions); + Preconditions.checkState(this.transitions == null); + this.transitions = transitions; + } + + public Transitions getTransitions() { + Preconditions.checkState(this.transitions != null || isHostConfiguration()); + return transitions; + } + + /** + * Returns all configurations that can be reached from this configuration through any kind of + * configuration transition. + */ + public synchronized Collection<BuildConfiguration> getAllReachableConfigurations() { + if (allReachableConfigurations == null) { + // This is needed for every configured target in skyframe m2, so we cache it. + // We could alternatively make the corresponding dependencies into a skyframe node. + this.allReachableConfigurations = computeAllReachableConfigurations(); + } + return allReachableConfigurations; + } + + /** + * Returns all configurations that can be reached from this configuration through any kind of + * configuration transition. + */ + private Set<BuildConfiguration> computeAllReachableConfigurations() { + Set<BuildConfiguration> result = new LinkedHashSet<>(); + Queue<BuildConfiguration> queue = new LinkedList<>(); + queue.add(this); + while (!queue.isEmpty()) { + BuildConfiguration config = queue.remove(); + if (!result.add(config)) { + continue; + } + config.getTransitions().addDirectlyReachableConfigurations(queue); + } + return result; + } + + /** + * Returns the new configuration after traversing a dependency edge with a given configuration + * transition. + * + * @param transition the configuration transition + * @return the new configuration + * @throws IllegalArgumentException if the transition is a {@link SplitTransition} + */ + public BuildConfiguration getConfiguration(Transition transition) { + Preconditions.checkArgument(!(transition instanceof SplitTransition)); + return transitions.getConfiguration(transition); + } + + /** + * Returns the new configurations after traversing a dependency edge with a given split + * transition. + * + * @param transition the split configuration transition + * @return the new configurations + */ + public List<BuildConfiguration> getSplitConfigurations(SplitTransition<?> transition) { + return transitions.getSplitConfigurations(transition); + } + + /** + * Calculates the configurations of a direct dependency. If a rule in some BUILD file refers + * to a target (like another rule or a source file) using a label attribute, that target needs + * to have a configuration, too. This method figures out the proper configuration for the + * dependency. + * + * @param fromRule the rule that's depending on some target + * @param attribute the attribute using which the rule depends on that target (eg. "srcs") + * @param toTarget the target that's dependeded on + * @return the configuration that should be associated to {@code toTarget} + */ + public Iterable<BuildConfiguration> evaluateTransition(final Rule fromRule, + final Attribute attribute, final Target toTarget) { + // Fantastic configurations and where to find them: + + // I. Input files and package groups have no configurations. We don't want to duplicate them. + if (toTarget instanceof InputFile || toTarget instanceof PackageGroup) { + return NULL_LIST; + } + + // II. Host configurations never switch to another. All prerequisites of host targets have the + // same host configuration. + if (isHostConfiguration()) { + return ImmutableList.of(this); + } + + // Make sure config_setting dependencies are resolved in the referencing rule's configuration, + // unconditionally. For example, given: + // + // genrule( + // name = 'myrule', + // tools = select({ '//a:condition': [':sometool'] }) + // + // all labels in "tools" get resolved in the host configuration (since the "tools" attribute + // declares a host configuration transition). We want to explicitly exclude configuration labels + // from these transitions, since their *purpose* is to do computation on the owning + // rule's configuration. + // TODO(bazel-team): implement this more elegantly. This is far too hackish. Specifically: + // don't reference the rule name explicitly and don't require special-casing here. + if (toTarget instanceof Rule && ((Rule) toTarget).getRuleClass().equals("config_setting")) { + return ImmutableList.of(this); + } + + List<BuildConfiguration> toConfigurations; + if (attribute.getConfigurationTransition() instanceof SplitTransition) { + Preconditions.checkState(attribute.getConfigurator() == null); + toConfigurations = getSplitConfigurations( + (SplitTransition<?>) attribute.getConfigurationTransition()); + } else { + // III. Attributes determine configurations. The configuration of a prerequisite is determined + // by the attribute. + @SuppressWarnings("unchecked") + Configurator<BuildConfiguration, Rule> configurator = + (Configurator<BuildConfiguration, Rule>) attribute.getConfigurator(); + toConfigurations = ImmutableList.of((configurator != null) + ? configurator.apply(fromRule, this, attribute, toTarget) + : getConfiguration(attribute.getConfigurationTransition())); + } + + return Iterables.transform(toConfigurations, + new Function<BuildConfiguration, BuildConfiguration>() { + @Override + public BuildConfiguration apply(BuildConfiguration input) { + // IV. Allow the transition object to perform an arbitrary switch. Blaze modules can inject + // configuration transition logic by extending the Transitions class. + BuildConfiguration actual = getTransitions().configurationHook( + fromRule, attribute, toTarget, input); + + // V. Allow rule classes to override their own configurations. + Rule associatedRule = toTarget.getAssociatedRule(); + if (associatedRule != null) { + @SuppressWarnings("unchecked") + RuleClass.Configurator<BuildConfiguration, Rule> func = + associatedRule.getRuleClassObject().<BuildConfiguration, Rule>getConfigurator(); + actual = func.apply(associatedRule, actual); + } + + return actual; + } + }); + } + + /** + * Returns a multimap of all labels that should be implicitly loaded from labels that were + * specified as options, keyed by the name to be displayed to the user if something goes wrong. + * The returned set only contains labels that were derived from command-line options; the + * intention is that it can be used to sanity-check that the command-line options actually contain + * these in their transitive closure. + */ + public ListMultimap<String, Label> getImplicitLabels() { + ListMultimap<String, Label> implicitLabels = ArrayListMultimap.create(); + for (Fragment fragment : fragments.values()) { + fragment.addImplicitLabels(implicitLabels); + } + return implicitLabels; + } + + /** + * For an given environment, returns a subset containing all + * variables in the given list if they are defined in the given + * environment. + */ + @VisibleForTesting + static Map<String, String> getMapping(List<String> variables, + Map<String, String> environment) { + Map<String, String> result = new HashMap<>(); + for (String var : variables) { + if (environment.containsKey(var)) { + result.put(var, environment.get(var)); + } + } + return result; + } + + /** + * Avoid this method. The client environment is not part of the configuration's signature, so + * calls to this method introduce a non-hermetic access to data that is not visible to Skyframe. + * + * @return an unmodifiable view of the bazel client's environment + * upon its most recent request. + */ + // TODO(bazel-team): Remove this. + public Map<String, String> getClientEnv() { + return clientEnvironment; + } + + /** + * Returns the {@link Option} class the defines the given option, null if the + * option isn't recognized. + * + * <p>optionName is the name of the option as it appears on the command line + * e.g. {@link Option#name}). + */ + Class<? extends OptionsBase> getOptionClass(String optionName) { + OptionDetails optionData = transitiveOptionsMap.get(optionName); + return optionData == null ? null : optionData.optionsClass; + } + + /** + * Returns the value of the specified option for this configuration or null if the + * option isn't recognized. Since an option's legitimate value could be null, use + * {@link #getOptionClass} to distinguish between that and an unknown option. + * + * <p>optionName is the name of the option as it appears on the command line + * e.g. {@link Option#name}). + */ + Object getOptionValue(String optionName) { + OptionDetails optionData = transitiveOptionsMap.get(optionName); + return (optionData == null) ? null : optionData.value; + } + + /** + * Returns whether or not the given option supports multiple values at the command line (e.g. + * "--myoption value1 --myOption value2 ..."). Returns false for unrecognized options. Use + * {@link #getOptionClass} to distinguish between those and legitimate single-value options. + * + * <p>As declared in {@link Option#allowMultiple}, multi-value options are expected to be + * of type {@code List<T>}. + */ + boolean allowsMultipleValues(String optionName) { + OptionDetails optionData = transitiveOptionsMap.get(optionName); + return (optionData == null) ? false : optionData.allowsMultiple; + } + + /** + * The platform string, suitable for use as a key into a MakeEnvironment. + */ + public String getPlatformName() { + return platformName; + } + + /** + * Returns the output directory for this build configuration. + */ + public Root getOutputDirectory() { + return outputDirectory; + } + + /** + * Returns the bin directory for this build configuration. + */ + @SkylarkCallable(name = "bin_dir", structField = true, + doc = "The root corresponding to bin directory.") + public Root getBinDirectory() { + return binDirectory; + } + + /** + * Returns a relative path to the bin directory at execution time. + */ + public PathFragment getBinFragment() { + return binFragment; + } + + /** + * Returns the include directory for this build configuration. + */ + public Root getIncludeDirectory() { + return includeDirectory; + } + + /** + * Returns the genfiles directory for this build configuration. + */ + @SkylarkCallable(name = "genfiles_dir", structField = true, + doc = "The root corresponding to genfiles directory.") + public Root getGenfilesDirectory() { + return genfilesDirectory; + } + + /** + * Returns the directory where coverage-related artifacts and metadata files + * should be stored. This includes for example uninstrumented class files + * needed for Jacoco's coverage reporting tools. + */ + public Root getCoverageMetadataDirectory() { + return coverageMetadataDirectory; + } + + /** + * Returns the testlogs directory for this build configuration. + */ + public Root getTestLogsDirectory() { + return testLogsDirectory; + } + + /** + * Returns a relative path to the genfiles directory at execution time. + */ + public PathFragment getGenfilesFragment() { + return genfilesFragment; + } + + /** + * Returns the path separator for the host platform. This is basically the same as {@link + * java.io.File#pathSeparator}, except that that returns the value for this JVM, which may or may + * not match the host platform. You should only use this when invoking tools that are known to use + * the native path separator, i.e., the path separator for the machine that they run on. + */ + @SkylarkCallable(name = "host_path_separator", structField = true, + doc = "Returns the separator for PATH variable, which is ':' on Unix.") + public String getHostPathSeparator() { + // TODO(bazel-team): This needs to change when we support Windows. + return ":"; + } + + /** + * Returns the internal directory (used for middlemen) for this build configuration. + */ + public Root getMiddlemanDirectory() { + return middlemanDirectory; + } + + public boolean getAllowRuntimeDepsOnNeverLink() { + return options.allowRuntimeDepsOnNeverLink; + } + + public boolean isStrictFilesets() { + return options.strictFilesets; + } + + public List<Label> getPlugins() { + return options.pluginList; + } + + public List<Map.Entry<String, String>> getPluginCopts() { + return options.pluginCoptList; + } + + /** + * Implements a non-injective mapping from BuildConfiguration instances to + * strings. The result should identify the aspects of the configuration + * that should be reflected in the output file names. Furthermore the + * returned string must not contain shell metacharacters. + * + * <p>The intention here is that we use this string as the directory name + * for artifacts of this build. + * + * <p>For configuration settings which are NOT part of the short name, + * rebuilding with a different value of such a setting will build in + * the same output directory. This means that any actions whose + * keys (see Action.getKey()) have changed will be rerun. That + * may result in a lot of recompilation. + * + * <p>For configuration settings which ARE part of the short name, + * rebuilding with a different value of such a setting will rebuild + * in a different output directory; this will result in higher disk + * usage and more work the _first_ time you rebuild with a different + * setting, but will result in less work if you regularly switch + * back and forth between different settings. + * + * <p>With one important exception, it's sound to choose any subset of the + * config's components for this string, it just alters the dimensionality + * of the cache. In other words, it's a trade-off on the "injectiveness" + * scale: at one extreme (shortName is in fact a complete fingerprint, and + * thus injective) you get extremely precise caching (no competition for the + * same output-file locations) but you have to rebuild for even the + * slightest change in configuration. At the other extreme + * (PartialFingerprint is a constant) you have very high competition for + * output-file locations, but if a slight change in configuration doesn't + * affect a particular build step, you're guaranteed not to have to + * rebuild it. The important exception has to do with cross-compilation: + * the host and target configurations must not map to the same output + * directory, because then files would need to get built for the host + * and then rebuilt for the target even within a single build, and that + * wouldn't work. + * + * <p>Just to re-iterate: cross-compilation builds (i.e. hostConfig != + * targetConfig) will not work if the two configurations' short names are + * equal. This is an important practical case: the mere addition of + * a compile flag to the target configuration would cause the build to + * fail. In other words, it would break if the host and target + * configurations are not identical but are "too close". The current + * solution is to set the host configuration equal to the target + * configuration if they are "too close"; this may cause the tools to get + * rebuild for the new host configuration though. + */ + public String getShortName() { + return shortName; + } + + /** + * Like getShortName(), but always returns a configuration-dependent string even for + * the host configuration. + */ + public String getMnemonic() { + return mnemonic; + } + + @Override + public String toString() { + return getShortName(); + } + + /** + * Returns the default shell environment + */ + @SkylarkCallable(name = "default_shell_env", structField = true, + doc = "A dictionary representing the default environment. It maps variables " + + "to their values (strings).") + public ImmutableMap<String, String> getDefaultShellEnvironment() { + return defaultShellEnvironment; + } + + /** + * Returns the path to sh. + */ + public PathFragment getShExecutable() { + return executables.get("sh"); + } + + /** + * Returns a regex-based instrumentation filter instance that used to match label + * names to identify targets to be instrumented in the coverage mode. + */ + public RegexFilter getInstrumentationFilter() { + return options.instrumentationFilter; + } + + /** + * Returns the set of labels for coverage. + */ + public Set<Label> getCoverageLabels() { + return coverageLabels; + } + + /** + * Returns the set of labels for the coverage report generator. + */ + public Set<Label> getCoverageReportGeneratorLabels() { + return coverageReportGeneratorLabels; + } + + /** + * Returns true if bazel should show analyses results, even if it did not + * re-run the analysis. + */ + public boolean showCachedAnalysisResults() { + return options.showCachedAnalysisResults; + } + + /** + * Returns a new, unordered mapping of names to values of "Make" variables defined by this + * configuration. + * + * <p>This does *not* include package-defined overrides (e.g. vardef) + * and so should not be used by the build logic. This is used only for + * the 'info' command. + * + * <p>Command-line definitions of make enviroments override variables defined by + * {@code Fragment.addGlobalMakeVariables()}. + */ + public Map<String, String> getMakeEnvironment() { + Map<String, String> makeEnvironment = new HashMap<>(); + makeEnvironment.putAll(globalMakeEnv); + for (Fragment fragment : fragments.values()) { + makeEnvironment.putAll(fragment.getCommandLineDefines()); + } + return ImmutableMap.copyOf(makeEnvironment); + } + + /** + * Returns a new, unordered mapping of names that are set through the command lines. + * (Fragments, in particular the Google C++ support, can set variables through the + * command line.) + */ + public Map<String, String> getCommandLineDefines() { + ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); + for (Fragment fragment : fragments.values()) { + builder.putAll(fragment.getCommandLineDefines()); + } + return builder.build(); + } + + /** + * Returns the global defaults for this configuration for the Make environment. + */ + public Map<String, String> getGlobalMakeEnvironment() { + return globalMakeEnv; + } + + /** + * Returns a (key, value) mapping to insert into the subcommand environment for coverage + * actions. + */ + public Map<String, String> getCoverageEnvironment() { + Map<String, String> env = new HashMap<>(); + for (Fragment fragment : fragments.values()) { + env.putAll(fragment.getCoverageEnvironment()); + } + return env; + } + + /** + * Returns the default value for the specified "Make" variable for this + * configuration. Returns null if no value was found. + */ + public String getMakeVariableDefault(String var) { + return globalMakeEnv.get(var); + } + + /** + * Returns a configuration fragment instances of the given class. + */ + @SkylarkCallable(name = "fragment", doc = "Returns a configuration fragment using the key.") + public <T extends Fragment> T getFragment(Class<T> clazz) { + return clazz.cast(fragments.get(clazz)); + } + + /** + * Returns true if the requested configuration fragment is present. + */ + public <T extends Fragment> boolean hasFragment(Class<T> clazz) { + return getFragment(clazz) != null; + } + + /** + * Returns true if all requested configuration fragment are present (this may be slow). + */ + public boolean hasAllFragments(Set<Class<?>> fragmentClasses) { + for (Class<?> fragmentClass : fragmentClasses) { + if (!hasFragment(fragmentClass.asSubclass(Fragment.class))) { + return false; + } + } + return true; + } + + /** + * Returns true if non-functional build stamps are enabled. + */ + public boolean stampBinaries() { + return options.stampBinaries; + } + + /** + * Returns true if extended sanity checks should be enabled. + */ + public boolean extendedSanityChecks() { + return options.extendedSanityChecks; + } + + /** + * Returns true if we are building runfiles symlinks for this configuration. + */ + public boolean buildRunfiles() { + return options.buildRunfiles; + } + + public boolean getCheckFilesetDependenciesRecursively() { + return options.checkFilesetDependenciesRecursively; + } + + public List<String> getTestArguments() { + return options.testArguments; + } + + public String getTestFilter() { + return options.testFilter; + } + + /** + * Returns user-specified test environment variables and their values, as + * set by the --test_env options. + */ + public Map<String, String> getTestEnv() { + return getTestEnv(options.testEnvironment, clientEnvironment); + } + + /** + * Returns user-specified test environment variables and their values, as + * set by the --test_env options. + * + * @param envOverrides The --test_env flag values. + * @param clientEnvironment The full client environment. + */ + public static Map<String, String> getTestEnv(List<Map.Entry<String, String>> envOverrides, + Map<String, String> clientEnvironment) { + Map<String, String> testEnv = new HashMap<>(); + for (Map.Entry<String, String> var : envOverrides) { + if (var.getValue() != null) { + testEnv.put(var.getKey(), var.getValue()); + } else { + String value = clientEnvironment.get(var.getKey()); + if (value != null) { + testEnv.put(var.getKey(), value); + } + } + } + return testEnv; + } + + public TriState cacheTestResults() { + return options.cacheTestResults; + } + + public int getMinParamFileSize() { + return options.minParamFileSize; + } + + @SkylarkCallable(name = "coverage_enabled", structField = true, + doc = "A boolean that tells whether code coverage is enabled.") + public boolean isCodeCoverageEnabled() { + return options.collectCodeCoverage; + } + + public boolean isMicroCoverageEnabled() { + return options.collectMicroCoverage; + } + + public boolean isActionsEnabled() { + return actionsEnabled; + } + + public TestActionBuilder.TestShardingStrategy testShardingStrategy() { + return options.testShardingStrategy; + } + + /** + * @return number of times the given test should run. + * If the test doesn't match any of the filters, runs it once. + */ + public int getRunsPerTestForLabel(Label label) { + for (PerLabelOptions perLabelRuns : options.runsPerTest) { + if (perLabelRuns.isIncluded(label)) { + return Integer.parseInt(Iterables.getOnlyElement(perLabelRuns.getOptions())); + } + } + return 1; + } + + public RunUnder getRunUnder() { + return options.runUnder; + } + + /** + * Returns true if this is a host configuration. + */ + public boolean isHostConfiguration() { + return options.isHost; + } + + public boolean checkVisibility() { + return options.checkVisibility; + } + + public boolean checkLicenses() { + return options.checkLicenses; + } + + public boolean enforceConstraints() { + return options.enforceConstraints; + } + + public List<Label> getActionListeners() { + return actionsEnabled ? options.actionListeners : ImmutableList.<Label>of(); + } + + /** + * Returns compilation mode. + */ + public CompilationMode getCompilationMode() { + return options.compilationMode; + } + + /** + * Helper method to create a key from the BuildConfiguration initialization + * parameters and any additional component suppliers. + */ + static String computeCacheKey(BlazeDirectories directories, + Map<Class<? extends Fragment>, Fragment> fragments, + BuildOptions buildOptions, Map<String, String> clientEnv) { + + // Creates a full fingerprint of all constructor parameters, used for + // canonicalization. + // + // Note the use of each Path's FileSystem field; the test suite creates + // many paths of equal name but belonging to distinct filesystems, so we + // have to detect this. (Note however that we're relying on the + // injectiveness of identityHashCode for FileSystem, which is inelegant, + // but only affects the tests, since the production code uses only one + // instance.) + + ImmutableList.Builder<String> keys = ImmutableList.builder(); + + // NOTE: identityHashCode isn't sound; may cause tests to fail. + keys.add(String.valueOf(System.identityHashCode(directories.getOutputBase().getFileSystem()))); + keys.add(directories.getOutputBase().toString()); + keys.add(buildOptions.computeCacheKey()); + // This is needed so that if we have --test_env=VAR, the configuration key is updated if the + // environment variable VAR is updated. + keys.add(BuildConfiguration.getTestEnv( + buildOptions.get(Options.class).testEnvironment, clientEnv).toString()); + keys.add(directories.getWorkspace().toString()); + + for (Fragment fragment : fragments.values()) { + keys.add(fragment.cacheKey()); + } + + // TODO(bazel-team): add hash of the FDO/LIPO profile file to config cache key + + return StringUtilities.combineKeys(keys.build()); + } + + /** + * Returns a string that identifies the configuration. + * + * <p>The string uniquely identifies the configuration. As a result, it can be rather long and + * include spaces and other non-alphanumeric characters. If you need a shorter key, use + * {@link #shortCacheKey()}. + * + * @see #computeCacheKey + */ + public final String cacheKey() { + return cacheKey; + } + + /** + * Returns a (relatively) short key that identifies the configuration. + * + * <p>The short key is the short name of the configuration concatenated with a hash of the + * {@link #cacheKey()}. + */ + public final String shortCacheKey() { + return shortCacheKey; + } + + /** Returns a copy of the build configuration options for this configuration. */ + public BuildOptions cloneOptions() { + return buildOptions.clone(); + } + + /** + * Prepare the fdo support. It reads data into memory that is used during analysis. The analysis + * phase is generally not allowed to perform disk I/O. This code is here because it is + * conceptually part of the analysis phase, and it needs to happen when the loading phase is + * complete. + */ + public void prepareToBuild(Path execRoot, ArtifactFactory artifactFactory, + PackageRootResolver resolver) throws ViewCreationFailedException { + for (Fragment fragment : fragments.values()) { + fragment.prepareHook(execRoot, artifactFactory, getGenfilesFragment(), resolver); + } + } + + /** + * Declares dependencies on any relevant Skyframe values (for example, relevant FileValues). + */ + public void declareSkyframeDependencies(SkyFunction.Environment env) { + for (Fragment fragment : fragments.values()) { + fragment.declareSkyframeDependencies(env); + } + } + + /** + * Returns all the roots for this configuration. + */ + public List<Root> getRoots() { + List<Root> roots = new ArrayList<>(); + + // Configuration-specific roots. + roots.add(getBinDirectory()); + roots.add(getGenfilesDirectory()); + roots.add(getIncludeDirectory()); + roots.add(getMiddlemanDirectory()); + roots.add(getTestLogsDirectory()); + + // Fragment-defined roots + for (Fragment fragment : fragments.values()) { + fragment.addRoots(roots); + } + + return ImmutableList.copyOf(roots); + } + + public ListMultimap<String, Label> getAllLabels() { + return buildOptions.getAllLabels(); + } + + public String getCpu() { + return options.cpu; + } + + /** + * Returns true is incremental builds are supported with this configuration. + */ + public boolean supportsIncrementalBuild() { + for (Fragment fragment : fragments.values()) { + if (!fragment.supportsIncrementalBuild()) { + return false; + } + } + return true; + } + + /** + * Returns true if the configuration performs static linking. + */ + public boolean performsStaticLink() { + for (Fragment fragment : fragments.values()) { + if (fragment.performsStaticLink()) { + return true; + } + } + return false; + } + + /** + * Deletes temporary directories before execution phase. This is only called for + * target configuration. + */ + public void prepareForExecutionPhase() throws IOException { + for (Fragment fragment : fragments.values()) { + fragment.prepareForExecutionPhase(); + } + } + + /** + * Collects executables defined by fragments. + */ + private ImmutableMap<String, PathFragment> collectExecutables() { + ImmutableMap.Builder<String, PathFragment> builder = new ImmutableMap.Builder<>(); + for (Fragment fragment : fragments.values()) { + fragment.defineExecutables(builder); + } + return builder.build(); + } + + /** + * See {@code BuildConfigurationCollection.Transitions.getArtifactOwnerConfiguration()}. + */ + public BuildConfiguration getArtifactOwnerConfiguration() { + return transitions.getArtifactOwnerConfiguration(); + } + + /** + * @return whether proto header modules should be built. + */ + public boolean getProtoHeaderModules() { + return options.protoHeaderModules; + } + + /** + * @return the list of default features used for all packages. + */ + public List<String> getDefaultFeatures() { + return options.defaultFeatures; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationCollection.java new file mode 100644 index 0000000..e36a681 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationCollection.java
@@ -0,0 +1,276 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; +import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; +import com.google.devtools.build.lib.packages.Attribute; +import com.google.devtools.build.lib.packages.Attribute.SplitTransition; +import com.google.devtools.build.lib.packages.Attribute.Transition; +import com.google.devtools.build.lib.packages.InputFile; +import com.google.devtools.build.lib.packages.PackageGroup; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.Target; + +import java.io.PrintStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * The primary container for all main {@link BuildConfiguration} instances, + * currently "target", "data", and "host". + * + * <p>The target configuration is used for all targets specified on the command + * line. Data dependencies of targets in the target configuration use the data + * configuration instead. + * + * <p>The host configuration is used for tools that are executed during the + * build, e. g, compilers. + * + * <p>The "related" configurations are also contained in this class. + */ +@ThreadSafe +public final class BuildConfigurationCollection implements Serializable { + private final ImmutableList<BuildConfiguration> targetConfigurations; + + public BuildConfigurationCollection(List<BuildConfiguration> targetConfigurations) + throws InvalidConfigurationException { + this.targetConfigurations = ImmutableList.copyOf(targetConfigurations); + + // Except for the host configuration (which may be identical across target configs), the other + // configurations must all have different cache keys or we will end up with problems. + HashMap<String, BuildConfiguration> cacheKeyConflictDetector = new HashMap<>(); + for (BuildConfiguration config : getAllConfigurations()) { + if (cacheKeyConflictDetector.containsKey(config.cacheKey())) { + throw new InvalidConfigurationException("Conflicting configurations: " + config + " & " + + cacheKeyConflictDetector.get(config.cacheKey())); + } + cacheKeyConflictDetector.put(config.cacheKey(), config); + } + } + + /** + * Creates an empty configuration collection which will return null for everything. + */ + public BuildConfigurationCollection() { + this.targetConfigurations = ImmutableList.of(); + } + + public static BuildConfiguration configureTopLevelTarget(BuildConfiguration topLevelConfiguration, + Target toTarget) { + if (toTarget instanceof InputFile || toTarget instanceof PackageGroup) { + return null; + } + return topLevelConfiguration.getTransitions().toplevelConfigurationHook(toTarget); + } + + public ImmutableList<BuildConfiguration> getTargetConfigurations() { + return targetConfigurations; + } + + /** + * Returns all configurations that can be reached from the target configuration through any kind + * of configuration transition. + */ + public Collection<BuildConfiguration> getAllConfigurations() { + Set<BuildConfiguration> result = new LinkedHashSet<>(); + for (BuildConfiguration config : targetConfigurations) { + result.addAll(config.getAllReachableConfigurations()); + } + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof BuildConfigurationCollection)) { + return false; + } + BuildConfigurationCollection that = (BuildConfigurationCollection) obj; + return this.targetConfigurations.equals(that.targetConfigurations); + } + + @Override + public int hashCode() { + return targetConfigurations.hashCode(); + } + + /** + * Prints the configuration graph in dot format to the given print stream. This is only intended + * for debugging. + */ + public void dumpAsDotGraph(PrintStream out) { + out.println("digraph g {"); + out.println(" ratio = 0.3;"); + for (BuildConfiguration config : getAllConfigurations()) { + String from = config.shortCacheKey(); + for (Map.Entry<? extends Transition, ConfigurationHolder> entry : + config.getTransitions().getTransitionTable().entrySet()) { + BuildConfiguration toConfig = entry.getValue().getConfiguration(); + if (toConfig == config) { + continue; + } + String to = toConfig == null ? "ERROR" : toConfig.shortCacheKey(); + out.println(" \"" + from + "\" -> \"" + to + "\" [label=\"" + entry.getKey() + "\"]"); + } + } + out.println("}"); + } + + /** + * The outgoing transitions for a build configuration. + */ + public static class Transitions implements Serializable { + protected final BuildConfiguration configuration; + + /** + * Look up table for the configuration transitions, i.e., HOST, DATA, etc. + */ + private final Map<? extends Transition, ConfigurationHolder> transitionTable; + + // TODO(bazel-team): Consider merging transitionTable into this. + private final ListMultimap<? super SplitTransition<?>, BuildConfiguration> splitTransitionTable; + + public Transitions(BuildConfiguration configuration, + Map<? extends Transition, ConfigurationHolder> transitionTable, + ListMultimap<? extends SplitTransition<?>, BuildConfiguration> splitTransitionTable) { + this.configuration = configuration; + this.transitionTable = ImmutableMap.copyOf(transitionTable); + this.splitTransitionTable = ImmutableListMultimap.copyOf(splitTransitionTable); + } + + public Transitions(BuildConfiguration configuration, + Map<? extends Transition, ConfigurationHolder> transitionTable) { + this(configuration, transitionTable, + ImmutableListMultimap.<SplitTransition<?>, BuildConfiguration>of()); + } + + public Map<? extends Transition, ConfigurationHolder> getTransitionTable() { + return transitionTable; + } + + public ListMultimap<? super SplitTransition<?>, BuildConfiguration> getSplitTransitionTable() { + return splitTransitionTable; + } + + public List<BuildConfiguration> getSplitConfigurations(SplitTransition<?> transition) { + if (splitTransitionTable.containsKey(transition)) { + return splitTransitionTable.get(transition); + } else { + Preconditions.checkState(transition.defaultsToSelf()); + return ImmutableList.of(configuration); + } + } + + /** + * Adds all configurations that are directly reachable from this configuration through + * any kind of configuration transition. + */ + public void addDirectlyReachableConfigurations(Collection<BuildConfiguration> queue) { + for (ConfigurationHolder holder : transitionTable.values()) { + if (holder.configuration != null) { + queue.add(holder.configuration); + } + } + queue.addAll(splitTransitionTable.values()); + } + + /** + * Artifacts need an owner in Skyframe. By default it's the same configuration as what + * the configured target has, but it can be overridden if necessary. + * + * @return the artifact owner configuration + */ + public BuildConfiguration getArtifactOwnerConfiguration() { + return configuration; + } + + /** + * Returns the new configuration after traversing a dependency edge with a + * given configuration transition. + * + * @param configurationTransition the configuration transition + * @return the new configuration + */ + public BuildConfiguration getConfiguration(Transition configurationTransition) { + ConfigurationHolder holder = transitionTable.get(configurationTransition); + if (holder == null && configurationTransition.defaultsToSelf()) { + return configuration; + } + return holder.configuration; + } + + /** + * Arbitrary configuration transitions can be implemented by overriding this hook. + */ + @SuppressWarnings("unused") + public BuildConfiguration configurationHook(Rule fromTarget, + Attribute attribute, Target toTarget, BuildConfiguration toConfiguration) { + return toConfiguration; + } + + /** + * Associating configurations to top-level targets can be implemented by overriding this hook. + */ + @SuppressWarnings("unused") + public BuildConfiguration toplevelConfigurationHook(Target toTarget) { + return configuration; + } + } + + /** + * A holder class for {@link BuildConfiguration} instances that allows {@code null} values, + * because none of the Table implementations allow them. + */ + public static final class ConfigurationHolder implements Serializable { + private final BuildConfiguration configuration; + + public ConfigurationHolder(BuildConfiguration configuration) { + this.configuration = configuration; + } + + public BuildConfiguration getConfiguration() { + return configuration; + } + + @Override + public int hashCode() { + return configuration == null ? 0 : configuration.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof ConfigurationHolder)) { + return false; + } + return Objects.equals(configuration, ((ConfigurationHolder) o).configuration); + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationKey.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationKey.java new file mode 100644 index 0000000..e8fcf34 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationKey.java
@@ -0,0 +1,92 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.ListMultimap; +import com.google.devtools.build.lib.analysis.BlazeDirectories; +import com.google.devtools.build.lib.syntax.Label; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * A key for the creation of {@link BuildConfigurationCollection} instances. + */ +public final class BuildConfigurationKey { + + private final BuildOptions buildOptions; + private final BlazeDirectories directories; + private final Map<String, String> clientEnv; + private final ImmutableSortedSet<String> multiCpu; + + /** + * Creates a key for the creation of {@link BuildConfigurationCollection} instances. + * + * Note that the BuildConfiguration.Options instance must not contain unresolved relative paths. + */ + public BuildConfigurationKey(BuildOptions buildOptions, BlazeDirectories directories, + Map<String, String> clientEnv, Set<String> multiCpu) { + this.buildOptions = Preconditions.checkNotNull(buildOptions); + this.directories = Preconditions.checkNotNull(directories); + this.clientEnv = ImmutableMap.copyOf(clientEnv); + this.multiCpu = ImmutableSortedSet.copyOf(multiCpu); + } + + public BuildConfigurationKey(BuildOptions buildOptions, BlazeDirectories directories, + Map<String, String> clientEnv) { + this(buildOptions, directories, clientEnv, ImmutableSet.<String>of()); + } + + public BuildOptions getBuildOptions() { + return buildOptions; + } + + public BlazeDirectories getDirectories() { + return directories; + } + + public Map<String, String> getClientEnv() { + return clientEnv; + } + + public ImmutableSortedSet<String> getMultiCpu() { + return multiCpu; + } + + public ListMultimap<String, Label> getLabelsToLoadUnconditionally() { + return buildOptions.getAllLabels(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof BuildConfigurationKey)) { + return false; + } + BuildConfigurationKey k = (BuildConfigurationKey) o; + return buildOptions.equals(k.buildOptions) + && directories.equals(k.directories) + && clientEnv.equals(k.clientEnv) + && multiCpu.equals(k.multiCpu); + } + + @Override + public int hashCode() { + return Objects.hash(buildOptions, directories, clientEnv, multiCpu); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java new file mode 100644 index 0000000..afe408f --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
@@ -0,0 +1,254 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ListMultimap; +import com.google.devtools.build.lib.packages.Attribute.SplitTransition; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.common.options.Options; +import com.google.devtools.common.options.OptionsBase; +import com.google.devtools.common.options.OptionsClassProvider; +import com.google.devtools.common.options.OptionsParser; +import com.google.devtools.common.options.OptionsParsingException; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * This is a collection of command-line options from all configuration fragments. Contains + * a single instance for all FragmentOptions classes provided by Blaze language modules. + */ +public final class BuildOptions implements Cloneable, Serializable { + /** + * Creates a BuildOptions object with all options set to its default value. + */ + public static BuildOptions createDefaults(Iterable<Class<? extends FragmentOptions>> options) { + Builder builder = builder(); + for (Class<? extends FragmentOptions> optionsClass : options) { + builder.add(Options.getDefaults(optionsClass)); + } + return builder.build(); + } + + /** + * This function creates a new BuildOptions instance for host. + * + * @param fallback if true, we have already tried the user specified hostCpu options + * and it didn't work, so now we try the default options instead. + */ + public BuildOptions createHostOptions(boolean fallback) { + Builder builder = builder(); + for (FragmentOptions options : fragmentOptionsMap.values()) { + builder.add(options.getHost(fallback)); + } + return builder.build(); + } + + /** + * Returns a list of potential split configuration transitions by calling {@link + * FragmentOptions#getPotentialSplitTransitions} on all the fragments. + */ + public List<SplitTransition<BuildOptions>> getPotentialSplitTransitions() { + List<SplitTransition<BuildOptions>> result = new ArrayList<>(); + for (FragmentOptions options : fragmentOptionsMap.values()) { + result.addAll(options.getPotentialSplitTransitions()); + } + return result; + } + + /** + * Creates an BuildOptions class by taking the option values from an options provider + * (eg. an OptionsParser). + */ + public static BuildOptions of(List<Class<? extends FragmentOptions>> optionsList, + OptionsClassProvider provider) { + Builder builder = builder(); + for (Class<? extends FragmentOptions> optionsClass : optionsList) { + builder.add(provider.getOptions(optionsClass)); + } + return builder.build(); + } + + /** + * Creates an BuildOptions class by taking the option values from command-line arguments + */ + @VisibleForTesting + public static BuildOptions of(List<Class<? extends FragmentOptions>> optionsList, String... args) + throws OptionsParsingException { + Builder builder = builder(); + OptionsParser parser = OptionsParser.newOptionsParser( + ImmutableList.<Class<? extends OptionsBase>>copyOf(optionsList)); + parser.parse(args); + for (Class<? extends FragmentOptions> optionsClass : optionsList) { + builder.add(parser.getOptions(optionsClass)); + } + return builder.build(); + } + + /** + * Returns the actual instance of a FragmentOptions class. + */ + @SuppressWarnings("unchecked") + public <T extends FragmentOptions> T get(Class<T> optionsClass) { + FragmentOptions options = fragmentOptionsMap.get(optionsClass); + Preconditions.checkNotNull(options); + Preconditions.checkArgument(optionsClass.isAssignableFrom(options.getClass())); + return (T) options; + } + + /** + * Returns a multimap of all labels that were specified as options, keyed by the name to be + * displayed to the user if something goes wrong. This should be the set of all labels + * mentioned in explicit command line options that are not already covered by the + * tools/defaults package (see the DefaultsPackage class), and nothing else. + */ + public ListMultimap<String, Label> getAllLabels() { + ListMultimap<String, Label> labels = ArrayListMultimap.create(); + for (FragmentOptions optionsBase : fragmentOptionsMap.values()) { + optionsBase.addAllLabels(labels); + } + return labels; + } + + // It would be very convenient to use a Multimap here, but we cannot do that because we need to + // support defaults labels that have zero elements. + ImmutableMap<String, ImmutableSet<Label>> getDefaultsLabels() { + BuildConfiguration.Options opts = get(BuildConfiguration.Options.class); + Map<String, Set<Label>> collector = new TreeMap<>(); + for (FragmentOptions fragment : fragmentOptionsMap.values()) { + for (Map.Entry<String, Set<Label>> entry : fragment.getDefaultsLabels(opts).entrySet()) { + if (!collector.containsKey(entry.getKey())) { + collector.put(entry.getKey(), new TreeSet<Label>()); + } + collector.get(entry.getKey()).addAll(entry.getValue()); + } + } + + ImmutableMap.Builder<String, ImmutableSet<Label>> result = new ImmutableMap.Builder<>(); + for (Map.Entry<String, Set<Label>> entry : collector.entrySet()) { + result.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue())); + } + + return result.build(); + } + + /** + * The cache key for the options collection. Recomputes cache key every time it's called. + */ + public String computeCacheKey() { + StringBuilder keyBuilder = new StringBuilder(); + for (FragmentOptions options : fragmentOptionsMap.values()) { + keyBuilder.append(options.cacheKey()); + } + return keyBuilder.toString(); + } + + /** + * String representation of build options. + */ + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + for (FragmentOptions options : fragmentOptionsMap.values()) { + stringBuilder.append(options.toString()); + } + return stringBuilder.toString(); + } + + /** + * Returns the options contained in this collection. + */ + public Iterable<FragmentOptions> getOptions() { + return fragmentOptionsMap.values(); + } + + /** + * Creates a copy of the BuildOptions object that contains copies of the FragmentOptions. + */ + @Override + public BuildOptions clone() { + ImmutableMap.Builder<Class<? extends FragmentOptions>, FragmentOptions> builder = + ImmutableMap.builder(); + for (Map.Entry<Class<? extends FragmentOptions>, FragmentOptions> entry : + fragmentOptionsMap.entrySet()) { + builder.put(entry.getKey(), entry.getValue().clone()); + } + return new BuildOptions(builder.build()); + } + + @Override + public boolean equals(Object other) { + return (this == other) || (other instanceof BuildOptions && + fragmentOptionsMap.equals(((BuildOptions) other).fragmentOptionsMap)); + } + + @Override + public int hashCode() { + return fragmentOptionsMap.hashCode(); + } + + /** + * Maps options class definitions to FragmentOptions objects + */ + private final ImmutableMap<Class<? extends FragmentOptions>, FragmentOptions> fragmentOptionsMap; + + private BuildOptions( + ImmutableMap<Class<? extends FragmentOptions>, FragmentOptions> fragmentOptionsMap) { + this.fragmentOptionsMap = fragmentOptionsMap; + } + + /** + * Creates a builder object for BuildOptions + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for BuildOptions. + */ + public static class Builder { + /** + * Adds a new FragmentOptions instance to the builder. Overrides previous instances of the + * exact same subclass of FragmentOptions. + */ + public <T extends FragmentOptions> Builder add(T options) { + builderMap.put(options.getClass(), options); + return this; + } + + public BuildOptions build() { + return new BuildOptions(ImmutableMap.copyOf(builderMap)); + } + + private Map<Class<? extends FragmentOptions>, FragmentOptions> builderMap; + + private Builder() { + builderMap = new HashMap<>(); + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/CompilationMode.java b/src/main/java/com/google/devtools/build/lib/analysis/config/CompilationMode.java new file mode 100644 index 0000000..7f24351 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/CompilationMode.java
@@ -0,0 +1,50 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.devtools.common.options.EnumConverter; + +/** + * This class represents the debug/optimization mode the binaries will be + * built for. + */ +public enum CompilationMode { + + // Fast build mode (-g0). + FASTBUILD("fastbuild"), + // Debug mode (-g). + DBG("dbg"), + // Release mode (-g0 -O2 -DNDEBUG). + OPT("opt"); + + private final String mode; + + private CompilationMode(String mode) { + this.mode = mode; + } + + @Override + public String toString() { + return mode; + } + + /** + * Converts to {@link CompilationMode}. + */ + public static class Converter extends EnumConverter<CompilationMode> { + public Converter() { + super(CompilationMode.class, "compilation mode"); + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigMatchingProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigMatchingProvider.java new file mode 100644 index 0000000..c719191 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigMatchingProvider.java
@@ -0,0 +1,54 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.devtools.build.lib.analysis.TransitiveInfoProvider; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.syntax.Label; + +/** + * A "configuration target" that asserts whether or not it matches the + * configuration it's bound to. + * + * <p>This can be used, e.g., to declare a BUILD target that defines the + * conditions which trigger a configurable attribute branch. In general, + * this can be used to trigger for any user-configurable build behavior. + */ +@Immutable +public final class ConfigMatchingProvider implements TransitiveInfoProvider { + + private final Label label; + private final boolean matches; + + public ConfigMatchingProvider(Label label, boolean matches) { + this.label = label; + this.matches = matches; + } + + /** + * The target's label. + */ + public Label label() { + return label; + } + + /** + * Whether or not the configuration criteria defined by this target match + * its actual configuration. + */ + public boolean matches() { + return matches; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigRuleClasses.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigRuleClasses.java new file mode 100644 index 0000000..6ee4cce --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigRuleClasses.java
@@ -0,0 +1,204 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import static com.google.devtools.build.lib.packages.Attribute.attr; +import static com.google.devtools.build.lib.packages.Type.STRING_DICT; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.analysis.BaseRuleClasses; +import com.google.devtools.build.lib.analysis.BlazeRule; +import com.google.devtools.build.lib.analysis.RuleDefinition; +import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.Type; + +/** + * Definitions for rule classes that specify or manipulate configuration settings. + * + * <p>These are not "traditional" rule classes in that they can't be requested as top-level + * targets and don't translate input artifacts into output artifacts. Instead, they affect + * how *other* rules work. See individual class comments for details. + */ +public class ConfigRuleClasses { + + private static final String NONCONFIGURABLE_ATTRIBUTE_REASON = + "part of a rule class that *triggers* configurable behavior"; + + /** + * Common settings for all configurability rules. + */ + @BlazeRule(name = "$config_base_rule", + type = RuleClass.Builder.RuleClassType.ABSTRACT, + ancestors = { BaseRuleClasses.BaseRule.class }) + public static final class ConfigBaseRule implements RuleDefinition { + @Override + public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { + return builder + .override(attr("tags", Type.STRING_LIST) + // No need to show up in ":all", etc. target patterns. + .value(ImmutableList.of("manual")) + .nonconfigurable(NONCONFIGURABLE_ATTRIBUTE_REASON)) + .build(); + } + } + + /** + * A named "partial configuration setting" that specifies a set of command-line + * "flag=value" bindings. + * + * <p>For example: + * <pre> + * config_setting( + * name = 'foo', + * values = { + * 'flag1': 'aValue' + * 'flag2': 'bValue' + * }) + * </pre> + * + * <p>declares a setting that binds command-line flag <pre>flag1</pre> to value + * <pre>aValue</pre> and <pre>flag2</pre> to <pre>bValue</pre>. + * + * <p>This is used by configurable attributes to determine which branch to + * follow based on which <pre>config_setting</pre> instance matches all its + * flag values in the configurable attribute owner's configuration. + * + * <p>This rule isn't accessed through the standard {@link RuleContext#getPrerequisites} + * interface. This is because Bazel constructs a rule's configured attribute map *before* + * its {@link RuleContext} is created (in fact, the map is an input to the context's + * constructor). And the config_settings referenced by the rule's configurable attributes are + * themselves inputs to that map. So Bazel has special logic to read and properly apply + * config_setting instances. See {@link ConfiguredTargetFunction#getConfigConditions} for details. + */ + @BlazeRule(name = "config_setting", + type = RuleClass.Builder.RuleClassType.NORMAL, + ancestors = { ConfigBaseRule.class }, + factoryClass = ConfigSetting.class) + public static final class ConfigSettingRule implements RuleDefinition { + /** + * The name of the attribute that declares flag bindings. + */ + public static final String SETTINGS_ATTRIBUTE = "values"; + + @Override + public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { + return builder + /* <!-- #BLAZE_RULE(config_setting).ATTRIBUTE(values) --> + The set of configuration values that match this rule (expressed as Blaze flags) + + <i>(Dictionary mapping flags to expected values, both expressed as strings; + mandatory)</i> + + <p>This rule inherits the configuration of the configured target that + references it in a <code>select</code> statement. It is considered to + "match" a Blaze invocation if, for every entry in the dictionary, its + configuration matches the entry's expected value. For example + <code>values = {"compilation_mode": "opt"}</code> matches the invocations + <code>blaze build --compilation_mode=opt ...</code> and + <code>blaze build -c opt ...</code> on target-configured rules. + </p> + + <p>For convenience's sake, configuration values are specified as Blaze flags (without + the preceding <code>"--"</code>). But keep in mind that the two are not the same. This + is because targets can be built in multiple configurations within the same + build. For example, a host configuration's "cpu" matches the value of + <code>--host_cpu</code>, not <code>--cpu</code>. So different instances of the + same <code>config_setting</code> may match the same invocation differently + depending on the configuration of the rule using them. + </p> + + <p>If a flag is not explicitly set at the command line, its default value is used. + If a key appears multiple times in the dictionary, only the last instance is used. + If a key references a flag that can be set multiple times on the command line (e.g. + <code>blaze build --copt=foo --copt=bar --copt=baz ...</code>), a match occurs if + *any* of those settings match. + <p> + + <p>This attribute cannot be empty. + </p> + <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ + .add(attr(SETTINGS_ATTRIBUTE, STRING_DICT).mandatory() + .nonconfigurable(NONCONFIGURABLE_ATTRIBUTE_REASON)) + .build(); + } + } + +/*<!-- #BLAZE_RULE (NAME = config_setting, TYPE = OTHER, FAMILY = General)[GENERIC_RULE] --> + +${ATTRIBUTE_SIGNATURE} + +<p> + Matches an expected configuration state (expressed as Blaze flags) for the purpose of triggering + configurable attributes. See <a href="#select">select</a> for how to consume this rule and + <a href="#configurable-attributes">Configurable attributes</a> for an overview of + the general feature. + +${ATTRIBUTE_DEFINITION} + +<h4 id="config_setting_examples">Examples</h4> + +<p>The following matches any Blaze invocation that specifies <code>--compilation_mode=opt</code> + or <code>-c opt</code> (either explicitly at the command line or implicitly from .blazerc + files, etc.), when applied to a target configuration rule: +</p> + +<pre class="code"> +config_setting( + name = "simple", + values = {"compilation_mode": "opt"} +) +</pre> + +<p>The following matches any Blaze invocation that builds for ARM and applies a custom define + (e.g. <code>blaze build --cpu=armeabi --define FOO=bar ...</code>), , when applied to a target + configuration rule: +</p> + +<pre class="code"> +config_setting( + name = "two_conditions", + values = { + "cpu": "armeabi", + "define": "FOO=bar" + } +) +</pre> + +<h4 id="config_setting_notes">Notes</h4> + +<p>See <a href="#select">select</a> for policies on what happens depending on how many rules match + an invocation. +</p> + +<p>For flags that support shorthand forms (e.g. <code>--compilation_mode</code> vs. + <code>-c</code>), <code>values</code> definitions must use the full form. These automatically + match invocations using either form. +</p> + +<p>The currently endorsed method for creating custom conditions that can't be expressed through + dedicated build flags is through the --define flag. Use this flag with caution: it's not ideal + and only endorsed for lack of a currently better workaround. See the + <a href="#configurable-attributes">Configurable attributes</a> section for more discussion. +</p> + +<p>Try to consolidate <code>config_setting</code> definitions as much as possible. In other words, + define <code>//common/conditions:foo</code> in one common package instead of repeating separate + instances in <code>//project1:foo</code>, <code>//project2:foo</code>, etc. that all mean the + same thing. +</p> + +<!-- #END_BLAZE_RULE -->*/ +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigSetting.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigSetting.java new file mode 100644 index 0000000..97ad4ff --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigSetting.java
@@ -0,0 +1,170 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.FileProvider; +import com.google.devtools.build.lib.analysis.FilesToRunProvider; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.RunfilesProvider; +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.NonconfigurableAttributeMapper; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; +import com.google.devtools.common.options.OptionsBase; +import com.google.devtools.common.options.OptionsParser; +import com.google.devtools.common.options.OptionsParsingException; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implementation for the config_setting rule. + * + * <p>This is a "pseudo-rule" in that its purpose isn't to generate output artifacts + * from input artifacts. Rather, it provides configuration context to rules that + * depend on it. + */ +public class ConfigSetting implements RuleConfiguredTargetFactory { + + @Override + public ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException { + // Get the required flag=value settings for this rule. + Map<String, String> settings = NonconfigurableAttributeMapper.of(ruleContext.getRule()) + .get(ConfigRuleClasses.ConfigSettingRule.SETTINGS_ATTRIBUTE, Type.STRING_DICT); + if (settings.isEmpty()) { + ruleContext.attributeError(ConfigRuleClasses.ConfigSettingRule.SETTINGS_ATTRIBUTE, + "no settings specified"); + return null; + } + + ConfigMatchingProvider configMatcher; + try { + configMatcher = new ConfigMatchingProvider(ruleContext.getLabel(), + matchesConfig(settings, ruleContext.getConfiguration())); + } catch (OptionsParsingException e) { + ruleContext.attributeError(ConfigRuleClasses.ConfigSettingRule.SETTINGS_ATTRIBUTE, + "error while parsing configuration settings: " + e.getMessage()); + return null; + } + + return new RuleConfiguredTargetBuilder(ruleContext) + .add(RunfilesProvider.class, RunfilesProvider.EMPTY) + .add(FileProvider.class, new FileProvider(ruleContext.getLabel(), + NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER))) + .add(FilesToRunProvider.class, new FilesToRunProvider(ruleContext.getLabel(), + ImmutableList.<Artifact>of(), null, null)) + .add(ConfigMatchingProvider.class, configMatcher) + .build(); + } + + /** + * Given a list of [flagName, flagValue] pairs, returns true if flagName == flagValue for + * every item in the list under this configuration, false otherwise. + */ + private boolean matchesConfig(Map<String, String> expectedSettings, BuildConfiguration config) + throws OptionsParsingException { + // Rather than returning fast when we find a mismatch, continue looking at the other flags + // to check that they're indeed valid flag specifications. + boolean foundMismatch = false; + + // Since OptionsParser instantiation involves reflection, let's try to minimize that happening. + Map<Class<? extends OptionsBase>, OptionsParser> parserCache = new HashMap<>(); + + for (Map.Entry<String, String> setting : expectedSettings.entrySet()) { + String optionName = setting.getKey(); + String expectedRawValue = setting.getValue(); + + Class<? extends OptionsBase> optionClass = config.getOptionClass(optionName); + if (optionClass == null) { + throw new OptionsParsingException("unknown option: '" + optionName + "'"); + } + + OptionsParser parser = parserCache.get(optionClass); + if (parser == null) { + parser = OptionsParser.newOptionsParser(optionClass); + parserCache.put(optionClass, parser); + } + parser.parse("--" + optionName + "=" + expectedRawValue); + Object expectedParsedValue = parser.getOptions(optionClass).asMap().get(optionName); + + if (!optionMatches(config, optionName, expectedParsedValue)) { + foundMismatch = true; + } + } + return !foundMismatch; + } + + /** + * For single-value options, returns true iff the option's value matches the expected value. + * + * <p>For multi-value List options, returns true iff any of the option's values matches + * the expected value. This means, e.g. "--tool_tag=foo --tool_tag=bar" would match the + * expected condition { 'tool_tag': 'bar' }. + * + * <p>For multi-value Map options, returns true iff the last instance with the same key as the + * expected key has the same value. This means, e.g. "--define foo=1 --define bar=2" would + * match { 'define': 'foo=1' }, but "--define foo=1 --define bar=2 --define foo=3" would not + * match. Note that the definition of --define states that the last instance takes precedence. + */ + private static boolean optionMatches(BuildConfiguration config, String optionName, + Object expectedValue) { + Object actualValue = config.getOptionValue(optionName); + if (actualValue == null) { + return expectedValue == null; + + // Single-value case: + } else if (!config.allowsMultipleValues(optionName)) { + return actualValue.equals(expectedValue); + } + + // Multi-value case: + Preconditions.checkState(actualValue instanceof List); + Preconditions.checkState(expectedValue instanceof List); + List<?> actualList = (List<?>) actualValue; + List<?> expectedList = (List<?>) expectedValue; + + if (actualList.isEmpty() || expectedList.isEmpty()) { + return actualList.isEmpty() && expectedList.isEmpty(); + } + + // We're expecting a single value of a multi-value type: the options parser still embeds + // that single value within a List container. Retrieve it here. + Object expectedSingleValue = Iterables.getOnlyElement(expectedList); + + // Multi-value map: + if (actualList.get(0) instanceof Map.Entry) { + Map.Entry<?, ?> expectedEntry = (Map.Entry<?, ?>) expectedSingleValue; + for (Map.Entry<?, ?> actualEntry : Lists.reverse((List<Map.Entry<?, ?>>) actualList)) { + if (actualEntry.getKey().equals(expectedEntry.getKey())) { + // Found a key match! + return actualEntry.getValue().equals(expectedEntry.getValue()); + } + } + return false; // Never found any matching key. + } + + // Multi-value list: + return actualList.contains(expectedSingleValue); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationEnvironment.java new file mode 100644 index 0000000..89722b5 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationEnvironment.java
@@ -0,0 +1,96 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.devtools.build.lib.analysis.BlazeDirectories; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment; +import com.google.devtools.build.lib.packages.NoSuchPackageException; +import com.google.devtools.build.lib.packages.NoSuchTargetException; +import com.google.devtools.build.lib.packages.Package; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.pkgcache.LoadedPackageProvider; +import com.google.devtools.build.lib.pkgcache.PackageProvider; +import com.google.devtools.build.lib.pkgcache.TargetProvider; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.vfs.Path; + +import javax.annotation.Nullable; + +/** + * An environment to support creating BuildConfiguration instances in a hermetic fashion; all + * accesses to packages or the file system <b>must</b> go through this interface, so that they can + * be recorded for correct caching. + */ +public interface ConfigurationEnvironment { + + /** + * Returns a target for the given label, loading it if necessary, and throwing an exception if it + * does not exist. + * + * @see TargetProvider#getTarget + */ + Target getTarget(Label label) throws NoSuchPackageException, NoSuchTargetException; + + /** Returns a path for the given file within the given package. */ + Path getPath(Package pkg, String fileName); + + /** Returns fragment based on fragment class and build options. */ + <T extends Fragment> T getFragment(BuildOptions buildOptions, Class<T> fragmentType) + throws InvalidConfigurationException; + + /** Returns global value of BlazeDirectories. */ + @Nullable + BlazeDirectories getBlazeDirectories(); + + /** + * An implementation backed by a {@link PackageProvider} instance. + */ + public static final class TargetProviderEnvironment implements ConfigurationEnvironment { + + private final LoadedPackageProvider loadedPackageProvider; + private final BlazeDirectories blazeDirectories; + + public TargetProviderEnvironment(LoadedPackageProvider loadedPackageProvider, + BlazeDirectories blazeDirectories) { + this.loadedPackageProvider = loadedPackageProvider; + this.blazeDirectories = blazeDirectories; + } + + public TargetProviderEnvironment(LoadedPackageProvider loadedPackageProvider) { + this.loadedPackageProvider = loadedPackageProvider; + this.blazeDirectories = null; + } + + @Override + public Target getTarget(Label label) throws NoSuchPackageException, NoSuchTargetException { + return loadedPackageProvider.getLoadedTarget(label); + } + + @Override + public Path getPath(Package pkg, String fileName) { + return pkg.getPackageDirectory().getRelative(fileName); + } + + @Override + public <T extends Fragment> T getFragment(BuildOptions buildOptions, Class<T> fragmentType) { + throw new UnsupportedOperationException(); + } + + @Override + public BlazeDirectories getBlazeDirectories() { + return blazeDirectories; + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationFactory.java new file mode 100644 index 0000000..13afb2a --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationFactory.java
@@ -0,0 +1,145 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedMap; +import com.google.devtools.build.lib.analysis.BlazeDirectories; +import com.google.devtools.build.lib.analysis.ConfigurationCollectionFactory; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment; +import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible; +import com.google.devtools.build.lib.events.EventHandler; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A factory class for {@link BuildConfiguration} instances. This is unfortunately more complex, + * and should be simplified in the future, if + * possible. Right now, creating a {@link BuildConfiguration} instance involves + * creating the instance itself and the related configurations; the main method + * is {@link #createConfiguration}. + * + * <p>Avoid calling into this class, and instead use the skyframe infrastructure to obtain + * configuration instances. + * + * <p>Blaze currently relies on the fact that all {@link BuildConfiguration} + * instances used in a build can be constructed ahead of time by this class. + */ +@ThreadCompatible // safe as long as separate instances are used +public final class ConfigurationFactory { + private final List<ConfigurationFragmentFactory> configurationFragmentFactories; + private final ConfigurationCollectionFactory configurationCollectionFactory; + + // A cache of key to configuration instances. + private final Cache<String, BuildConfiguration> hostConfigCache = + CacheBuilder.newBuilder().softValues().build(); + + private boolean performSanityCheck = true; + + public ConfigurationFactory( + ConfigurationCollectionFactory configurationCollectionFactory, + List<ConfigurationFragmentFactory> fragmentFactories) { + this.configurationCollectionFactory = + Preconditions.checkNotNull(configurationCollectionFactory); + this.configurationFragmentFactories = fragmentFactories; + } + + @VisibleForTesting + public void forbidSanityCheck() { + performSanityCheck = false; + } + + /** Create the build configurations with the given options. */ + @Nullable + public BuildConfiguration createConfiguration( + PackageProviderForConfigurations loadedPackageProvider, BuildOptions buildOptions, + BuildConfigurationKey key, EventHandler errorEventListener) + throws InvalidConfigurationException { + return configurationCollectionFactory.createConfigurations(this, + loadedPackageProvider, buildOptions, key.getClientEnv(), + errorEventListener, performSanityCheck); + } + + /** + * Returns a (possibly new) canonical host BuildConfiguration instance based + * upon a given request configuration + */ + @Nullable + public BuildConfiguration getHostConfiguration( + PackageProviderForConfigurations loadedPackageProvider, Map<String, String> clientEnv, + BuildOptions buildOptions, boolean fallback) throws InvalidConfigurationException { + return getConfiguration(loadedPackageProvider, buildOptions.createHostOptions(fallback), + clientEnv, false, hostConfigCache); + } + + /** + * The core of BuildConfiguration creation. All host and target instances are + * constructed and cached here. + */ + @Nullable + public BuildConfiguration getConfiguration(PackageProviderForConfigurations loadedPackageProvider, + BuildOptions buildOptions, Map<String, String> clientEnv, + boolean actionsDisabled, Cache<String, BuildConfiguration> cache) + throws InvalidConfigurationException { + + Map<Class<? extends Fragment>, Fragment> fragments = new HashMap<>(); + // Create configuration fragments + for (ConfigurationFragmentFactory factory : configurationFragmentFactories) { + Class<? extends Fragment> fragmentType = factory.creates(); + Fragment fragment = loadedPackageProvider.getFragment(buildOptions, fragmentType); + if (fragment != null && fragments.get(fragment) == null) { + fragments.put(fragment.getClass(), fragment); + } + } + BlazeDirectories directories = loadedPackageProvider.getDirectories(); + if (loadedPackageProvider.valuesMissing()) { + return null; + } + + // Sort the fragments by class name to make sure that the order is stable. Afterwards, copy to + // an ImmutableMap, which keeps the order stable, but uses hashing, and drops the reference to + // the Comparator object. + fragments = ImmutableSortedMap.copyOf(fragments, new Comparator<Class<? extends Fragment>>() { + @Override + public int compare(Class<? extends Fragment> o1, Class<? extends Fragment> o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + fragments = ImmutableMap.copyOf(fragments); + + String key = BuildConfiguration.computeCacheKey( + directories, fragments, buildOptions, clientEnv); + BuildConfiguration configuration = cache.getIfPresent(key); + if (configuration == null) { + configuration = new BuildConfiguration(directories, fragments, buildOptions, + clientEnv, actionsDisabled); + cache.put(key, configuration); + } + return configuration; + } + + public List<ConfigurationFragmentFactory> getFactories() { + return configurationFragmentFactories; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationFragmentFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationFragmentFactory.java new file mode 100644 index 0000000..8ca8f1c --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationFragmentFactory.java
@@ -0,0 +1,39 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment; + +import javax.annotation.Nullable; + +/** + * A factory that creates configuration fragments. + */ +public interface ConfigurationFragmentFactory { + /** + * Creates a configuration fragment. + * + * @param env the ConfigurationEnvironment for querying targets and paths + * @param buildOptions command-line options (see {@link FragmentOptions}) + * @return the configuration fragment or null if some required dependencies are missing. + */ + @Nullable + BuildConfiguration.Fragment create(ConfigurationEnvironment env, BuildOptions buildOptions) + throws InvalidConfigurationException; + + /** + * @return the exact type of the fragment this factory creates. + */ + Class<? extends Fragment> creates(); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/DefaultsPackage.java b/src/main/java/com/google/devtools/build/lib/analysis/config/DefaultsPackage.java new file mode 100644 index 0000000..207d49a --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/DefaultsPackage.java
@@ -0,0 +1,164 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.Label.SyntaxException; + +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * A helper class to compute and inject a defaults package into the package cache. + * + * <p>The <code>//tools/defaults</code> package provides a mechanism let tool locations be + * specified over the commandline, without requiring any special support in the rule code. + * As such, it can be used in genrule <code>$(location)</code> substitutions. + * + * <p>It works as follows: + * <ul> + * + * <li> SomeLanguage.createCompileAction will refer to a host-configured target for the + * compiler by looking for + * <code>env.getHostPrerequisiteArtifact("$somelanguage_compiler")</code>. + * + * <li> the attribute <code>$somelanguage_compiler</code> is defined in the + * {@link RuleDefinition} subclass for that language. + * + * <li> if the attribute cannot be set on the command-line, its value may be a normal label. + * + * <li> if the attribute can be set on the command-line, its value will be + * <code>//tools/defaults:somelanguage_compiler</code>. + * + * <li> in the latter case, the {@link BuildConfiguration.Fragment} subclass will define the + * option (with an existing target, eg. <code>//third_party/somelanguage:compiler</code>), and + * return the name in its implementation of {@link FragmentOptions#getDefaultsLabels}. + * + * <li> On startup, the rule is wired up with <code>//tools/defaults:somelanguage_compiler</code>. + * + * <li> On starting a build, the <code>//tools/defaults</code> package is synthesized, using + * the values as specified on the command-line. The contents of + * <code>tools/defaults/BUILD</code> is ignored. + * + * <li> Hence, changes in the command line values for tools are now handled exactly as if they + * were changes in a BUILD file. + * + * <li> The file <code>tools/defaults/BUILD</code> must exist, so we create a package in that + * location. + * + * <li> The code in {@link DefaultsPackage} can dump the synthesized package as a BUILD file, + * so external tooling does not need to understand the intricacies of handling command-line + * options. + * + * </ul> + * + * <p>For built-in rules (as opposed to genrules), late-bound labels provide an alternative + * method of depending on command-line values. These work by declaring attribute default values + * to be {@link LateBoundLabel} instances, whose <code>getDefault(Rule rule, T + * configuration)</code> method will have access to {@link BuildConfiguration}, which in turn + * may depend on command line flag values. + */ +public final class DefaultsPackage { + + // The template contents are broken into lines such that the resulting file has no more than 80 + // characters per line. + private static final String HEADER = "" + + "# DO NOT EDIT THIS FILE!\n" + + "#\n" + + "# Bazel does not read this file. Instead, it internally replaces the targets in\n" + + "# this package with the correct packages as given on the command line.\n" + + "#\n" + + "# If these options are not given on the command line, Bazel will use the exact\n" + + "# same targets as given here." + + "\n" + + "package(default_visibility = ['//visibility:public'])\n"; + + /** + * The map from entries to their values. + */ + private ImmutableMap<String, ImmutableSet<Label>> values; + + private DefaultsPackage(BuildOptions buildOptions) { + values = buildOptions.getDefaultsLabels(); + } + + private String labelsToString(Set<Label> labels) { + StringBuffer result = new StringBuffer(); + for (Label label : labels) { + if (result.length() != 0) { + result.append(", "); + } + result.append("'").append(label).append("'"); + } + return result.toString(); + } + + /** + * Returns a string of the defaults package with the given settings. + */ + private String getContent() { + Preconditions.checkState(!values.isEmpty()); + StringBuilder result = new StringBuilder(HEADER); + for (Map.Entry<String, ImmutableSet<Label>> entry : values.entrySet()) { + result + .append("filegroup(name = '") + .append(entry.getKey().toLowerCase(Locale.US)).append("',\n") + .append(" srcs = [") + .append(labelsToString(entry.getValue())).append("])\n"); + } + return result.toString(); + } + + /** + * Returns the defaults package for the default settings. + */ + public static String getDefaultsPackageContent( + Iterable<Class<? extends FragmentOptions>> options) { + return getDefaultsPackageContent(BuildOptions.createDefaults(options)); + } + + /** + * Returns the defaults package for the given options. + */ + public static String getDefaultsPackageContent(BuildOptions buildOptions) { + return new DefaultsPackage(buildOptions).getContent(); + } + + public static void parseAndAdd(Set<Label> labels, String optionalLabel) { + if (optionalLabel != null) { + Label label = parseOptionalLabel(optionalLabel); + if (label != null) { + labels.add(label); + } + } + } + + public static Label parseOptionalLabel(String value) { + if (value.startsWith("//")) { + try { + return Label.parseAbsolute(value); + } catch (SyntaxException e) { + // We ignore this exception here - it will cause an error message at a later time. + return null; + } + } else { + return null; + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/FragmentOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/FragmentOptions.java new file mode 100644 index 0000000..ce4b2d2 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/FragmentOptions.java
@@ -0,0 +1,115 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multimap; +import com.google.devtools.build.lib.packages.Attribute.SplitTransition; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.Label.SyntaxException; +import com.google.devtools.common.options.Options; +import com.google.devtools.common.options.OptionsBase; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Command-line build options for a Blaze module. + */ +public abstract class FragmentOptions extends OptionsBase implements Cloneable, Serializable { + + /** + * Adds all labels defined by the options to a multimap. See {@code BuildOptions.getAllLabels()}. + * + * <p>There should generally be no code duplication between this code and DefaultsPackage. Either + * the labels are loaded unconditionally using this method, or they are added as magic labels + * using the tools/defaults package, but not both. + * + * @param labelMap a mutable multimap to which the labels of this fragment should be added + */ + public void addAllLabels(Multimap<String, Label> labelMap) { + } + + /** + * Returns the labels contributed to the defaults package by this fragment. + * + * <p>The set of keys returned by this function should be constant, however, the values are + * allowed to change depending on the value of the options. + */ + @SuppressWarnings("unused") + public Map<String, Set<Label>> getDefaultsLabels(BuildConfiguration.Options commonOptions) { + return ImmutableMap.of(); + } + + /** + * Returns a list of potential split configuration transitions for this fragment. Split + * configurations usually need to be explicitly enabled by passing in an option. + */ + public List<SplitTransition<BuildOptions>> getPotentialSplitTransitions() { + return ImmutableList.of(); + } + + @Override + public FragmentOptions clone() { + try { + return (FragmentOptions) super.clone(); + } catch (CloneNotSupportedException e) { + // This can't happen. + throw new IllegalStateException(e); + } + } + + /** + * Creates a new FragmentOptions instance with all flags set to default. + */ + public FragmentOptions getDefault() { + return Options.getDefaults(getClass()); + } + + /** + * Creates a new FragmentOptions instance with flags adjusted to host platform. + * + * @param fallback see {@code BuildOptions.createHostOptions} + */ + @SuppressWarnings("unused") + public FragmentOptions getHost(boolean fallback) { + return getDefault(); + } + + protected void addOptionalLabel(Multimap<String, Label> map, String key, String value) { + Label label = parseOptionalLabel(value); + if (label != null) { + map.put(key, label); + } + } + + private static Label parseOptionalLabel(String value) { + if ((value != null) && value.startsWith("//")) { + try { + return Label.parseAbsolute(value); + } catch (SyntaxException e) { + // We ignore this exception here - it will cause an error message at a later time. + // TODO(bazel-team): We can use a Converter to check the validity of the crosstoolTop + // earlier. + return null; + } + } else { + return null; + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/InvalidConfigurationException.java b/src/main/java/com/google/devtools/build/lib/analysis/config/InvalidConfigurationException.java new file mode 100644 index 0000000..c39325d --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/InvalidConfigurationException.java
@@ -0,0 +1,33 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +/** + * Thrown if the configuration options lead to an invalid configuration, or if any of the + * configuration labels cannot be loaded. + */ +public class InvalidConfigurationException extends Exception { + + public InvalidConfigurationException(String message) { + super(message); + } + + public InvalidConfigurationException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidConfigurationException(Throwable cause) { + this(cause.getMessage(), cause); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/PackageProviderForConfigurations.java b/src/main/java/com/google/devtools/build/lib/analysis/config/PackageProviderForConfigurations.java new file mode 100644 index 0000000..83b4715 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/PackageProviderForConfigurations.java
@@ -0,0 +1,49 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.devtools.build.lib.analysis.BlazeDirectories; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment; +import com.google.devtools.build.lib.packages.Package; +import com.google.devtools.build.lib.pkgcache.LoadedPackageProvider; +import com.google.devtools.build.lib.syntax.Label.SyntaxException; + +import java.io.IOException; + +/** + * Extended LoadedPackageProvider which is used during a creation of BuildConfiguration.Fragments. + */ +public interface PackageProviderForConfigurations extends LoadedPackageProvider { + /** + * Adds dependency to fileName if needed. Used only in skyframe, for creating correct dependencies + * for {@link com.google.devtools.build.lib.skyframe.ConfigurationCollectionValue}. + */ + void addDependency(Package pkg, String fileName) throws SyntaxException, IOException; + + /** + * Returns fragment based on fragment type and build options. + */ + <T extends Fragment> T getFragment(BuildOptions buildOptions, Class<T> fragmentType) + throws InvalidConfigurationException; + + /** + * Returns blaze directories and adds dependency to that value. + */ + BlazeDirectories getDirectories(); + + /** + * Returns true if any dependency is missing (value of some node hasn't been evaluated yet). + */ + boolean valuesMissing(); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/PerLabelOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/PerLabelOptions.java new file mode 100644 index 0000000..1e921e5 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/PerLabelOptions.java
@@ -0,0 +1,128 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.util.RegexFilter; +import com.google.devtools.build.lib.util.RegexFilter.RegexFilterConverter; +import com.google.devtools.common.options.Converter; +import com.google.devtools.common.options.OptionsParsingException; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Models options that can be added to a command line when a label matches a + * given {@link RegexFilter}. + */ +public class PerLabelOptions implements Serializable { + + /** The filter used to match labels */ + private final RegexFilter regexFilter; + + /** The list of options to add when the filter matches a label */ + private final List<String> optionsList; + + /** + * Converts a String to a {@link PerLabelOptions} object. The syntax of the + * string is {@code regex_filter@option_1,option_2,...,option_n}. Where + * regex_filter stands for the String representation of a {@link RegexFilter}, + * and {@code option_1} to {@code option_n} stand for arbitrary command line + * options. If an option contains a comma it has to be quoted with a + * backslash. Options can contain @. Only the first @ is used to split the + * string. + */ + public static class PerLabelOptionsConverter implements Converter<PerLabelOptions> { + + @Override + public PerLabelOptions convert(String input) throws OptionsParsingException { + int atIndex = input.indexOf('@'); + RegexFilterConverter converter = new RegexFilter.RegexFilterConverter(); + if (atIndex < 0) { + return new PerLabelOptions(converter.convert(input), ImmutableList.<String> of()); + } else { + String filterPiece = input.substring(0, atIndex); + String optionsPiece = input.substring(atIndex + 1); + List<String> optionsList = new ArrayList<>(); + for (String option : optionsPiece.split("(?<!\\\\),")) { // Split on ',' but not on '\,' + if (option != null && !option.trim().equals("")) { + optionsList.add(option.replace("\\,", ",")); + } + } + return new PerLabelOptions(converter.convert(filterPiece), optionsList); + } + } + + @Override + public String getTypeDescription() { + return "a comma-separated list of regex expressions with prefix '-' specifying" + + " excluded paths followed by an @ and a comma separated list of options"; + } + } + + public PerLabelOptions(RegexFilter regexFilter, List<String> optionsList) { + this.regexFilter = regexFilter; + this.optionsList = optionsList; + } + + /** + * @return true if the given label is matched by the {@link RegexFilter}. + */ + public boolean isIncluded(Label label) { + return regexFilter.isIncluded(label.toString()); + } + + /** + * @return true if the execution path (which includes the base name of the file) + * of the given file is matched by the {@link RegexFilter}. + */ + public boolean isIncluded(Artifact artifact) { + return regexFilter.isIncluded(artifact.getExecPathString()); + } + + /** + * Returns the list of options to add to a command line. + */ + public List<String> getOptions() { + return optionsList; + } + + RegexFilter getRegexFilter() { + return regexFilter; + } + + @Override + public String toString() { + return regexFilter + " Options: " + optionsList; + } + + @Override + public boolean equals(Object other) { + PerLabelOptions otherOptions = + other instanceof PerLabelOptions ? (PerLabelOptions) other : null; + return this == other || (otherOptions != null && + this.regexFilter.equals(otherOptions.regexFilter) && + this.optionsList.equals(otherOptions.optionsList)); + } + + @Override + public int hashCode() { + return Objects.hash(regexFilter, optionsList); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/RunUnder.java b/src/main/java/com/google/devtools/build/lib/analysis/config/RunUnder.java new file mode 100644 index 0000000..a51ea25 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/RunUnder.java
@@ -0,0 +1,52 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.devtools.build.lib.syntax.Label; + +import java.io.Serializable; +import java.util.List; + +/** + * Components of --run_under option. + */ +public interface RunUnder extends Serializable { + /** + * @return the whole value passed to --run_under option. + */ + String getValue(); + + /** + * Returns label corresponding to the first word (according to shell + * tokenization) passed to --run_under. + * + * @return if the first word (according to shell tokenization) passed to + * --run_under starts with {@code "//"} returns the label + * corresponding to that word otherwise {@code null} + */ + Label getLabel(); + + /** + * @return if the first word (according to shell tokenization) passed to + * --run_under starts with {@code "//"} returns {@code null} + * otherwise the first word + */ + String getCommand(); + + /** + * @return everything except the first word (according to shell + * tokenization) passed to --run_under. + */ + List<String> getOptions(); +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/RunUnderConverter.java b/src/main/java/com/google/devtools/build/lib/analysis/config/RunUnderConverter.java new file mode 100644 index 0000000..1f7b660 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/RunUnderConverter.java
@@ -0,0 +1,133 @@ +// Copyright 2014 Google Inc. 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.analysis.config; + +import com.google.devtools.build.lib.shell.ShellUtils; +import com.google.devtools.build.lib.shell.ShellUtils.TokenizationException; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.Label.SyntaxException; +import com.google.devtools.common.options.Converter; +import com.google.devtools.common.options.OptionsParsingException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * --run_under options converter. + */ +public class RunUnderConverter implements Converter<RunUnder> { + @Override + public RunUnder convert(final String input) throws OptionsParsingException { + final List<String> runUnderList = new ArrayList<>(); + try { + ShellUtils.tokenize(runUnderList, input); + } catch (TokenizationException e) { + throw new OptionsParsingException("Not a valid command prefix " + e.getMessage()); + } + if (runUnderList.isEmpty()) { + throw new OptionsParsingException("Empty command"); + } + final String runUnderCommand = runUnderList.get(0); + if (runUnderCommand.startsWith("//")) { + try { + final Label runUnderLabel = Label.parseAbsolute(runUnderCommand); + return new RunUnderLabel(input, runUnderLabel, runUnderList); + } catch (SyntaxException e) { + throw new OptionsParsingException("Not a valid label " + e.getMessage()); + } + } else { + return new RunUnderCommand(input, runUnderCommand, runUnderList); + } + } + + private static final class RunUnderLabel implements RunUnder { + private final String input; + private final Label runUnderLabel; + private final List<String> runUnderList; + + public RunUnderLabel(String input, Label runUnderLabel, List<String> runUnderList) { + this.input = input; + this.runUnderLabel = runUnderLabel; + this.runUnderList = new ArrayList<String>(runUnderList.subList(1, runUnderList.size())); + } + + @Override public String getValue() { return input; } + @Override public Label getLabel() { return runUnderLabel; } + @Override public String getCommand() { return null; } + @Override public List<String> getOptions() { return runUnderList; } + @Override public String toString() { return input; } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof RunUnderLabel) { + RunUnderLabel otherRunUnderLabel = (RunUnderLabel) other; + return Objects.equals(input, otherRunUnderLabel.input) + && Objects.equals(runUnderLabel, otherRunUnderLabel.runUnderLabel) + && Objects.equals(runUnderList, otherRunUnderLabel.runUnderList); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(input, runUnderLabel, runUnderList); + } + } + + private static final class RunUnderCommand implements RunUnder { + private final String input; + private final String runUnderCommand; + private final List<String> runUnderList; + + public RunUnderCommand(String input, String runUnderCommand, List<String> runUnderList) { + this.input = input; + this.runUnderCommand = runUnderCommand; + this.runUnderList = new ArrayList<String>(runUnderList.subList(1, runUnderList.size())); + } + + @Override public String getValue() { return input; } + @Override public Label getLabel() { return null; } + @Override public String getCommand() { return runUnderCommand; } + @Override public List<String> getOptions() { return runUnderList; } + @Override public String toString() { return input; } + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof RunUnderCommand) { + RunUnderCommand otherRunUnderCommand = (RunUnderCommand) other; + return Objects.equals(input, otherRunUnderCommand.input) + && Objects.equals(runUnderCommand, otherRunUnderCommand.runUnderCommand) + && Objects.equals(runUnderList, otherRunUnderCommand.runUnderList); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(input, runUnderCommand, runUnderList); + } + } + @Override + public String getTypeDescription() { + return "a prefix in front of command"; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java new file mode 100644 index 0000000..14ac2bc --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java
@@ -0,0 +1,473 @@ +// Copyright 2015 Google Inc. 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.analysis.constraints; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; +import com.google.devtools.build.lib.analysis.constraints.EnvironmentCollection.EnvironmentWithGroup; +import com.google.devtools.build.lib.packages.AttributeMap; +import com.google.devtools.build.lib.packages.EnvironmentGroup; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.syntax.Label; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Implementation of the semantics of Bazel's constraint specification and enforcement system. + * + * <p>This is how the system works: + * + * <p>All build rules can declare which "environments" they can be built for, where an "environment" + * is a label instance of an {@link EnvironmentRule} rule declared in a BUILD file. There are + * various ways to do this: + * + * <ul> + * <li>Through a "restricted to" attribute setting + * ({@link RuleClass#RESTRICTED_ENVIRONMENT_ATTR}). This is the most direct form of + * specification - it declares the exact set of environments the rule supports (for its group - + * see precise details below). + * <li>Through a "compatible with" attribute setting + * ({@link RuleClass#COMPATIBLE_ENVIRONMENT_ATTR}. This declares <b>additional</b> + * environments a rule supports in addition to "standard" environments that are supported by + * default (see below). + * <li>Through "default" specifications in {@link EnvironmentGroup} rules. Every environment + * belongs to a group of thematically related peers (e.g. "target architectures", "JDK versions", + * or "mobile devices"). An environment group's definition includes which of these + * environments should be supported "by default" if not otherwise specified by one of the above + * mechanisms. In particular, a rule with no environment-related attributes automatically + * inherits all defaults. + * <li>Through a rule class default ({@link RuleClass.Builder#restrictedTo} and + * {@link RuleClass.Builder#compatibleWith}). This overrides global defaults for all instances + * of the given rule class. This can be used, for example, to make all *_test rules "testable" + * without each instance having to explicitly declare this capability. + * </ul> + * + * <p>Groups exist to model the idea that some environments are related while others have nothing + * to do with each other. Say, for example, we want to say a rule works for PowerPC platforms but + * not x86. We can do so by setting its "restricted to" attribute to + * {@code ['//sample/path:powerpc']}. Because both PowerPC and x86 are in the same + * "target architectures" group, this setting removes x86 from the set of supported environments. + * But since JDK support belongs to its own group ("JDK versions") it says nothing about which JDK + * the rule supports. + * + * <p>More precisely, if a rule has a "restricted to" value of [A, B, C], this removes support + * for all default environments D such that group(D) is in [group(A), group(B), group(C)] AND + * D is not in [A, B, C] (in other words, D isn't explicitly opted back in). The rule's full + * set of supported environments thus becomes [A, B, C] + all defaults that belong to unrelated + * groups. + * + * <p>If the rule has a "compatible with" value of [E, F, G], these are unconditionally + * added to its set of supported environments (in addition to the results from above). + * + * <p>An environment may not appear in both a rule's "restricted to" and "compatible with" values. + * If two environments belong to the same group, they must either both be in "restricted to", + * both be in "compatible with", or not explicitly specified. + * + * <p>Given all the above, constraint enforcement is this: rule A can depend on rule B if, for + * every environment A supports, B also supports that environment. + */ +public class ConstraintSemantics { + private ConstraintSemantics() { + } + + /** + * Provides a set of default environments for a given environment group. + */ + private interface DefaultsProvider { + Collection<Label> getDefaults(EnvironmentGroup group); + } + + /** + * Provides a group's defaults as specified in the environment group's BUILD declaration. + */ + private static class GroupDefaultsProvider implements DefaultsProvider { + @Override + public Collection<Label> getDefaults(EnvironmentGroup group) { + return group.getDefaults(); + } + } + + /** + * Provides a group's defaults, factoring in rule class defaults as specified by + * {@link com.google.devtools.build.lib.packages.RuleClass.Builder#compatibleWith} + * and {@link com.google.devtools.build.lib.packages.RuleClass.Builder#restrictedTo}. + */ + private static class RuleClassDefaultsProvider implements DefaultsProvider { + private final EnvironmentCollection ruleClassDefaults; + private final GroupDefaultsProvider groupDefaults; + + RuleClassDefaultsProvider(EnvironmentCollection ruleClassDefaults) { + this.ruleClassDefaults = ruleClassDefaults; + this.groupDefaults = new GroupDefaultsProvider(); + } + + @Override + public Collection<Label> getDefaults(EnvironmentGroup group) { + if (ruleClassDefaults.getGroups().contains(group)) { + return ruleClassDefaults.getEnvironments(group); + } else { + // If there are no rule class defaults for this group, just inherit global defaults. + return groupDefaults.getDefaults(group); + } + } + } + + /** + * Collects the set of supported environments for a given rule by merging its + * restriction-style and compatibility-style environment declarations as specified by + * the given attributes. Only includes environments from "known" groups, i.e. the groups + * owning the environments explicitly referenced from these attributes. + */ + private static class EnvironmentCollector { + private final RuleContext ruleContext; + private final String restrictionAttr; + private final String compatibilityAttr; + private final DefaultsProvider defaultsProvider; + + private final EnvironmentCollection restrictionEnvironments; + private final EnvironmentCollection compatibilityEnvironments; + private final EnvironmentCollection supportedEnvironments; + + /** + * Constructs a new collector on the given attributes. + * + * @param ruleContext analysis context for the rule + * @param restrictionAttr the name of the attribute that declares "restricted to"-style + * environments. If the rule doesn't have this attribute, this is considered an + * empty declaration. + * @param compatibilityAttr the name of the attribute that declares "compatible with"-style + * environments. If the rule doesn't have this attribute, this is considered an + * empty declaration. + * @param defaultsProvider provider for the default environments within a group if not + * otherwise overriden by the above attributes + */ + EnvironmentCollector(RuleContext ruleContext, String restrictionAttr, String compatibilityAttr, + DefaultsProvider defaultsProvider) { + this.ruleContext = ruleContext; + this.restrictionAttr = restrictionAttr; + this.compatibilityAttr = compatibilityAttr; + this.defaultsProvider = defaultsProvider; + + EnvironmentCollection.Builder environmentsBuilder = new EnvironmentCollection.Builder(); + restrictionEnvironments = collectRestrictionEnvironments(environmentsBuilder); + compatibilityEnvironments = collectCompatibilityEnvironments(environmentsBuilder); + supportedEnvironments = environmentsBuilder.build(); + } + + /** + * Returns the set of environments supported by this rule, as determined by the + * restriction-style attribute, compatibility-style attribute, and group defaults + * provider instantiated with this class. + */ + EnvironmentCollection getEnvironments() { + return supportedEnvironments; + } + + /** + * Validity-checks that no group has its environment referenced in both the "compatible with" + * and restricted to" attributes. Returns true if all is good, returns false and reports + * appropriate errors if there are any problems. + */ + boolean validateEnvironmentSpecifications() { + ImmutableCollection<EnvironmentGroup> restrictionGroups = restrictionEnvironments.getGroups(); + boolean hasErrors = false; + + for (EnvironmentGroup group : compatibilityEnvironments.getGroups()) { + if (restrictionGroups.contains(group)) { + // To avoid error-spamming the user, when we find a conflict we only report one example + // environment from each attribute for that group. + Label compatibilityEnv = + compatibilityEnvironments.getEnvironments(group).iterator().next(); + Label restrictionEnv = restrictionEnvironments.getEnvironments(group).iterator().next(); + + if (compatibilityEnv.equals(restrictionEnv)) { + ruleContext.attributeError(compatibilityAttr, compatibilityEnv + + " cannot appear both here and in " + restrictionAttr); + } else { + ruleContext.attributeError(compatibilityAttr, compatibilityEnv + " and " + + restrictionEnv + " belong to the same environment group. They should be declared " + + "together either here or in " + restrictionAttr); + } + hasErrors = true; + } + } + + return !hasErrors; + } + + /** + * Adds environments specified in the "restricted to" attribute to the set of supported + * environments and returns the environments added. + */ + private EnvironmentCollection collectRestrictionEnvironments( + EnvironmentCollection.Builder supportedEnvironments) { + return collectEnvironments(restrictionAttr, supportedEnvironments); + } + + /** + * Adds environments specified in the "compatible with" attribute to the set of supported + * environments, along with all defaults from the groups they belong to. Returns these + * environments, not including the defaults. + */ + private EnvironmentCollection collectCompatibilityEnvironments( + EnvironmentCollection.Builder supportedEnvironments) { + EnvironmentCollection compatibilityEnvironments = + collectEnvironments(compatibilityAttr, supportedEnvironments); + for (EnvironmentGroup group : compatibilityEnvironments.getGroups()) { + supportedEnvironments.putAll(group, defaultsProvider.getDefaults(group)); + } + return compatibilityEnvironments; + } + + /** + * Adds environments specified by the given attribute to the set of supported environments + * and returns the environments added. + * + * <p>If this rule doesn't have the given attributes, returns an empty set. + */ + private EnvironmentCollection collectEnvironments(String attrName, + EnvironmentCollection.Builder supportedEnvironments) { + if (!ruleContext.getRule().isAttrDefined(attrName, Type.LABEL_LIST)) { + return EnvironmentCollection.EMPTY; + } + EnvironmentCollection.Builder environments = new EnvironmentCollection.Builder(); + for (TransitiveInfoCollection envTarget : + ruleContext.getPrerequisites(attrName, RuleConfiguredTarget.Mode.DONT_CHECK)) { + EnvironmentWithGroup envInfo = resolveEnvironment(envTarget); + environments.put(envInfo.group(), envInfo.environment()); + supportedEnvironments.put(envInfo.group(), envInfo.environment()); + } + return environments.build(); + } + + /** + * Returns the environment and its group. An {@link Environment} rule only "supports" one + * environment: itself. Extract that from its more generic provider interface and sanity + * check that that's in fact what we see. + */ + private static EnvironmentWithGroup resolveEnvironment(TransitiveInfoCollection envRule) { + SupportedEnvironmentsProvider prereq = + Preconditions.checkNotNull(envRule.getProvider(SupportedEnvironmentsProvider.class)); + return Iterables.getOnlyElement(prereq.getEnvironments().getGroupedEnvironments()); + } + } + + /** + * Returns the set of environments this rule supports, applying the logic described in + * {@link ConstraintSemantics}. + * + * <p>Note this set is <b>not complete</b> - it doesn't include environments from groups we don't + * "know about". Environments and groups can be declared in any package. If the rule includes + * no references to that package, then it simply doesn't know anything about them. But the + * constraint semantics say the rule should support the defaults for that group. We encode this + * implicitly: given the returned set, for any group that's not in the set the rule is also + * considered to support that group's defaults. + * + * @param ruleContext analysis context for the rule. A rule error is triggered here if + * invalid constraint settings are discovered. + * @return the environments this rule supports, not counting defaults "unknown" to this rule + * as described above. Returns null if any errors are encountered. + */ + @Nullable + public static EnvironmentCollection getSupportedEnvironments(RuleContext ruleContext) { + if (!validateAttributes(ruleContext)) { + return null; + } + + // This rule's rule class defaults (or null if the rule class has no defaults). + EnvironmentCollector ruleClassCollector = maybeGetRuleClassDefaults(ruleContext); + // Default environments for this rule. If the rule has rule class defaults, this is + // those defaults. Otherwise it's the global defaults specified by environment_group + // declarations. + DefaultsProvider ruleDefaults; + + if (ruleClassCollector != null) { + if (!ruleClassCollector.validateEnvironmentSpecifications()) { + return null; + } + ruleDefaults = new RuleClassDefaultsProvider(ruleClassCollector.getEnvironments()); + } else { + ruleDefaults = new GroupDefaultsProvider(); + } + + EnvironmentCollector ruleCollector = new EnvironmentCollector(ruleContext, + RuleClass.RESTRICTED_ENVIRONMENT_ATTR, RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, ruleDefaults); + if (!ruleCollector.validateEnvironmentSpecifications()) { + return null; + } + + EnvironmentCollection supportedEnvironments = ruleCollector.getEnvironments(); + if (ruleClassCollector != null) { + // If we have rule class defaults from groups that aren't referenced from the rule itself, + // we need to add them in too to override the global defaults. + supportedEnvironments = + addUnknownGroupsToCollection(supportedEnvironments, ruleClassCollector.getEnvironments()); + } + return supportedEnvironments; + } + + /** + * Returns the rule class defaults specified for this rule, or null if there are + * no such defaults. + */ + @Nullable + private static EnvironmentCollector maybeGetRuleClassDefaults(RuleContext ruleContext) { + Rule rule = ruleContext.getRule(); + String restrictionAttr = RuleClass.DEFAULT_RESTRICTED_ENVIRONMENT_ATTR; + String compatibilityAttr = RuleClass.DEFAULT_COMPATIBLE_ENVIRONMENT_ATTR; + + if (rule.isAttrDefined(restrictionAttr, Type.LABEL_LIST) + || rule.isAttrDefined(compatibilityAttr, Type.LABEL_LIST)) { + return new EnvironmentCollector(ruleContext, restrictionAttr, compatibilityAttr, + new GroupDefaultsProvider()); + } else { + return null; + } + } + + /** + * Adds environments to an {@link EnvironmentCollection} from groups that aren't already + * a part of that collection. + * + * @param environments the collection to add to + * @param toAdd the collection to add. All environments in this collection in groups + * that aren't represented in {@code environments} are added to {@code environments}. + * @return the expanded collection. + */ + private static EnvironmentCollection addUnknownGroupsToCollection( + EnvironmentCollection environments, EnvironmentCollection toAdd) { + EnvironmentCollection.Builder builder = new EnvironmentCollection.Builder(); + builder.putAll(environments); + for (EnvironmentGroup candidateGroup : toAdd.getGroups()) { + if (!environments.getGroups().contains(candidateGroup)) { + builder.putAll(candidateGroup, toAdd.getEnvironments(candidateGroup)); + } + } + return builder.build(); + } + + /** + * Validity-checks this rule's constraint-related attributes. Returns true if all is good, + * returns false and reports appropriate errors if there are any problems. + */ + private static boolean validateAttributes(RuleContext ruleContext) { + AttributeMap attributes = ruleContext.attributes(); + + // Report an error if "restricted to" is explicitly set to nothing. Even if this made + // conceptual sense, we don't know which groups we should apply that to. + String restrictionAttr = RuleClass.RESTRICTED_ENVIRONMENT_ATTR; + List<? extends TransitiveInfoCollection> restrictionEnvironments = ruleContext + .getPrerequisites(restrictionAttr, RuleConfiguredTarget.Mode.DONT_CHECK); + if (restrictionEnvironments.isEmpty() + && attributes.isAttributeValueExplicitlySpecified(restrictionAttr)) { + ruleContext.attributeError(restrictionAttr, "attribute cannot be empty"); + return false; + } + + return true; + } + + /** + * Performs constraint checking on the given rule's dependencies and reports any errors. + * + * @param ruleContext the rule to analyze + * @param supportedEnvironments the rule's supported environments, as defined by the return + * value of {@link #getSupportedEnvironments}. In particular, for any environment group that's + * not in this collection, the rule is assumed to support the defaults for that group. + */ + public static void checkConstraints(RuleContext ruleContext, + EnvironmentCollection supportedEnvironments) { + + Set<EnvironmentGroup> knownGroups = supportedEnvironments.getGroups(); + + for (TransitiveInfoCollection dependency : getAllPrerequisites(ruleContext)) { + SupportedEnvironmentsProvider depProvider = + dependency.getProvider(SupportedEnvironmentsProvider.class); + if (depProvider == null) { + // Input files (InputFileConfiguredTarget) don't support environments. We may subsequently + // opt them into constraint checking, but for now just pass them by. + continue; + } + Collection<Label> depEnvironments = depProvider.getEnvironments().getEnvironments(); + Set<EnvironmentGroup> groupsKnownToDep = depProvider.getEnvironments().getGroups(); + + // Environments we support that the dependency does not support. + Set<Label> disallowedEnvironments = new LinkedHashSet<>(); + + // For every environment we support, either the dependency must also support it OR it must be + // a default for a group the dependency doesn't know about. + for (EnvironmentWithGroup supportedEnv : supportedEnvironments.getGroupedEnvironments()) { + EnvironmentGroup group = supportedEnv.group(); + Label environment = supportedEnv.environment(); + if (!depEnvironments.contains(environment) + && (groupsKnownToDep.contains(group) || !group.isDefault(environment))) { + disallowedEnvironments.add(environment); + } + } + + // For any environment group we don't know about, we implicitly support its defaults. Check + // that the dep does, too. + for (EnvironmentGroup depGroup : groupsKnownToDep) { + if (!knownGroups.contains(depGroup)) { + for (Label defaultEnv : depGroup.getDefaults()) { + if (!depEnvironments.contains(defaultEnv)) { + disallowedEnvironments.add(defaultEnv); + } + } + } + } + + // Report errors on bad environments. + if (!disallowedEnvironments.isEmpty()) { + ruleContext.ruleError("dependency " + dependency.getLabel() + + " doesn't support expected environment" + + (disallowedEnvironments.size() == 1 ? "" : "s") + + ": " + Joiner.on(", ").join(disallowedEnvironments)); + } + } + } + + /** + * Returns all dependencies that should be constraint-checked against the current rule. + */ + private static Iterable<TransitiveInfoCollection> getAllPrerequisites(RuleContext ruleContext) { + Set<TransitiveInfoCollection> prerequisites = new LinkedHashSet<>(); + AttributeMap attributes = ruleContext.attributes(); + + for (String attr : attributes.getAttributeNames()) { + Type<?> attrType = attributes.getAttributeType(attr); + // TODO(bazel-team): support specifying which attributes are subject to constraint checking + if ((attrType == Type.LABEL || attrType == Type.LABEL_LIST) + && !RuleClass.isConstraintAttribute(attr) + && !attr.equals("visibility")) { + prerequisites.addAll( + ruleContext.getPrerequisites(attr, RuleConfiguredTarget.Mode.DONT_CHECK)); + } + } + return prerequisites; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java new file mode 100644 index 0000000..912ed72 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java
@@ -0,0 +1,71 @@ +// Copyright 2015 Google Inc. 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.analysis.constraints; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.FileProvider; +import com.google.devtools.build.lib.analysis.FilesToRunProvider; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.RunfilesProvider; +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.EnvironmentGroup; +import com.google.devtools.build.lib.packages.Package; +import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; +import com.google.devtools.build.lib.syntax.Label; + +/** + * Implementation for the environment rule. + */ +public class Environment implements RuleConfiguredTargetFactory { + + @Override + public ConfiguredTarget create(RuleContext ruleContext) { + + // The main analysis work to do here is to simply fill in SupportedEnvironmentsProvider to + // pass the environment itself to depending rules. + // + // This will likely expand when we add support for environments fulfilling other environments. + Label label = ruleContext.getLabel(); + Package pkg = ruleContext.getRule().getPackage(); + + EnvironmentGroup group = null; + for (EnvironmentGroup pkgGroup : pkg.getTargets(EnvironmentGroup.class)) { + if (pkgGroup.getEnvironments().contains(label)) { + group = pkgGroup; + break; + } + } + + if (group == null) { + ruleContext.ruleError("no matching environment group from the same package"); + return null; + } + + return new RuleConfiguredTargetBuilder(ruleContext) + .addProvider(SupportedEnvironmentsProvider.class, + new SupportedEnvironments( + new EnvironmentCollection.Builder().put(group, label).build())) + .addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY) + .add(FileProvider.class, new FileProvider(ruleContext.getLabel(), + NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER))) + .add(FilesToRunProvider.class, new FilesToRunProvider(ruleContext.getLabel(), + ImmutableList.<Artifact>of(), null, null)) + .build(); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentCollection.java new file mode 100644 index 0000000..1ce5f1c --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentCollection.java
@@ -0,0 +1,126 @@ +// Copyright 2015 Google Inc. 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.analysis.constraints; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.packages.EnvironmentGroup; +import com.google.devtools.build.lib.syntax.Label; + +import java.util.Map; + +/** + * Contains a set of {@link Environment} labels and their associated groups. + */ +@Immutable +public class EnvironmentCollection { + private final ImmutableMultimap<EnvironmentGroup, Label> map; + + private EnvironmentCollection(ImmutableMultimap<EnvironmentGroup, Label> map) { + this.map = map; + } + + /** + * Stores an environment's build label along with the group it belongs to. + */ + static class EnvironmentWithGroup { + private final Label environment; + private final EnvironmentGroup group; + EnvironmentWithGroup(Label environment, EnvironmentGroup group) { + this.environment = environment; + this.group = group; + } + Label environment() { return environment; } + EnvironmentGroup group() { return group; } + } + + /** + * Returns the build labels of each environment in this collection, ordered by + * their insertion order in {@link Builder}. + */ + ImmutableCollection<Label> getEnvironments() { + return map.values(); + } + + /** + * Returns the set of groups the environments in this collection belong to, ordered by + * their insertion order in {@link Builder} + */ + ImmutableSet<EnvironmentGroup> getGroups() { + return map.keySet(); + } + + /** + * Returns the build labels of each environment in this collection paired with the + * group each environment belongs to, ordered by their insertion order in {@link Builder}. + */ + ImmutableCollection<EnvironmentWithGroup> getGroupedEnvironments() { + ImmutableSet.Builder<EnvironmentWithGroup> builder = ImmutableSet.builder(); + for (Map.Entry<EnvironmentGroup, Label> entry : map.entries()) { + builder.add(new EnvironmentWithGroup(entry.getValue(), entry.getKey())); + } + return builder.build(); + } + + /** + * Returns the environments in this collection that belong to the given group, ordered by + * their insertion order in {@link Builder}. If no environments belong to the given group, + * returns an empty collection. + */ + ImmutableCollection<Label> getEnvironments(EnvironmentGroup group) { + return map.get(group); + } + + /** + * An empty collection. + */ + static final EnvironmentCollection EMPTY = + new EnvironmentCollection(ImmutableMultimap.<EnvironmentGroup, Label>of()); + + static class Builder { + private final ImmutableMultimap.Builder<EnvironmentGroup, Label> mapBuilder = + ImmutableMultimap.builder(); + + /** + * Inserts the given environment / owning group pair. + */ + Builder put(EnvironmentGroup group, Label environment) { + mapBuilder.put(group, environment); + return this; + } + + /** + * Inserts the given set of environments, all belonging to the specified group. + */ + Builder putAll(EnvironmentGroup group, Iterable<Label> environments) { + mapBuilder.putAll(group, environments); + return this; + } + + /** + * Inserts the contents of another {@link EnvironmentCollection} into this one. + */ + Builder putAll(EnvironmentCollection other) { + mapBuilder.putAll(other.map); + return this; + } + + EnvironmentCollection build() { + return new EnvironmentCollection(mapBuilder.build()); + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentRule.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentRule.java new file mode 100644 index 0000000..0553af0 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentRule.java
@@ -0,0 +1,48 @@ +// Copyright 2015 Google Inc. 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.analysis.constraints; + +import static com.google.devtools.build.lib.packages.Attribute.attr; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.analysis.BaseRuleClasses; +import com.google.devtools.build.lib.analysis.BlazeRule; +import com.google.devtools.build.lib.analysis.RuleDefinition; +import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.Type; + +/** + * Rule definition for environment rules (for Bazel's constraint enforcement system). + */ +@BlazeRule(name = EnvironmentRule.RULE_NAME, + ancestors = { BaseRuleClasses.BaseRule.class }, + factoryClass = Environment.class) +public final class EnvironmentRule implements RuleDefinition { + public static final String RULE_NAME = "environment"; + + @Override + public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { + return builder + .override(attr("tags", Type.STRING_LIST) + // No need to show up in ":all", etc. target patterns. + .value(ImmutableList.of("manual")) + .nonconfigurable("low-level attribute, used in TargetUtils without configurations")) + .removeAttribute(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR) + .removeAttribute(RuleClass.RESTRICTED_ENVIRONMENT_ATTR) + .setUndocumented() + .build(); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironments.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironments.java new file mode 100644 index 0000000..78c835e --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironments.java
@@ -0,0 +1,31 @@ +// Copyright 2015 Google Inc. 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.analysis.constraints; + +/** + * Standard {@link SupportedEnvironmentsProvider} implementation. + */ +public class SupportedEnvironments implements SupportedEnvironmentsProvider { + private final EnvironmentCollection supportedEnvironments; + + public SupportedEnvironments(EnvironmentCollection supportedEnvironments) { + this.supportedEnvironments = supportedEnvironments; + } + + @Override + public EnvironmentCollection getEnvironments() { + return supportedEnvironments; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironmentsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironmentsProvider.java new file mode 100644 index 0000000..8200386 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironmentsProvider.java
@@ -0,0 +1,29 @@ +// Copyright 2015 Google Inc. 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.analysis.constraints; + +import com.google.devtools.build.lib.analysis.TransitiveInfoProvider; + +/** + * A provider that advertises which environments the associated target is compatible with + * (from the point of view of the constraint enforcement system). + */ +public interface SupportedEnvironmentsProvider extends TransitiveInfoProvider { + + /** + * Returns the environments the associated target is compatible with. + */ + EnvironmentCollection getEnvironments(); +}