// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.rules.objc; import static com.google.devtools.build.lib.collect.nestedset.Order.STABLE_ORDER; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; 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.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.ArtifactResolver; import com.google.devtools.build.lib.actions.Executor; import com.google.devtools.build.lib.actions.ResourceSet; import com.google.devtools.build.lib.actions.Spawn; import com.google.devtools.build.lib.analysis.actions.CommandLine; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible; import com.google.devtools.build.lib.profiler.Profiler; import com.google.devtools.build.lib.profiler.ProfilerTask; import com.google.devtools.build.lib.rules.apple.AppleConfiguration; import com.google.devtools.build.lib.rules.apple.Platform; import com.google.devtools.build.lib.rules.cpp.CppCompileAction.DotdFile; import com.google.devtools.build.lib.rules.cpp.HeaderDiscovery; import com.google.devtools.build.lib.rules.cpp.IncludeScanningContext; import com.google.devtools.build.lib.util.DependencySet; 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; import java.util.HashMap; import java.util.Map; /** * An action that compiles objc or objc++ source. * *

We don't use a plain SpawnAction here because we implement .d input pruning, which requires * post-execution filtering of input artifacts. * *

We don't use a CppCompileAction because the ObjcCompileAction uses custom logic instead of the * CROSSTOOL to construct its command line. */ public class ObjcCompileAction extends SpawnAction { /** * A spawn that provides all headers to sandboxed execution to allow pruned headers to be * re-introduced into action inputs. */ public class ObjcCompileActionSpawn extends ActionSpawn { public ObjcCompileActionSpawn(Map clientEnv) { super(clientEnv); } @Override public Iterable getInputFiles() { return Iterables.concat(super.getInputFiles(), headers); } } private final DotdFile dotdFile; private final Artifact sourceFile; private final NestedSet mandatoryInputs; private final HeaderDiscovery.DotdPruningMode dotdPruningPlan; private final NestedSet headers; private static final String GUID = "a00d5bac-a72c-4f0f-99a7-d5fdc6072137"; private ObjcCompileAction( ActionOwner owner, Iterable tools, Iterable inputs, Iterable outputs, ResourceSet resourceSet, CommandLine argv, ImmutableMap environment, ImmutableMap executionInfo, String progressMessage, ImmutableMap inputManifests, String mnemonic, boolean executeUnconditionally, ExtraActionInfoSupplier extraActionInfoSupplier, DotdFile dotdFile, Artifact sourceFile, NestedSet mandatoryInputs, HeaderDiscovery.DotdPruningMode dotdPruningPlan, NestedSet headers) { super( owner, tools, inputs, outputs, resourceSet, argv, environment, ImmutableSet.of(), executionInfo, progressMessage, inputManifests, mnemonic, executeUnconditionally, extraActionInfoSupplier); this.dotdFile = dotdFile; this.sourceFile = sourceFile; this.mandatoryInputs = mandatoryInputs; this.dotdPruningPlan = dotdPruningPlan; this.headers = headers; } /** Returns the DotdPruningPlan for this compile */ @VisibleForTesting public HeaderDiscovery.DotdPruningMode getDotdPruningPlan() { return dotdPruningPlan; } @Override public final Spawn getSpawn(Map clientEnv) { return new ObjcCompileActionSpawn(clientEnv); } @Override public boolean discoversInputs() { return true; } @Override public Iterable discoverInputs(ActionExecutionContext actionExecutionContext) { return headers; } @Override public ImmutableSet getMandatoryOutputs() { return ImmutableSet.of(dotdFile.artifact()); } @Override public void execute(ActionExecutionContext actionExecutionContext) throws ActionExecutionException, InterruptedException { super.execute(actionExecutionContext); if (dotdPruningPlan == HeaderDiscovery.DotdPruningMode.USE) { Executor executor = actionExecutionContext.getExecutor(); IncludeScanningContext scanningContext = executor.getContext(IncludeScanningContext.class); NestedSet discoveredInputs = discoverInputsFromDotdFiles( executor.getExecRoot(), scanningContext.getArtifactResolver()); updateActionInputs(discoveredInputs); } } @VisibleForTesting public NestedSet discoverInputsFromDotdFiles( Path execRoot, ArtifactResolver artifactResolver) throws ActionExecutionException { if (dotdFile == null) { return NestedSetBuilder.stableOrder().build(); } return new HeaderDiscovery.Builder() .setAction(this) .setSourceFile(sourceFile) .setDotdFile(dotdFile) .setDependencySet(processDepset(execRoot)) .setPermittedSystemIncludePrefixes(ImmutableList.of()) .setAllowedDerivedinputsMap(getAllowedDerivedInputsMap()) .build() .discoverInputsFromDotdFiles(execRoot, artifactResolver); } private DependencySet processDepset(Path execRoot) throws ActionExecutionException { try { DependencySet depSet = new DependencySet(execRoot); return depSet.read(dotdFile.getPath()); } catch (IOException e) { // Some kind of IO or parse exception--wrap & rethrow it to stop the build. throw new ActionExecutionException("error while parsing .d file", e, this, false); } } /** Utility function that adds artifacts to an input map, but only if they are sources. */ private void addToMapIfSource(Map map, Iterable artifacts) { for (Artifact artifact : artifacts) { if (!artifact.isSourceArtifact()) { map.put(artifact.getExecPath(), artifact); } } } private Map getAllowedDerivedInputsMap() { Map allowedDerivedInputMap = new HashMap<>(); addToMapIfSource(allowedDerivedInputMap, getInputs()); allowedDerivedInputMap.put(sourceFile.getExecPath(), sourceFile); return allowedDerivedInputMap; } /** * Recalculates this action's live input collection, including sources, middlemen. * * @throws ActionExecutionException iff any errors happen during update. */ @VisibleForTesting @ThreadCompatible public final synchronized void updateActionInputs(NestedSet discoveredInputs) throws ActionExecutionException { NestedSetBuilder inputs = NestedSetBuilder.stableOrder(); Profiler.instance().startTask(ProfilerTask.ACTION_UPDATE, this); try { inputs.addTransitive(mandatoryInputs); inputs.addTransitive(discoveredInputs); } finally { Profiler.instance().completeTask(ProfilerTask.ACTION_UPDATE); setInputs(inputs.build()); } } @Override public String computeKey() { Fingerprint f = new Fingerprint(); f.addString(GUID); f.addString(super.computeKey()); f.addBoolean(dotdFile.artifact() == null); f.addBoolean(dotdPruningPlan == HeaderDiscovery.DotdPruningMode.USE); f.addPath(dotdFile.getSafeExecPath()); return f.hexDigestAndReset(); } /** A Builder for ObjcCompileAction */ public static class Builder extends SpawnAction.Builder { private DotdFile dotdFile; private Artifact sourceFile; private final NestedSetBuilder mandatoryInputs = new NestedSetBuilder<>(STABLE_ORDER); private HeaderDiscovery.DotdPruningMode dotdPruningPlan; private NestedSet headers; /** * Creates a new compile action builder with apple environment variables set that are typically * needed by the apple toolchain. */ public static ObjcCompileAction.Builder createObjcCompileActionBuilderWithAppleEnv( AppleConfiguration appleConfiguration, Platform targetPlatform) { return (Builder) new ObjcCompileAction.Builder() .setExecutionInfo(ObjcRuleClasses.darwinActionExecutionRequirement()) .setEnvironment( ObjcRuleClasses.appleToolchainEnvironment(appleConfiguration, targetPlatform)); } @Override public Builder addTools(Iterable artifacts) { super.addTools(artifacts); mandatoryInputs.addAll(artifacts); return this; } /** Sets a .d file that will used to prune input headers */ public Builder setDotdFile(DotdFile dotdFile) { Preconditions.checkNotNull(dotdFile); this.dotdFile = dotdFile; return this; } /** Sets the source file that is being compiled in this action */ public Builder setSourceFile(Artifact sourceFile) { Preconditions.checkNotNull(sourceFile); this.sourceFile = sourceFile; this.mandatoryInputs.add(sourceFile); this.addInput(sourceFile); return this; } /** Add an input that cannot be pruned */ public Builder addMandatoryInput(Artifact input) { Preconditions.checkNotNull(input); this.mandatoryInputs.add(input); this.addInput(input); return this; } /** Add inputs that cannot be pruned */ public Builder addMandatoryInputs(Iterable input) { Preconditions.checkNotNull(input); this.mandatoryInputs.addAll(input); this.addInputs(input); return this; } /** Add inputs that cannot be pruned */ public Builder addTransitiveMandatoryInputs(NestedSet input) { Preconditions.checkNotNull(input); this.mandatoryInputs.addTransitive(input); this.addTransitiveInputs(input); return this; } /** Indicates that this compile action should perform .d pruning */ public Builder setDotdPruningPlan(HeaderDiscovery.DotdPruningMode dotdPruningPlan) { Preconditions.checkNotNull(dotdPruningPlan); this.dotdPruningPlan = dotdPruningPlan; return this; } /** Sets the set of all possible headers that could be required by this compile action. */ public Builder setHeaders(NestedSet headers) { this.headers = Preconditions.checkNotNull(headers); return this; } @Override protected SpawnAction createSpawnAction( ActionOwner owner, NestedSet tools, NestedSet inputsAndTools, ImmutableList outputs, ResourceSet resourceSet, CommandLine actualCommandLine, ImmutableMap env, ImmutableSet clientEnvironmentVariables, ImmutableMap executionInfo, String progressMessage, ImmutableMap inputAndToolManifests, String mnemonic) { return new ObjcCompileAction( owner, tools, inputsAndTools, outputs, resourceSet, actualCommandLine, env, executionInfo, progressMessage, inputAndToolManifests, mnemonic, executeUnconditionally, extraActionInfoSupplier, dotdFile, sourceFile, mandatoryInputs.build(), dotdPruningPlan, headers); } } }