blob: 19c261fb7e92624f48ad8a5fcb7498fc6838c333 [file] [log] [blame]
// 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<String> 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<BuildConfiguration, ObjcProvider> configurationToDepsMap =
ruleContext.getPrerequisitesByConfiguration(
"deps", Mode.SPLIT, ObjcProvider.SKYLARK_CONSTRUCTOR);
Artifact outputArtifact =
ObjcRuleClasses.intermediateArtifacts(ruleContext).combinedArchitectureBinary();
registerActions(ruleContext, platform, outputArtifact);
NestedSetBuilder<Artifact> filesToBuild =
NestedSetBuilder.<Artifact>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);
}
}