blob: 1cac4a449b55f88ed4159656814f0fa9a54872f1 [file] [log] [blame]
// 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.common.base.Preconditions.checkState;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
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.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
import com.google.devtools.build.lib.rules.cpp.CcToolchainProvider;
import com.google.devtools.build.lib.rules.proto.ProtoInfo;
import com.google.devtools.build.lib.rules.proto.ProtoSourceFileBlacklist;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Support for generating Objective C proto static libraries that registers actions which generate
* and compile the Objective C protos by using the open source protobuf library and compiler.
*
* <p>Each group represents one proto_library target depended on by objc_proto_library targets using
* the portable_proto_filters attribute. This group contains all the necessary protos to satisfy its
* internal dependencies.
*
* <p>Grouping has a first pass in which for each proto required to be built, we find the smallest
* group containing it, and store that information in a map. We then reverse that map into a multi
* map, in which the keys are the input protos and the values are the output protos to be
* generated/compiled with the input group as dependencies. This minimizes the number of inputs
* required for each generation/compilation action and the probability of regeneration when one of
* the proto files change, improving cache hits.
*/
final class ProtobufSupport {
private static final String HEADER_SUFFIX = ".pbobjc.h";
private static final String SOURCE_SUFFIX = ".pbobjc.m";
private static final String BUNDLED_PROTOS_IDENTIFIER = "BundledProtos";
private static final String UNIQUE_DIRECTORY_NAME = "_generated_objc_protos";
private final RuleContext ruleContext;
private final BuildConfiguration buildConfiguration;
private final ProtoAttributes attributes;
private final Collection<ObjcProtoProvider> objcProtoProviders;
private final NestedSet<Artifact> portableProtoFilters;
private final CcToolchainProvider toolchain;
private final ImmutableSet<Artifact> dylibHandledProtos;
private Optional<ObjcProvider> objcProvider;
/**
* Creates a new proto support for the protobuf library. This support code bundles up all the
* transitive protos within the groups in which they were defined. We use that information to
* minimize the number of inputs per generation/compilation actions by only providing what is
* really needed to the actions.
*
* @param ruleContext context this proto library is constructed in
* @param buildConfiguration the configuration from which to get prerequisites when building proto
* targets in a split configuration
* @param dylibHandledProtos a set of protos linked into dynamic libraries that the current rule
* depends on; these protos will not be output by this support, thus avoiding duplicate
* symbols
* @param objcProtoProviders the list of ObjcProtoProviders that this proto support should process
* @param toolchain if not null, the toolchain to override the default toolchain for the rule
* context.
*/
ProtobufSupport(
RuleContext ruleContext,
BuildConfiguration buildConfiguration,
NestedSet<Artifact> dylibHandledProtos,
Collection<ObjcProtoProvider> objcProtoProviders,
NestedSet<Artifact> portableProtoFilters,
CcToolchainProvider toolchain) {
this.ruleContext = ruleContext;
this.buildConfiguration = buildConfiguration;
this.attributes = new ProtoAttributes(ruleContext);
this.objcProtoProviders = objcProtoProviders;
this.portableProtoFilters = portableProtoFilters;
this.toolchain = toolchain;
this.dylibHandledProtos = dylibHandledProtos.toSet();
this.objcProvider = Optional.absent();
}
/** Registers the action that will compile the generated code. */
ProtobufSupport registerCompilationAction() throws RuleErrorException, InterruptedException {
if (!hasOutputProtos()) {
return this;
}
List<PathFragment> userHeaderSearchPaths = ImmutableList.of(getWorkspaceRelativeOutputDir());
CompilationArtifacts compilationArtifacts =
new CompilationArtifacts.Builder()
.setIntermediateArtifacts(getUniqueIntermediateArtifactsForSourceCompile())
.addNonArcSrcs(getGeneratedProtoOutputs(getOutputProtos(), SOURCE_SUFFIX))
.addAdditionalHdrs(getGeneratedProtoOutputs(getAllProtos(), HEADER_SUFFIX))
.addAdditionalHdrs(getProtobufHeaders())
.build();
ObjcCommon common =
getCommon(getUniqueIntermediateArtifactsForSourceCompile(), compilationArtifacts);
CompilationSupport compilationSupport =
new CompilationSupport.Builder()
.setRuleContext(ruleContext)
.setConfig(buildConfiguration)
.setIntermediateArtifacts(getUniqueIntermediateArtifactsForSourceCompile())
.setCompilationAttributes(new CompilationAttributes.Builder().build())
.setToolchainProvider(toolchain)
.doNotUsePch()
.build();
compilationSupport.registerCompileAndArchiveActions(common, userHeaderSearchPaths);
setObjcProvider(compilationSupport.getObjcProvider());
return this;
}
private void setObjcProvider(ObjcProvider objcProvider) {
checkState(!this.objcProvider.isPresent());
this.objcProvider = Optional.of(objcProvider);
}
/**
* Returns the ObjcProvider for this target, or Optional.absent() if there were no protos to
* generate.
*/
public Optional<ObjcProvider> getObjcProvider() {
return objcProvider;
}
private NestedSet<Artifact> getProtobufHeaders() {
NestedSetBuilder<Artifact> protobufHeaders = NestedSetBuilder.stableOrder();
for (ObjcProtoProvider objcProtoProvider : objcProtoProviders) {
protobufHeaders.addTransitive(objcProtoProvider.getProtobufHeaders());
}
return protobufHeaders.build();
}
private NestedSet<Artifact> getAllProtos() {
NestedSetBuilder<Artifact> protosSet = NestedSetBuilder.stableOrder();
for (ObjcProtoProvider objcProtoProvider : objcProtoProviders) {
protosSet.addTransitive(objcProtoProvider.getProtoFiles());
}
return protosSet.build();
}
private Boolean hasOutputProtos() {
Set<PathFragment> dylibHandledProtoPaths = runfilesPaths(dylibHandledProtos);
return Iterables.any(
getAllProtos().toSet(),
artifact -> !dylibHandledProtoPaths.contains(artifact.getRunfilesPath()));
}
private Iterable<Artifact> getOutputProtos() {
Set<PathFragment> dylibHandledProtoPaths = runfilesPaths(dylibHandledProtos);
return Iterables.filter(
getAllProtos().toSet(),
artifact -> !dylibHandledProtoPaths.contains(artifact.getRunfilesPath()));
}
private NestedSet<PathFragment> getProtobufHeaderSearchPaths() {
NestedSetBuilder<PathFragment> protobufHeaderSearchPaths = NestedSetBuilder.stableOrder();
for (ObjcProtoProvider objcProtoProvider : objcProtoProviders) {
protobufHeaderSearchPaths.addTransitive(objcProtoProvider.getProtobufHeaderSearchPaths());
}
return protobufHeaderSearchPaths.build();
}
private static Set<PathFragment> runfilesPaths(Iterable<Artifact> artifacts) {
HashSet<PathFragment> pathsSet = new HashSet<>();
for (Artifact artifact : artifacts) {
pathsSet.add(artifact.getRunfilesPath());
}
return pathsSet;
}
private static String getBundledProtosSuffix() {
return "_" + BUNDLED_PROTOS_IDENTIFIER;
}
private static String getBundledProtosPrefix() {
return BUNDLED_PROTOS_IDENTIFIER + "_";
}
private IntermediateArtifacts getUniqueIntermediateArtifactsForSourceCompile() {
return new IntermediateArtifacts(
ruleContext, getBundledProtosSuffix(), getBundledProtosPrefix(), buildConfiguration);
}
private ObjcCommon getCommon(
IntermediateArtifacts intermediateArtifacts, CompilationArtifacts compilationArtifacts)
throws InterruptedException {
return new ObjcCommon.Builder(ObjcCommon.Purpose.COMPILE_AND_LINK, ruleContext)
.setIntermediateArtifacts(intermediateArtifacts)
.setCompilationArtifacts(compilationArtifacts)
.addIncludes(getProtobufHeaderSearchPaths())
.build();
}
ProtobufSupport registerGenerationAction() {
if (!hasOutputProtos()) {
return this;
}
Artifact outputGroupFile =
ruleContext.getUniqueDirectoryArtifact(
"_protos", "output_group", buildConfiguration.getGenfilesDirectory());
Artifact skipGroupFile =
ruleContext.getUniqueDirectoryArtifact(
"_protos", "skip_group", buildConfiguration.getGenfilesDirectory());
ruleContext.registerAction(
FileWriteAction.create(
ruleContext, skipGroupFile, getProtoInputsFileContents(dylibHandledProtos), false));
ruleContext.registerAction(
FileWriteAction.create(
ruleContext, outputGroupFile, getProtoInputsFileContents(getAllProtos()), false));
ruleContext.registerAction(
new SpawnAction.Builder()
.setMnemonic("GenObjcBundledProtos")
.addInput(attributes.getProtoCompiler())
.addInputs(attributes.getProtoCompilerSupport())
.addTransitiveInputs(portableProtoFilters)
.addInput(outputGroupFile)
.addInput(skipGroupFile)
.addTransitiveInputs(getAllProtos())
.addOutputs(getGeneratedProtoOutputs(getAllProtos(), HEADER_SUFFIX))
.addOutputs(getGeneratedProtoOutputs(getOutputProtos(), SOURCE_SUFFIX))
.setExecutable(attributes.getProtoCompiler().getExecPath())
.addCommandLine(getGenerationCommandLine(outputGroupFile, skipGroupFile))
.build(ruleContext));
return this;
}
private static String getProtoInputsFileContents(NestedSet<Artifact> protoFiles) {
return getProtoInputsFileContents(protoFiles.toList());
}
private static String getProtoInputsFileContents(Iterable<Artifact> protoFiles) {
// Sort the file names to make the remote action key independent of the precise deps structure.
// compile_protos.py will sort the input list anyway.
Iterable<Artifact> sorted = Ordering.natural().immutableSortedCopy(protoFiles);
return Artifact.joinRootRelativePaths("\n", sorted);
}
private CustomCommandLine getGenerationCommandLine(
Artifact outputGroupFile, Artifact skipGroupFile) {
return new CustomCommandLine.Builder()
.add("--output-dir")
.addDynamicString(getWorkspaceRelativeOutputDir().getSafePathString())
.add("--proto-root-dir")
.addDynamicString(getGenfilesPathString())
.add("--proto-root-dir")
.add(".")
.add("--input-file-list")
.addExecPath(outputGroupFile)
.addExecPaths(VectorArg.addBefore("--config").each(portableProtoFilters))
.add("--skip-groups-impls")
.addExecPath(skipGroupFile)
.build();
}
private String getGenfilesPathString() {
return buildConfiguration.getGenfilesDirectory().getExecPathString();
}
private PathFragment getWorkspaceRelativeOutputDir() {
// Generate sources in a package-and-rule-scoped directory; adds both the
// package-and-rule-scoped directory and the header-containing-directory to the include path
// of dependers.
PathFragment rootRelativeOutputDir = ruleContext.getUniqueDirectory(UNIQUE_DIRECTORY_NAME);
return buildConfiguration.getBinDirectory().getExecPath().getRelative(rootRelativeOutputDir);
}
private List<Artifact> getGeneratedProtoOutputs(
NestedSet<Artifact> protoFiles, String extension) {
return getGeneratedProtoOutputs(protoFiles.toList(), extension);
}
private List<Artifact> getGeneratedProtoOutputs(Iterable<Artifact> protoFiles, String extension) {
ImmutableList.Builder<Artifact> builder = new ImmutableList.Builder<>();
ProtoSourceFileBlacklist wellKnownProtoBlacklist =
new ProtoSourceFileBlacklist(ruleContext, attributes.getWellKnownTypeProtos());
for (Artifact protoFile : protoFiles) {
if (wellKnownProtoBlacklist.isBlacklisted(protoFile)) {
continue;
}
String protoFileName = FileSystemUtils.removeExtension(protoFile.getFilename());
String generatedOutputName = attributes.getGeneratedProtoFilename(protoFileName, true);
PathFragment generatedFilePath =
protoFile.getRootRelativePath().getParentDirectory().getRelative(generatedOutputName);
PathFragment outputFile = FileSystemUtils.appendExtension(generatedFilePath, extension);
if (outputFile != null) {
builder.add(
ruleContext.getUniqueDirectoryArtifact(
UNIQUE_DIRECTORY_NAME, outputFile, buildConfiguration.getBinDirectory()));
}
}
return builder.build();
}
/** Returns the transitive portable proto filter files from a list of ObjcProtoProviders. */
static NestedSet<Artifact> getTransitivePortableProtoFilters(
Iterable<ObjcProtoProvider> objcProtoProviders) {
NestedSetBuilder<Artifact> portableProtoFilters = NestedSetBuilder.stableOrder();
for (ObjcProtoProvider objcProtoProvider : objcProtoProviders) {
portableProtoFilters.addTransitive(objcProtoProvider.getPortableProtoFilters());
}
return portableProtoFilters.build();
}
/** Returns a target specific generated artifact that represents a portable filter file. */
static Artifact getGeneratedPortableFilter(
RuleContext ruleContext, BuildConfiguration buildConfiguration) {
return ruleContext.getUniqueDirectoryArtifact(
"_proto_filters",
"generated_filter_file.pbascii",
buildConfiguration.getGenfilesDirectory());
}
/**
* Registers a FileWriteAction what writes a filter file into the given artifact. The contents of
* this file is a portable filter that allows all the transitive proto files contained in the
* given {@link ProtoInfo} providers.
*/
static void registerPortableFilterGenerationAction(
RuleContext ruleContext, Artifact generatedPortableFilter, List<ProtoInfo> protoInfos) {
ruleContext.registerAction(
FileWriteAction.create(
ruleContext,
generatedPortableFilter,
getGeneratedPortableFilterContents(ruleContext, protoInfos),
false));
}
private static String getGeneratedPortableFilterContents(
RuleContext ruleContext, Iterable<ProtoInfo> protoInfos) {
NestedSetBuilder<Artifact> protoFilesBuilder = NestedSetBuilder.stableOrder();
for (ProtoInfo protoInfo : protoInfos) {
protoFilesBuilder.addTransitive(protoInfo.getTransitiveProtoSources());
}
Iterable<String> protoFilePaths =
Artifact.toRootRelativePaths(
Ordering.natural().immutableSortedCopy(protoFilesBuilder.build().toList()));
Iterable<String> filterLines =
Iterables.transform(
protoFilePaths, protoFilePath -> String.format("allowed_file: \"%s\"", protoFilePath));
return String.format(
"# Generated portable filter for %s\n\n", ruleContext.getLabel().getCanonicalForm())
+ Joiner.on("\n").join(filterLines);
}
}