2
0
Fork 0
mirror of https://github.com/bazelbuild/rules_cc synced 2024-11-27 20:43:26 +00:00
rules_cc/tools/migration/crosstool_to_starlark_lib.go
rosica 2312d72134 Fix allowed values in 'compiler' attribute of cc_toolchain_config rule.
When cpu value is not enough to distinguish between two toolchains, we use the compiler value. In that case, the user needs to specify the compiler value for all cc_toolchain_config rules, even for the ones with unique cpu. This cl fixes the allowed values in the compiler attribute to account for the values from unique toolchains.

Issue #5380
RELNOTES: None.
PiperOrigin-RevId: 239396228
2019-03-20 07:55:12 -07:00

1420 lines
43 KiB
Go

/*
Package crosstooltostarlarklib provides the Transform method
for conversion of a CROSSTOOL file to a Starlark rule.
https://github.com/bazelbuild/bazel/issues/5380
*/
package crosstooltostarlarklib
import (
"bytes"
"errors"
"fmt"
"sort"
"strings"
crosstoolpb "third_party/com/github/bazelbuild/bazel/src/main/protobuf/crosstool_config_go_proto"
)
// CToolchainIdentifier is what we'll use to differ between CToolchains
// If a CToolchain can be distinguished from the other CToolchains
// by only one of the fields (eg if cpu is different for each CToolchain
// then only that field will be set.
type CToolchainIdentifier struct {
cpu string
compiler string
}
// Writes the load statement for the cc_toolchain_config_lib
func getCcToolchainConfigHeader() string {
return `load("@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
"action_config",
"artifact_name_pattern",
"env_entry",
"env_set",
"feature",
"feature_set",
"flag_group",
"flag_set",
"make_variable",
"tool",
"tool_path",
"variable_with_value",
"with_feature_set",
)
`
}
var allCompileActions = []string{
"c-compile",
"c++-compile",
"linkstamp-compile",
"assemble",
"preprocess-assemble",
"c++-header-parsing",
"c++-module-compile",
"c++-module-codegen",
"clif-match",
"lto-backend",
}
var allCppCompileActions = []string{
"c++-compile",
"linkstamp-compile",
"c++-header-parsing",
"c++-module-compile",
"c++-module-codegen",
"clif-match",
}
var preprocessorCompileActions = []string{
"c-compile",
"c++-compile",
"linkstamp-compile",
"preprocess-assemble",
"c++-header-parsing",
"c++-module-compile",
"clif-match",
}
var codegenCompileActions = []string{
"c-compile",
"c++-compile",
"linkstamp-compile",
"assemble",
"preprocess-assemble",
"c++-module-codegen",
"lto-backend",
}
var allLinkActions = []string{
"c++-link-executable",
"c++-link-dynamic-library",
"c++-link-nodeps-dynamic-library",
}
var actionNames = map[string]string{
"c-compile": "ACTION_NAMES.c_compile",
"c++-compile": "ACTION_NAMES.cpp_compile",
"linkstamp-compile": "ACTION_NAMES.linkstamp_compile",
"cc-flags-make-variable": "ACTION_NAMES.cc_flags_make_variable",
"c++-module-codegen": "ACTION_NAMES.cpp_module_codegen",
"c++-header-parsing": "ACTION_NAMES.cpp_header_parsing",
"c++-module-compile": "ACTION_NAMES.cpp_module_compile",
"assemble": "ACTION_NAMES.assemble",
"preprocess-assemble": "ACTION_NAMES.preprocess_assemble",
"lto-indexing": "ACTION_NAMES.lto_indexing",
"lto-backend": "ACTION_NAMES.lto_backend",
"c++-link-executable": "ACTION_NAMES.cpp_link_executable",
"c++-link-dynamic-library": "ACTION_NAMES.cpp_link_dynamic_library",
"c++-link-nodeps-dynamic-library": "ACTION_NAMES.cpp_link_nodeps_dynamic_library",
"c++-link-static-library": "ACTION_NAMES.cpp_link_static_library",
"strip": "ACTION_NAMES.strip",
"objc-compile": "ACTION_NAMES.objc_compile",
"objc++-compile": "ACTION_NAMES.objcpp_compile",
"clif-match": "ACTION_NAMES.clif_match",
// "objcopy_embed_data": "ACTION_NAMES.objcopy_embed_data", // copybara-comment-this-out-please
// "ld_embed_data": "ACTION_NAMES.ld_embed_data", // copybara-comment-this-out-please
}
func getLoadActionsStmt() string {
return "load(\"@bazel_tools//tools/build_defs/cc:action_names.bzl\", \"ACTION_NAMES\")\n\n"
}
// Returns a map {toolchain_identifier : CToolchainIdentifier}
func toolchainToCToolchainIdentifier(
crosstool *crosstoolpb.CrosstoolRelease) map[string]CToolchainIdentifier {
cpuToCompiler := make(map[string][]string)
compilerToCPU := make(map[string][]string)
var cpus []string
var compilers []string
var identifiers []string
res := make(map[string]CToolchainIdentifier)
for _, cToolchain := range crosstool.GetToolchain() {
cpu := cToolchain.GetTargetCpu()
compiler := cToolchain.GetCompiler()
cpuToCompiler[cpu] = append(cpuToCompiler[cpu], compiler)
compilerToCPU[compiler] = append(compilerToCPU[compiler], cpu)
cpus = append(cpus, cToolchain.GetTargetCpu())
compilers = append(compilers, cToolchain.GetCompiler())
identifiers = append(identifiers, cToolchain.GetToolchainIdentifier())
}
for i := range cpus {
if len(cpuToCompiler[cpus[i]]) == 1 {
// if cpu is unique among CToolchains, we don't need the compiler field
res[identifiers[i]] = CToolchainIdentifier{cpu: cpus[i], compiler: ""}
} else {
res[identifiers[i]] = CToolchainIdentifier{
cpu: cpus[i],
compiler: compilers[i],
}
}
}
return res
}
func getConditionStatementForCToolchainIdentifier(identifier CToolchainIdentifier) string {
if identifier.compiler != "" {
return fmt.Sprintf(
"ctx.attr.cpu == \"%s\" and ctx.attr.compiler == \"%s\"",
identifier.cpu,
identifier.compiler)
}
return fmt.Sprintf("ctx.attr.cpu == \"%s\"", identifier.cpu)
}
func isArrayPrefix(prefix []string, arr []string) bool {
if len(prefix) > len(arr) {
return false
}
for i := 0; i < len(prefix); i++ {
if arr[i] != prefix[i] {
return false
}
}
return true
}
func isAllCompileActions(actions []string) (bool, []string) {
if isArrayPrefix(allCompileActions, actions) {
return true, actions[len(allCompileActions):]
}
return false, actions
}
func isAllCppCompileActions(actions []string) (bool, []string) {
if isArrayPrefix(allCppCompileActions, actions) {
return true, actions[len(allCppCompileActions):]
}
return false, actions
}
func isPreprocessorCompileActions(actions []string) (bool, []string) {
if isArrayPrefix(preprocessorCompileActions, actions) {
return true, actions[len(preprocessorCompileActions):]
}
return false, actions
}
func isCodegenCompileActions(actions []string) (bool, []string) {
if isArrayPrefix(codegenCompileActions, actions) {
return true, actions[len(codegenCompileActions):]
}
return false, actions
}
func isAllLinkActions(actions []string) (bool, []string) {
if isArrayPrefix(allLinkActions, actions) {
return true, actions[len(allLinkActions):]
}
return false, actions
}
func getActionNames(actions []string) []string {
var res []string
for _, el := range actions {
if name, ok := actionNames[el]; ok {
res = append(res, name)
} else {
res = append(res, "\""+el+"\"")
}
}
return res
}
func getListOfActions(name string, depth int) string {
var res []string
if name == "all_compile_actions" {
res = getActionNames(allCompileActions)
} else if name == "all_cpp_compile_actions" {
res = getActionNames(allCppCompileActions)
} else if name == "preprocessor_compile_actions" {
res = getActionNames(preprocessorCompileActions)
} else if name == "codegen_compile_actions" {
res = getActionNames(codegenCompileActions)
} else if name == "all_link_actions" {
res = getActionNames(allLinkActions)
}
stmt := fmt.Sprintf("%s%s = %s\n\n", getTabs(depth),
name, makeStringArr(res, depth /* isPlainString= */, false))
return stmt
}
func processActions(actions []string, depth int) []string {
var res []string
var ok bool
initLen := len(actions)
if ok, actions = isAllCompileActions(actions); ok {
res = append(res, "all_compile_actions")
}
if ok, actions = isAllCppCompileActions(actions); ok {
res = append(res, "all_cpp_compile_actions")
}
if ok, actions = isPreprocessorCompileActions(actions); ok {
res = append(res, "preprocessor_compile_actions")
}
if ok, actions = isCodegenCompileActions(actions); ok {
res = append(res, "codegen_actions")
}
if ok, actions = isAllLinkActions(actions); ok {
res = append(res, "all_link_actions")
}
if len(actions) != 0 {
actions = getActionNames(actions)
newDepth := depth + 1
if len(actions) != initLen {
newDepth++
}
res = append(res, makeStringArr(actions, newDepth /* isPlainString= */, false))
}
return res
}
func getUniqueValues(arr []string) []string {
valuesSet := make(map[string]bool)
for _, val := range arr {
valuesSet[val] = true
}
var uniques []string
for val, _ := range valuesSet {
uniques = append(uniques, val)
}
sort.Strings(uniques)
return uniques
}
func getRule(cToolchainIdentifiers map[string]CToolchainIdentifier,
allowedCompilers []string) string {
cpus := make(map[string]bool)
shouldUseCompilerAttribute := false
for _, val := range cToolchainIdentifiers {
cpus[val.cpu] = true
if val.compiler != "" {
shouldUseCompilerAttribute = true
}
}
var cpuValues []string
for cpu := range cpus {
cpuValues = append(cpuValues, cpu)
}
var args []string
sort.Strings(cpuValues)
args = append(args,
fmt.Sprintf(
`"cpu": attr.string(mandatory=True, values=["%s"]),`,
strings.Join(cpuValues, "\", \"")))
if shouldUseCompilerAttribute {
// If there are two CToolchains that share the cpu we need the compiler attribute
// for our cc_toolchain_config rule.
allowedCompilers = getUniqueValues(allowedCompilers)
args = append(args,
fmt.Sprintf(`"compiler": attr.string(mandatory=True, values=["%s"]),`,
strings.Join(allowedCompilers, "\", \"")))
}
return fmt.Sprintf(`cc_toolchain_config = rule(
implementation = _impl,
attrs = {
%s
},
provides = [CcToolchainConfigInfo],
executable = True,
)
`, strings.Join(args, "\n "))
}
func getImplHeader() string {
return "def _impl(ctx):\n"
}
func getStringStatement(crosstool *crosstoolpb.CrosstoolRelease,
cToolchainIdentifiers map[string]CToolchainIdentifier, field string,
depth int) string {
identifiers := getToolchainIdentifiers(crosstool)
var fieldValues []string
if field == "toolchain_identifier" {
fieldValues = getToolchainIdentifiers(crosstool)
} else if field == "host_system_name" {
fieldValues = getHostSystemNames(crosstool)
} else if field == "target_system_name" {
fieldValues = getTargetSystemNames(crosstool)
} else if field == "target_cpu" {
fieldValues = getTargetCpus(crosstool)
} else if field == "target_libc" {
fieldValues = getTargetLibcs(crosstool)
} else if field == "compiler" {
fieldValues = getCompilers(crosstool)
} else if field == "abi_version" {
fieldValues = getAbiVersions(crosstool)
} else if field == "abi_libc_version" {
fieldValues = getAbiLibcVersions(crosstool)
} else if field == "cc_target_os" {
fieldValues = getCcTargetOss(crosstool)
} else if field == "builtin_sysroot" {
fieldValues = getBuiltinSysroots(crosstool)
}
mappedValuesToIds := getMappedStringValuesToIdentifiers(identifiers, fieldValues)
return getAssignmentStatement(field, mappedValuesToIds, crosstool,
cToolchainIdentifiers, depth /* isPlainString= */, true /* shouldFail= */, true)
}
func getFeatures(crosstool *crosstoolpb.CrosstoolRelease) (
map[string][]string, map[string]map[string][]string, error) {
featureNameToFeature := make(map[string]map[string][]string)
toolchainToFeatures := make(map[string][]string)
for _, toolchain := range crosstool.GetToolchain() {
id := toolchain.GetToolchainIdentifier()
if len(toolchain.GetFeature()) == 0 {
toolchainToFeatures[id] = []string{}
}
for _, feature := range toolchain.GetFeature() {
featureName := strings.ToLower(feature.GetName()) + "_feature"
featureName = strings.Replace(featureName, "+", "p", -1)
featureName = strings.Replace(featureName, ".", "_", -1)
featureName = strings.Replace(featureName, "-", "_", -1)
stringFeature, err := parseFeature(feature, 1)
if err != nil {
return nil, nil, fmt.Errorf(
"Error in feature '%s': %v", feature.GetName(), err)
}
if _, ok := featureNameToFeature[featureName]; !ok {
featureNameToFeature[featureName] = make(map[string][]string)
}
featureNameToFeature[featureName][stringFeature] = append(
featureNameToFeature[featureName][stringFeature], id)
toolchainToFeatures[id] = append(toolchainToFeatures[id], featureName)
}
}
return toolchainToFeatures, featureNameToFeature, nil
}
func getFeaturesDeclaration(crosstool *crosstoolpb.CrosstoolRelease,
cToolchainIdentifiers map[string]CToolchainIdentifier,
featureNameToFeature map[string]map[string][]string, depth int) string {
var res []string
for featureName, featureStringToID := range featureNameToFeature {
res = append(res,
getAssignmentStatement(
featureName,
featureStringToID,
crosstool,
cToolchainIdentifiers,
depth,
/* isPlainString= */ false,
/* shouldFail= */ false))
}
return strings.Join(res, "")
}
func getFeaturesStmt(cToolchainIdentifiers map[string]CToolchainIdentifier,
toolchainToFeatures map[string][]string, depth int) string {
var res []string
arrToIdentifier := make(map[string][]string)
for id, features := range toolchainToFeatures {
arrayString := strings.Join(features, "{arrayFieldDelimiter}")
arrToIdentifier[arrayString] = append(arrToIdentifier[arrayString], id)
}
res = append(res,
getStringArrStatement(
"features",
arrToIdentifier,
cToolchainIdentifiers,
depth,
/* isPlainString= */ false))
return strings.Join(res, "\n")
}
func getActions(crosstool *crosstoolpb.CrosstoolRelease) (
map[string][]string, map[string]map[string][]string, error) {
actionNameToAction := make(map[string]map[string][]string)
toolchainToActions := make(map[string][]string)
for _, toolchain := range crosstool.GetToolchain() {
id := toolchain.GetToolchainIdentifier()
var actionName string
if len(toolchain.GetActionConfig()) == 0 {
toolchainToActions[id] = []string{}
}
for _, action := range toolchain.GetActionConfig() {
if aName, ok := actionNames[action.GetActionName()]; ok {
actionName = aName
} else {
actionName = strings.ToLower(action.GetActionName())
actionName = strings.Replace(actionName, "+", "p", -1)
actionName = strings.Replace(actionName, ".", "_", -1)
actionName = strings.Replace(actionName, "-", "_", -1)
}
stringAction, err := parseAction(action, 1)
if err != nil {
return nil, nil, fmt.Errorf(
"Error in action_config '%s': %v", action.GetActionName(), err)
}
if _, ok := actionNameToAction[actionName]; !ok {
actionNameToAction[actionName] = make(map[string][]string)
}
actionNameToAction[actionName][stringAction] = append(
actionNameToAction[actionName][stringAction], id)
toolchainToActions[id] = append(
toolchainToActions[id],
strings.TrimPrefix(strings.ToLower(actionName), "action_names.")+"_action")
}
}
return toolchainToActions, actionNameToAction, nil
}
func getActionConfigsDeclaration(
crosstool *crosstoolpb.CrosstoolRelease,
cToolchainIdentifiers map[string]CToolchainIdentifier,
actionNameToAction map[string]map[string][]string, depth int) string {
var res []string
for actionName, actionStringToID := range actionNameToAction {
variableName := strings.TrimPrefix(strings.ToLower(actionName), "action_names.") + "_action"
res = append(res,
getAssignmentStatement(
variableName,
actionStringToID,
crosstool,
cToolchainIdentifiers,
depth,
/* isPlainString= */ false,
/* shouldFail= */ false))
}
return strings.Join(res, "")
}
func getActionConfigsStmt(
cToolchainIdentifiers map[string]CToolchainIdentifier,
toolchainToActions map[string][]string, depth int) string {
var res []string
arrToIdentifier := make(map[string][]string)
for id, actions := range toolchainToActions {
var arrayString string
arrayString = strings.Join(actions, "{arrayFieldDelimiter}")
arrToIdentifier[arrayString] = append(arrToIdentifier[arrayString], id)
}
res = append(res,
getStringArrStatement(
"action_configs",
arrToIdentifier,
cToolchainIdentifiers,
depth,
/* isPlainString= */ false))
return strings.Join(res, "\n")
}
func parseAction(action *crosstoolpb.CToolchain_ActionConfig, depth int) (string, error) {
actionName := action.GetActionName()
aName := ""
if val, ok := actionNames[actionName]; ok {
aName = val
} else {
aName = "\"" + action.GetActionName() + "\""
}
name := fmt.Sprintf("action_name = %s", aName)
fields := []string{name}
if action.GetEnabled() {
fields = append(fields, "enabled = True")
}
if len(action.GetFlagSet()) != 0 {
flagSets, err := parseFlagSets(action.GetFlagSet(), depth+1)
if err != nil {
return "", err
}
fields = append(fields, "flag_sets = "+flagSets)
}
if len(action.GetImplies()) != 0 {
implies := "implies = " +
makeStringArr(action.GetImplies(), depth+1 /* isPlainString= */, true)
fields = append(fields, implies)
}
if len(action.GetTool()) != 0 {
tools := "tools = " + parseTools(action.GetTool(), depth+1)
fields = append(fields, tools)
}
return createObject("action_config", fields, depth), nil
}
func getStringArrStatement(attr string, arrValToIds map[string][]string,
cToolchainIdentifiers map[string]CToolchainIdentifier, depth int, plainString bool) string {
var b bytes.Buffer
if len(arrValToIds) == 0 {
b.WriteString(fmt.Sprintf("%s%s = []\n", getTabs(depth), attr))
} else if len(arrValToIds) == 1 {
for value := range arrValToIds {
var arr []string
if value == "" {
arr = []string{}
} else if value == "None" {
b.WriteString(fmt.Sprintf("%s%s = None\n", getTabs(depth), attr))
break
} else {
arr = strings.Split(value, "{arrayFieldDelimiter}")
}
b.WriteString(
fmt.Sprintf(
"%s%s = %s\n",
getTabs(depth),
attr,
makeStringArr(arr, depth+1, plainString)))
break
}
} else {
first := true
var keys []string
for k := range arrValToIds {
keys = append(keys, k)
}
sort.Strings(keys)
for _, value := range keys {
ids := arrValToIds[value]
branch := "elif"
if first {
branch = "if"
}
first = false
var arr []string
if value == "" {
arr = []string{}
} else if value == "None" {
b.WriteString(
getIfStatement(
branch, ids, attr, "None", cToolchainIdentifiers,
depth /* isPlainString= */, true))
continue
} else {
arr = strings.Split(value, "{arrayFieldDelimiter}")
}
b.WriteString(
getIfStatement(branch, ids, attr,
makeStringArr(arr, depth+1, plainString),
cToolchainIdentifiers, depth /* isPlainString= */, false))
}
b.WriteString(fmt.Sprintf("%selse:\n%sfail(\"Unreachable\")\n", getTabs(depth), getTabs(depth+1)))
}
b.WriteString("\n")
return b.String()
}
func getStringArr(crosstool *crosstoolpb.CrosstoolRelease,
cToolchainIdentifiers map[string]CToolchainIdentifier, attr string, depth int) string {
var res []string
arrToIdentifier := make(map[string][]string)
for _, toolchain := range crosstool.GetToolchain() {
id := toolchain.GetToolchainIdentifier()
arrayString := strings.Join(getArrField(attr, toolchain), "{arrayFieldDelimiter}")
arrToIdentifier[arrayString] = append(arrToIdentifier[arrayString], id)
}
statement := getStringArrStatement(attr, arrToIdentifier, cToolchainIdentifiers, depth /* isPlainString= */, true)
res = append(res, statement)
return strings.Join(res, "\n")
}
func getArrField(attr string, toolchain *crosstoolpb.CToolchain) []string {
var arr []string
if attr == "cxx_builtin_include_directories" {
arr = toolchain.GetCxxBuiltinIncludeDirectory()
}
return arr
}
func getTabs(depth int) string {
var res string
for i := 0; i < depth; i++ {
res = res + " "
}
return res
}
func createObject(objtype string, fields []string, depth int) string {
if len(fields) == 0 {
return objtype + "()"
}
singleLine := objtype + "(" + strings.Join(fields, ", ") + ")"
if len(singleLine) < 60 {
return singleLine
}
return objtype +
"(\n" +
getTabs(depth+1) +
strings.Join(fields, ",\n"+getTabs(depth+1)) +
",\n" + getTabs(depth) +
")"
}
func getArtifactNamePatterns(crosstool *crosstoolpb.CrosstoolRelease,
cToolchainIdentifiers map[string]CToolchainIdentifier, depth int) string {
var res []string
artifactToIds := make(map[string][]string)
for _, toolchain := range crosstool.GetToolchain() {
artifactNamePatterns := parseArtifactNamePatterns(
toolchain.GetArtifactNamePattern(),
depth)
artifactToIds[artifactNamePatterns] = append(
artifactToIds[artifactNamePatterns],
toolchain.GetToolchainIdentifier())
}
res = append(res,
getAssignmentStatement(
"artifact_name_patterns",
artifactToIds,
crosstool,
cToolchainIdentifiers,
depth,
/* isPlainString= */ false,
/* shouldFail= */ true))
return strings.Join(res, "\n")
}
func parseArtifactNamePatterns(
artifactNamePatterns []*crosstoolpb.CToolchain_ArtifactNamePattern, depth int) string {
var res []string
for _, pattern := range artifactNamePatterns {
res = append(res, parseArtifactNamePattern(pattern, depth+1))
}
return makeStringArr(res, depth /* isPlainString= */, false)
}
func parseArtifactNamePattern(
artifactNamePattern *crosstoolpb.CToolchain_ArtifactNamePattern, depth int) string {
categoryName := fmt.Sprintf("category_name = \"%s\"", artifactNamePattern.GetCategoryName())
prefix := fmt.Sprintf("prefix = \"%s\"", artifactNamePattern.GetPrefix())
extension := fmt.Sprintf("extension = \"%s\"", artifactNamePattern.GetExtension())
fields := []string{categoryName, prefix, extension}
return createObject("artifact_name_pattern", fields, depth)
}
func parseFeature(feature *crosstoolpb.CToolchain_Feature, depth int) (string, error) {
name := fmt.Sprintf("name = \"%s\"", feature.GetName())
fields := []string{name}
if feature.GetEnabled() {
fields = append(fields, "enabled = True")
}
if len(feature.GetFlagSet()) > 0 {
flagSets, err := parseFlagSets(feature.GetFlagSet(), depth+1)
if err != nil {
return "", err
}
fields = append(fields, "flag_sets = "+flagSets)
}
if len(feature.GetEnvSet()) > 0 {
envSets := "env_sets = " + parseEnvSets(feature.GetEnvSet(), depth+1)
fields = append(fields, envSets)
}
if len(feature.GetRequires()) > 0 {
requires := "requires = " + parseFeatureSets(feature.GetRequires(), depth+1)
fields = append(fields, requires)
}
if len(feature.GetImplies()) > 0 {
implies := "implies = " +
makeStringArr(feature.GetImplies(), depth+1 /* isPlainString= */, true)
fields = append(fields, implies)
}
if len(feature.GetProvides()) > 0 {
provides := "provides = " +
makeStringArr(feature.GetProvides(), depth+1 /* isPlainString= */, true)
fields = append(fields, provides)
}
return createObject("feature", fields, depth), nil
}
func parseFlagSets(flagSets []*crosstoolpb.CToolchain_FlagSet, depth int) (string, error) {
var res []string
for _, flagSet := range flagSets {
parsedFlagset, err := parseFlagSet(flagSet, depth+1)
if err != nil {
return "", err
}
res = append(res, parsedFlagset)
}
return makeStringArr(res, depth /* isPlainString= */, false), nil
}
func parseFlagSet(flagSet *crosstoolpb.CToolchain_FlagSet, depth int) (string, error) {
var fields []string
if len(flagSet.GetAction()) > 0 {
actionArr := processActions(flagSet.GetAction(), depth)
actions := "actions = " + strings.Join(actionArr, " +\n"+getTabs(depth+2))
fields = append(fields, actions)
}
if len(flagSet.GetFlagGroup()) > 0 {
flagGroups, err := parseFlagGroups(flagSet.GetFlagGroup(), depth+1)
if err != nil {
return "", err
}
fields = append(fields, "flag_groups = "+flagGroups)
}
if len(flagSet.GetWithFeature()) > 0 {
withFeatures := "with_features = " +
parseWithFeatureSets(flagSet.GetWithFeature(), depth+1)
fields = append(fields, withFeatures)
}
return createObject("flag_set", fields, depth), nil
}
func parseFlagGroups(flagGroups []*crosstoolpb.CToolchain_FlagGroup, depth int) (string, error) {
var res []string
for _, flagGroup := range flagGroups {
flagGroupString, err := parseFlagGroup(flagGroup, depth+1)
if err != nil {
return "", err
}
res = append(res, flagGroupString)
}
return makeStringArr(res, depth /* isPlainString= */, false), nil
}
func parseFlagGroup(flagGroup *crosstoolpb.CToolchain_FlagGroup, depth int) (string, error) {
var res []string
if len(flagGroup.GetFlag()) != 0 {
res = append(res, "flags = "+makeStringArr(flagGroup.GetFlag(), depth+1, true))
}
if flagGroup.GetIterateOver() != "" {
res = append(res, fmt.Sprintf("iterate_over = \"%s\"", flagGroup.GetIterateOver()))
}
if len(flagGroup.GetFlagGroup()) != 0 {
flagGroupString, err := parseFlagGroups(flagGroup.GetFlagGroup(), depth+1)
if err != nil {
return "", err
}
res = append(res, "flag_groups = "+flagGroupString)
}
if len(flagGroup.GetExpandIfAllAvailable()) > 1 {
return "", errors.New("Flag group must not have more than one 'expand_if_all_available' field")
}
if len(flagGroup.GetExpandIfAllAvailable()) != 0 {
res = append(res,
fmt.Sprintf(
"expand_if_available = \"%s\"",
flagGroup.GetExpandIfAllAvailable()[0]))
}
if len(flagGroup.GetExpandIfNoneAvailable()) > 1 {
return "", errors.New("Flag group must not have more than one 'expand_if_none_available' field")
}
if len(flagGroup.GetExpandIfNoneAvailable()) != 0 {
res = append(res,
fmt.Sprintf(
"expand_if_not_available = \"%s\"",
flagGroup.GetExpandIfNoneAvailable()[0]))
}
if flagGroup.GetExpandIfTrue() != "" {
res = append(res, fmt.Sprintf("expand_if_true = \"%s\"",
flagGroup.GetExpandIfTrue()))
}
if flagGroup.GetExpandIfFalse() != "" {
res = append(res, fmt.Sprintf("expand_if_false = \"%s\"",
flagGroup.GetExpandIfFalse()))
}
if flagGroup.GetExpandIfEqual() != nil {
res = append(res,
"expand_if_equal = "+parseVariableWithValue(
flagGroup.GetExpandIfEqual(), depth+1))
}
return createObject("flag_group", res, depth), nil
}
func parseVariableWithValue(variable *crosstoolpb.CToolchain_VariableWithValue, depth int) string {
variableName := fmt.Sprintf("name = \"%s\"", variable.GetVariable())
value := fmt.Sprintf("value = \"%s\"", variable.GetValue())
return createObject("variable_with_value", []string{variableName, value}, depth)
}
func getToolPaths(crosstool *crosstoolpb.CrosstoolRelease,
cToolchainIdentifiers map[string]CToolchainIdentifier, depth int) string {
var res []string
toolPathsToIds := make(map[string][]string)
for _, toolchain := range crosstool.GetToolchain() {
toolPaths := parseToolPaths(toolchain.GetToolPath(), depth)
toolPathsToIds[toolPaths] = append(
toolPathsToIds[toolPaths],
toolchain.GetToolchainIdentifier())
}
res = append(res,
getAssignmentStatement(
"tool_paths",
toolPathsToIds,
crosstool,
cToolchainIdentifiers,
depth,
/* isPlainString= */ false,
/* shouldFail= */ true))
return strings.Join(res, "\n")
}
func parseToolPaths(toolPaths []*crosstoolpb.ToolPath, depth int) string {
var res []string
for _, toolPath := range toolPaths {
res = append(res, parseToolPath(toolPath, depth+1))
}
return makeStringArr(res, depth /* isPlainString= */, false)
}
func parseToolPath(toolPath *crosstoolpb.ToolPath, depth int) string {
name := fmt.Sprintf("name = \"%s\"", toolPath.GetName())
path := toolPath.GetPath()
if path == "" {
path = "NOT_USED"
}
path = fmt.Sprintf("path = \"%s\"", path)
return createObject("tool_path", []string{name, path}, depth)
}
func getMakeVariables(crosstool *crosstoolpb.CrosstoolRelease,
cToolchainIdentifiers map[string]CToolchainIdentifier, depth int) string {
var res []string
makeVariablesToIds := make(map[string][]string)
for _, toolchain := range crosstool.GetToolchain() {
makeVariables := parseMakeVariables(toolchain.GetMakeVariable(), depth)
makeVariablesToIds[makeVariables] = append(
makeVariablesToIds[makeVariables],
toolchain.GetToolchainIdentifier())
}
res = append(res,
getAssignmentStatement(
"make_variables",
makeVariablesToIds,
crosstool,
cToolchainIdentifiers,
depth,
/* isPlainString= */ false,
/* shouldFail= */ true))
return strings.Join(res, "\n")
}
func parseMakeVariables(makeVariables []*crosstoolpb.MakeVariable, depth int) string {
var res []string
for _, makeVariable := range makeVariables {
res = append(res, parseMakeVariable(makeVariable, depth+1))
}
return makeStringArr(res, depth /* isPlainString= */, false)
}
func parseMakeVariable(makeVariable *crosstoolpb.MakeVariable, depth int) string {
name := fmt.Sprintf("name = \"%s\"", makeVariable.GetName())
value := fmt.Sprintf("value = \"%s\"", makeVariable.GetValue())
return createObject("make_variable", []string{name, value}, depth)
}
func parseTools(tools []*crosstoolpb.CToolchain_Tool, depth int) string {
var res []string
for _, tool := range tools {
res = append(res, parseTool(tool, depth+1))
}
return makeStringArr(res, depth /* isPlainString= */, false)
}
func parseTool(tool *crosstoolpb.CToolchain_Tool, depth int) string {
toolPath := "path = \"NOT_USED\""
if tool.GetToolPath() != "" {
toolPath = fmt.Sprintf("path = \"%s\"", tool.GetToolPath())
}
fields := []string{toolPath}
if len(tool.GetWithFeature()) != 0 {
withFeatures := "with_features = " + parseWithFeatureSets(tool.GetWithFeature(), depth+1)
fields = append(fields, withFeatures)
}
if len(tool.GetExecutionRequirement()) != 0 {
executionRequirements := "execution_requirements = " +
makeStringArr(tool.GetExecutionRequirement(), depth+1 /* isPlainString= */, true)
fields = append(fields, executionRequirements)
}
return createObject("tool", fields, depth)
}
func parseEnvEntries(envEntries []*crosstoolpb.CToolchain_EnvEntry, depth int) string {
var res []string
for _, envEntry := range envEntries {
res = append(res, parseEnvEntry(envEntry, depth+1))
}
return makeStringArr(res, depth /* isPlainString= */, false)
}
func parseEnvEntry(envEntry *crosstoolpb.CToolchain_EnvEntry, depth int) string {
key := fmt.Sprintf("key = \"%s\"", envEntry.GetKey())
value := fmt.Sprintf("value = \"%s\"", envEntry.GetValue())
return createObject("env_entry", []string{key, value}, depth)
}
func parseWithFeatureSets(withFeatureSets []*crosstoolpb.CToolchain_WithFeatureSet,
depth int) string {
var res []string
for _, withFeature := range withFeatureSets {
res = append(res, parseWithFeatureSet(withFeature, depth+1))
}
return makeStringArr(res, depth /* isPlainString= */, false)
}
func parseWithFeatureSet(withFeature *crosstoolpb.CToolchain_WithFeatureSet,
depth int) string {
var fields []string
if len(withFeature.GetFeature()) != 0 {
features := "features = " +
makeStringArr(withFeature.GetFeature(), depth+1 /* isPlainString= */, true)
fields = append(fields, features)
}
if len(withFeature.GetNotFeature()) != 0 {
notFeatures := "not_features = " +
makeStringArr(withFeature.GetNotFeature(), depth+1 /* isPlainString= */, true)
fields = append(fields, notFeatures)
}
return createObject("with_feature_set", fields, depth)
}
func parseEnvSets(envSets []*crosstoolpb.CToolchain_EnvSet, depth int) string {
var res []string
for _, envSet := range envSets {
envSetString := parseEnvSet(envSet, depth+1)
res = append(res, envSetString)
}
return makeStringArr(res, depth /* isPlainString= */, false)
}
func parseEnvSet(envSet *crosstoolpb.CToolchain_EnvSet, depth int) string {
actionsStatement := processActions(envSet.GetAction(), depth)
actions := "actions = " + strings.Join(actionsStatement, " +\n"+getTabs(depth+2))
fields := []string{actions}
if len(envSet.GetEnvEntry()) != 0 {
envEntries := "env_entries = " + parseEnvEntries(envSet.GetEnvEntry(), depth+1)
fields = append(fields, envEntries)
}
if len(envSet.GetWithFeature()) != 0 {
withFeatures := "with_features = " + parseWithFeatureSets(envSet.GetWithFeature(), depth+1)
fields = append(fields, withFeatures)
}
return createObject("env_set", fields, depth)
}
func parseFeatureSets(featureSets []*crosstoolpb.CToolchain_FeatureSet, depth int) string {
var res []string
for _, featureSet := range featureSets {
res = append(res, parseFeatureSet(featureSet, depth+1))
}
return makeStringArr(res, depth /* isPlainString= */, false)
}
func parseFeatureSet(featureSet *crosstoolpb.CToolchain_FeatureSet, depth int) string {
features := "features = " +
makeStringArr(featureSet.GetFeature(), depth+1 /* isPlainString= */, true)
return createObject("feature_set", []string{features}, depth)
}
// Takes in a list of string elements and returns a string that represents
// an array :
// [
// "element1",
// "element2",
// ]
// The isPlainString argument tells us whether the input elements should be
// treated as string (eg, flags), or not (eg, variable names)
func makeStringArr(arr []string, depth int, isPlainString bool) string {
if len(arr) == 0 {
return "[]"
}
var escapedArr []string
for _, el := range arr {
if isPlainString {
escapedArr = append(escapedArr, strings.Replace(el, "\"", "\\\"", -1))
} else {
escapedArr = append(escapedArr, el)
}
}
addQuote := ""
if isPlainString {
addQuote = "\""
}
singleLine := "[" + addQuote + strings.Join(escapedArr, addQuote+", "+addQuote) + addQuote + "]"
if len(singleLine) < 60 {
return singleLine
}
return "[\n" +
getTabs(depth+1) +
addQuote +
strings.Join(escapedArr, addQuote+",\n"+getTabs(depth+1)+addQuote) +
addQuote +
",\n" +
getTabs(depth) +
"]"
}
// Returns a string that represents a value assignment
// (eg if ctx.attr.cpu == "linux":
// compiler = "llvm"
// elif ctx.attr.cpu == "windows":
// compiler = "mingw"
// else:
// fail("Unreachable")
func getAssignmentStatement(field string, valToIds map[string][]string,
crosstool *crosstoolpb.CrosstoolRelease,
toCToolchainIdentifier map[string]CToolchainIdentifier,
depth int, isPlainString, shouldFail bool) string {
var b bytes.Buffer
if len(valToIds) <= 1 {
// if there is only one possible value for this field, we don't need if statements
for val := range valToIds {
if val != "None" && isPlainString {
val = "\"" + val + "\""
}
b.WriteString(fmt.Sprintf("%s%s = %s\n", getTabs(depth), field, val))
break
}
} else {
first := true
var keys []string
for k := range valToIds {
keys = append(keys, k)
}
sort.Strings(keys)
for _, value := range keys {
ids := valToIds[value]
branch := "elif"
if first {
branch = "if"
}
b.WriteString(
getIfStatement(branch, ids, field, value,
toCToolchainIdentifier, depth, isPlainString))
first = false
}
if shouldFail {
b.WriteString(
fmt.Sprintf(
"%selse:\n%sfail(\"Unreachable\")\n",
getTabs(depth), getTabs(depth+1)))
} else {
b.WriteString(
fmt.Sprintf(
"%selse:\n%s%s = None\n",
getTabs(depth), getTabs(depth+1), field))
}
}
b.WriteString("\n")
return b.String()
}
func getCPUToCompilers(identifiers []CToolchainIdentifier) map[string][]string {
res := make(map[string][]string)
for _, identifier := range identifiers {
if identifier.compiler != "" {
res[identifier.cpu] = append(res[identifier.cpu], identifier.compiler)
}
}
return res
}
func getIfStatement(ifOrElseIf string, identifiers []string, field, val string,
toCToolchainIdentifier map[string]CToolchainIdentifier, depth int,
isPlainString bool) string {
usedStmts := make(map[string]bool)
if val != "None" && isPlainString {
val = "\"" + val + "\""
}
var cToolchainIdentifiers []CToolchainIdentifier
for _, value := range toCToolchainIdentifier {
cToolchainIdentifiers = append(cToolchainIdentifiers, value)
}
cpuToCompilers := getCPUToCompilers(cToolchainIdentifiers)
countCpus := make(map[string]int)
var conditions []string
for _, id := range identifiers {
identifier := toCToolchainIdentifier[id]
stmt := getConditionStatementForCToolchainIdentifier(identifier)
if _, ok := usedStmts[stmt]; !ok {
conditions = append(conditions, stmt)
usedStmts[stmt] = true
if identifier.compiler != "" {
countCpus[identifier.cpu]++
}
}
}
var compressedConditions []string
usedStmtsOptimized := make(map[string]bool)
for _, id := range identifiers {
identifier := toCToolchainIdentifier[id]
var stmt string
if _, ok := countCpus[identifier.cpu]; ok {
if countCpus[identifier.cpu] == len(cpuToCompilers[identifier.cpu]) {
stmt = getConditionStatementForCToolchainIdentifier(
CToolchainIdentifier{cpu: identifier.cpu, compiler: ""})
} else {
stmt = getConditionStatementForCToolchainIdentifier(identifier)
}
} else {
stmt = getConditionStatementForCToolchainIdentifier(identifier)
}
if _, ok := usedStmtsOptimized[stmt]; !ok {
compressedConditions = append(compressedConditions, stmt)
usedStmtsOptimized[stmt] = true
}
}
sort.Strings(compressedConditions)
val = strings.Join(strings.Split(val, "\n"+getTabs(depth)), "\n"+getTabs(depth+1))
return fmt.Sprintf(`%s%s %s:
%s%s = %s
`, getTabs(depth),
ifOrElseIf,
"("+strings.Join(compressedConditions, "\n"+getTabs(depth+1)+"or ")+")",
getTabs(depth+1),
field,
val)
}
func getToolchainIdentifiers(crosstool *crosstoolpb.CrosstoolRelease) []string {
var res []string
for _, toolchain := range crosstool.GetToolchain() {
res = append(res, toolchain.GetToolchainIdentifier())
}
return res
}
func getHostSystemNames(crosstool *crosstoolpb.CrosstoolRelease) []string {
var res []string
for _, toolchain := range crosstool.GetToolchain() {
res = append(res, toolchain.GetHostSystemName())
}
return res
}
func getTargetSystemNames(crosstool *crosstoolpb.CrosstoolRelease) []string {
var res []string
for _, toolchain := range crosstool.GetToolchain() {
res = append(res, toolchain.GetTargetSystemName())
}
return res
}
func getTargetCpus(crosstool *crosstoolpb.CrosstoolRelease) []string {
var res []string
for _, toolchain := range crosstool.GetToolchain() {
res = append(res, toolchain.GetTargetCpu())
}
return res
}
func getTargetLibcs(crosstool *crosstoolpb.CrosstoolRelease) []string {
var res []string
for _, toolchain := range crosstool.GetToolchain() {
res = append(res, toolchain.GetTargetLibc())
}
return res
}
func getCompilers(crosstool *crosstoolpb.CrosstoolRelease) []string {
var res []string
for _, toolchain := range crosstool.GetToolchain() {
res = append(res, toolchain.GetCompiler())
}
return res
}
func getAbiVersions(crosstool *crosstoolpb.CrosstoolRelease) []string {
var res []string
for _, toolchain := range crosstool.GetToolchain() {
res = append(res, toolchain.GetAbiVersion())
}
return res
}
func getAbiLibcVersions(crosstool *crosstoolpb.CrosstoolRelease) []string {
var res []string
for _, toolchain := range crosstool.GetToolchain() {
res = append(res, toolchain.GetAbiLibcVersion())
}
return res
}
func getCcTargetOss(crosstool *crosstoolpb.CrosstoolRelease) []string {
var res []string
for _, toolchain := range crosstool.GetToolchain() {
targetOS := "None"
if toolchain.GetCcTargetOs() != "" {
targetOS = toolchain.GetCcTargetOs()
}
res = append(res, targetOS)
}
return res
}
func getBuiltinSysroots(crosstool *crosstoolpb.CrosstoolRelease) []string {
var res []string
for _, toolchain := range crosstool.GetToolchain() {
sysroot := "None"
if toolchain.GetBuiltinSysroot() != "" {
sysroot = toolchain.GetBuiltinSysroot()
}
res = append(res, sysroot)
}
return res
}
func getMappedStringValuesToIdentifiers(identifiers, fields []string) map[string][]string {
res := make(map[string][]string)
for i := range identifiers {
res[fields[i]] = append(res[fields[i]], identifiers[i])
}
return res
}
func getReturnStatement() string {
return `
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(out, "Fake executable")
return [
cc_common.create_cc_toolchain_config_info(
ctx = ctx,
features = features,
action_configs = action_configs,
artifact_name_patterns = artifact_name_patterns,
cxx_builtin_include_directories = cxx_builtin_include_directories,
toolchain_identifier = toolchain_identifier,
host_system_name = host_system_name,
target_system_name = target_system_name,
target_cpu = target_cpu,
target_libc = target_libc,
compiler = compiler,
abi_version = abi_version,
abi_libc_version = abi_libc_version,
tool_paths = tool_paths,
make_variables = make_variables,
builtin_sysroot = builtin_sysroot,
cc_target_os = cc_target_os
),
DefaultInfo(
executable = out,
),
]
`
}
// Transform writes a cc_toolchain_config rule functionally equivalent to the
// CROSSTOOL file.
func Transform(crosstool *crosstoolpb.CrosstoolRelease) (string, error) {
var b bytes.Buffer
cToolchainIdentifiers := toolchainToCToolchainIdentifier(crosstool)
toolchainToFeatures, featureNameToFeature, err := getFeatures(crosstool)
if err != nil {
return "", err
}
toolchainToActions, actionNameToAction, err := getActions(crosstool)
if err != nil {
return "", err
}
header := getCcToolchainConfigHeader()
if _, err := b.WriteString(header); err != nil {
return "", err
}
loadActionsStmt := getLoadActionsStmt()
if _, err := b.WriteString(loadActionsStmt); err != nil {
return "", err
}
implHeader := getImplHeader()
if _, err := b.WriteString(implHeader); err != nil {
return "", err
}
stringFields := []string{
"toolchain_identifier",
"host_system_name",
"target_system_name",
"target_cpu",
"target_libc",
"compiler",
"abi_version",
"abi_libc_version",
"cc_target_os",
"builtin_sysroot",
}
for _, stringField := range stringFields {
stmt := getStringStatement(crosstool, cToolchainIdentifiers, stringField, 1)
if _, err := b.WriteString(stmt); err != nil {
return "", err
}
}
listsOfActions := []string{
"all_compile_actions",
"all_cpp_compile_actions",
"preprocessor_compile_actions",
"codegen_compile_actions",
"all_link_actions",
}
for _, listOfActions := range listsOfActions {
actions := getListOfActions(listOfActions, 1)
if _, err := b.WriteString(actions); err != nil {
return "", err
}
}
actionConfigDeclaration := getActionConfigsDeclaration(
crosstool, cToolchainIdentifiers, actionNameToAction, 1)
if _, err := b.WriteString(actionConfigDeclaration); err != nil {
return "", err
}
actionConfigStatement := getActionConfigsStmt(
cToolchainIdentifiers, toolchainToActions, 1)
if _, err := b.WriteString(actionConfigStatement); err != nil {
return "", err
}
featureDeclaration := getFeaturesDeclaration(
crosstool, cToolchainIdentifiers, featureNameToFeature, 1)
if _, err := b.WriteString(featureDeclaration); err != nil {
return "", err
}
featuresStatement := getFeaturesStmt(
cToolchainIdentifiers, toolchainToFeatures, 1)
if _, err := b.WriteString(featuresStatement); err != nil {
return "", err
}
includeDirectories := getStringArr(
crosstool, cToolchainIdentifiers, "cxx_builtin_include_directories", 1)
if _, err := b.WriteString(includeDirectories); err != nil {
return "", err
}
artifactNamePatterns := getArtifactNamePatterns(
crosstool, cToolchainIdentifiers, 1)
if _, err := b.WriteString(artifactNamePatterns); err != nil {
return "", err
}
makeVariables := getMakeVariables(crosstool, cToolchainIdentifiers, 1)
if _, err := b.WriteString(makeVariables); err != nil {
return "", err
}
toolPaths := getToolPaths(crosstool, cToolchainIdentifiers, 1)
if _, err := b.WriteString(toolPaths); err != nil {
return "", err
}
if _, err := b.WriteString(getReturnStatement()); err != nil {
return "", err
}
rule := getRule(cToolchainIdentifiers, getCompilers(crosstool))
if _, err := b.WriteString(rule); err != nil {
return "", err
}
return b.String(), nil
}