// 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 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 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 compilationSupport = new CompilationSupport.Builder() .setRuleContext(ruleContext) .doNotUseDeps() .doNotUsePch() .build(); compilationSupport.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(); } 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.SKYLARK_CONSTRUCTOR)) .build(); } private CompilationArtifacts getCompilationArtifacts() { Iterable generatedSources = getGeneratedProtoOutputs(getSourceExtension()); return new CompilationArtifacts.Builder() .setIntermediateArtifacts(new IntermediateArtifacts(ruleContext, "")) .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() .addExecPath(attributes.getProtoCompiler()) .add("--input-file-list") .addExecPath(getProtoInputsFile()) .add("--output-dir") .addDynamicString(getWorkspaceRelativeOutputDir().getSafePathString()) .add("--working-dir") .add("."); if (attributes.getOptionsFile().isPresent()) { commandLineBuilder .add("--compiler-options-path") .addExecPath(attributes.getOptionsFile().get()); } 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()), input -> input.getExecPath().getParentDirectory())); } 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(); } }