aboutsummaryrefslogtreecommitdiff
path: root/tools/migration/crosstool_to_starlark_lib_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'tools/migration/crosstool_to_starlark_lib_test.go')
-rw-r--r--tools/migration/crosstool_to_starlark_lib_test.go1756
1 files changed, 1756 insertions, 0 deletions
diff --git a/tools/migration/crosstool_to_starlark_lib_test.go b/tools/migration/crosstool_to_starlark_lib_test.go
new file mode 100644
index 0000000..a5db02f
--- /dev/null
+++ b/tools/migration/crosstool_to_starlark_lib_test.go
@@ -0,0 +1,1756 @@
+package crosstooltostarlarklib
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "log"
+ crosstoolpb "third_party/com/github/bazelbuild/bazel/src/main/protobuf/crosstool_config_go_proto"
+ "github.com/golang/protobuf/proto"
+)
+
+func makeCToolchainString(lines []string) string {
+ return fmt.Sprintf(`toolchain {
+ %s
+}`, strings.Join(lines, "\n "))
+}
+
+func makeCrosstool(CToolchains []string) *crosstoolpb.CrosstoolRelease {
+ crosstool := &crosstoolpb.CrosstoolRelease{}
+ requiredFields := []string{
+ "major_version: '0'",
+ "minor_version: '0'",
+ "default_target_cpu: 'cpu'",
+ }
+ CToolchains = append(CToolchains, requiredFields...)
+ if err := proto.UnmarshalText(strings.Join(CToolchains, "\n"), crosstool); err != nil {
+ log.Fatalf("Failed to parse CROSSTOOL:", err)
+ }
+ return crosstool
+}
+
+func getSimpleCToolchain(id string) string {
+ lines := []string{
+ "toolchain_identifier: 'id-" + id + "'",
+ "host_system_name: 'host-" + id + "'",
+ "target_system_name: 'target-" + id + "'",
+ "target_cpu: 'cpu-" + id + "'",
+ "compiler: 'compiler-" + id + "'",
+ "target_libc: 'libc-" + id + "'",
+ "abi_version: 'version-" + id + "'",
+ "abi_libc_version: 'libc_version-" + id + "'",
+ }
+ return makeCToolchainString(lines)
+}
+
+func getCToolchain(id, cpu, compiler string, extraLines []string) string {
+ lines := []string{
+ "toolchain_identifier: '" + id + "'",
+ "host_system_name: 'host'",
+ "target_system_name: 'target'",
+ "target_cpu: '" + cpu + "'",
+ "compiler: '" + compiler + "'",
+ "target_libc: 'libc'",
+ "abi_version: 'version'",
+ "abi_libc_version: 'libc_version'",
+ }
+ lines = append(lines, extraLines...)
+ return makeCToolchainString(lines)
+}
+
+func TestStringFieldsConditionStatement(t *testing.T) {
+ toolchain1 := getSimpleCToolchain("1")
+ toolchain2 := getSimpleCToolchain("2")
+ toolchains := []string{toolchain1, toolchain2}
+ crosstool := makeCrosstool(toolchains)
+
+ testCases := []struct {
+ field string
+ expectedText string
+ }{
+ {field: "toolchain_identifier",
+ expectedText: `
+ if (ctx.attr.cpu == "cpu-1"):
+ toolchain_identifier = "id-1"
+ elif (ctx.attr.cpu == "cpu-2"):
+ toolchain_identifier = "id-2"
+ else:
+ fail("Unreachable")`},
+ {field: "host_system_name",
+ expectedText: `
+ if (ctx.attr.cpu == "cpu-1"):
+ host_system_name = "host-1"
+ elif (ctx.attr.cpu == "cpu-2"):
+ host_system_name = "host-2"
+ else:
+ fail("Unreachable")`},
+ {field: "target_system_name",
+ expectedText: `
+ if (ctx.attr.cpu == "cpu-1"):
+ target_system_name = "target-1"
+ elif (ctx.attr.cpu == "cpu-2"):
+ target_system_name = "target-2"
+ else:
+ fail("Unreachable")`},
+ {field: "target_cpu",
+ expectedText: `
+ if (ctx.attr.cpu == "cpu-1"):
+ target_cpu = "cpu-1"
+ elif (ctx.attr.cpu == "cpu-2"):
+ target_cpu = "cpu-2"
+ else:
+ fail("Unreachable")`},
+ {field: "target_libc",
+ expectedText: `
+ if (ctx.attr.cpu == "cpu-1"):
+ target_libc = "libc-1"
+ elif (ctx.attr.cpu == "cpu-2"):
+ target_libc = "libc-2"
+ else:
+ fail("Unreachable")`},
+ {field: "compiler",
+ expectedText: `
+ if (ctx.attr.cpu == "cpu-1"):
+ compiler = "compiler-1"
+ elif (ctx.attr.cpu == "cpu-2"):
+ compiler = "compiler-2"
+ else:
+ fail("Unreachable")`},
+ {field: "abi_version",
+ expectedText: `
+ if (ctx.attr.cpu == "cpu-1"):
+ abi_version = "version-1"
+ elif (ctx.attr.cpu == "cpu-2"):
+ abi_version = "version-2"
+ else:
+ fail("Unreachable")`},
+ {field: "abi_libc_version",
+ expectedText: `
+ if (ctx.attr.cpu == "cpu-1"):
+ abi_libc_version = "libc_version-1"
+ elif (ctx.attr.cpu == "cpu-2"):
+ abi_libc_version = "libc_version-2"
+ else:
+ fail("Unreachable")`}}
+
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+
+ failed := false
+ for _, tc := range testCases {
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
+ tc.field, tc.expectedText)
+ failed = true
+ }
+ }
+ if failed {
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(toolchains, "\n"), got)
+ }
+}
+
+func TestConditionsSameCpu(t *testing.T) {
+ toolchainAA := getCToolchain("1", "cpuA", "compilerA", []string{})
+ toolchainAB := getCToolchain("2", "cpuA", "compilerB", []string{})
+ toolchains := []string{toolchainAA, toolchainAB}
+ crosstool := makeCrosstool(toolchains)
+
+ testCases := []struct {
+ field string
+ expectedText string
+ }{
+ {field: "toolchain_identifier",
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerA"):
+ toolchain_identifier = "1"
+ elif (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerB"):
+ toolchain_identifier = "2"
+ else:
+ fail("Unreachable")`},
+ {field: "host_system_name",
+ expectedText: `
+ host_system_name = "host"`},
+ {field: "target_system_name",
+ expectedText: `
+ target_system_name = "target"`},
+ {field: "target_cpu",
+ expectedText: `
+ target_cpu = "cpuA"`},
+ {field: "target_libc",
+ expectedText: `
+ target_libc = "libc"`},
+ {field: "compiler",
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerA"):
+ compiler = "compilerA"
+ elif (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerB"):
+ compiler = "compilerB"
+ else:
+ fail("Unreachable")`},
+ {field: "abi_version",
+ expectedText: `
+ abi_version = "version"`},
+ {field: "abi_libc_version",
+ expectedText: `
+ abi_libc_version = "libc_version"`}}
+
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+
+ failed := false
+ for _, tc := range testCases {
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
+ tc.field, tc.expectedText)
+ failed = true
+ }
+ }
+ if failed {
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(toolchains, "\n"), got)
+ }
+}
+
+func TestConditionsSameCompiler(t *testing.T) {
+ toolchainAA := getCToolchain("1", "cpuA", "compilerA", []string{})
+ toolchainBA := getCToolchain("2", "cpuB", "compilerA", []string{})
+ toolchains := []string{toolchainAA, toolchainBA}
+ crosstool := makeCrosstool(toolchains)
+
+ testCases := []struct {
+ field string
+ expectedText string
+ }{
+ {field: "toolchain_identifier",
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA"):
+ toolchain_identifier = "1"
+ elif (ctx.attr.cpu == "cpuB"):
+ toolchain_identifier = "2"
+ else:
+ fail("Unreachable")`},
+ {field: "target_cpu",
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA"):
+ target_cpu = "cpuA"
+ elif (ctx.attr.cpu == "cpuB"):
+ target_cpu = "cpuB"
+ else:
+ fail("Unreachable")`},
+ {field: "compiler",
+ expectedText: `
+ compiler = "compilerA"`}}
+
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+
+ failed := false
+ for _, tc := range testCases {
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
+ tc.field, tc.expectedText)
+ failed = true
+ }
+ }
+ if failed {
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(toolchains, "\n"), got)
+ }
+}
+
+func TestNonMandatoryStrings(t *testing.T) {
+ toolchainAA := getCToolchain("1", "cpuA", "compilerA", []string{"cc_target_os: 'osA'"})
+ toolchainBB := getCToolchain("2", "cpuB", "compilerB", []string{})
+ toolchains := []string{toolchainAA, toolchainBB}
+ crosstool := makeCrosstool(toolchains)
+
+ testCases := []struct {
+ field string
+ expectedText string
+ }{
+ {field: "cc_target_os",
+ expectedText: `
+ if (ctx.attr.cpu == "cpuB"):
+ cc_target_os = None
+ elif (ctx.attr.cpu == "cpuA"):
+ cc_target_os = "osA"
+ else:
+ fail("Unreachable")`},
+ {field: "builtin_sysroot",
+ expectedText: `
+ builtin_sysroot = None`}}
+
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+
+ failed := false
+ for _, tc := range testCases {
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
+ tc.field, tc.expectedText)
+ failed = true
+ }
+ }
+ if failed {
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(toolchains, "\n"), got)
+ }
+}
+
+func TestBuiltinIncludeDirectories(t *testing.T) {
+ toolchainAA := getCToolchain("1", "cpuA", "compilerA", []string{})
+ toolchainBA := getCToolchain("2", "cpuB", "compilerA", []string{})
+ toolchainCA := getCToolchain("3", "cpuC", "compilerA",
+ []string{"cxx_builtin_include_directory: 'dirC'"})
+ toolchainCB := getCToolchain("4", "cpuC", "compilerB",
+ []string{"cxx_builtin_include_directory: 'dirC'",
+ "cxx_builtin_include_directory: 'dirB'"})
+ toolchainDA := getCToolchain("5", "cpuD", "compilerA",
+ []string{"cxx_builtin_include_directory: 'dirC'"})
+
+ toolchainsEmpty := []string{toolchainAA, toolchainBA}
+
+ toolchainsOneNonempty := []string{toolchainAA, toolchainBA, toolchainCA}
+
+ toolchainsSameNonempty := []string{toolchainCA, toolchainDA}
+
+ allToolchains := []string{toolchainAA, toolchainBA, toolchainCA, toolchainCB, toolchainDA}
+
+ testCases := []struct {
+ field string
+ toolchains []string
+ expectedText string
+ }{
+ {field: "cxx_builtin_include_directories",
+ toolchains: toolchainsEmpty,
+ expectedText: `
+ cxx_builtin_include_directories = []`},
+ {field: "cxx_builtin_include_directories",
+ toolchains: toolchainsOneNonempty,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA"
+ or ctx.attr.cpu == "cpuB"):
+ cxx_builtin_include_directories = []
+ elif (ctx.attr.cpu == "cpuC"):
+ cxx_builtin_include_directories = ["dirC"]
+ else:
+ fail("Unreachable")`},
+ {field: "cxx_builtin_include_directories",
+ toolchains: toolchainsSameNonempty,
+ expectedText: `
+ cxx_builtin_include_directories = ["dirC"]`},
+ {field: "cxx_builtin_include_directories",
+ toolchains: allToolchains,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA"
+ or ctx.attr.cpu == "cpuB"):
+ cxx_builtin_include_directories = []
+ elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"
+ or ctx.attr.cpu == "cpuD"):
+ cxx_builtin_include_directories = ["dirC"]
+ elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
+ cxx_builtin_include_directories = ["dirC", "dirB"]`}}
+
+ for _, tc := range testCases {
+ crosstool := makeCrosstool(tc.toolchains)
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
+ tc.field, tc.expectedText)
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(tc.toolchains, "\n"), got)
+ }
+ }
+}
+
+func TestMakeVariables(t *testing.T) {
+ toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
+ toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
+ toolchainA1 := getCToolchain("3", "cpuC", "compilerA",
+ []string{"make_variable {name: 'A', value: 'a/b/c'}"})
+ toolchainA2 := getCToolchain("4", "cpuC", "compilerB",
+ []string{"make_variable {name: 'A', value: 'a/b/c'}"})
+ toolchainAB := getCToolchain("5", "cpuC", "compilerC",
+ []string{"make_variable {name: 'A', value: 'a/b/c'}",
+ "make_variable {name: 'B', value: 'a/b/c'}"})
+ toolchainBA := getCToolchain("6", "cpuD", "compilerA",
+ []string{"make_variable {name: 'B', value: 'a/b/c'}",
+ "make_variable {name: 'A', value: 'a b c'}"})
+
+ toolchainsEmpty := []string{toolchainEmpty1, toolchainEmpty2}
+
+ toolchainsOneNonempty := []string{toolchainEmpty1, toolchainA1}
+
+ toolchainsSameNonempty := []string{toolchainA1, toolchainA2}
+
+ toolchainsDifferentOrder := []string{toolchainAB, toolchainBA}
+
+ allToolchains := []string{
+ toolchainEmpty1,
+ toolchainEmpty2,
+ toolchainA1,
+ toolchainA2,
+ toolchainAB,
+ toolchainBA,
+ }
+
+ testCases := []struct {
+ field string
+ toolchains []string
+ expectedText string
+ }{
+ {field: "make_variables",
+ toolchains: toolchainsEmpty,
+ expectedText: `
+ make_variables = []`},
+ {field: "make_variables",
+ toolchains: toolchainsOneNonempty,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA"):
+ make_variables = []
+ elif (ctx.attr.cpu == "cpuC"):
+ make_variables = [make_variable(name = "A", value = "a/b/c")]
+ else:
+ fail("Unreachable")`},
+ {field: "make_variables",
+ toolchains: toolchainsSameNonempty,
+ expectedText: `
+ make_variables = [make_variable(name = "A", value = "a/b/c")]`},
+ {field: "make_variables",
+ toolchains: toolchainsDifferentOrder,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuC"):
+ make_variables = [
+ make_variable(name = "A", value = "a/b/c"),
+ make_variable(name = "B", value = "a/b/c"),
+ ]
+ elif (ctx.attr.cpu == "cpuD"):
+ make_variables = [
+ make_variable(name = "B", value = "a/b/c"),
+ make_variable(name = "A", value = "a b c"),
+ ]
+ else:
+ fail("Unreachable")`},
+ {field: "make_variables",
+ toolchains: allToolchains,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerC"):
+ make_variables = [
+ make_variable(name = "A", value = "a/b/c"),
+ make_variable(name = "B", value = "a/b/c"),
+ ]
+ elif (ctx.attr.cpu == "cpuD"):
+ make_variables = [
+ make_variable(name = "B", value = "a/b/c"),
+ make_variable(name = "A", value = "a b c"),
+ ]
+ elif (ctx.attr.cpu == "cpuA"
+ or ctx.attr.cpu == "cpuB"):
+ make_variables = []
+ elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"
+ or ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
+ make_variables = [make_variable(name = "A", value = "a/b/c")]
+ else:
+ fail("Unreachable")`}}
+
+ for _, tc := range testCases {
+ crosstool := makeCrosstool(tc.toolchains)
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
+ tc.field, tc.expectedText)
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(tc.toolchains, "\n"), got)
+ }
+ }
+}
+
+func TestToolPaths(t *testing.T) {
+ toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
+ toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
+ toolchainA1 := getCToolchain("3", "cpuC", "compilerA",
+ []string{"tool_path {name: 'A', path: 'a/b/c'}"})
+ toolchainA2 := getCToolchain("4", "cpuC", "compilerB",
+ []string{"tool_path {name: 'A', path: 'a/b/c'}"})
+ toolchainAB := getCToolchain("5", "cpuC", "compilerC",
+ []string{"tool_path {name: 'A', path: 'a/b/c'}",
+ "tool_path {name: 'B', path: 'a/b/c'}"})
+ toolchainBA := getCToolchain("6", "cpuD", "compilerA",
+ []string{"tool_path {name: 'B', path: 'a/b/c'}",
+ "tool_path {name: 'A', path: 'a/b/c'}"})
+
+ toolchainsEmpty := []string{toolchainEmpty1, toolchainEmpty2}
+
+ toolchainsOneNonempty := []string{toolchainEmpty1, toolchainA1}
+
+ toolchainsSameNonempty := []string{toolchainA1, toolchainA2}
+
+ toolchainsDifferentOrder := []string{toolchainAB, toolchainBA}
+
+ allToolchains := []string{
+ toolchainEmpty1,
+ toolchainEmpty2,
+ toolchainA1,
+ toolchainA2,
+ toolchainAB,
+ toolchainBA,
+ }
+
+ testCases := []struct {
+ field string
+ toolchains []string
+ expectedText string
+ }{
+ {field: "tool_paths",
+ toolchains: toolchainsEmpty,
+ expectedText: `
+ tool_paths = []`},
+ {field: "tool_paths",
+ toolchains: toolchainsOneNonempty,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA"):
+ tool_paths = []
+ elif (ctx.attr.cpu == "cpuC"):
+ tool_paths = [tool_path(name = "A", path = "a/b/c")]
+ else:
+ fail("Unreachable")`},
+ {field: "tool_paths",
+ toolchains: toolchainsSameNonempty,
+ expectedText: `
+ tool_paths = [tool_path(name = "A", path = "a/b/c")]`},
+ {field: "tool_paths",
+ toolchains: toolchainsDifferentOrder,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuC"):
+ tool_paths = [
+ tool_path(name = "A", path = "a/b/c"),
+ tool_path(name = "B", path = "a/b/c"),
+ ]
+ elif (ctx.attr.cpu == "cpuD"):
+ tool_paths = [
+ tool_path(name = "B", path = "a/b/c"),
+ tool_path(name = "A", path = "a/b/c"),
+ ]
+ else:
+ fail("Unreachable")`},
+ {field: "tool_paths",
+ toolchains: allToolchains,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerC"):
+ tool_paths = [
+ tool_path(name = "A", path = "a/b/c"),
+ tool_path(name = "B", path = "a/b/c"),
+ ]
+ elif (ctx.attr.cpu == "cpuD"):
+ tool_paths = [
+ tool_path(name = "B", path = "a/b/c"),
+ tool_path(name = "A", path = "a/b/c"),
+ ]
+ elif (ctx.attr.cpu == "cpuA"
+ or ctx.attr.cpu == "cpuB"):
+ tool_paths = []
+ elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"
+ or ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
+ tool_paths = [tool_path(name = "A", path = "a/b/c")]
+ else:
+ fail("Unreachable")`}}
+
+ for _, tc := range testCases {
+ crosstool := makeCrosstool(tc.toolchains)
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
+ tc.field, tc.expectedText)
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(tc.toolchains, "\n"), got)
+ }
+ }
+}
+
+func getArtifactNamePattern(lines []string) string {
+ return fmt.Sprintf(`artifact_name_pattern {
+ %s
+}`, strings.Join(lines, "\n "))
+}
+
+func TestArtifactNamePatterns(t *testing.T) {
+ toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
+ toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
+ toolchainA1 := getCToolchain("3", "cpuC", "compilerA",
+ []string{
+ getArtifactNamePattern([]string{
+ "category_name: 'A'",
+ "prefix: 'p'",
+ "extension: '.exe'"}),
+ },
+ )
+ toolchainA2 := getCToolchain("4", "cpuC", "compilerB",
+ []string{
+ getArtifactNamePattern([]string{
+ "category_name: 'A'",
+ "prefix: 'p'",
+ "extension: '.exe'"}),
+ },
+ )
+ toolchainAB := getCToolchain("5", "cpuC", "compilerC",
+ []string{
+ getArtifactNamePattern([]string{
+ "category_name: 'A'",
+ "prefix: 'p'",
+ "extension: '.exe'"}),
+ getArtifactNamePattern([]string{
+ "category_name: 'B'",
+ "prefix: 'p'",
+ "extension: '.exe'"}),
+ },
+ )
+ toolchainBA := getCToolchain("6", "cpuD", "compilerA",
+ []string{
+ getArtifactNamePattern([]string{
+ "category_name: 'B'",
+ "prefix: 'p'",
+ "extension: '.exe'"}),
+ getArtifactNamePattern([]string{
+ "category_name: 'A'",
+ "prefix: 'p'",
+ "extension: '.exe'"}),
+ },
+ )
+ toolchainsEmpty := []string{toolchainEmpty1, toolchainEmpty2}
+
+ toolchainsOneNonempty := []string{toolchainEmpty1, toolchainA1}
+
+ toolchainsSameNonempty := []string{toolchainA1, toolchainA2}
+
+ toolchainsDifferentOrder := []string{toolchainAB, toolchainBA}
+
+ allToolchains := []string{
+ toolchainEmpty1,
+ toolchainEmpty2,
+ toolchainA1,
+ toolchainA2,
+ toolchainAB,
+ toolchainBA,
+ }
+
+ testCases := []struct {
+ field string
+ toolchains []string
+ expectedText string
+ }{
+ {field: "artifact_name_patterns",
+ toolchains: toolchainsEmpty,
+ expectedText: `
+ artifact_name_patterns = []`},
+ {field: "artifact_name_patterns",
+ toolchains: toolchainsOneNonempty,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuC"):
+ artifact_name_patterns = [
+ artifact_name_pattern(
+ category_name = "A",
+ prefix = "p",
+ extension = ".exe",
+ ),
+ ]
+ elif (ctx.attr.cpu == "cpuA"):
+ artifact_name_patterns = []
+ else:
+ fail("Unreachable")`},
+ {field: "artifact_name_patterns",
+ toolchains: toolchainsSameNonempty,
+ expectedText: `
+ artifact_name_patterns = [
+ artifact_name_pattern(
+ category_name = "A",
+ prefix = "p",
+ extension = ".exe",
+ ),
+ ]`},
+ {field: "artifact_name_patterns",
+ toolchains: toolchainsDifferentOrder,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuC"):
+ artifact_name_patterns = [
+ artifact_name_pattern(
+ category_name = "A",
+ prefix = "p",
+ extension = ".exe",
+ ),
+ artifact_name_pattern(
+ category_name = "B",
+ prefix = "p",
+ extension = ".exe",
+ ),
+ ]
+ elif (ctx.attr.cpu == "cpuD"):
+ artifact_name_patterns = [
+ artifact_name_pattern(
+ category_name = "B",
+ prefix = "p",
+ extension = ".exe",
+ ),
+ artifact_name_pattern(
+ category_name = "A",
+ prefix = "p",
+ extension = ".exe",
+ ),
+ ]
+ else:
+ fail("Unreachable")`},
+ {field: "artifact_name_patterns",
+ toolchains: allToolchains,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerC"):
+ artifact_name_patterns = [
+ artifact_name_pattern(
+ category_name = "A",
+ prefix = "p",
+ extension = ".exe",
+ ),
+ artifact_name_pattern(
+ category_name = "B",
+ prefix = "p",
+ extension = ".exe",
+ ),
+ ]
+ elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"
+ or ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
+ artifact_name_patterns = [
+ artifact_name_pattern(
+ category_name = "A",
+ prefix = "p",
+ extension = ".exe",
+ ),
+ ]
+ elif (ctx.attr.cpu == "cpuD"):
+ artifact_name_patterns = [
+ artifact_name_pattern(
+ category_name = "B",
+ prefix = "p",
+ extension = ".exe",
+ ),
+ artifact_name_pattern(
+ category_name = "A",
+ prefix = "p",
+ extension = ".exe",
+ ),
+ ]
+ elif (ctx.attr.cpu == "cpuA"
+ or ctx.attr.cpu == "cpuB"):
+ artifact_name_patterns = []
+ else:
+ fail("Unreachable")`}}
+
+ for _, tc := range testCases {
+ crosstool := makeCrosstool(tc.toolchains)
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
+ tc.field, tc.expectedText)
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(tc.toolchains, "\n"), got)
+ }
+ }
+}
+
+func getFeature(lines []string) string {
+ return fmt.Sprintf(`feature {
+ %s
+}`, strings.Join(lines, "\n "))
+}
+
+func TestFeatureListAssignment(t *testing.T) {
+ toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
+ toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
+ toolchainA1 := getCToolchain("3", "cpuC", "compilerA",
+ []string{getFeature([]string{"name: 'A'"})},
+ )
+ toolchainA2 := getCToolchain("4", "cpuC", "compilerB",
+ []string{getFeature([]string{"name: 'A'"})},
+ )
+ toolchainAB := getCToolchain("5", "cpuC", "compilerC",
+ []string{
+ getFeature([]string{"name: 'A'"}),
+ getFeature([]string{"name: 'B'"}),
+ },
+ )
+ toolchainBA := getCToolchain("6", "cpuD", "compilerA",
+ []string{
+ getFeature([]string{"name: 'B'"}),
+ getFeature([]string{"name: 'A'"}),
+ },
+ )
+ toolchainsEmpty := []string{toolchainEmpty1, toolchainEmpty2}
+
+ toolchainsOneNonempty := []string{toolchainEmpty1, toolchainA1}
+
+ toolchainsSameNonempty := []string{toolchainA1, toolchainA2}
+
+ toolchainsDifferentOrder := []string{toolchainAB, toolchainBA}
+
+ allToolchains := []string{
+ toolchainEmpty1,
+ toolchainEmpty2,
+ toolchainA1,
+ toolchainA2,
+ toolchainAB,
+ toolchainBA,
+ }
+
+ testCases := []struct {
+ field string
+ toolchains []string
+ expectedText string
+ }{
+ {field: "features",
+ toolchains: toolchainsEmpty,
+ expectedText: `
+ features = []`},
+ {field: "features",
+ toolchains: toolchainsOneNonempty,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA"):
+ features = []
+ elif (ctx.attr.cpu == "cpuC"):
+ features = [a_feature]
+ else:
+ fail("Unreachable")`},
+ {field: "features",
+ toolchains: toolchainsSameNonempty,
+ expectedText: `
+ features = [a_feature]`},
+ {field: "features",
+ toolchains: toolchainsDifferentOrder,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuC"):
+ features = [a_feature, b_feature]
+ elif (ctx.attr.cpu == "cpuD"):
+ features = [b_feature, a_feature]
+ else:
+ fail("Unreachable")`},
+ {field: "features",
+ toolchains: allToolchains,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA"
+ or ctx.attr.cpu == "cpuB"):
+ features = []
+ elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"
+ or ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
+ features = [a_feature]
+ elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerC"):
+ features = [a_feature, b_feature]
+ elif (ctx.attr.cpu == "cpuD"):
+ features = [b_feature, a_feature]
+ else:
+ fail("Unreachable")`}}
+
+ for _, tc := range testCases {
+ crosstool := makeCrosstool(tc.toolchains)
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
+ tc.field, tc.expectedText)
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(tc.toolchains, "\n"), got)
+ }
+ }
+}
+
+func getActionConfig(lines []string) string {
+ return fmt.Sprintf(`action_config {
+ %s
+}`, strings.Join(lines, "\n "))
+}
+
+func TestActionConfigListAssignment(t *testing.T) {
+ toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
+ toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
+ toolchainA1 := getCToolchain("3", "cpuC", "compilerA",
+ []string{
+ getActionConfig([]string{"action_name: 'A'", "config_name: 'A'"}),
+ },
+ )
+ toolchainA2 := getCToolchain("4", "cpuC", "compilerB",
+ []string{
+ getActionConfig([]string{"action_name: 'A'", "config_name: 'A'"}),
+ },
+ )
+ toolchainAB := getCToolchain("5", "cpuC", "compilerC",
+ []string{
+ getActionConfig([]string{"action_name: 'A'", "config_name: 'A'"}),
+ getActionConfig([]string{"action_name: 'B'", "config_name: 'B'"}),
+ },
+ )
+ toolchainBA := getCToolchain("6", "cpuD", "compilerA",
+ []string{
+ getActionConfig([]string{"action_name: 'B'", "config_name: 'B'"}),
+ getActionConfig([]string{"action_name: 'A'", "config_name: 'A'"}),
+ },
+ )
+ toolchainsEmpty := []string{toolchainEmpty1, toolchainEmpty2}
+
+ toolchainsOneNonempty := []string{toolchainEmpty1, toolchainA1}
+
+ toolchainsSameNonempty := []string{toolchainA1, toolchainA2}
+
+ toolchainsDifferentOrder := []string{toolchainAB, toolchainBA}
+
+ allToolchains := []string{
+ toolchainEmpty1,
+ toolchainEmpty2,
+ toolchainA1,
+ toolchainA2,
+ toolchainAB,
+ toolchainBA,
+ }
+
+ testCases := []struct {
+ field string
+ toolchains []string
+ expectedText string
+ }{
+ {field: "action_configs",
+ toolchains: toolchainsEmpty,
+ expectedText: `
+ action_configs = []`},
+ {field: "action_configs",
+ toolchains: toolchainsOneNonempty,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA"):
+ action_configs = []
+ elif (ctx.attr.cpu == "cpuC"):
+ action_configs = [a_action]
+ else:
+ fail("Unreachable")`},
+ {field: "action_configs",
+ toolchains: toolchainsSameNonempty,
+ expectedText: `
+ action_configs = [a_action]`},
+ {field: "action_configs",
+ toolchains: toolchainsDifferentOrder,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuC"):
+ action_configs = [a_action, b_action]
+ elif (ctx.attr.cpu == "cpuD"):
+ action_configs = [b_action, a_action]
+ else:
+ fail("Unreachable")`},
+ {field: "action_configs",
+ toolchains: allToolchains,
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA"
+ or ctx.attr.cpu == "cpuB"):
+ action_configs = []
+ elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"
+ or ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
+ action_configs = [a_action]
+ elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerC"):
+ action_configs = [a_action, b_action]
+ elif (ctx.attr.cpu == "cpuD"):
+ action_configs = [b_action, a_action]
+ else:
+ fail("Unreachable")`}}
+
+ for _, tc := range testCases {
+ crosstool := makeCrosstool(tc.toolchains)
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
+ tc.field, tc.expectedText)
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(tc.toolchains, "\n"), got)
+ }
+ }
+}
+
+func TestAllAndNoneAvailableErrorsWhenMoreThanOneElement(t *testing.T) {
+ toolchainFeatureAllAvailable := getCToolchain("1", "cpu", "compiler",
+ []string{getFeature([]string{
+ "name: 'A'",
+ "flag_set {",
+ " action: 'A'",
+ " flag_group {",
+ " flag: 'f'",
+ " expand_if_all_available: 'e1'",
+ " expand_if_all_available: 'e2'",
+ " }",
+ "}",
+ })},
+ )
+ toolchainFeatureNoneAvailable := getCToolchain("1", "cpu", "compiler",
+ []string{getFeature([]string{
+ "name: 'A'",
+ "flag_set {",
+ " action: 'A'",
+ " flag_group {",
+ " flag: 'f'",
+ " expand_if_none_available: 'e1'",
+ " expand_if_none_available: 'e2'",
+ " }",
+ "}",
+ })},
+ )
+ toolchainActionConfigAllAvailable := getCToolchain("1", "cpu", "compiler",
+ []string{getActionConfig([]string{
+ "config_name: 'A'",
+ "action_name: 'A'",
+ "flag_set {",
+ " action: 'A'",
+ " flag_group {",
+ " flag: 'f'",
+ " expand_if_all_available: 'e1'",
+ " expand_if_all_available: 'e2'",
+ " }",
+ "}",
+ })},
+ )
+ toolchainActionConfigNoneAvailable := getCToolchain("1", "cpu", "compiler",
+ []string{getActionConfig([]string{
+ "config_name: 'A'",
+ "action_name: 'A'",
+ "flag_set {",
+ " action: 'A'",
+ " flag_group {",
+ " flag: 'f'",
+ " expand_if_none_available: 'e1'",
+ " expand_if_none_available: 'e2'",
+ " }",
+ "}",
+ })},
+ )
+
+ testCases := []struct {
+ field string
+ toolchain string
+ expectedText string
+ }{
+ {field: "features",
+ toolchain: toolchainFeatureAllAvailable,
+ expectedText: "Error in feature 'A': Flag group must not have more " +
+ "than one 'expand_if_all_available' field"},
+ {field: "features",
+ toolchain: toolchainFeatureNoneAvailable,
+ expectedText: "Error in feature 'A': Flag group must not have more " +
+ "than one 'expand_if_none_available' field"},
+ {field: "action_configs",
+ toolchain: toolchainActionConfigAllAvailable,
+ expectedText: "Error in action_config 'A': Flag group must not have more " +
+ "than one 'expand_if_all_available' field"},
+ {field: "action_configs",
+ toolchain: toolchainActionConfigNoneAvailable,
+ expectedText: "Error in action_config 'A': Flag group must not have more " +
+ "than one 'expand_if_none_available' field"},
+ }
+
+ for _, tc := range testCases {
+ crosstool := makeCrosstool([]string{tc.toolchain})
+ _, err := Transform(crosstool)
+ if err == nil || !strings.Contains(err.Error(), tc.expectedText) {
+ t.Errorf("Expected error: %s, got: %v", tc.expectedText, err)
+ }
+ }
+}
+
+func TestFeaturesAndActionConfigsSetToNoneWhenAllOptionsAreExausted(t *testing.T) {
+ toolchainFeatureAEnabled := getCToolchain("1", "cpuA", "compilerA",
+ []string{getFeature([]string{"name: 'A'", "enabled: true"})},
+ )
+ toolchainFeatureADisabled := getCToolchain("2", "cpuA", "compilerB",
+ []string{getFeature([]string{"name: 'A'", "enabled: false"})},
+ )
+
+ toolchainWithoutFeatureA := getCToolchain("3", "cpuA", "compilerC", []string{})
+
+ toolchainActionConfigAEnabled := getCToolchain("4", "cpuA", "compilerD",
+ []string{getActionConfig([]string{
+ "config_name: 'A'",
+ "action_name: 'A'",
+ "enabled: true",
+ })})
+
+ toolchainActionConfigADisabled := getCToolchain("5", "cpuA", "compilerE",
+ []string{getActionConfig([]string{
+ "config_name: 'A'",
+ "action_name: 'A'",
+ })})
+
+ toolchainWithoutActionConfigA := getCToolchain("6", "cpuA", "compilerF", []string{})
+
+ testCases := []struct {
+ field string
+ toolchains []string
+ expectedText string
+ }{
+ {field: "features",
+ toolchains: []string{
+ toolchainFeatureAEnabled, toolchainFeatureADisabled, toolchainWithoutFeatureA},
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerB"):
+ a_feature = feature(name = "A")
+ elif (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerA"):
+ a_feature = feature(name = "A", enabled = True)
+ else:
+ a_feature = None
+`},
+ {field: "action_config",
+ toolchains: []string{
+ toolchainActionConfigAEnabled, toolchainActionConfigADisabled, toolchainWithoutActionConfigA},
+ expectedText: `
+ if (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerE"):
+ a_action = action_config(action_name = "A")
+ elif (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerD"):
+ a_action = action_config(action_name = "A", enabled = True)
+ else:
+ a_action = None
+`},
+ }
+
+ for _, tc := range testCases {
+ crosstool := makeCrosstool(tc.toolchains)
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
+ tc.field, tc.expectedText)
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(tc.toolchains, "\n"), got)
+ }
+ }
+}
+
+func TestActionConfigDeclaration(t *testing.T) {
+ toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
+ toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
+
+ toolchainNameNotInDict := getCToolchain("3", "cpBC", "compilerB",
+ []string{
+ getActionConfig([]string{"action_name: 'A-B.C'", "config_name: 'A-B.C'"}),
+ },
+ )
+ toolchainNameInDictA := getCToolchain("4", "cpuC", "compilerA",
+ []string{
+ getActionConfig([]string{"action_name: 'c++-compile'", "config_name: 'c++-compile'"}),
+ },
+ )
+ toolchainNameInDictB := getCToolchain("5", "cpuC", "compilerB",
+ []string{
+ getActionConfig([]string{
+ "action_name: 'c++-compile'",
+ "config_name: 'c++-compile'",
+ "tool {",
+ " tool_path: '/a/b/c'",
+ "}",
+ }),
+ },
+ )
+ toolchainComplexActionConfig := getCToolchain("6", "cpuC", "compilerC",
+ []string{
+ getActionConfig([]string{
+ "action_name: 'action-complex'",
+ "config_name: 'action-complex'",
+ "enabled: true",
+ "tool {",
+ " tool_path: '/a/b/c'",
+ " with_feature {",
+ " feature: 'a'",
+ " feature: 'b'",
+ " not_feature: 'c'",
+ " not_feature: 'd'",
+ " }",
+ " with_feature{",
+ " feature: 'e'",
+ " }",
+ " execution_requirement: 'a'",
+ "}",
+ "tool {",
+ " tool_path: ''",
+ "}",
+ "flag_set {",
+ " flag_group {",
+ " flag: 'a'",
+ " flag: '%b'",
+ " iterate_over: 'c'",
+ " expand_if_all_available: 'd'",
+ " expand_if_none_available: 'e'",
+ " expand_if_true: 'f'",
+ " expand_if_false: 'g'",
+ " expand_if_equal {",
+ " variable: 'var'",
+ " value: 'val'",
+ " }",
+ " }",
+ " flag_group {",
+ " flag_group {",
+ " flag: 'a'",
+ " }",
+ " }",
+ "}",
+ "flag_set {",
+ " with_feature {",
+ " feature: 'a'",
+ " feature: 'b'",
+ " not_feature: 'c'",
+ " not_feature: 'd'",
+ " }",
+ "}",
+ "env_set {",
+ " action: 'a'",
+ " env_entry {",
+ " key: 'k'",
+ " value: 'v'",
+ " }",
+ " with_feature {",
+ " feature: 'a'",
+ " }",
+ "}",
+ "requires {",
+ " feature: 'a'",
+ " feature: 'b'",
+ "}",
+ "implies: 'a'",
+ "implies: 'b'",
+ }),
+ },
+ )
+
+ testCases := []struct {
+ toolchains []string
+ expectedText string
+ }{
+ {
+ toolchains: []string{toolchainEmpty1, toolchainEmpty2},
+ expectedText: `
+ action_configs = []`},
+ {
+ toolchains: []string{toolchainEmpty1, toolchainNameNotInDict},
+ expectedText: `
+ a_b_c_action = action_config(action_name = "A-B.C")`},
+ {
+ toolchains: []string{toolchainNameInDictA, toolchainNameInDictB},
+ expectedText: `
+ if (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
+ cpp_compile_action = action_config(
+ action_name = ACTION_NAMES.cpp_compile,
+ tools = [tool(path = "/a/b/c")],
+ )
+ elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"):
+ cpp_compile_action = action_config(action_name = ACTION_NAMES.cpp_compile)`},
+ {
+ toolchains: []string{toolchainComplexActionConfig},
+ expectedText: `
+ action_complex_action = action_config(
+ action_name = "action-complex",
+ enabled = True,
+ flag_sets = [
+ flag_set(
+ flag_groups = [
+ flag_group(
+ flags = ["a", "%b"],
+ iterate_over = "c",
+ expand_if_available = "d",
+ expand_if_not_available = "e",
+ expand_if_true = "f",
+ expand_if_false = "g",
+ expand_if_equal = variable_with_value(name = "var", value = "val"),
+ ),
+ flag_group(flag_groups = [flag_group(flags = ["a"])]),
+ ],
+ ),
+ flag_set(
+ with_features = [
+ with_feature_set(
+ features = ["a", "b"],
+ not_features = ["c", "d"],
+ ),
+ ],
+ ),
+ ],
+ implies = ["a", "b"],
+ tools = [
+ tool(
+ path = "/a/b/c",
+ with_features = [
+ with_feature_set(
+ features = ["a", "b"],
+ not_features = ["c", "d"],
+ ),
+ with_feature_set(features = ["e"]),
+ ],
+ execution_requirements = ["a"],
+ ),
+ tool(path = "NOT_USED"),
+ ],
+ )`}}
+
+ for _, tc := range testCases {
+ crosstool := makeCrosstool(tc.toolchains)
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly declare an action_config, expected to contain:\n%v\n",
+ tc.expectedText)
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(tc.toolchains, "\n"), got)
+ }
+ }
+}
+
+func TestFeatureDeclaration(t *testing.T) {
+ toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
+ toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
+
+ toolchainSimpleFeatureA1 := getCToolchain("3", "cpuB", "compilerB",
+ []string{
+ getFeature([]string{"name: 'Feature-c++.a'", "enabled: true"}),
+ },
+ )
+ toolchainSimpleFeatureA2 := getCToolchain("4", "cpuC", "compilerA",
+ []string{
+ getFeature([]string{"name: 'Feature-c++.a'"}),
+ },
+ )
+ toolchainComplexFeature := getCToolchain("5", "cpuC", "compilerC",
+ []string{
+ getFeature([]string{
+ "name: 'complex-feature'",
+ "enabled: true",
+ "flag_set {",
+ " action: 'c++-compile'", // in ACTION_NAMES
+ " action: 'something-else'", // not in ACTION_NAMES
+ " flag_group {",
+ " flag: 'a'",
+ " flag: '%b'",
+ " iterate_over: 'c'",
+ " expand_if_all_available: 'd'",
+ " expand_if_none_available: 'e'",
+ " expand_if_true: 'f'",
+ " expand_if_false: 'g'",
+ " expand_if_equal {",
+ " variable: 'var'",
+ " value: 'val'",
+ " }",
+ " }",
+ " flag_group {",
+ " flag_group {",
+ " flag: 'a'",
+ " }",
+ " }",
+ "}",
+ "flag_set {", // all_compile_actions
+ " action: 'c-compile'",
+ " action: 'c++-compile'",
+ " action: 'linkstamp-compile'",
+ " action: 'assemble'",
+ " action: 'preprocess-assemble'",
+ " action: 'c++-header-parsing'",
+ " action: 'c++-module-compile'",
+ " action: 'c++-module-codegen'",
+ " action: 'clif-match'",
+ " action: 'lto-backend'",
+ "}",
+ "flag_set {", // all_cpp_compile_actions
+ " action: 'c++-compile'",
+ " action: 'linkstamp-compile'",
+ " action: 'c++-header-parsing'",
+ " action: 'c++-module-compile'",
+ " action: 'c++-module-codegen'",
+ " action: 'clif-match'",
+ "}",
+ "flag_set {", // all_link_actions
+ " action: 'c++-link-executable'",
+ " action: 'c++-link-dynamic-library'",
+ " action: 'c++-link-nodeps-dynamic-library'",
+ "}",
+ "flag_set {", // all_cpp_compile_actions + all_link_actions
+ " action: 'c++-compile'",
+ " action: 'linkstamp-compile'",
+ " action: 'c++-header-parsing'",
+ " action: 'c++-module-compile'",
+ " action: 'c++-module-codegen'",
+ " action: 'clif-match'",
+ " action: 'c++-link-executable'",
+ " action: 'c++-link-dynamic-library'",
+ " action: 'c++-link-nodeps-dynamic-library'",
+ "}",
+ "flag_set {", // all_link_actions + something else
+ " action: 'c++-link-executable'",
+ " action: 'c++-link-dynamic-library'",
+ " action: 'c++-link-nodeps-dynamic-library'",
+ " action: 'some.unknown-c++.action'",
+ "}",
+ "env_set {",
+ " action: 'a'",
+ " env_entry {",
+ " key: 'k'",
+ " value: 'v'",
+ " }",
+ " with_feature {",
+ " feature: 'a'",
+ " }",
+ "}",
+ "env_set {",
+ " action: 'c-compile'",
+ "}",
+ "env_set {", // all_compile_actions
+ " action: 'c-compile'",
+ " action: 'c++-compile'",
+ " action: 'linkstamp-compile'",
+ " action: 'assemble'",
+ " action: 'preprocess-assemble'",
+ " action: 'c++-header-parsing'",
+ " action: 'c++-module-compile'",
+ " action: 'c++-module-codegen'",
+ " action: 'clif-match'",
+ " action: 'lto-backend'",
+ "}",
+ "requires {",
+ " feature: 'a'",
+ " feature: 'b'",
+ "}",
+ "implies: 'a'",
+ "implies: 'b'",
+ "provides: 'c'",
+ "provides: 'd'",
+ }),
+ },
+ )
+
+ testCases := []struct {
+ toolchains []string
+ expectedText string
+ }{
+ {
+ toolchains: []string{toolchainEmpty1, toolchainEmpty2},
+ expectedText: `
+ features = []
+`},
+ {
+ toolchains: []string{toolchainEmpty1, toolchainSimpleFeatureA1},
+ expectedText: `
+ feature_cpp_a_feature = feature(name = "Feature-c++.a", enabled = True)`},
+ {
+ toolchains: []string{toolchainSimpleFeatureA1, toolchainSimpleFeatureA2},
+ expectedText: `
+ if (ctx.attr.cpu == "cpuC"):
+ feature_cpp_a_feature = feature(name = "Feature-c++.a")
+ elif (ctx.attr.cpu == "cpuB"):
+ feature_cpp_a_feature = feature(name = "Feature-c++.a", enabled = True)`},
+ {
+ toolchains: []string{toolchainComplexFeature},
+ expectedText: `
+ complex_feature_feature = feature(
+ name = "complex-feature",
+ enabled = True,
+ flag_sets = [
+ flag_set(
+ actions = [ACTION_NAMES.cpp_compile, "something-else"],
+ flag_groups = [
+ flag_group(
+ flags = ["a", "%b"],
+ iterate_over = "c",
+ expand_if_available = "d",
+ expand_if_not_available = "e",
+ expand_if_true = "f",
+ expand_if_false = "g",
+ expand_if_equal = variable_with_value(name = "var", value = "val"),
+ ),
+ flag_group(flag_groups = [flag_group(flags = ["a"])]),
+ ],
+ ),
+ flag_set(actions = all_compile_actions),
+ flag_set(actions = all_cpp_compile_actions),
+ flag_set(actions = all_link_actions),
+ flag_set(
+ actions = all_cpp_compile_actions +
+ all_link_actions,
+ ),
+ flag_set(
+ actions = all_link_actions +
+ ["some.unknown-c++.action"],
+ ),
+ ],
+ env_sets = [
+ env_set(
+ actions = ["a"],
+ env_entries = [env_entry(key = "k", value = "v")],
+ with_features = [with_feature_set(features = ["a"])],
+ ),
+ env_set(actions = [ACTION_NAMES.c_compile]),
+ env_set(actions = all_compile_actions),
+ ],
+ requires = [feature_set(features = ["a", "b"])],
+ implies = ["a", "b"],
+ provides = ["c", "d"],
+ )`}}
+
+ for _, tc := range testCases {
+ crosstool := makeCrosstool(tc.toolchains)
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly declare a feature, expected to contain:\n%v\n",
+ tc.expectedText)
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(tc.toolchains, "\n"), got)
+ }
+ }
+}
+
+func TestRule(t *testing.T) {
+ simpleToolchain := getSimpleCToolchain("simple")
+ expected := `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",
+)
+load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
+
+def _impl(ctx):
+ toolchain_identifier = "id-simple"
+
+ host_system_name = "host-simple"
+
+ target_system_name = "target-simple"
+
+ target_cpu = "cpu-simple"
+
+ target_libc = "libc-simple"
+
+ compiler = "compiler-simple"
+
+ abi_version = "version-simple"
+
+ abi_libc_version = "libc_version-simple"
+
+ cc_target_os = None
+
+ builtin_sysroot = None
+
+ all_compile_actions = [
+ ACTION_NAMES.c_compile,
+ ACTION_NAMES.cpp_compile,
+ ACTION_NAMES.linkstamp_compile,
+ ACTION_NAMES.assemble,
+ ACTION_NAMES.preprocess_assemble,
+ ACTION_NAMES.cpp_header_parsing,
+ ACTION_NAMES.cpp_module_compile,
+ ACTION_NAMES.cpp_module_codegen,
+ ACTION_NAMES.clif_match,
+ ACTION_NAMES.lto_backend,
+ ]
+
+ all_cpp_compile_actions = [
+ ACTION_NAMES.cpp_compile,
+ ACTION_NAMES.linkstamp_compile,
+ ACTION_NAMES.cpp_header_parsing,
+ ACTION_NAMES.cpp_module_compile,
+ ACTION_NAMES.cpp_module_codegen,
+ ACTION_NAMES.clif_match,
+ ]
+
+ preprocessor_compile_actions = [
+ ACTION_NAMES.c_compile,
+ ACTION_NAMES.cpp_compile,
+ ACTION_NAMES.linkstamp_compile,
+ ACTION_NAMES.preprocess_assemble,
+ ACTION_NAMES.cpp_header_parsing,
+ ACTION_NAMES.cpp_module_compile,
+ ACTION_NAMES.clif_match,
+ ]
+
+ codegen_compile_actions = [
+ ACTION_NAMES.c_compile,
+ ACTION_NAMES.cpp_compile,
+ ACTION_NAMES.linkstamp_compile,
+ ACTION_NAMES.assemble,
+ ACTION_NAMES.preprocess_assemble,
+ ACTION_NAMES.cpp_module_codegen,
+ ACTION_NAMES.lto_backend,
+ ]
+
+ all_link_actions = [
+ ACTION_NAMES.cpp_link_executable,
+ ACTION_NAMES.cpp_link_dynamic_library,
+ ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+ ]
+
+ action_configs = []
+
+ features = []
+
+ cxx_builtin_include_directories = []
+
+ artifact_name_patterns = []
+
+ make_variables = []
+
+ tool_paths = []
+
+
+ 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,
+ ),
+ ]
+cc_toolchain_config = rule(
+ implementation = _impl,
+ attrs = {
+ "cpu": attr.string(mandatory=True, values=["cpu-simple"]),
+ },
+ provides = [CcToolchainConfigInfo],
+ executable = True,
+)
+`
+ crosstool := makeCrosstool([]string{simpleToolchain})
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+ if got != expected {
+ t.Fatalf("Expected:\n%v\nGot:\n%v\nTested CROSSTOOL:\n%v",
+ expected, got, simpleToolchain)
+ }
+}
+
+func TestAllowedCompilerValues(t *testing.T) {
+ toolchainAA := getCToolchain("1", "cpuA", "compilerA", []string{})
+ toolchainBA := getCToolchain("2", "cpuB", "compilerA", []string{})
+ toolchainBB := getCToolchain("3", "cpuB", "compilerB", []string{})
+ toolchainCC := getCToolchain("4", "cpuC", "compilerC", []string{})
+
+ testCases := []struct {
+ toolchains []string
+ expectedText string
+ }{
+ {
+ toolchains: []string{toolchainAA, toolchainBA},
+ expectedText: `
+cc_toolchain_config = rule(
+ implementation = _impl,
+ attrs = {
+ "cpu": attr.string(mandatory=True, values=["cpuA", "cpuB"]),
+ },
+ provides = [CcToolchainConfigInfo],
+ executable = True,
+)
+`},
+ {
+ toolchains: []string{toolchainBA, toolchainBB},
+ expectedText: `
+cc_toolchain_config = rule(
+ implementation = _impl,
+ attrs = {
+ "cpu": attr.string(mandatory=True, values=["cpuB"]),
+ "compiler": attr.string(mandatory=True, values=["compilerA", "compilerB"]),
+ },
+ provides = [CcToolchainConfigInfo],
+ executable = True,
+)
+`},
+ {
+ toolchains: []string{toolchainAA, toolchainBA, toolchainBB},
+ expectedText: `
+cc_toolchain_config = rule(
+ implementation = _impl,
+ attrs = {
+ "cpu": attr.string(mandatory=True, values=["cpuA", "cpuB"]),
+ "compiler": attr.string(mandatory=True, values=["compilerA", "compilerB"]),
+ },
+ provides = [CcToolchainConfigInfo],
+ executable = True,
+)
+`},
+ {
+ toolchains: []string{toolchainAA, toolchainBA, toolchainBB, toolchainCC},
+ expectedText: `
+cc_toolchain_config = rule(
+ implementation = _impl,
+ attrs = {
+ "cpu": attr.string(mandatory=True, values=["cpuA", "cpuB", "cpuC"]),
+ "compiler": attr.string(mandatory=True, values=["compilerA", "compilerB", "compilerC"]),
+ },
+ provides = [CcToolchainConfigInfo],
+ executable = True,
+)
+`}}
+
+ for _, tc := range testCases {
+ crosstool := makeCrosstool(tc.toolchains)
+ got, err := Transform(crosstool)
+ if err != nil {
+ t.Fatalf("CROSSTOOL conversion failed: %v", err)
+ }
+ if !strings.Contains(got, tc.expectedText) {
+ t.Errorf("Failed to correctly declare the rule, expected to contain:\n%v\n",
+ tc.expectedText)
+ t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
+ strings.Join(tc.toolchains, "\n"), got)
+ }
+ }
+}