blob: efa07cc3d1f89572c8bb5d7002889f830001e9dd [file] [log] [blame]
// Copyright 2015 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.apple;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.RedirectChaser;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
import com.google.devtools.build.lib.syntax.Type;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Implementation for the {@code xcode_config} rule.
*/
public class XcodeConfig implements RuleConfiguredTargetFactory {
@Override
public ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException {
return new RuleConfiguredTargetBuilder(ruleContext)
.addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY)
.build();
}
/**
* Uses the {@link AppleCommandLineOptions#xcodeVersion} and
* {@link AppleCommandLineOptions#xcodeVersionConfig} command line options to determine and
* return the effective xcode version and its properties.
*
* @param env the current configuration environment
* @param xcodeConfigLabel the label for the xcode_config target to parse
* @param xcodeVersionOverrideFlag the value of the command line flag to override the default
* xcode version, absent if unspecified
* @param errorDescription a description of the origin of {@code #xcodeConfigLabel} for messaging
* parse errors
* @throws InvalidConfigurationException if the options given (or configuration targets) were
* malformed and thus the xcode version could not be determined
*/
public static XcodeVersionProperties resolveXcodeVersion(ConfigurationEnvironment env,
Label xcodeConfigLabel, Optional<DottedVersion> xcodeVersionOverrideFlag,
String errorDescription) throws InvalidConfigurationException {
Rule xcodeConfigRule =
getRuleForLabel(xcodeConfigLabel, "xcode_config", env, errorDescription);
XcodeVersionRuleData xcodeVersion =
resolveExplicitlyDefinedVersion(env, xcodeConfigRule, xcodeVersionOverrideFlag);
if (xcodeVersion != null) {
return xcodeVersion.getXcodeVersionProperties();
} else if (xcodeVersionOverrideFlag.isPresent()) {
return new XcodeVersionProperties(xcodeVersionOverrideFlag.get());
} else {
return XcodeVersionProperties.unknownXcodeVersionProperties();
}
}
/**
* Returns the {@link XcodeVersionRuleData} associated with the {@code xcode_version} target
* explicitly defined in the {@code --xcode_version_config} build flag and selected by the
* {@code --xcode_version} flag. If {@code --xcode_version} is unspecified, then this
* will return the default rule data as specified in the {@code --xcode_version_config} target.
* Returns null if either the {@code --xcode_version} did not match any {@code xcode_version}
* target, or if {@code --xcode_version} is unspecified and {@code --xcode_version_config}
* specified no default target.
*/
@Nullable private static XcodeVersionRuleData resolveExplicitlyDefinedVersion(
ConfigurationEnvironment env, Rule xcodeConfigTarget,
Optional<DottedVersion> versionOverrideFlag) throws InvalidConfigurationException {
if (versionOverrideFlag.isPresent()) {
// The version override flag is not necessarily an actual version - it may be a version
// alias.
XcodeVersionRuleData explicitVersion =
aliasesToVersionMap(env, xcodeConfigTarget).get(versionOverrideFlag.get().toString());
if (explicitVersion != null) {
return explicitVersion;
}
} else { // No override specified. Use default.
XcodeVersionRuleData defaultVersion = getDefaultVersion(env, xcodeConfigTarget);
if (defaultVersion != null) {
return defaultVersion;
}
}
boolean requireDefinedVersions = NonconfigurableAttributeMapper.of(xcodeConfigTarget)
.get(XcodeConfigRule.REQUIRE_DEFINED_VERSIONS_ATTR_NAME, Type.BOOLEAN);
if (requireDefinedVersions) {
throw new InvalidConfigurationException(
"xcode version config required an explicitly defined version, but none was available");
}
return null;
}
/**
* Returns the default xcode version to use, if no {@code --xcode_version} command line flag
* was specified.
*/
@Nullable private static XcodeVersionRuleData getDefaultVersion(ConfigurationEnvironment env,
Rule xcodeConfigTarget) throws InvalidConfigurationException {
Label defaultVersionLabel = NonconfigurableAttributeMapper.of(xcodeConfigTarget)
.get(XcodeConfigRule.DEFAULT_ATTR_NAME, BuildType.LABEL);
if (defaultVersionLabel != null) {
Rule defaultVersionRule = getRuleForLabel(
defaultVersionLabel, "xcode_version", env, "default xcode version");
return new XcodeVersionRuleData(defaultVersionLabel, defaultVersionRule);
} else {
return null;
}
}
private static Map<String, XcodeVersionRuleData> aliasesToVersionMap(ConfigurationEnvironment env,
Rule xcodeConfigTarget) throws InvalidConfigurationException {
List<Label> xcodeVersionLabels = NonconfigurableAttributeMapper.of(xcodeConfigTarget)
.get(XcodeConfigRule.VERSIONS_ATTR_NAME, BuildType.LABEL_LIST);
ImmutableList.Builder<XcodeVersionRuleData> xcodeVersionRuleListBuilder =
ImmutableList.builder();
for (Label label : xcodeVersionLabels) {
Rule xcodeVersionRule = getRuleForLabel(label, "xcode_version", env, "xcode_version");
xcodeVersionRuleListBuilder.add(new XcodeVersionRuleData(label, xcodeVersionRule));
}
ImmutableList<XcodeVersionRuleData> xcodeVersionRules = xcodeVersionRuleListBuilder.build();
Map<String, XcodeVersionRuleData> aliasesToXcodeRules = Maps.newLinkedHashMap();
for (XcodeVersionRuleData xcodeVersionRule : xcodeVersionRules) {
for (String alias : xcodeVersionRule.getAliases()) {
if (aliasesToXcodeRules.put(alias, xcodeVersionRule) != null) {
configErrorDuplicateAlias(alias, xcodeVersionRules);
}
}
if (aliasesToXcodeRules.put(
xcodeVersionRule.getVersion().toString(), xcodeVersionRule) != null) {
configErrorDuplicateAlias(xcodeVersionRule.getVersion().toString(), xcodeVersionRules);
}
}
return aliasesToXcodeRules;
}
/**
* Convenience method for throwing an {@link InvalidConfigurationException} due to presence
* of duplicate aliases in an {@code xcode_config} target definition.
*/
private static void configErrorDuplicateAlias(String alias,
List<XcodeVersionRuleData> xcodeVersionRules) throws InvalidConfigurationException {
ImmutableList.Builder<Label> labelsContainingAlias = ImmutableList.builder();
for (XcodeVersionRuleData xcodeVersionRule : xcodeVersionRules) {
if (xcodeVersionRule.getAliases().contains(alias)
|| xcodeVersionRule.getVersion().toString().equals(alias)) {
labelsContainingAlias.add(xcodeVersionRule.getLabel());
}
}
throw new InvalidConfigurationException(
String.format("'%s' is registered to multiple labels (%s) in a single xcode_config rule",
alias, Joiner.on(", ").join(labelsContainingAlias.build())));
}
/**
* If the given label (following redirects) is a target for a rule of type {@code type},
* then returns the {@link Rule} representing that target. Otherwise, throws a
* {@link InvalidConfigurationException}.
*/
private static Rule getRuleForLabel(Label label, String type, ConfigurationEnvironment env,
String description) throws InvalidConfigurationException {
label = RedirectChaser.followRedirects(env, label, description);
if (label == null) {
throw new InvalidConfigurationException(String.format(
"Expected value of %s (%s) to resolve to a target of type %s",
description, label, type));
}
try {
Target target = env.getTarget(label);
if (target instanceof Rule && ((Rule) target).getRuleClass().equals(type)) {
return (Rule) target;
} else {
throw new InvalidConfigurationException(String.format(
"Expected value of %s (%s) to resolve to a target of type %s",
description, label, type));
}
} catch (NoSuchPackageException | NoSuchTargetException exception) {
env.getEventHandler().handle(Event.error(exception.getMessage()));
throw new InvalidConfigurationException(exception);
}
}
}