1// Copyright 2021 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package bp2build
16
17import (
18	"android/soong/android"
19	"android/soong/cc"
20	"fmt"
21	"strings"
22	"testing"
23)
24
25func TestCcObjectBp2Build(t *testing.T) {
26	testCases := []struct {
27		description                        string
28		moduleTypeUnderTest                string
29		moduleTypeUnderTestFactory         android.ModuleFactory
30		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
31		blueprint                          string
32		expectedBazelTargets               []string
33		filesystem                         map[string]string
34	}{
35		{
36			description:                        "simple cc_object generates cc_object with include header dep",
37			moduleTypeUnderTest:                "cc_object",
38			moduleTypeUnderTestFactory:         cc.ObjectFactory,
39			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
40			filesystem: map[string]string{
41				"a/b/foo.h":     "",
42				"a/b/bar.h":     "",
43				"a/b/exclude.c": "",
44				"a/b/c.c":       "",
45			},
46			blueprint: `cc_object {
47    name: "foo",
48    local_include_dirs: ["include"],
49    cflags: [
50        "-Wno-gcc-compat",
51        "-Wall",
52        "-Werror",
53    ],
54    srcs: [
55        "a/b/*.c"
56    ],
57    exclude_srcs: ["a/b/exclude.c"],
58}
59`,
60			expectedBazelTargets: []string{`cc_object(
61    name = "foo",
62    copts = [
63        "-fno-addrsig",
64        "-Wno-gcc-compat",
65        "-Wall",
66        "-Werror",
67        "-Iinclude",
68        "-I.",
69    ],
70    srcs = ["a/b/c.c"],
71)`,
72			},
73		},
74		{
75			description:                        "simple cc_object with defaults",
76			moduleTypeUnderTest:                "cc_object",
77			moduleTypeUnderTestFactory:         cc.ObjectFactory,
78			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
79			blueprint: `cc_object {
80    name: "foo",
81    local_include_dirs: ["include"],
82    srcs: [
83        "a/b/*.h",
84        "a/b/c.c"
85    ],
86
87    defaults: ["foo_defaults"],
88}
89
90cc_defaults {
91    name: "foo_defaults",
92    defaults: ["foo_bar_defaults"],
93}
94
95cc_defaults {
96    name: "foo_bar_defaults",
97    cflags: [
98        "-Wno-gcc-compat",
99        "-Wall",
100        "-Werror",
101    ],
102}
103`,
104			expectedBazelTargets: []string{`cc_object(
105    name = "foo",
106    copts = [
107        "-Wno-gcc-compat",
108        "-Wall",
109        "-Werror",
110        "-fno-addrsig",
111        "-Iinclude",
112        "-I.",
113    ],
114    srcs = ["a/b/c.c"],
115)`,
116			},
117		},
118		{
119			description:                        "cc_object with cc_object deps in objs props",
120			moduleTypeUnderTest:                "cc_object",
121			moduleTypeUnderTestFactory:         cc.ObjectFactory,
122			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
123			filesystem: map[string]string{
124				"a/b/c.c": "",
125				"x/y/z.c": "",
126			},
127			blueprint: `cc_object {
128    name: "foo",
129    srcs: ["a/b/c.c"],
130    objs: ["bar"],
131}
132
133cc_object {
134    name: "bar",
135    srcs: ["x/y/z.c"],
136}
137`,
138			expectedBazelTargets: []string{`cc_object(
139    name = "bar",
140    copts = [
141        "-fno-addrsig",
142        "-I.",
143    ],
144    srcs = ["x/y/z.c"],
145)`, `cc_object(
146    name = "foo",
147    copts = [
148        "-fno-addrsig",
149        "-I.",
150    ],
151    deps = [":bar"],
152    srcs = ["a/b/c.c"],
153)`,
154			},
155		},
156		{
157			description:                        "cc_object with include_build_dir: false",
158			moduleTypeUnderTest:                "cc_object",
159			moduleTypeUnderTestFactory:         cc.ObjectFactory,
160			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
161			filesystem: map[string]string{
162				"a/b/c.c": "",
163				"x/y/z.c": "",
164			},
165			blueprint: `cc_object {
166    name: "foo",
167    srcs: ["a/b/c.c"],
168    include_build_directory: false,
169}
170`,
171			expectedBazelTargets: []string{`cc_object(
172    name = "foo",
173    copts = ["-fno-addrsig"],
174    srcs = ["a/b/c.c"],
175)`,
176			},
177		},
178		{
179			description:                        "cc_object with product variable",
180			moduleTypeUnderTest:                "cc_object",
181			moduleTypeUnderTestFactory:         cc.ObjectFactory,
182			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
183			blueprint: `cc_object {
184    name: "foo",
185    include_build_directory: false,
186    product_variables: {
187        platform_sdk_version: {
188            asflags: ["-DPLATFORM_SDK_VERSION=%d"],
189        },
190    },
191}
192`,
193			expectedBazelTargets: []string{`cc_object(
194    name = "foo",
195    asflags = ["-DPLATFORM_SDK_VERSION={Platform_sdk_version}"],
196    copts = ["-fno-addrsig"],
197)`,
198			},
199		},
200	}
201
202	dir := "."
203	for _, testCase := range testCases {
204		filesystem := make(map[string][]byte)
205		toParse := []string{
206			"Android.bp",
207		}
208		for f, content := range testCase.filesystem {
209			if strings.HasSuffix(f, "Android.bp") {
210				toParse = append(toParse, f)
211			}
212			filesystem[f] = []byte(content)
213		}
214		config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem)
215		ctx := android.NewTestContext(config)
216		// Always register cc_defaults module factory
217		ctx.RegisterModuleType("cc_defaults", func() android.Module { return cc.DefaultsFactory() })
218
219		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
220		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
221		ctx.RegisterBp2BuildConfig(bp2buildConfig)
222		ctx.RegisterForBazelConversion()
223
224		_, errs := ctx.ParseFileList(dir, toParse)
225		if Errored(t, testCase.description, errs) {
226			continue
227		}
228		_, errs = ctx.ResolveDependencies(config)
229		if Errored(t, testCase.description, errs) {
230			continue
231		}
232
233		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
234		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
235		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
236			fmt.Println(bazelTargets)
237			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
238		} else {
239			for i, target := range bazelTargets {
240				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
241					t.Errorf(
242						"%s: Expected generated Bazel target to be '%s', got '%s'",
243						testCase.description,
244						w,
245						g,
246					)
247				}
248			}
249		}
250	}
251}
252
253func TestCcObjectConfigurableAttributesBp2Build(t *testing.T) {
254	testCases := []struct {
255		description                        string
256		moduleTypeUnderTest                string
257		moduleTypeUnderTestFactory         android.ModuleFactory
258		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
259		blueprint                          string
260		expectedBazelTargets               []string
261		filesystem                         map[string]string
262	}{
263		{
264			description:                        "cc_object setting cflags for one arch",
265			moduleTypeUnderTest:                "cc_object",
266			moduleTypeUnderTestFactory:         cc.ObjectFactory,
267			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
268			blueprint: `cc_object {
269    name: "foo",
270    srcs: ["a.cpp"],
271    arch: {
272        x86: {
273            cflags: ["-fPIC"], // string list
274        },
275        arm: {
276            srcs: ["arch/arm/file.S"], // label list
277        },
278    },
279}
280`,
281			expectedBazelTargets: []string{
282				`cc_object(
283    name = "foo",
284    copts = [
285        "-fno-addrsig",
286        "-I.",
287    ] + select({
288        "//build/bazel/platforms/arch:x86": ["-fPIC"],
289        "//conditions:default": [],
290    }),
291    srcs = ["a.cpp"] + select({
292        "//build/bazel/platforms/arch:arm": ["arch/arm/file.S"],
293        "//conditions:default": [],
294    }),
295)`,
296			},
297		},
298		{
299			description:                        "cc_object setting cflags for 4 architectures",
300			moduleTypeUnderTest:                "cc_object",
301			moduleTypeUnderTestFactory:         cc.ObjectFactory,
302			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
303			blueprint: `cc_object {
304    name: "foo",
305    srcs: ["base.cpp"],
306    arch: {
307        x86: {
308            srcs: ["x86.cpp"],
309            cflags: ["-fPIC"],
310        },
311        x86_64: {
312            srcs: ["x86_64.cpp"],
313            cflags: ["-fPIC"],
314        },
315        arm: {
316            srcs: ["arm.cpp"],
317            cflags: ["-Wall"],
318        },
319        arm64: {
320            srcs: ["arm64.cpp"],
321            cflags: ["-Wall"],
322        },
323    },
324}
325`,
326			expectedBazelTargets: []string{
327				`cc_object(
328    name = "foo",
329    copts = [
330        "-fno-addrsig",
331        "-I.",
332    ] + select({
333        "//build/bazel/platforms/arch:arm": ["-Wall"],
334        "//build/bazel/platforms/arch:arm64": ["-Wall"],
335        "//build/bazel/platforms/arch:x86": ["-fPIC"],
336        "//build/bazel/platforms/arch:x86_64": ["-fPIC"],
337        "//conditions:default": [],
338    }),
339    srcs = ["base.cpp"] + select({
340        "//build/bazel/platforms/arch:arm": ["arm.cpp"],
341        "//build/bazel/platforms/arch:arm64": ["arm64.cpp"],
342        "//build/bazel/platforms/arch:x86": ["x86.cpp"],
343        "//build/bazel/platforms/arch:x86_64": ["x86_64.cpp"],
344        "//conditions:default": [],
345    }),
346)`,
347			},
348		},
349		{
350			description:                        "cc_object setting cflags for multiple OSes",
351			moduleTypeUnderTest:                "cc_object",
352			moduleTypeUnderTestFactory:         cc.ObjectFactory,
353			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
354			blueprint: `cc_object {
355    name: "foo",
356    srcs: ["base.cpp"],
357    target: {
358        android: {
359            cflags: ["-fPIC"],
360        },
361        windows: {
362            cflags: ["-fPIC"],
363        },
364        darwin: {
365            cflags: ["-Wall"],
366        },
367    },
368}
369`,
370			expectedBazelTargets: []string{
371				`cc_object(
372    name = "foo",
373    copts = [
374        "-fno-addrsig",
375        "-I.",
376    ] + select({
377        "//build/bazel/platforms/os:android": ["-fPIC"],
378        "//build/bazel/platforms/os:darwin": ["-Wall"],
379        "//build/bazel/platforms/os:windows": ["-fPIC"],
380        "//conditions:default": [],
381    }),
382    srcs = ["base.cpp"],
383)`,
384			},
385		},
386	}
387
388	dir := "."
389	for _, testCase := range testCases {
390		filesystem := make(map[string][]byte)
391		toParse := []string{
392			"Android.bp",
393		}
394		config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem)
395		ctx := android.NewTestContext(config)
396		// Always register cc_defaults module factory
397		ctx.RegisterModuleType("cc_defaults", func() android.Module { return cc.DefaultsFactory() })
398
399		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
400		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
401		ctx.RegisterBp2BuildConfig(bp2buildConfig)
402		ctx.RegisterForBazelConversion()
403
404		_, errs := ctx.ParseFileList(dir, toParse)
405		if Errored(t, testCase.description, errs) {
406			continue
407		}
408		_, errs = ctx.ResolveDependencies(config)
409		if Errored(t, testCase.description, errs) {
410			continue
411		}
412
413		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
414		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
415		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
416			fmt.Println(bazelTargets)
417			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
418		} else {
419			for i, target := range bazelTargets {
420				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
421					t.Errorf(
422						"%s: Expected generated Bazel target to be '%s', got '%s'",
423						testCase.description,
424						w,
425						g,
426					)
427				}
428			}
429		}
430	}
431}
432