diff options
Diffstat (limited to 'tools/migration/crosstool_to_starlark_lib_test.go')
-rw-r--r-- | tools/migration/crosstool_to_starlark_lib_test.go | 1756 |
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) + } + } +} |