blob: 562ad457ab5bb03da8df43b5e54c229bd71ba8ce [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.actions;
import com.google.common.base.Preconditions;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata.MiddlemanType;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.PathFragment;
import javax.annotation.Nullable;
/**
* A factory to create middleman objects.
*/
@ThreadSafe
public final class MiddlemanFactory {
private final ArtifactFactory artifactFactory;
private final ActionRegistry actionRegistry;
public MiddlemanFactory(
ArtifactFactory artifactFactory, ActionRegistry actionRegistry) {
this.artifactFactory = Preconditions.checkNotNull(artifactFactory);
this.actionRegistry = Preconditions.checkNotNull(actionRegistry);
}
/**
* Returns <code>null</code> iff inputs is empty. Returns the sole element of inputs iff <code>
* inputs.size()==1</code>. Otherwise, returns a middleman artifact and creates a middleman action
* that generates that artifact.
*
* @param owner the owner of the action that will be created.
* @param owningArtifact the artifact of the file for which the runfiles should be created. There
* may be at most one set of runfiles for an owning artifact, unless the owning artifact is
* null. There may be at most one set of runfiles per owner with a null owning artifact.
* Further, if the owning Artifact is non-null, the owning Artifacts' root-relative path must
* be unique and the artifact must be part of the runfiles tree for which this middleman is
* created. Usually this artifact will be an executable program.
* @param inputs the set of artifacts for which the created artifact is to be the middleman.
* @param middlemanDir the directory in which to place the middleman.
*/
public Artifact createRunfilesMiddleman(
ActionOwner owner,
@Nullable Artifact owningArtifact,
NestedSet<Artifact> inputs,
ArtifactRoot middlemanDir,
String tag) {
Preconditions.checkArgument(middlemanDir.isMiddlemanRoot());
if (inputs.isSingleton()) { // Optimization: No middleman for just one input.
return inputs.getSingleton();
}
String middlemanPath = owningArtifact == null
? Label.print(owner.getLabel())
: owningArtifact.getRootRelativePath().getPathString();
return createMiddleman(owner, middlemanPath, tag, inputs, middlemanDir,
MiddlemanType.RUNFILES_MIDDLEMAN).getFirst();
}
/**
* Creates a {@link MiddlemanType#SCHEDULING_DEPENDENCY_MIDDLEMAN scheduling dependency}
* middleman.
*
* @param owner the owner of the action that will be created. May not be null.
* @param middlemanName a unique file name for the middleman artifact in the {@code middlemanDir};
* in practice this is usually the owning rule's label (so it gets escaped as such)
* @param purpose the purpose for which this middleman is created. This should be a string which
* is suitable for use as a filename. A single rule may have many middlemen with distinct
* purposes.
* @param inputs the set of artifacts for which the created artifact is to be the middleman; must
* not be null or empty
* @param middlemanDir the directory in which to place the middleman.
* @return a middleman that enforces scheduling order (just like a scheduling middleman) and
* propagates errors, but is ignored by the dependency checker
* @throws IllegalArgumentException if {@code inputs} is null or empty
*/
public Artifact createSchedulingDependencyMiddleman(
ActionOwner owner,
String middlemanName,
String purpose,
NestedSet<Artifact> inputs,
ArtifactRoot middlemanDir) {
Preconditions.checkArgument(inputs != null);
Preconditions.checkArgument(!inputs.isEmpty());
// We must always create this middleman even if there is only one input.
return createMiddleman(
owner,
middlemanName,
purpose,
inputs,
middlemanDir,
MiddlemanType.SCHEDULING_DEPENDENCY_MIDDLEMAN)
.getFirst();
}
/**
* Creates both normal and scheduling middlemen.
*
* <p>Note: there's no need to synchronize this method; the only use of a field is via a call to
* another synchronized method (getArtifact()).
*
* @return null iff {@code inputs} is null or empty; the middleman file and the middleman action
* otherwise
*/
private Pair<Artifact, Action> createMiddleman(
ActionOwner owner,
String middlemanName,
String purpose,
NestedSet<Artifact> inputs,
ArtifactRoot middlemanDir,
MiddlemanType middlemanType) {
if (inputs == null || inputs.isEmpty()) {
return null;
}
Artifact stampFile = getStampFileArtifact(middlemanName, purpose, middlemanDir);
Action action =
MiddlemanAction.create(actionRegistry, owner, inputs, stampFile, purpose, middlemanType);
return Pair.of(stampFile, action);
}
/**
* Creates a normal middleman.
*
* <p>If called multiple times, it always returns the same object depending on the {@code
* purpose}. It does not check that the list of inputs is identical. In contrast to other
* middleman methods, this one also returns an object if the list of inputs is empty.
*
* <p>Note: there's no need to synchronize this method; the only use of a field is via a call to
* another synchronized method (getArtifact()).
*/
public Artifact.DerivedArtifact createMiddlemanAllowMultiple(
ActionRegistry registry,
ActionOwner owner,
PathFragment packageDirectory,
String purpose,
NestedSet<Artifact> inputs,
ArtifactRoot middlemanDir) {
String escapedPackageDirectory = Actions.escapedPath(packageDirectory.getPathString());
PathFragment stampName =
PathFragment.create("_middlemen/" + (purpose.startsWith(escapedPackageDirectory)
? purpose : (escapedPackageDirectory + purpose)));
Artifact.DerivedArtifact stampFile =
artifactFactory.getDerivedArtifact(stampName, middlemanDir, actionRegistry.getOwner());
MiddlemanAction.create(
registry, owner, inputs, stampFile, purpose, MiddlemanType.AGGREGATING_MIDDLEMAN);
return stampFile;
}
private Artifact.DerivedArtifact getStampFileArtifact(
String middlemanName, String purpose, ArtifactRoot middlemanDir) {
String escapedFilename = Actions.escapedPath(middlemanName);
PathFragment stampName = PathFragment.create("_middlemen/" + escapedFilename + "-" + purpose);
Artifact.DerivedArtifact stampFile =
artifactFactory.getDerivedArtifact(stampName, middlemanDir, actionRegistry.getOwner());
return stampFile;
}
}