Skip to content

Commit

Permalink
Do not rely on CppConfiguration package loading when selecting toolch…
Browse files Browse the repository at this point in the history
…ain in cc_toolchain

Rather re-select the toolchain for yourself. The next step will be to stop relying on CROSSTOOL in the CppConfiguration.
#6072.

RELNOTES: None.
PiperOrigin-RevId: 212813981
  • Loading branch information
hlopko authored and Copybara-Service committed Sep 13, 2018
1 parent cf6cb2a commit 46e65d1
Show file tree
Hide file tree
Showing 15 changed files with 403 additions and 317 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
// Copyright 2018 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.cpp;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.util.StringUtil;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CrosstoolRelease;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import javax.annotation.Nullable;

/**
* Utils class for logic responsible for selecting a CToolchain from the CROSSTOOL file given the
* cc_toolchain.
*/
public class CToolchainSelectionUtils {

/**
* Do-it-all CToolchain selection method that considers toolchain identifiers, cpu/compiler
* attributes, and cpu/compiler options. Returns the CToolchain from the CROSSTOOL. If you need to
* call anything else than this, you're holding it wrong.
*
* @param identifierAttribute value of the cc_toolchain.toolchain_identifier attribute
* @param cpuAttribute value of the cc_toolchain.cpu attribute
* @param compilerAttribute value of the cc_toolchain.compiler attribute
* @param cpuOption value of the --cpu option
* @param compilerOption value of the --compiler option
* @param proto content of the CROSSTOOL file
* @param cpuTransformer because life is hard
* @return selected CToolchain or throws InvalidConfigurationException when not found. Never
* returns null.
*/
public static CToolchain selectCToolchain(
@Nullable String identifierAttribute,
@Nullable String cpuAttribute,
@Nullable String compilerAttribute,
String cpuOption,
@Nullable String compilerOption,
CrosstoolRelease proto,
Function<String, String> cpuTransformer)
throws InvalidConfigurationException {
String identifierAttributeOrNull = StringUtil.emptyToNull(identifierAttribute);
String cpuAttributeOrNull = StringUtil.emptyToNull(cpuAttribute);
String compilerAttributeOrNull = StringUtil.emptyToNull(compilerAttribute);

Preconditions.checkNotNull(cpuOption);
String compilerOptionOrNull = StringUtil.emptyToNull(compilerOption);

return selectCToolchainNoEmptyStrings(
identifierAttributeOrNull,
cpuAttributeOrNull,
compilerAttributeOrNull,
cpuOption,
compilerOptionOrNull,
proto,
cpuTransformer);
}

private static CToolchain selectCToolchainNoEmptyStrings(
String identifierAttribute,
String cpuAttribute,
String compilerAttribute,
String cpuOption,
String compilerOption,
CrosstoolRelease proto,
Function<String, String> cpuTransformer)
throws InvalidConfigurationException {
CToolchain cToolchain = null;
// Use the identifier to find the CToolchain from the CROSSTOOL (this is the way how
// cc_toolchain will select CToolchain in the happy future, since it works with platforms).
if (identifierAttribute != null) {
cToolchain = getToolchainByIdentifier(proto, identifierAttribute);
}
if (cToolchain == null && cpuAttribute != null) {
// Let's try to select the CToolchain using cpu and compiler rule attributes (the semi-new
// way, compatible with platforms).
try {
cToolchain =
selectToolchainUsingCpuAndMaybeCompiler(
proto,
new CrosstoolConfigurationIdentifier(cpuAttribute, compilerAttribute),
cpuTransformer);
} catch (InvalidConfigurationException e) {
// We couldn't find the CToolchain using attributes, let's catch the exception and try
// with options. It's safe to ignore the exception here, since if it was caused by
// something else than the selection, it will be re-thrown below.
}
}
if (cToolchain == null) {
// We couldn't find the CToolchain using cpu and compiler attributes, let's try to select
// it using --cpu/--compiler options (the legacy way, doesn't work with platforms).
cToolchain =
selectToolchainUsingCpuAndMaybeCompiler(
proto,
new CrosstoolConfigurationIdentifier(cpuOption, compilerOption),
cpuTransformer);
}
return cToolchain;
}

/**
* Selects a crosstool toolchain based on the toolchain identifier.
*
* @throws InvalidConfigurationException if no matching toolchain can be found, or if multiple
* toolchains with the same identifier are found.
*/
private static CToolchain getToolchainByIdentifier(
CrosstoolRelease proto, String toolchainIdentifier) throws InvalidConfigurationException {
checkToolchain(toolchainIdentifier);
CToolchain selectedToolchain = null;
for (CToolchain toolchain : proto.getToolchainList()) {
if (toolchain.getToolchainIdentifier().equals(toolchainIdentifier)) {
if (selectedToolchain != null) {
throw new InvalidConfigurationException(
String.format("Multiple toolchains with '%s' identifier", toolchainIdentifier));
}
selectedToolchain = toolchain;
}
}
if (selectedToolchain == null) {
throw new InvalidConfigurationException(
String.format("Toolchain identifier '%s' was not found", toolchainIdentifier));
}
return selectedToolchain;
}

/**
* Makes sure that {@code selectedIdentifier} is a valid identifier for a toolchain, i.e. it
* starts with a letter or an underscore and continues with only dots, dashes, spaces, letters,
* digits or underscores (i.e. matches the following regular expression: "[a-zA-Z_][\.\- \w]*").
*
* @throws InvalidConfigurationException if selectedIdentifier does not match the aforementioned
* regular expression.
*/
private static void checkToolchain(String selectedIdentifier)
throws InvalidConfigurationException {
// If you update this regex, please do so in the javadoc comment too, and also in the
// crosstool_config.proto file.
String rx = "[a-zA-Z_][\\.\\- \\w]*";
if (!selectedIdentifier.matches(rx)) {
throw new InvalidConfigurationException(
String.format(
"Toolchain identifier '%s' is illegal (does not match '%s')",
selectedIdentifier, rx));
}
}

/**
* Selects a crosstool toolchain corresponding to the given crosstool configuration options. If
* all of these options are null, it returns the default toolchain specified in the crosstool
* release. If only cpu is non-null, it returns the default toolchain for that cpu, as specified
* in the crosstool release. Otherwise, all values must be non-null, and this method returns the
* toolchain which matches all of the values.
*
* @throws NullPointerException if {@code release} is null
* @throws InvalidConfigurationException if no matching toolchain can be found, or if the input
* parameters do not obey the constraints described above
*/
public static CToolchain selectToolchainUsingCpuAndMaybeCompiler(
CrosstoolRelease release,
CrosstoolConfigurationIdentifier config,
Function<String, String> cpuTransformer)
throws InvalidConfigurationException {
if (config.getCompiler() != null) {
ArrayList<CToolchain> candidateToolchains = new ArrayList<>();
for (CToolchain toolchain : release.getToolchainList()) {
if (config.isCandidateToolchain(toolchain)) {
candidateToolchains.add(toolchain);
}
}
switch (candidateToolchains.size()) {
case 0:
{
StringBuilder message = new StringBuilder();
message.append("No toolchain found for");
message.append(config.describeFlags());
message.append(". Valid toolchains are: ");
describeToolchainList(message, release.getToolchainList());
throw new InvalidConfigurationException(message.toString());
}
case 1:
return candidateToolchains.get(0);
default:
{
StringBuilder message = new StringBuilder();
message.append("Multiple toolchains found for");
message.append(config.describeFlags());
message.append(": ");
describeToolchainList(message, candidateToolchains);
throw new InvalidConfigurationException(message.toString());
}
}
}
String selectedIdentifier = null;
// We use fake CPU values to allow cross-platform builds for other languages that use the
// C++ toolchain. Translate to the actual target architecture.
String desiredCpu = cpuTransformer.apply(config.getCpu());
for (CrosstoolConfig.DefaultCpuToolchain selector : release.getDefaultToolchainList()) {
if (selector.getCpu().equals(desiredCpu)) {
selectedIdentifier = selector.getToolchainIdentifier();
break;
}
}

if (selectedIdentifier == null) {
HashSet<String> seenCpus = new HashSet<>();
StringBuilder cpuBuilder = new StringBuilder();
for (CrosstoolConfig.DefaultCpuToolchain selector : release.getDefaultToolchainList()) {
if (seenCpus.add(selector.getCpu())) {
cpuBuilder.append(" ").append(selector.getCpu()).append(",\n");
}
}
throw new InvalidConfigurationException(
"No default_toolchain found for cpu '"
+ desiredCpu
+ "'. Valid cpus are: [\n"
+ cpuBuilder
+ "]");
}
checkToolchain(selectedIdentifier);

for (CToolchain toolchain : release.getToolchainList()) {
if (toolchain.getToolchainIdentifier().equals(selectedIdentifier)) {
return toolchain;
}
}

throw new InvalidConfigurationException(
"Inconsistent crosstool configuration; no toolchain "
+ "corresponding to '"
+ selectedIdentifier
+ "' found for cpu '"
+ config.getCpu()
+ "'");
}

/**
* Appends a series of toolchain descriptions (as the blaze command line flags that would specify
* that toolchain) to 'message'.
*/
private static void describeToolchainList(
StringBuilder message, Collection<CToolchain> toolchains) {
message.append("[\n");
for (CrosstoolConfig.CToolchain toolchain : toolchains) {
message.append(" ");
message.append(toolchain.getToolchainIdentifier());
message.append(": ");
message.append(
CrosstoolConfigurationIdentifier.fromToolchain(toolchain).describeFlags().trim());
message.append(",\n");
}
message.append("]");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import com.google.devtools.build.lib.analysis.LicensesProvider.TargetLicense;
import com.google.devtools.build.lib.analysis.LicensesProviderImpl;
import com.google.devtools.build.lib.analysis.MiddlemanProvider;
import com.google.devtools.build.lib.analysis.PlatformConfiguration;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory;
import com.google.devtools.build.lib.analysis.RuleContext;
Expand Down Expand Up @@ -689,11 +688,6 @@ private CppToolchainInfo getCppToolchainInfo(
// Attempt to find a toolchain based on the target attributes, not the configuration.
CToolchain toolchain = getToolchainFromAttributes(ruleContext, cppConfiguration);

if (toolchain == null) {
// Fall back to the toolchain info in the current configuration.
return cppConfiguration.getCppToolchainInfo();
}

// If we found a toolchain, use it.
try {
toolchain =
Expand Down Expand Up @@ -770,41 +764,28 @@ private static ImmutableMap<String, Object> getToolchainForSkylark(
@Nullable
private CToolchain getToolchainFromAttributes(
RuleContext ruleContext, CppConfiguration cppConfiguration) throws RuleErrorException {
PlatformConfiguration platformConfig =
Preconditions.checkNotNull(ruleContext.getFragment(PlatformConfiguration.class));

if (!platformConfig.isToolchainTypeEnabled(
CppHelper.getToolchainTypeFromRuleClass(ruleContext))) {
return null;
}

// Is there a toolchain proto available on the target directly?
CToolchain toolchain = parseToolchainFromAttributes(ruleContext);
if (toolchain != null) {
return toolchain;
}

// Use the attributes to find the proper toolchain from the CROSSTOOL.
if (ruleContext.attributes().get("cpu", Type.STRING).isEmpty()) {
ruleContext.throwWithRuleError("Using cc_toolchain target requires the attribute 'cpu' "
+ "to be present");
}

String toolchainIdentifier = ruleContext.attributes().get("toolchain_identifier", Type.STRING);
String cpu = ruleContext.attributes().get("cpu", Type.STRING);
String compiler = ruleContext.attributes().get("compiler", Type.STRING);
if (compiler.isEmpty()) {
compiler = null;
}
CrosstoolConfigurationIdentifier config = new CrosstoolConfigurationIdentifier(cpu, compiler);

try {
return CrosstoolConfigurationLoader.selectToolchain(
return CToolchainSelectionUtils.selectCToolchain(
toolchainIdentifier,
cpu,
compiler,
cppConfiguration.getTransformedCpuFromOptions(),
cppConfiguration.getCompilerFromOptions(),
cppConfiguration.getCrosstoolFile().getProto(),
config,
cppConfiguration.getCpuTransformer());
} catch (InvalidConfigurationException e) {
ruleContext.throwWithRuleError(
String.format("Error while using cc_toolchain: %s", e.getMessage()));
String.format("Error while selecting cc_toolchain: %s", e.getMessage()));
return null;
}
}
Expand Down
Loading

0 comments on commit 46e65d1

Please sign in to comment.