// 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.rules.objc.XcodeProductType.LIBRARY_STATIC;
import com.google.common.base.Function;
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.RuleConfiguredTarget.Mode;
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.FileWriteAction;
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.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.PathFragment;
/**
* Support for generating Objective C proto static libraries that registers actions which generate
* and compile the Objective C protos by using the deprecated ProtocolBuffers2 library and compiler.
*
*
Methods on this class can be called in any order without impacting the result.
*/
final class ProtocolBuffers2Support {
private static final String UNIQUE_DIRECTORY_NAME = "_generated_protos";
private static final Function PARENT_PATHFRAGMENT =
new Function() {
@Override
public PathFragment apply(Artifact input) {
return input.getExecPath().getParentDirectory();
}
};
private final RuleContext ruleContext;
private final ProtoAttributes attributes;
/**
* Creates a new proto support for the ProtocolBuffers2 library.
*
* @param ruleContext context this proto library is constructed in
*/
public ProtocolBuffers2Support(RuleContext ruleContext) {
this.ruleContext = ruleContext;
this.attributes = new ProtoAttributes(ruleContext);
}
/**
* Register the proto generation actions. These actions generate the ObjC/CPP code to be compiled
* by this rule.
*/
public ProtocolBuffers2Support registerGenerationActions() throws InterruptedException {
ruleContext.registerAction(
FileWriteAction.create(
ruleContext,
getProtoInputsFile(),
getProtoInputsFileContents(
attributes.filterWellKnownProtos(attributes.getProtoFiles())),
false));
ruleContext.registerAction(
ObjcRuleClasses.spawnOnDarwinActionBuilder()
.setMnemonic("GenObjcPB2Protos")
.addInput(attributes.getProtoCompiler())
.addInputs(attributes.getProtoCompilerSupport())
.addInput(getProtoInputsFile())
.addTransitiveInputs(attributes.getProtoFiles())
.addInputs(attributes.getOptionsFile().asSet())
.addOutputs(getGeneratedProtoOutputs(getHeaderExtension()))
.addOutputs(getGeneratedProtoOutputs(getSourceExtension()))
.setExecutable(PathFragment.create("/usr/bin/python"))
.setCommandLine(getGenerationCommandLine())
.build(ruleContext));
return this;
}
/**
* Registers the actions that will compile the generated code.
*/
public ProtocolBuffers2Support registerCompilationActions()
throws RuleErrorException, InterruptedException {
CompilationSupport.createWithoutDeps(ruleContext)
.registerCompileAndArchiveActions(getCommon());
return this;
}
/** Adds the generated files to the set of files to be output when this rule is built. */
public ProtocolBuffers2Support addFilesToBuild(NestedSetBuilder filesToBuild)
throws InterruptedException {
filesToBuild
.addAll(getGeneratedProtoOutputs(getHeaderExtension()))
.addAll(getGeneratedProtoOutputs(getSourceExtension()))
.addAll(getCompilationArtifacts().getArchive().asSet());
return this;
}
/** Returns the ObjcProvider for this target. */
public ObjcProvider getObjcProvider() {
return getCommon().getObjcProvider();
}
/** Returns the XcodeProvider for this target. */
public XcodeProvider getXcodeProvider() {
XcodeProvider.Builder xcodeProviderBuilder =
new XcodeProvider.Builder()
.addUserHeaderSearchPaths(getIncludes())
.setCompilationArtifacts(getCompilationArtifacts());
new XcodeSupport(ruleContext)
.addXcodeSettings(xcodeProviderBuilder, getCommon().getObjcProvider(), LIBRARY_STATIC)
.addDependencies(
xcodeProviderBuilder, new Attribute(ObjcRuleClasses.PROTO_LIB_ATTR, Mode.TARGET));
return xcodeProviderBuilder.build();
}
private String getHeaderExtension() {
return ".pb" + (attributes.usesObjcHeaderNames() ? "objc.h" : ".h");
}
private String getSourceExtension() {
return ".pb.m";
}
private ObjcCommon getCommon() {
return new ObjcCommon.Builder(ruleContext)
.setIntermediateArtifacts(new IntermediateArtifacts(ruleContext, ""))
.setHasModuleMap()
.setCompilationArtifacts(getCompilationArtifacts())
.addIncludes(getIncludes())
.addDepObjcProviders(
ruleContext.getPrerequisites(
ObjcRuleClasses.PROTO_LIB_ATTR, Mode.TARGET, ObjcProvider.class))
.build();
}
private CompilationArtifacts getCompilationArtifacts() {
Iterable generatedSources = getGeneratedProtoOutputs(getSourceExtension());
return new CompilationArtifacts.Builder()
.setIntermediateArtifacts(new IntermediateArtifacts(ruleContext, ""))
.setPchFile(Optional.absent())
.addAdditionalHdrs(getGeneratedProtoOutputs(getHeaderExtension()))
.addAdditionalHdrs(generatedSources)
.addNonArcSrcs(generatedSources)
.build();
}
private Artifact getProtoInputsFile() {
return ruleContext.getUniqueDirectoryArtifact(
"_protos", "_proto_input_files", ruleContext.getConfiguration().getGenfilesDirectory());
}
private String getProtoInputsFileContents(Iterable outputProtos) {
// 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 sorted = Ordering.natural().immutableSortedCopy(outputProtos);
return Artifact.joinExecPaths("\n", sorted);
}
private CustomCommandLine getGenerationCommandLine() {
CustomCommandLine.Builder commandLineBuilder =
new CustomCommandLine.Builder()
.add(attributes.getProtoCompiler().getExecPathString())
.add("--input-file-list")
.add(getProtoInputsFile().getExecPathString())
.add("--output-dir")
.add(getWorkspaceRelativeOutputDir().getSafePathString())
.add("--working-dir")
.add(".");
if (attributes.getOptionsFile().isPresent()) {
commandLineBuilder
.add("--compiler-options-path")
.add(attributes.getOptionsFile().get().getExecPathString());
}
if (attributes.usesObjcHeaderNames()) {
commandLineBuilder.add("--use-objc-header-names");
}
return commandLineBuilder.build();
}
public ImmutableSet getIncludes() {
ImmutableSet.Builder searchPathEntriesBuilder =
new ImmutableSet.Builder().add(getWorkspaceRelativeOutputDir());
if (attributes.needsPerProtoIncludes()) {
PathFragment generatedProtoDir = PathFragment.create(
getWorkspaceRelativeOutputDir(), ruleContext.getLabel().getPackageFragment());
searchPathEntriesBuilder
.add(generatedProtoDir)
.addAll(
Iterables.transform(
getGeneratedProtoOutputs(getHeaderExtension()), PARENT_PATHFRAGMENT));
}
return searchPathEntriesBuilder.build();
}
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 PathFragment.create(
ruleContext.getBinOrGenfilesDirectory().getExecPath(), rootRelativeOutputDir);
}
private Iterable getGeneratedProtoOutputs(String extension) {
ImmutableList.Builder builder = new ImmutableList.Builder<>();
for (Artifact protoFile : attributes.filterWellKnownProtos(attributes.getProtoFiles())) {
String protoFileName = FileSystemUtils.removeExtension(protoFile.getFilename());
String generatedOutputName = attributes.getGeneratedProtoFilename(protoFileName, false);
PathFragment generatedFilePath = PathFragment.create(
protoFile.getRootRelativePath().getParentDirectory(),
PathFragment.create(generatedOutputName));
PathFragment outputFile = FileSystemUtils.appendExtension(generatedFilePath, extension);
if (outputFile != null) {
builder.add(
ruleContext.getUniqueDirectoryArtifact(
UNIQUE_DIRECTORY_NAME, outputFile, ruleContext.getBinOrGenfilesDirectory()));
}
}
return builder.build();
}
}