diff options
Diffstat (limited to 'tools/migration/crosstool_to_starlark_lib.go')
-rw-r--r-- | tools/migration/crosstool_to_starlark_lib.go | 1419 |
1 files changed, 1419 insertions, 0 deletions
diff --git a/tools/migration/crosstool_to_starlark_lib.go b/tools/migration/crosstool_to_starlark_lib.go new file mode 100644 index 0000000..4403a4b --- /dev/null +++ b/tools/migration/crosstool_to_starlark_lib.go @@ -0,0 +1,1419 @@ +/* +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 +} |