blob: 3fe7927faa54b1066ccdfaeca9ad64ac7cffc904 [file] [log] [blame]
// 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.Preconditions;
import com.google.common.collect.ImmutableList;
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 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
* @return selected CToolchain or throws InvalidConfigurationException when not found. Never
* returns null.
*/
static CToolchain selectCToolchain(
@Nullable String identifierAttribute,
@Nullable String cpuAttribute,
@Nullable String compilerAttribute,
String cpuOption,
@Nullable String compilerOption,
CrosstoolRelease proto)
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);
}
private static CToolchain selectCToolchainNoEmptyStrings(
String identifierAttribute,
String cpuAttribute,
String compilerAttribute,
String cpuOption,
String compilerOption,
CrosstoolRelease proto)
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));
} 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));
}
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, valid identifiers are %s",
toolchainIdentifier,
proto.getToolchainList().stream()
.map(CToolchain::getToolchainIdentifier)
.collect(ImmutableList.toImmutableList())));
}
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
*/
private static CToolchain selectToolchainUsingCpuAndMaybeCompiler(
CrosstoolRelease release, CrosstoolConfigurationIdentifier config)
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());
}
}
}
StringBuilder errorMessageBuilder = new StringBuilder();
errorMessageBuilder
.append("No toolchain found for cpu '")
.append(config.getCpu())
.append("'. Valid toolchains are: ");
describeToolchainList(errorMessageBuilder, release.getToolchainList());
throw new InvalidConfigurationException(errorMessageBuilder.toString());
}
/**
* 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("]");
}
}