xref: /aosp_15_r20/external/bazelbuild-rules_cc/tools/migration/crosstool_to_starlark_lib_test.go (revision eed53cd41c5909d05eedc7ad9720bb158fd93452)
1package crosstooltostarlarklib
2
3import (
4	"fmt"
5	"strings"
6	"testing"
7
8	"log"
9	crosstoolpb "third_party/com/github/bazelbuild/bazel/src/main/protobuf/crosstool_config_go_proto"
10	"github.com/golang/protobuf/proto"
11)
12
13func makeCToolchainString(lines []string) string {
14	return fmt.Sprintf(`toolchain {
15  %s
16}`, strings.Join(lines, "\n  "))
17}
18
19func makeCrosstool(CToolchains []string) *crosstoolpb.CrosstoolRelease {
20	crosstool := &crosstoolpb.CrosstoolRelease{}
21	requiredFields := []string{
22		"major_version: '0'",
23		"minor_version: '0'",
24		"default_target_cpu: 'cpu'",
25	}
26	CToolchains = append(CToolchains, requiredFields...)
27	if err := proto.UnmarshalText(strings.Join(CToolchains, "\n"), crosstool); err != nil {
28		log.Fatalf("Failed to parse CROSSTOOL:", err)
29	}
30	return crosstool
31}
32
33func getSimpleCToolchain(id string) string {
34	lines := []string{
35		"toolchain_identifier: 'id-" + id + "'",
36		"host_system_name: 'host-" + id + "'",
37		"target_system_name: 'target-" + id + "'",
38		"target_cpu: 'cpu-" + id + "'",
39		"compiler: 'compiler-" + id + "'",
40		"target_libc: 'libc-" + id + "'",
41		"abi_version: 'version-" + id + "'",
42		"abi_libc_version: 'libc_version-" + id + "'",
43	}
44	return makeCToolchainString(lines)
45}
46
47func getCToolchain(id, cpu, compiler string, extraLines []string) string {
48	lines := []string{
49		"toolchain_identifier: '" + id + "'",
50		"host_system_name: 'host'",
51		"target_system_name: 'target'",
52		"target_cpu: '" + cpu + "'",
53		"compiler: '" + compiler + "'",
54		"target_libc: 'libc'",
55		"abi_version: 'version'",
56		"abi_libc_version: 'libc_version'",
57	}
58	lines = append(lines, extraLines...)
59	return makeCToolchainString(lines)
60}
61
62func TestStringFieldsConditionStatement(t *testing.T) {
63	toolchain1 := getSimpleCToolchain("1")
64	toolchain2 := getSimpleCToolchain("2")
65	toolchains := []string{toolchain1, toolchain2}
66	crosstool := makeCrosstool(toolchains)
67
68	testCases := []struct {
69		field        string
70		expectedText string
71	}{
72		{field: "toolchain_identifier",
73			expectedText: `
74    if (ctx.attr.cpu == "cpu-1"):
75        toolchain_identifier = "id-1"
76    elif (ctx.attr.cpu == "cpu-2"):
77        toolchain_identifier = "id-2"
78    else:
79        fail("Unreachable")`},
80		{field: "host_system_name",
81			expectedText: `
82    if (ctx.attr.cpu == "cpu-1"):
83        host_system_name = "host-1"
84    elif (ctx.attr.cpu == "cpu-2"):
85        host_system_name = "host-2"
86    else:
87        fail("Unreachable")`},
88		{field: "target_system_name",
89			expectedText: `
90    if (ctx.attr.cpu == "cpu-1"):
91        target_system_name = "target-1"
92    elif (ctx.attr.cpu == "cpu-2"):
93        target_system_name = "target-2"
94    else:
95        fail("Unreachable")`},
96		{field: "target_cpu",
97			expectedText: `
98    if (ctx.attr.cpu == "cpu-1"):
99        target_cpu = "cpu-1"
100    elif (ctx.attr.cpu == "cpu-2"):
101        target_cpu = "cpu-2"
102    else:
103        fail("Unreachable")`},
104		{field: "target_libc",
105			expectedText: `
106    if (ctx.attr.cpu == "cpu-1"):
107        target_libc = "libc-1"
108    elif (ctx.attr.cpu == "cpu-2"):
109        target_libc = "libc-2"
110    else:
111        fail("Unreachable")`},
112		{field: "compiler",
113			expectedText: `
114    if (ctx.attr.cpu == "cpu-1"):
115        compiler = "compiler-1"
116    elif (ctx.attr.cpu == "cpu-2"):
117        compiler = "compiler-2"
118    else:
119        fail("Unreachable")`},
120		{field: "abi_version",
121			expectedText: `
122    if (ctx.attr.cpu == "cpu-1"):
123        abi_version = "version-1"
124    elif (ctx.attr.cpu == "cpu-2"):
125        abi_version = "version-2"
126    else:
127        fail("Unreachable")`},
128		{field: "abi_libc_version",
129			expectedText: `
130    if (ctx.attr.cpu == "cpu-1"):
131        abi_libc_version = "libc_version-1"
132    elif (ctx.attr.cpu == "cpu-2"):
133        abi_libc_version = "libc_version-2"
134    else:
135        fail("Unreachable")`}}
136
137	got, err := Transform(crosstool)
138	if err != nil {
139		t.Fatalf("CROSSTOOL conversion failed: %v", err)
140	}
141
142	failed := false
143	for _, tc := range testCases {
144		if !strings.Contains(got, tc.expectedText) {
145			t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
146				tc.field, tc.expectedText)
147			failed = true
148		}
149	}
150	if failed {
151		t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
152			strings.Join(toolchains, "\n"), got)
153	}
154}
155
156func TestConditionsSameCpu(t *testing.T) {
157	toolchainAA := getCToolchain("1", "cpuA", "compilerA", []string{})
158	toolchainAB := getCToolchain("2", "cpuA", "compilerB", []string{})
159	toolchains := []string{toolchainAA, toolchainAB}
160	crosstool := makeCrosstool(toolchains)
161
162	testCases := []struct {
163		field        string
164		expectedText string
165	}{
166		{field: "toolchain_identifier",
167			expectedText: `
168    if (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerA"):
169        toolchain_identifier = "1"
170    elif (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerB"):
171        toolchain_identifier = "2"
172    else:
173        fail("Unreachable")`},
174		{field: "host_system_name",
175			expectedText: `
176    host_system_name = "host"`},
177		{field: "target_system_name",
178			expectedText: `
179    target_system_name = "target"`},
180		{field: "target_cpu",
181			expectedText: `
182    target_cpu = "cpuA"`},
183		{field: "target_libc",
184			expectedText: `
185    target_libc = "libc"`},
186		{field: "compiler",
187			expectedText: `
188    if (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerA"):
189        compiler = "compilerA"
190    elif (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerB"):
191        compiler = "compilerB"
192    else:
193        fail("Unreachable")`},
194		{field: "abi_version",
195			expectedText: `
196    abi_version = "version"`},
197		{field: "abi_libc_version",
198			expectedText: `
199    abi_libc_version = "libc_version"`}}
200
201	got, err := Transform(crosstool)
202	if err != nil {
203		t.Fatalf("CROSSTOOL conversion failed: %v", err)
204	}
205
206	failed := false
207	for _, tc := range testCases {
208		if !strings.Contains(got, tc.expectedText) {
209			t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
210				tc.field, tc.expectedText)
211			failed = true
212		}
213	}
214	if failed {
215		t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
216			strings.Join(toolchains, "\n"), got)
217	}
218}
219
220func TestConditionsSameCompiler(t *testing.T) {
221	toolchainAA := getCToolchain("1", "cpuA", "compilerA", []string{})
222	toolchainBA := getCToolchain("2", "cpuB", "compilerA", []string{})
223	toolchains := []string{toolchainAA, toolchainBA}
224	crosstool := makeCrosstool(toolchains)
225
226	testCases := []struct {
227		field        string
228		expectedText string
229	}{
230		{field: "toolchain_identifier",
231			expectedText: `
232    if (ctx.attr.cpu == "cpuA"):
233        toolchain_identifier = "1"
234    elif (ctx.attr.cpu == "cpuB"):
235        toolchain_identifier = "2"
236    else:
237        fail("Unreachable")`},
238		{field: "target_cpu",
239			expectedText: `
240    if (ctx.attr.cpu == "cpuA"):
241        target_cpu = "cpuA"
242    elif (ctx.attr.cpu == "cpuB"):
243        target_cpu = "cpuB"
244    else:
245        fail("Unreachable")`},
246		{field: "compiler",
247			expectedText: `
248    compiler = "compilerA"`}}
249
250	got, err := Transform(crosstool)
251	if err != nil {
252		t.Fatalf("CROSSTOOL conversion failed: %v", err)
253	}
254
255	failed := false
256	for _, tc := range testCases {
257		if !strings.Contains(got, tc.expectedText) {
258			t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
259				tc.field, tc.expectedText)
260			failed = true
261		}
262	}
263	if failed {
264		t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
265			strings.Join(toolchains, "\n"), got)
266	}
267}
268
269func TestNonMandatoryStrings(t *testing.T) {
270	toolchainAA := getCToolchain("1", "cpuA", "compilerA", []string{"cc_target_os: 'osA'"})
271	toolchainBB := getCToolchain("2", "cpuB", "compilerB", []string{})
272	toolchains := []string{toolchainAA, toolchainBB}
273	crosstool := makeCrosstool(toolchains)
274
275	testCases := []struct {
276		field        string
277		expectedText string
278	}{
279		{field: "cc_target_os",
280			expectedText: `
281    if (ctx.attr.cpu == "cpuB"):
282        cc_target_os = None
283    elif (ctx.attr.cpu == "cpuA"):
284        cc_target_os = "osA"
285    else:
286        fail("Unreachable")`},
287		{field: "builtin_sysroot",
288			expectedText: `
289    builtin_sysroot = None`}}
290
291	got, err := Transform(crosstool)
292	if err != nil {
293		t.Fatalf("CROSSTOOL conversion failed: %v", err)
294	}
295
296	failed := false
297	for _, tc := range testCases {
298		if !strings.Contains(got, tc.expectedText) {
299			t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
300				tc.field, tc.expectedText)
301			failed = true
302		}
303	}
304	if failed {
305		t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
306			strings.Join(toolchains, "\n"), got)
307	}
308}
309
310func TestBuiltinIncludeDirectories(t *testing.T) {
311	toolchainAA := getCToolchain("1", "cpuA", "compilerA", []string{})
312	toolchainBA := getCToolchain("2", "cpuB", "compilerA", []string{})
313	toolchainCA := getCToolchain("3", "cpuC", "compilerA",
314		[]string{"cxx_builtin_include_directory: 'dirC'"})
315	toolchainCB := getCToolchain("4", "cpuC", "compilerB",
316		[]string{"cxx_builtin_include_directory: 'dirC'",
317			"cxx_builtin_include_directory: 'dirB'"})
318	toolchainDA := getCToolchain("5", "cpuD", "compilerA",
319		[]string{"cxx_builtin_include_directory: 'dirC'"})
320
321	toolchainsEmpty := []string{toolchainAA, toolchainBA}
322
323	toolchainsOneNonempty := []string{toolchainAA, toolchainBA, toolchainCA}
324
325	toolchainsSameNonempty := []string{toolchainCA, toolchainDA}
326
327	allToolchains := []string{toolchainAA, toolchainBA, toolchainCA, toolchainCB, toolchainDA}
328
329	testCases := []struct {
330		field        string
331		toolchains   []string
332		expectedText string
333	}{
334		{field: "cxx_builtin_include_directories",
335			toolchains: toolchainsEmpty,
336			expectedText: `
337    cxx_builtin_include_directories = []`},
338		{field: "cxx_builtin_include_directories",
339			toolchains: toolchainsOneNonempty,
340			expectedText: `
341    if (ctx.attr.cpu == "cpuA"
342        or ctx.attr.cpu == "cpuB"):
343        cxx_builtin_include_directories = []
344    elif (ctx.attr.cpu == "cpuC"):
345        cxx_builtin_include_directories = ["dirC"]
346    else:
347        fail("Unreachable")`},
348		{field: "cxx_builtin_include_directories",
349			toolchains: toolchainsSameNonempty,
350			expectedText: `
351    cxx_builtin_include_directories = ["dirC"]`},
352		{field: "cxx_builtin_include_directories",
353			toolchains: allToolchains,
354			expectedText: `
355    if (ctx.attr.cpu == "cpuA"
356        or ctx.attr.cpu == "cpuB"):
357        cxx_builtin_include_directories = []
358    elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"
359        or ctx.attr.cpu == "cpuD"):
360        cxx_builtin_include_directories = ["dirC"]
361    elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
362        cxx_builtin_include_directories = ["dirC", "dirB"]`}}
363
364	for _, tc := range testCases {
365		crosstool := makeCrosstool(tc.toolchains)
366		got, err := Transform(crosstool)
367		if err != nil {
368			t.Fatalf("CROSSTOOL conversion failed: %v", err)
369		}
370		if !strings.Contains(got, tc.expectedText) {
371			t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
372				tc.field, tc.expectedText)
373			t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
374				strings.Join(tc.toolchains, "\n"), got)
375		}
376	}
377}
378
379func TestMakeVariables(t *testing.T) {
380	toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
381	toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
382	toolchainA1 := getCToolchain("3", "cpuC", "compilerA",
383		[]string{"make_variable {name: 'A', value: 'a/b/c'}"})
384	toolchainA2 := getCToolchain("4", "cpuC", "compilerB",
385		[]string{"make_variable {name: 'A', value: 'a/b/c'}"})
386	toolchainAB := getCToolchain("5", "cpuC", "compilerC",
387		[]string{"make_variable {name: 'A', value: 'a/b/c'}",
388			"make_variable {name: 'B', value: 'a/b/c'}"})
389	toolchainBA := getCToolchain("6", "cpuD", "compilerA",
390		[]string{"make_variable {name: 'B', value: 'a/b/c'}",
391			"make_variable {name: 'A', value: 'a b c'}"})
392
393	toolchainsEmpty := []string{toolchainEmpty1, toolchainEmpty2}
394
395	toolchainsOneNonempty := []string{toolchainEmpty1, toolchainA1}
396
397	toolchainsSameNonempty := []string{toolchainA1, toolchainA2}
398
399	toolchainsDifferentOrder := []string{toolchainAB, toolchainBA}
400
401	allToolchains := []string{
402		toolchainEmpty1,
403		toolchainEmpty2,
404		toolchainA1,
405		toolchainA2,
406		toolchainAB,
407		toolchainBA,
408	}
409
410	testCases := []struct {
411		field        string
412		toolchains   []string
413		expectedText string
414	}{
415		{field: "make_variables",
416			toolchains: toolchainsEmpty,
417			expectedText: `
418    make_variables = []`},
419		{field: "make_variables",
420			toolchains: toolchainsOneNonempty,
421			expectedText: `
422    if (ctx.attr.cpu == "cpuA"):
423        make_variables = []
424    elif (ctx.attr.cpu == "cpuC"):
425        make_variables = [make_variable(name = "A", value = "a/b/c")]
426    else:
427        fail("Unreachable")`},
428		{field: "make_variables",
429			toolchains: toolchainsSameNonempty,
430			expectedText: `
431    make_variables = [make_variable(name = "A", value = "a/b/c")]`},
432		{field: "make_variables",
433			toolchains: toolchainsDifferentOrder,
434			expectedText: `
435    if (ctx.attr.cpu == "cpuC"):
436        make_variables = [
437            make_variable(name = "A", value = "a/b/c"),
438            make_variable(name = "B", value = "a/b/c"),
439        ]
440    elif (ctx.attr.cpu == "cpuD"):
441        make_variables = [
442            make_variable(name = "B", value = "a/b/c"),
443            make_variable(name = "A", value = "a b c"),
444        ]
445    else:
446        fail("Unreachable")`},
447		{field: "make_variables",
448			toolchains: allToolchains,
449			expectedText: `
450    if (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerC"):
451        make_variables = [
452            make_variable(name = "A", value = "a/b/c"),
453            make_variable(name = "B", value = "a/b/c"),
454        ]
455    elif (ctx.attr.cpu == "cpuD"):
456        make_variables = [
457            make_variable(name = "B", value = "a/b/c"),
458            make_variable(name = "A", value = "a b c"),
459        ]
460    elif (ctx.attr.cpu == "cpuA"
461        or ctx.attr.cpu == "cpuB"):
462        make_variables = []
463    elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"
464        or ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
465        make_variables = [make_variable(name = "A", value = "a/b/c")]
466    else:
467        fail("Unreachable")`}}
468
469	for _, tc := range testCases {
470		crosstool := makeCrosstool(tc.toolchains)
471		got, err := Transform(crosstool)
472		if err != nil {
473			t.Fatalf("CROSSTOOL conversion failed: %v", err)
474		}
475		if !strings.Contains(got, tc.expectedText) {
476			t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
477				tc.field, tc.expectedText)
478			t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
479				strings.Join(tc.toolchains, "\n"), got)
480		}
481	}
482}
483
484func TestToolPaths(t *testing.T) {
485	toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
486	toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
487	toolchainA1 := getCToolchain("3", "cpuC", "compilerA",
488		[]string{"tool_path {name: 'A', path: 'a/b/c'}"})
489	toolchainA2 := getCToolchain("4", "cpuC", "compilerB",
490		[]string{"tool_path {name: 'A', path: 'a/b/c'}"})
491	toolchainAB := getCToolchain("5", "cpuC", "compilerC",
492		[]string{"tool_path {name: 'A', path: 'a/b/c'}",
493			"tool_path {name: 'B', path: 'a/b/c'}"})
494	toolchainBA := getCToolchain("6", "cpuD", "compilerA",
495		[]string{"tool_path {name: 'B', path: 'a/b/c'}",
496			"tool_path {name: 'A', path: 'a/b/c'}"})
497
498	toolchainsEmpty := []string{toolchainEmpty1, toolchainEmpty2}
499
500	toolchainsOneNonempty := []string{toolchainEmpty1, toolchainA1}
501
502	toolchainsSameNonempty := []string{toolchainA1, toolchainA2}
503
504	toolchainsDifferentOrder := []string{toolchainAB, toolchainBA}
505
506	allToolchains := []string{
507		toolchainEmpty1,
508		toolchainEmpty2,
509		toolchainA1,
510		toolchainA2,
511		toolchainAB,
512		toolchainBA,
513	}
514
515	testCases := []struct {
516		field        string
517		toolchains   []string
518		expectedText string
519	}{
520		{field: "tool_paths",
521			toolchains: toolchainsEmpty,
522			expectedText: `
523    tool_paths = []`},
524		{field: "tool_paths",
525			toolchains: toolchainsOneNonempty,
526			expectedText: `
527    if (ctx.attr.cpu == "cpuA"):
528        tool_paths = []
529    elif (ctx.attr.cpu == "cpuC"):
530        tool_paths = [tool_path(name = "A", path = "a/b/c")]
531    else:
532        fail("Unreachable")`},
533		{field: "tool_paths",
534			toolchains: toolchainsSameNonempty,
535			expectedText: `
536    tool_paths = [tool_path(name = "A", path = "a/b/c")]`},
537		{field: "tool_paths",
538			toolchains: toolchainsDifferentOrder,
539			expectedText: `
540    if (ctx.attr.cpu == "cpuC"):
541        tool_paths = [
542            tool_path(name = "A", path = "a/b/c"),
543            tool_path(name = "B", path = "a/b/c"),
544        ]
545    elif (ctx.attr.cpu == "cpuD"):
546        tool_paths = [
547            tool_path(name = "B", path = "a/b/c"),
548            tool_path(name = "A", path = "a/b/c"),
549        ]
550    else:
551        fail("Unreachable")`},
552		{field: "tool_paths",
553			toolchains: allToolchains,
554			expectedText: `
555    if (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerC"):
556        tool_paths = [
557            tool_path(name = "A", path = "a/b/c"),
558            tool_path(name = "B", path = "a/b/c"),
559        ]
560    elif (ctx.attr.cpu == "cpuD"):
561        tool_paths = [
562            tool_path(name = "B", path = "a/b/c"),
563            tool_path(name = "A", path = "a/b/c"),
564        ]
565    elif (ctx.attr.cpu == "cpuA"
566        or ctx.attr.cpu == "cpuB"):
567        tool_paths = []
568    elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"
569        or ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
570        tool_paths = [tool_path(name = "A", path = "a/b/c")]
571    else:
572        fail("Unreachable")`}}
573
574	for _, tc := range testCases {
575		crosstool := makeCrosstool(tc.toolchains)
576		got, err := Transform(crosstool)
577		if err != nil {
578			t.Fatalf("CROSSTOOL conversion failed: %v", err)
579		}
580		if !strings.Contains(got, tc.expectedText) {
581			t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
582				tc.field, tc.expectedText)
583			t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
584				strings.Join(tc.toolchains, "\n"), got)
585		}
586	}
587}
588
589func getArtifactNamePattern(lines []string) string {
590	return fmt.Sprintf(`artifact_name_pattern {
591  %s
592}`, strings.Join(lines, "\n  "))
593}
594
595func TestArtifactNamePatterns(t *testing.T) {
596	toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
597	toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
598	toolchainA1 := getCToolchain("3", "cpuC", "compilerA",
599		[]string{
600			getArtifactNamePattern([]string{
601				"category_name: 'A'",
602				"prefix: 'p'",
603				"extension: '.exe'"}),
604		},
605	)
606	toolchainA2 := getCToolchain("4", "cpuC", "compilerB",
607		[]string{
608			getArtifactNamePattern([]string{
609				"category_name: 'A'",
610				"prefix: 'p'",
611				"extension: '.exe'"}),
612		},
613	)
614	toolchainAB := getCToolchain("5", "cpuC", "compilerC",
615		[]string{
616			getArtifactNamePattern([]string{
617				"category_name: 'A'",
618				"prefix: 'p'",
619				"extension: '.exe'"}),
620			getArtifactNamePattern([]string{
621				"category_name: 'B'",
622				"prefix: 'p'",
623				"extension: '.exe'"}),
624		},
625	)
626	toolchainBA := getCToolchain("6", "cpuD", "compilerA",
627		[]string{
628			getArtifactNamePattern([]string{
629				"category_name: 'B'",
630				"prefix: 'p'",
631				"extension: '.exe'"}),
632			getArtifactNamePattern([]string{
633				"category_name: 'A'",
634				"prefix: 'p'",
635				"extension: '.exe'"}),
636		},
637	)
638	toolchainsEmpty := []string{toolchainEmpty1, toolchainEmpty2}
639
640	toolchainsOneNonempty := []string{toolchainEmpty1, toolchainA1}
641
642	toolchainsSameNonempty := []string{toolchainA1, toolchainA2}
643
644	toolchainsDifferentOrder := []string{toolchainAB, toolchainBA}
645
646	allToolchains := []string{
647		toolchainEmpty1,
648		toolchainEmpty2,
649		toolchainA1,
650		toolchainA2,
651		toolchainAB,
652		toolchainBA,
653	}
654
655	testCases := []struct {
656		field        string
657		toolchains   []string
658		expectedText string
659	}{
660		{field: "artifact_name_patterns",
661			toolchains: toolchainsEmpty,
662			expectedText: `
663    artifact_name_patterns = []`},
664		{field: "artifact_name_patterns",
665			toolchains: toolchainsOneNonempty,
666			expectedText: `
667    if (ctx.attr.cpu == "cpuC"):
668        artifact_name_patterns = [
669            artifact_name_pattern(
670                category_name = "A",
671                prefix = "p",
672                extension = ".exe",
673            ),
674        ]
675    elif (ctx.attr.cpu == "cpuA"):
676        artifact_name_patterns = []
677    else:
678        fail("Unreachable")`},
679		{field: "artifact_name_patterns",
680			toolchains: toolchainsSameNonempty,
681			expectedText: `
682    artifact_name_patterns = [
683        artifact_name_pattern(
684            category_name = "A",
685            prefix = "p",
686            extension = ".exe",
687        ),
688    ]`},
689		{field: "artifact_name_patterns",
690			toolchains: toolchainsDifferentOrder,
691			expectedText: `
692    if (ctx.attr.cpu == "cpuC"):
693        artifact_name_patterns = [
694            artifact_name_pattern(
695                category_name = "A",
696                prefix = "p",
697                extension = ".exe",
698            ),
699            artifact_name_pattern(
700                category_name = "B",
701                prefix = "p",
702                extension = ".exe",
703            ),
704        ]
705    elif (ctx.attr.cpu == "cpuD"):
706        artifact_name_patterns = [
707            artifact_name_pattern(
708                category_name = "B",
709                prefix = "p",
710                extension = ".exe",
711            ),
712            artifact_name_pattern(
713                category_name = "A",
714                prefix = "p",
715                extension = ".exe",
716            ),
717        ]
718    else:
719        fail("Unreachable")`},
720		{field: "artifact_name_patterns",
721			toolchains: allToolchains,
722			expectedText: `
723    if (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerC"):
724        artifact_name_patterns = [
725            artifact_name_pattern(
726                category_name = "A",
727                prefix = "p",
728                extension = ".exe",
729            ),
730            artifact_name_pattern(
731                category_name = "B",
732                prefix = "p",
733                extension = ".exe",
734            ),
735        ]
736    elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"
737        or ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
738        artifact_name_patterns = [
739            artifact_name_pattern(
740                category_name = "A",
741                prefix = "p",
742                extension = ".exe",
743            ),
744        ]
745    elif (ctx.attr.cpu == "cpuD"):
746        artifact_name_patterns = [
747            artifact_name_pattern(
748                category_name = "B",
749                prefix = "p",
750                extension = ".exe",
751            ),
752            artifact_name_pattern(
753                category_name = "A",
754                prefix = "p",
755                extension = ".exe",
756            ),
757        ]
758    elif (ctx.attr.cpu == "cpuA"
759        or ctx.attr.cpu == "cpuB"):
760        artifact_name_patterns = []
761    else:
762        fail("Unreachable")`}}
763
764	for _, tc := range testCases {
765		crosstool := makeCrosstool(tc.toolchains)
766		got, err := Transform(crosstool)
767		if err != nil {
768			t.Fatalf("CROSSTOOL conversion failed: %v", err)
769		}
770		if !strings.Contains(got, tc.expectedText) {
771			t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
772				tc.field, tc.expectedText)
773			t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
774				strings.Join(tc.toolchains, "\n"), got)
775		}
776	}
777}
778
779func getFeature(lines []string) string {
780	return fmt.Sprintf(`feature {
781  %s
782}`, strings.Join(lines, "\n  "))
783}
784
785func TestFeatureListAssignment(t *testing.T) {
786	toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
787	toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
788	toolchainA1 := getCToolchain("3", "cpuC", "compilerA",
789		[]string{getFeature([]string{"name: 'A'"})},
790	)
791	toolchainA2 := getCToolchain("4", "cpuC", "compilerB",
792		[]string{getFeature([]string{"name: 'A'"})},
793	)
794	toolchainAB := getCToolchain("5", "cpuC", "compilerC",
795		[]string{
796			getFeature([]string{"name: 'A'"}),
797			getFeature([]string{"name: 'B'"}),
798		},
799	)
800	toolchainBA := getCToolchain("6", "cpuD", "compilerA",
801		[]string{
802			getFeature([]string{"name: 'B'"}),
803			getFeature([]string{"name: 'A'"}),
804		},
805	)
806	toolchainsEmpty := []string{toolchainEmpty1, toolchainEmpty2}
807
808	toolchainsOneNonempty := []string{toolchainEmpty1, toolchainA1}
809
810	toolchainsSameNonempty := []string{toolchainA1, toolchainA2}
811
812	toolchainsDifferentOrder := []string{toolchainAB, toolchainBA}
813
814	allToolchains := []string{
815		toolchainEmpty1,
816		toolchainEmpty2,
817		toolchainA1,
818		toolchainA2,
819		toolchainAB,
820		toolchainBA,
821	}
822
823	testCases := []struct {
824		field        string
825		toolchains   []string
826		expectedText string
827	}{
828		{field: "features",
829			toolchains: toolchainsEmpty,
830			expectedText: `
831    features = []`},
832		{field: "features",
833			toolchains: toolchainsOneNonempty,
834			expectedText: `
835    if (ctx.attr.cpu == "cpuA"):
836        features = []
837    elif (ctx.attr.cpu == "cpuC"):
838        features = [a_feature]
839    else:
840        fail("Unreachable")`},
841		{field: "features",
842			toolchains: toolchainsSameNonempty,
843			expectedText: `
844    features = [a_feature]`},
845		{field: "features",
846			toolchains: toolchainsDifferentOrder,
847			expectedText: `
848    if (ctx.attr.cpu == "cpuC"):
849        features = [a_feature, b_feature]
850    elif (ctx.attr.cpu == "cpuD"):
851        features = [b_feature, a_feature]
852    else:
853        fail("Unreachable")`},
854		{field: "features",
855			toolchains: allToolchains,
856			expectedText: `
857    if (ctx.attr.cpu == "cpuA"
858        or ctx.attr.cpu == "cpuB"):
859        features = []
860    elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"
861        or ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
862        features = [a_feature]
863    elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerC"):
864        features = [a_feature, b_feature]
865    elif (ctx.attr.cpu == "cpuD"):
866        features = [b_feature, a_feature]
867    else:
868        fail("Unreachable")`}}
869
870	for _, tc := range testCases {
871		crosstool := makeCrosstool(tc.toolchains)
872		got, err := Transform(crosstool)
873		if err != nil {
874			t.Fatalf("CROSSTOOL conversion failed: %v", err)
875		}
876		if !strings.Contains(got, tc.expectedText) {
877			t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
878				tc.field, tc.expectedText)
879			t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
880				strings.Join(tc.toolchains, "\n"), got)
881		}
882	}
883}
884
885func getActionConfig(lines []string) string {
886	return fmt.Sprintf(`action_config {
887  %s
888}`, strings.Join(lines, "\n  "))
889}
890
891func TestActionConfigListAssignment(t *testing.T) {
892	toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
893	toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
894	toolchainA1 := getCToolchain("3", "cpuC", "compilerA",
895		[]string{
896			getActionConfig([]string{"action_name: 'A'", "config_name: 'A'"}),
897		},
898	)
899	toolchainA2 := getCToolchain("4", "cpuC", "compilerB",
900		[]string{
901			getActionConfig([]string{"action_name: 'A'", "config_name: 'A'"}),
902		},
903	)
904	toolchainAB := getCToolchain("5", "cpuC", "compilerC",
905		[]string{
906			getActionConfig([]string{"action_name: 'A'", "config_name: 'A'"}),
907			getActionConfig([]string{"action_name: 'B'", "config_name: 'B'"}),
908		},
909	)
910	toolchainBA := getCToolchain("6", "cpuD", "compilerA",
911		[]string{
912			getActionConfig([]string{"action_name: 'B'", "config_name: 'B'"}),
913			getActionConfig([]string{"action_name: 'A'", "config_name: 'A'"}),
914		},
915	)
916	toolchainsEmpty := []string{toolchainEmpty1, toolchainEmpty2}
917
918	toolchainsOneNonempty := []string{toolchainEmpty1, toolchainA1}
919
920	toolchainsSameNonempty := []string{toolchainA1, toolchainA2}
921
922	toolchainsDifferentOrder := []string{toolchainAB, toolchainBA}
923
924	allToolchains := []string{
925		toolchainEmpty1,
926		toolchainEmpty2,
927		toolchainA1,
928		toolchainA2,
929		toolchainAB,
930		toolchainBA,
931	}
932
933	testCases := []struct {
934		field        string
935		toolchains   []string
936		expectedText string
937	}{
938		{field: "action_configs",
939			toolchains: toolchainsEmpty,
940			expectedText: `
941    action_configs = []`},
942		{field: "action_configs",
943			toolchains: toolchainsOneNonempty,
944			expectedText: `
945    if (ctx.attr.cpu == "cpuA"):
946        action_configs = []
947    elif (ctx.attr.cpu == "cpuC"):
948        action_configs = [a_action]
949    else:
950        fail("Unreachable")`},
951		{field: "action_configs",
952			toolchains: toolchainsSameNonempty,
953			expectedText: `
954    action_configs = [a_action]`},
955		{field: "action_configs",
956			toolchains: toolchainsDifferentOrder,
957			expectedText: `
958    if (ctx.attr.cpu == "cpuC"):
959        action_configs = [a_action, b_action]
960    elif (ctx.attr.cpu == "cpuD"):
961        action_configs = [b_action, a_action]
962    else:
963        fail("Unreachable")`},
964		{field: "action_configs",
965			toolchains: allToolchains,
966			expectedText: `
967    if (ctx.attr.cpu == "cpuA"
968        or ctx.attr.cpu == "cpuB"):
969        action_configs = []
970    elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"
971        or ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
972        action_configs = [a_action]
973    elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerC"):
974        action_configs = [a_action, b_action]
975    elif (ctx.attr.cpu == "cpuD"):
976        action_configs = [b_action, a_action]
977    else:
978        fail("Unreachable")`}}
979
980	for _, tc := range testCases {
981		crosstool := makeCrosstool(tc.toolchains)
982		got, err := Transform(crosstool)
983		if err != nil {
984			t.Fatalf("CROSSTOOL conversion failed: %v", err)
985		}
986		if !strings.Contains(got, tc.expectedText) {
987			t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
988				tc.field, tc.expectedText)
989			t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
990				strings.Join(tc.toolchains, "\n"), got)
991		}
992	}
993}
994
995func TestAllAndNoneAvailableErrorsWhenMoreThanOneElement(t *testing.T) {
996	toolchainFeatureAllAvailable := getCToolchain("1", "cpu", "compiler",
997		[]string{getFeature([]string{
998			"name: 'A'",
999			"flag_set {",
1000			"  action: 'A'",
1001			"  flag_group {",
1002			"    flag: 'f'",
1003			"    expand_if_all_available: 'e1'",
1004			"    expand_if_all_available: 'e2'",
1005			"  }",
1006			"}",
1007		})},
1008	)
1009	toolchainFeatureNoneAvailable := getCToolchain("1", "cpu", "compiler",
1010		[]string{getFeature([]string{
1011			"name: 'A'",
1012			"flag_set {",
1013			"  action: 'A'",
1014			"  flag_group {",
1015			"    flag: 'f'",
1016			"    expand_if_none_available: 'e1'",
1017			"    expand_if_none_available: 'e2'",
1018			"  }",
1019			"}",
1020		})},
1021	)
1022	toolchainActionConfigAllAvailable := getCToolchain("1", "cpu", "compiler",
1023		[]string{getActionConfig([]string{
1024			"config_name: 'A'",
1025			"action_name: 'A'",
1026			"flag_set {",
1027			"  action: 'A'",
1028			"  flag_group {",
1029			"    flag: 'f'",
1030			"    expand_if_all_available: 'e1'",
1031			"    expand_if_all_available: 'e2'",
1032			"  }",
1033			"}",
1034		})},
1035	)
1036	toolchainActionConfigNoneAvailable := getCToolchain("1", "cpu", "compiler",
1037		[]string{getActionConfig([]string{
1038			"config_name: 'A'",
1039			"action_name: 'A'",
1040			"flag_set {",
1041			"  action: 'A'",
1042			"  flag_group {",
1043			"    flag: 'f'",
1044			"    expand_if_none_available: 'e1'",
1045			"    expand_if_none_available: 'e2'",
1046			"  }",
1047			"}",
1048		})},
1049	)
1050
1051	testCases := []struct {
1052		field        string
1053		toolchain    string
1054		expectedText string
1055	}{
1056		{field: "features",
1057			toolchain: toolchainFeatureAllAvailable,
1058			expectedText: "Error in feature 'A': Flag group must not have more " +
1059				"than one 'expand_if_all_available' field"},
1060		{field: "features",
1061			toolchain: toolchainFeatureNoneAvailable,
1062			expectedText: "Error in feature 'A': Flag group must not have more " +
1063				"than one 'expand_if_none_available' field"},
1064		{field: "action_configs",
1065			toolchain: toolchainActionConfigAllAvailable,
1066			expectedText: "Error in action_config 'A': Flag group must not have more " +
1067				"than one 'expand_if_all_available' field"},
1068		{field: "action_configs",
1069			toolchain: toolchainActionConfigNoneAvailable,
1070			expectedText: "Error in action_config 'A': Flag group must not have more " +
1071				"than one 'expand_if_none_available' field"},
1072	}
1073
1074	for _, tc := range testCases {
1075		crosstool := makeCrosstool([]string{tc.toolchain})
1076		_, err := Transform(crosstool)
1077		if err == nil || !strings.Contains(err.Error(), tc.expectedText) {
1078			t.Errorf("Expected error: %s, got: %v", tc.expectedText, err)
1079		}
1080	}
1081}
1082
1083func TestFeaturesAndActionConfigsSetToNoneWhenAllOptionsAreExausted(t *testing.T) {
1084	toolchainFeatureAEnabled := getCToolchain("1", "cpuA", "compilerA",
1085		[]string{getFeature([]string{"name: 'A'", "enabled: true"})},
1086	)
1087	toolchainFeatureADisabled := getCToolchain("2", "cpuA", "compilerB",
1088		[]string{getFeature([]string{"name: 'A'", "enabled: false"})},
1089	)
1090
1091	toolchainWithoutFeatureA := getCToolchain("3", "cpuA", "compilerC", []string{})
1092
1093	toolchainActionConfigAEnabled := getCToolchain("4", "cpuA", "compilerD",
1094		[]string{getActionConfig([]string{
1095			"config_name: 'A'",
1096			"action_name: 'A'",
1097			"enabled: true",
1098		})})
1099
1100	toolchainActionConfigADisabled := getCToolchain("5", "cpuA", "compilerE",
1101		[]string{getActionConfig([]string{
1102			"config_name: 'A'",
1103			"action_name: 'A'",
1104		})})
1105
1106	toolchainWithoutActionConfigA := getCToolchain("6", "cpuA", "compilerF", []string{})
1107
1108	testCases := []struct {
1109		field        string
1110		toolchains   []string
1111		expectedText string
1112	}{
1113		{field: "features",
1114			toolchains: []string{
1115				toolchainFeatureAEnabled, toolchainFeatureADisabled, toolchainWithoutFeatureA},
1116			expectedText: `
1117    if (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerB"):
1118        a_feature = feature(name = "A")
1119    elif (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerA"):
1120        a_feature = feature(name = "A", enabled = True)
1121    else:
1122        a_feature = None
1123`},
1124		{field: "action_config",
1125			toolchains: []string{
1126				toolchainActionConfigAEnabled, toolchainActionConfigADisabled, toolchainWithoutActionConfigA},
1127			expectedText: `
1128    if (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerE"):
1129        a_action = action_config(action_name = "A")
1130    elif (ctx.attr.cpu == "cpuA" and ctx.attr.compiler == "compilerD"):
1131        a_action = action_config(action_name = "A", enabled = True)
1132    else:
1133        a_action = None
1134`},
1135	}
1136
1137	for _, tc := range testCases {
1138		crosstool := makeCrosstool(tc.toolchains)
1139		got, err := Transform(crosstool)
1140		if err != nil {
1141			t.Fatalf("CROSSTOOL conversion failed: %v", err)
1142		}
1143		if !strings.Contains(got, tc.expectedText) {
1144			t.Errorf("Failed to correctly convert '%s' field, expected to contain:\n%v\n",
1145				tc.field, tc.expectedText)
1146			t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
1147				strings.Join(tc.toolchains, "\n"), got)
1148		}
1149	}
1150}
1151
1152func TestActionConfigDeclaration(t *testing.T) {
1153	toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
1154	toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
1155
1156	toolchainNameNotInDict := getCToolchain("3", "cpBC", "compilerB",
1157		[]string{
1158			getActionConfig([]string{"action_name: 'A-B.C'", "config_name: 'A-B.C'"}),
1159		},
1160	)
1161	toolchainNameInDictA := getCToolchain("4", "cpuC", "compilerA",
1162		[]string{
1163			getActionConfig([]string{"action_name: 'c++-compile'", "config_name: 'c++-compile'"}),
1164		},
1165	)
1166	toolchainNameInDictB := getCToolchain("5", "cpuC", "compilerB",
1167		[]string{
1168			getActionConfig([]string{
1169				"action_name: 'c++-compile'",
1170				"config_name: 'c++-compile'",
1171				"tool {",
1172				"  tool_path: '/a/b/c'",
1173				"}",
1174			}),
1175		},
1176	)
1177	toolchainComplexActionConfig := getCToolchain("6", "cpuC", "compilerC",
1178		[]string{
1179			getActionConfig([]string{
1180				"action_name: 'action-complex'",
1181				"config_name: 'action-complex'",
1182				"enabled: true",
1183				"tool {",
1184				"  tool_path: '/a/b/c'",
1185				"  with_feature {",
1186				"    feature: 'a'",
1187				"    feature: 'b'",
1188				"    not_feature: 'c'",
1189				"    not_feature: 'd'",
1190				"  }",
1191				"  with_feature{",
1192				"    feature: 'e'",
1193				"  }",
1194				"  execution_requirement: 'a'",
1195				"}",
1196				"tool {",
1197				"  tool_path: ''",
1198				"}",
1199				"flag_set {",
1200				"  flag_group {",
1201				"    flag: 'a'",
1202				"    flag: '%b'",
1203				"    iterate_over: 'c'",
1204				"    expand_if_all_available: 'd'",
1205				"    expand_if_none_available: 'e'",
1206				"    expand_if_true: 'f'",
1207				"    expand_if_false: 'g'",
1208				"    expand_if_equal {",
1209				"      variable: 'var'",
1210				"      value: 'val'",
1211				"    }",
1212				"  }",
1213				"  flag_group {",
1214				"    flag_group {",
1215				"      flag: 'a'",
1216				"    }",
1217				"  }",
1218				"}",
1219				"flag_set {",
1220				"  with_feature {",
1221				"    feature: 'a'",
1222				"    feature: 'b'",
1223				"    not_feature: 'c'",
1224				"    not_feature: 'd'",
1225				"  }",
1226				"}",
1227				"env_set {",
1228				"  action: 'a'",
1229				"  env_entry {",
1230				"    key: 'k'",
1231				"    value: 'v'",
1232				"  }",
1233				"  with_feature {",
1234				"    feature: 'a'",
1235				"  }",
1236				"}",
1237				"requires {",
1238				"  feature: 'a'",
1239				"  feature: 'b'",
1240				"}",
1241				"implies: 'a'",
1242				"implies: 'b'",
1243			}),
1244		},
1245	)
1246
1247	testCases := []struct {
1248		toolchains   []string
1249		expectedText string
1250	}{
1251		{
1252			toolchains: []string{toolchainEmpty1, toolchainEmpty2},
1253			expectedText: `
1254    action_configs = []`},
1255		{
1256			toolchains: []string{toolchainEmpty1, toolchainNameNotInDict},
1257			expectedText: `
1258    a_b_c_action = action_config(action_name = "A-B.C")`},
1259		{
1260			toolchains: []string{toolchainNameInDictA, toolchainNameInDictB},
1261			expectedText: `
1262    if (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerB"):
1263        cpp_compile_action = action_config(
1264            action_name = ACTION_NAMES.cpp_compile,
1265            tools = [tool(path = "/a/b/c")],
1266        )
1267    elif (ctx.attr.cpu == "cpuC" and ctx.attr.compiler == "compilerA"):
1268        cpp_compile_action = action_config(action_name = ACTION_NAMES.cpp_compile)`},
1269		{
1270			toolchains: []string{toolchainComplexActionConfig},
1271			expectedText: `
1272    action_complex_action = action_config(
1273        action_name = "action-complex",
1274        enabled = True,
1275        flag_sets = [
1276            flag_set(
1277                flag_groups = [
1278                    flag_group(
1279                        flags = ["a", "%b"],
1280                        iterate_over = "c",
1281                        expand_if_available = "d",
1282                        expand_if_not_available = "e",
1283                        expand_if_true = "f",
1284                        expand_if_false = "g",
1285                        expand_if_equal = variable_with_value(name = "var", value = "val"),
1286                    ),
1287                    flag_group(flag_groups = [flag_group(flags = ["a"])]),
1288                ],
1289            ),
1290            flag_set(
1291                with_features = [
1292                    with_feature_set(
1293                        features = ["a", "b"],
1294                        not_features = ["c", "d"],
1295                    ),
1296                ],
1297            ),
1298        ],
1299        implies = ["a", "b"],
1300        tools = [
1301            tool(
1302                path = "/a/b/c",
1303                with_features = [
1304                    with_feature_set(
1305                        features = ["a", "b"],
1306                        not_features = ["c", "d"],
1307                    ),
1308                    with_feature_set(features = ["e"]),
1309                ],
1310                execution_requirements = ["a"],
1311            ),
1312            tool(path = "NOT_USED"),
1313        ],
1314    )`}}
1315
1316	for _, tc := range testCases {
1317		crosstool := makeCrosstool(tc.toolchains)
1318		got, err := Transform(crosstool)
1319		if err != nil {
1320			t.Fatalf("CROSSTOOL conversion failed: %v", err)
1321		}
1322		if !strings.Contains(got, tc.expectedText) {
1323			t.Errorf("Failed to correctly declare an action_config, expected to contain:\n%v\n",
1324				tc.expectedText)
1325			t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
1326				strings.Join(tc.toolchains, "\n"), got)
1327		}
1328	}
1329}
1330
1331func TestFeatureDeclaration(t *testing.T) {
1332	toolchainEmpty1 := getCToolchain("1", "cpuA", "compilerA", []string{})
1333	toolchainEmpty2 := getCToolchain("2", "cpuB", "compilerA", []string{})
1334
1335	toolchainSimpleFeatureA1 := getCToolchain("3", "cpuB", "compilerB",
1336		[]string{
1337			getFeature([]string{"name: 'Feature-c++.a'", "enabled: true"}),
1338		},
1339	)
1340	toolchainSimpleFeatureA2 := getCToolchain("4", "cpuC", "compilerA",
1341		[]string{
1342			getFeature([]string{"name: 'Feature-c++.a'"}),
1343		},
1344	)
1345	toolchainComplexFeature := getCToolchain("5", "cpuC", "compilerC",
1346		[]string{
1347			getFeature([]string{
1348				"name: 'complex-feature'",
1349				"enabled: true",
1350				"flag_set {",
1351				"  action: 'c++-compile'",    // in ACTION_NAMES
1352				"  action: 'something-else'", // not in ACTION_NAMES
1353				"  flag_group {",
1354				"    flag: 'a'",
1355				"    flag: '%b'",
1356				"    iterate_over: 'c'",
1357				"    expand_if_all_available: 'd'",
1358				"    expand_if_none_available: 'e'",
1359				"    expand_if_true: 'f'",
1360				"    expand_if_false: 'g'",
1361				"    expand_if_equal {",
1362				"      variable: 'var'",
1363				"      value: 'val'",
1364				"    }",
1365				"  }",
1366				"  flag_group {",
1367				"    flag_group {",
1368				"      flag: 'a'",
1369				"    }",
1370				"  }",
1371				"}",
1372				"flag_set {", // all_compile_actions
1373				"  action: 'c-compile'",
1374				"  action: 'c++-compile'",
1375				"  action: 'linkstamp-compile'",
1376				"  action: 'assemble'",
1377				"  action: 'preprocess-assemble'",
1378				"  action: 'c++-header-parsing'",
1379				"  action: 'c++-module-compile'",
1380				"  action: 'c++-module-codegen'",
1381				"  action: 'clif-match'",
1382				"  action: 'lto-backend'",
1383				"}",
1384				"flag_set {", // all_cpp_compile_actions
1385				"  action: 'c++-compile'",
1386				"  action: 'linkstamp-compile'",
1387				"  action: 'c++-header-parsing'",
1388				"  action: 'c++-module-compile'",
1389				"  action: 'c++-module-codegen'",
1390				"  action: 'clif-match'",
1391				"}",
1392				"flag_set {", // all_link_actions
1393				"  action: 'c++-link-executable'",
1394				"  action: 'c++-link-dynamic-library'",
1395				"  action: 'c++-link-nodeps-dynamic-library'",
1396				"}",
1397				"flag_set {", // all_cpp_compile_actions + all_link_actions
1398				"  action: 'c++-compile'",
1399				"  action: 'linkstamp-compile'",
1400				"  action: 'c++-header-parsing'",
1401				"  action: 'c++-module-compile'",
1402				"  action: 'c++-module-codegen'",
1403				"  action: 'clif-match'",
1404				"  action: 'c++-link-executable'",
1405				"  action: 'c++-link-dynamic-library'",
1406				"  action: 'c++-link-nodeps-dynamic-library'",
1407				"}",
1408				"flag_set {", // all_link_actions + something else
1409				"  action: 'c++-link-executable'",
1410				"  action: 'c++-link-dynamic-library'",
1411				"  action: 'c++-link-nodeps-dynamic-library'",
1412				"  action: 'some.unknown-c++.action'",
1413				"}",
1414				"env_set {",
1415				"  action: 'a'",
1416				"  env_entry {",
1417				"    key: 'k'",
1418				"    value: 'v'",
1419				"  }",
1420				"  with_feature {",
1421				"    feature: 'a'",
1422				"  }",
1423				"}",
1424				"env_set {",
1425				"  action: 'c-compile'",
1426				"}",
1427				"env_set {", // all_compile_actions
1428				"  action: 'c-compile'",
1429				"  action: 'c++-compile'",
1430				"  action: 'linkstamp-compile'",
1431				"  action: 'assemble'",
1432				"  action: 'preprocess-assemble'",
1433				"  action: 'c++-header-parsing'",
1434				"  action: 'c++-module-compile'",
1435				"  action: 'c++-module-codegen'",
1436				"  action: 'clif-match'",
1437				"  action: 'lto-backend'",
1438				"}",
1439				"requires {",
1440				"  feature: 'a'",
1441				"  feature: 'b'",
1442				"}",
1443				"implies: 'a'",
1444				"implies: 'b'",
1445				"provides: 'c'",
1446				"provides: 'd'",
1447			}),
1448		},
1449	)
1450
1451	testCases := []struct {
1452		toolchains   []string
1453		expectedText string
1454	}{
1455		{
1456			toolchains: []string{toolchainEmpty1, toolchainEmpty2},
1457			expectedText: `
1458    features = []
1459`},
1460		{
1461			toolchains: []string{toolchainEmpty1, toolchainSimpleFeatureA1},
1462			expectedText: `
1463    feature_cpp_a_feature = feature(name = "Feature-c++.a", enabled = True)`},
1464		{
1465			toolchains: []string{toolchainSimpleFeatureA1, toolchainSimpleFeatureA2},
1466			expectedText: `
1467    if (ctx.attr.cpu == "cpuC"):
1468        feature_cpp_a_feature = feature(name = "Feature-c++.a")
1469    elif (ctx.attr.cpu == "cpuB"):
1470        feature_cpp_a_feature = feature(name = "Feature-c++.a", enabled = True)`},
1471		{
1472			toolchains: []string{toolchainComplexFeature},
1473			expectedText: `
1474    complex_feature_feature = feature(
1475        name = "complex-feature",
1476        enabled = True,
1477        flag_sets = [
1478            flag_set(
1479                actions = [ACTION_NAMES.cpp_compile, "something-else"],
1480                flag_groups = [
1481                    flag_group(
1482                        flags = ["a", "%b"],
1483                        iterate_over = "c",
1484                        expand_if_available = "d",
1485                        expand_if_not_available = "e",
1486                        expand_if_true = "f",
1487                        expand_if_false = "g",
1488                        expand_if_equal = variable_with_value(name = "var", value = "val"),
1489                    ),
1490                    flag_group(flag_groups = [flag_group(flags = ["a"])]),
1491                ],
1492            ),
1493            flag_set(actions = all_compile_actions),
1494            flag_set(actions = all_cpp_compile_actions),
1495            flag_set(actions = all_link_actions),
1496            flag_set(
1497                actions = all_cpp_compile_actions +
1498                    all_link_actions,
1499            ),
1500            flag_set(
1501                actions = all_link_actions +
1502                    ["some.unknown-c++.action"],
1503            ),
1504        ],
1505        env_sets = [
1506            env_set(
1507                actions = ["a"],
1508                env_entries = [env_entry(key = "k", value = "v")],
1509                with_features = [with_feature_set(features = ["a"])],
1510            ),
1511            env_set(actions = [ACTION_NAMES.c_compile]),
1512            env_set(actions = all_compile_actions),
1513        ],
1514        requires = [feature_set(features = ["a", "b"])],
1515        implies = ["a", "b"],
1516        provides = ["c", "d"],
1517    )`}}
1518
1519	for _, tc := range testCases {
1520		crosstool := makeCrosstool(tc.toolchains)
1521		got, err := Transform(crosstool)
1522		if err != nil {
1523			t.Fatalf("CROSSTOOL conversion failed: %v", err)
1524		}
1525		if !strings.Contains(got, tc.expectedText) {
1526			t.Errorf("Failed to correctly declare a feature, expected to contain:\n%v\n",
1527				tc.expectedText)
1528			t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
1529				strings.Join(tc.toolchains, "\n"), got)
1530		}
1531	}
1532}
1533
1534func TestRule(t *testing.T) {
1535	simpleToolchain := getSimpleCToolchain("simple")
1536	expected := `load("@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
1537    "action_config",
1538    "artifact_name_pattern",
1539    "env_entry",
1540    "env_set",
1541    "feature",
1542    "feature_set",
1543    "flag_group",
1544    "flag_set",
1545    "make_variable",
1546    "tool",
1547    "tool_path",
1548    "variable_with_value",
1549    "with_feature_set",
1550)
1551load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
1552
1553def _impl(ctx):
1554    toolchain_identifier = "id-simple"
1555
1556    host_system_name = "host-simple"
1557
1558    target_system_name = "target-simple"
1559
1560    target_cpu = "cpu-simple"
1561
1562    target_libc = "libc-simple"
1563
1564    compiler = "compiler-simple"
1565
1566    abi_version = "version-simple"
1567
1568    abi_libc_version = "libc_version-simple"
1569
1570    cc_target_os = None
1571
1572    builtin_sysroot = None
1573
1574    all_compile_actions = [
1575        ACTION_NAMES.c_compile,
1576        ACTION_NAMES.cpp_compile,
1577        ACTION_NAMES.linkstamp_compile,
1578        ACTION_NAMES.assemble,
1579        ACTION_NAMES.preprocess_assemble,
1580        ACTION_NAMES.cpp_header_parsing,
1581        ACTION_NAMES.cpp_module_compile,
1582        ACTION_NAMES.cpp_module_codegen,
1583        ACTION_NAMES.clif_match,
1584        ACTION_NAMES.lto_backend,
1585    ]
1586
1587    all_cpp_compile_actions = [
1588        ACTION_NAMES.cpp_compile,
1589        ACTION_NAMES.linkstamp_compile,
1590        ACTION_NAMES.cpp_header_parsing,
1591        ACTION_NAMES.cpp_module_compile,
1592        ACTION_NAMES.cpp_module_codegen,
1593        ACTION_NAMES.clif_match,
1594    ]
1595
1596    preprocessor_compile_actions = [
1597        ACTION_NAMES.c_compile,
1598        ACTION_NAMES.cpp_compile,
1599        ACTION_NAMES.linkstamp_compile,
1600        ACTION_NAMES.preprocess_assemble,
1601        ACTION_NAMES.cpp_header_parsing,
1602        ACTION_NAMES.cpp_module_compile,
1603        ACTION_NAMES.clif_match,
1604    ]
1605
1606    codegen_compile_actions = [
1607        ACTION_NAMES.c_compile,
1608        ACTION_NAMES.cpp_compile,
1609        ACTION_NAMES.linkstamp_compile,
1610        ACTION_NAMES.assemble,
1611        ACTION_NAMES.preprocess_assemble,
1612        ACTION_NAMES.cpp_module_codegen,
1613        ACTION_NAMES.lto_backend,
1614    ]
1615
1616    all_link_actions = [
1617        ACTION_NAMES.cpp_link_executable,
1618        ACTION_NAMES.cpp_link_dynamic_library,
1619        ACTION_NAMES.cpp_link_nodeps_dynamic_library,
1620    ]
1621
1622    action_configs = []
1623
1624    features = []
1625
1626    cxx_builtin_include_directories = []
1627
1628    artifact_name_patterns = []
1629
1630    make_variables = []
1631
1632    tool_paths = []
1633
1634
1635    out = ctx.actions.declare_file(ctx.label.name)
1636    ctx.actions.write(out, "Fake executable")
1637    return [
1638        cc_common.create_cc_toolchain_config_info(
1639            ctx = ctx,
1640            features = features,
1641            action_configs = action_configs,
1642            artifact_name_patterns = artifact_name_patterns,
1643            cxx_builtin_include_directories = cxx_builtin_include_directories,
1644            toolchain_identifier = toolchain_identifier,
1645            host_system_name = host_system_name,
1646            target_system_name = target_system_name,
1647            target_cpu = target_cpu,
1648            target_libc = target_libc,
1649            compiler = compiler,
1650            abi_version = abi_version,
1651            abi_libc_version = abi_libc_version,
1652            tool_paths = tool_paths,
1653            make_variables = make_variables,
1654            builtin_sysroot = builtin_sysroot,
1655            cc_target_os = cc_target_os
1656        ),
1657        DefaultInfo(
1658            executable = out,
1659        ),
1660    ]
1661cc_toolchain_config =  rule(
1662    implementation = _impl,
1663    attrs = {
1664        "cpu": attr.string(mandatory=True, values=["cpu-simple"]),
1665    },
1666    provides = [CcToolchainConfigInfo],
1667    executable = True,
1668)
1669`
1670	crosstool := makeCrosstool([]string{simpleToolchain})
1671	got, err := Transform(crosstool)
1672	if err != nil {
1673		t.Fatalf("CROSSTOOL conversion failed: %v", err)
1674	}
1675	if got != expected {
1676		t.Fatalf("Expected:\n%v\nGot:\n%v\nTested CROSSTOOL:\n%v",
1677			expected, got, simpleToolchain)
1678	}
1679}
1680
1681func TestAllowedCompilerValues(t *testing.T) {
1682	toolchainAA := getCToolchain("1", "cpuA", "compilerA", []string{})
1683	toolchainBA := getCToolchain("2", "cpuB", "compilerA", []string{})
1684	toolchainBB := getCToolchain("3", "cpuB", "compilerB", []string{})
1685	toolchainCC := getCToolchain("4", "cpuC", "compilerC", []string{})
1686
1687	testCases := []struct {
1688		toolchains   []string
1689		expectedText string
1690	}{
1691		{
1692			toolchains: []string{toolchainAA, toolchainBA},
1693			expectedText: `
1694cc_toolchain_config =  rule(
1695    implementation = _impl,
1696    attrs = {
1697        "cpu": attr.string(mandatory=True, values=["cpuA", "cpuB"]),
1698    },
1699    provides = [CcToolchainConfigInfo],
1700    executable = True,
1701)
1702`},
1703		{
1704			toolchains: []string{toolchainBA, toolchainBB},
1705			expectedText: `
1706cc_toolchain_config =  rule(
1707    implementation = _impl,
1708    attrs = {
1709        "cpu": attr.string(mandatory=True, values=["cpuB"]),
1710        "compiler": attr.string(mandatory=True, values=["compilerA", "compilerB"]),
1711    },
1712    provides = [CcToolchainConfigInfo],
1713    executable = True,
1714)
1715`},
1716		{
1717			toolchains: []string{toolchainAA, toolchainBA, toolchainBB},
1718			expectedText: `
1719cc_toolchain_config =  rule(
1720    implementation = _impl,
1721    attrs = {
1722        "cpu": attr.string(mandatory=True, values=["cpuA", "cpuB"]),
1723        "compiler": attr.string(mandatory=True, values=["compilerA", "compilerB"]),
1724    },
1725    provides = [CcToolchainConfigInfo],
1726    executable = True,
1727)
1728`},
1729		{
1730			toolchains: []string{toolchainAA, toolchainBA, toolchainBB, toolchainCC},
1731			expectedText: `
1732cc_toolchain_config =  rule(
1733    implementation = _impl,
1734    attrs = {
1735        "cpu": attr.string(mandatory=True, values=["cpuA", "cpuB", "cpuC"]),
1736        "compiler": attr.string(mandatory=True, values=["compilerA", "compilerB", "compilerC"]),
1737    },
1738    provides = [CcToolchainConfigInfo],
1739    executable = True,
1740)
1741`}}
1742
1743	for _, tc := range testCases {
1744		crosstool := makeCrosstool(tc.toolchains)
1745		got, err := Transform(crosstool)
1746		if err != nil {
1747			t.Fatalf("CROSSTOOL conversion failed: %v", err)
1748		}
1749		if !strings.Contains(got, tc.expectedText) {
1750			t.Errorf("Failed to correctly declare the rule, expected to contain:\n%v\n",
1751				tc.expectedText)
1752			t.Fatalf("Tested CROSSTOOL:\n%v\n\nGenerated rule:\n%v\n",
1753				strings.Join(tc.toolchains, "\n"), got)
1754		}
1755	}
1756}
1757