// Copyright 2017 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.ObjcProvider.MULTI_ARCH_LINKED_BINARIES; import static com.google.devtools.build.lib.syntax.Type.STRING; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException; import com.google.devtools.build.lib.analysis.ConfigurationMakeVariableContext; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.stringtemplate.ExpansionException; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.rules.apple.AppleConfiguration; import com.google.devtools.build.lib.rules.apple.ApplePlatform; import com.google.devtools.build.lib.rules.apple.ApplePlatform.PlatformType; import com.google.devtools.build.lib.rules.apple.AppleToolchain; import com.google.devtools.build.lib.rules.apple.XcodeConfigProvider; import com.google.devtools.build.lib.util.StringUtil; import com.google.devtools.build.lib.vfs.PathFragment; /** Implementation for the "apple_stub_binary" rule. */ public class AppleStubBinary implements RuleConfiguredTargetFactory { /** Provides substitutions for the make variables that can be used in an xcenv_based_path */ private static class XcenvBasedPathVariableContext extends ConfigurationMakeVariableContext { private final RuleContext ruleContext; /** The platform used to build $(PLATFORM_DIR). */ private final ApplePlatform platform; /** The complete set of variables that may be used in paths. */ public static final ImmutableList DEFINED_VARS = ImmutableList.of("$(SDKROOT)", "$(PLATFORM_DIR)"); public XcenvBasedPathVariableContext(RuleContext ruleContext, ApplePlatform platform) { super(ruleContext, ruleContext.getRule().getPackage(), ruleContext.getConfiguration()); this.ruleContext = ruleContext; this.platform = platform; } /** Throws an exception if the given path is not rooted at a defined Make variable. */ public void validatePathRoot(String path) throws RuleErrorException { for (String var : DEFINED_VARS) { if (path.startsWith(var)) { return; } } throw ruleContext.throwWithAttributeError( AppleStubBinaryRule.XCENV_BASED_PATH_ATTR, String.format( PATH_INCORRECTLY_ROOTED_ERROR_FORMAT, StringUtil.joinEnglishList(XcenvBasedPathVariableContext.DEFINED_VARS, "or"))); } @Override public String lookupVariable(String var) throws ExpansionException { if (var.equals("SDKROOT")) { return "__BAZEL_XCODE_SDKROOT__"; } if (var.equals("PLATFORM_DIR")) { return AppleToolchain.platformDir(platform.getNameInPlist()); } // Intentionally do not call super, because we only want to allow these specific variables and // discard any that might be inherited from toolchains and other contexts. throw new ExpansionException("$(" + var + ") not defined"); } } @VisibleForTesting public static final String PATH_INCORRECTLY_ROOTED_ERROR_FORMAT = "The stub binary path must be rooted at %s"; @VisibleForTesting public static final String PATH_NOT_NORMALIZED_ERROR = "The stub binary path must be normalized (i.e., not contain \".\" or \"..\")"; @Override public final ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException, RuleErrorException, ActionConflictException { MultiArchSplitTransitionProvider.validateMinimumOs(ruleContext); PlatformType platformType = MultiArchSplitTransitionProvider.getPlatformType(ruleContext); AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class); ApplePlatform platform = appleConfiguration.getMultiArchPlatform(platformType); ImmutableListMultimap configurationToDepsMap = ruleContext.getPrerequisitesByConfiguration( "deps", Mode.SPLIT, ObjcProvider.SKYLARK_CONSTRUCTOR); Artifact outputArtifact = ObjcRuleClasses.intermediateArtifacts(ruleContext).combinedArchitectureBinary(); registerActions(ruleContext, platform, outputArtifact); NestedSetBuilder filesToBuild = NestedSetBuilder.stableOrder().add(outputArtifact); RuleConfiguredTargetBuilder targetBuilder = ObjcRuleClasses.ruleConfiguredTarget(ruleContext, filesToBuild.build()); ObjcProvider.Builder objcProviderBuilder = new ObjcProvider.Builder(ruleContext.getAnalysisEnvironment().getSkylarkSemantics()); for (ObjcProvider depProvider : configurationToDepsMap.values()) { objcProviderBuilder.addTransitiveAndPropagate(depProvider); } objcProviderBuilder.add(MULTI_ARCH_LINKED_BINARIES, outputArtifact); ObjcProvider objcProvider = objcProviderBuilder.build(); // TODO(cparsons): Stop propagating ObjcProvider directly from this rule. targetBuilder.addNativeDeclaredProvider(objcProvider); targetBuilder.addNativeDeclaredProvider( new AppleExecutableBinaryInfo(outputArtifact, objcProvider)); return targetBuilder.build(); } private static FilesToRunProvider xcrunwrapper(RuleContext ruleContext) { return ruleContext.getExecutablePrerequisite("$xcrunwrapper", Mode.HOST); } /** Registers the actions that copy the stub binary to the target's output. */ private static void registerActions( RuleContext ruleContext, ApplePlatform platform, Artifact outputBinary) throws RuleErrorException { CustomCommandLine copyCommandLine = new CustomCommandLine.Builder() .add("/bin/cp") .addDynamicString(resolveXcenvBasedPath(ruleContext, platform)) .addExecPaths(ImmutableList.of(outputBinary)) .build(); ruleContext.registerAction( ObjcRuleClasses.spawnAppleEnvActionBuilder( XcodeConfigProvider.fromRuleContext(ruleContext), platform) .setExecutable(xcrunwrapper(ruleContext)) .addCommandLine(copyCommandLine) .setMnemonic("CopyStubExecutable") .addOutput(outputBinary) .disableSandboxing() .build(ruleContext)); } /** * Returns the value of the xcenv_based_path attribute, emitting an error if its format is * invalid. * * @param ruleContext the rule context * @param platform the Apple platform * @return the path string, if it was valid * @throws RuleErrorException If the path string was invalid because it was not rooted at one of * the allowed environment variables or it was not normalized */ private static String resolveXcenvBasedPath(RuleContext ruleContext, ApplePlatform platform) throws RuleErrorException { String pathString = ruleContext.attributes().get(AppleStubBinaryRule.XCENV_BASED_PATH_ATTR, STRING); XcenvBasedPathVariableContext makeVariableContext = new XcenvBasedPathVariableContext(ruleContext, platform); makeVariableContext.validatePathRoot(pathString); if (!PathFragment.isNormalized(pathString)) { throw ruleContext.throwWithAttributeError( AppleStubBinaryRule.XCENV_BASED_PATH_ATTR, PATH_NOT_NORMALIZED_ERROR); } return ruleContext .getExpander(makeVariableContext) .expand(AppleStubBinaryRule.XCENV_BASED_PATH_ATTR, pathString); } }