| // Copyright 2024 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package bazel2gn_test |
| |
| import ( |
| "fmt" |
| "os" |
| "path/filepath" |
| "strings" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "go.fuchsia.dev/fuchsia/build/tools/bazel2gn" |
| "go.starlark.net/syntax" |
| ) |
| |
| // toSyntaxFile is a test helper that parses the input string (content of a |
| // BUILD.bazel file) to a *syntax.File. |
| func toSyntaxFile(t *testing.T, s string) *syntax.File { |
| t.Helper() |
| |
| p := filepath.Join(t.TempDir(), "BUILD.bazel.test") |
| if err := os.WriteFile(p, []byte(s), 0600); err != nil { |
| t.Fatalf("Failed to write test Bazel file: %v", err) |
| } |
| |
| f, err := bazel2gn.Parse(p) |
| if err != nil { |
| t.Fatalf("Failed to parse test Bazel build file: %v, file content:\n%s", err, s) |
| } |
| return f |
| } |
| |
| // bazelToGN is a test helper that converts all statements in a *syntax.File to |
| // content of a BUILD.gn. |
| func bazelToGN(f *syntax.File) (string, error) { |
| var gotLines []string |
| for _, stmt := range f.Stmts { |
| lines, err := bazel2gn.StmtToGN(stmt) |
| if err != nil { |
| return "", fmt.Errorf("converting Bazel statement to GN: %v", err) |
| } |
| gotLines = append(gotLines, lines...) |
| } |
| return strings.Join(gotLines, "\n"), nil |
| } |
| |
| func TestStmtToGN(t *testing.T) { |
| for _, tc := range []struct { |
| name string |
| bazel string |
| wantGN string |
| }{ |
| { |
| name: "Simple Go targets", |
| bazel: `load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test") |
| |
| go_library( |
| name = "bazel2gn", |
| srcs = [ |
| "bazel2gn.go", |
| ], |
| importpath = "go.fuchsia.dev/fuchsia/build/tools/bazel2gn", |
| deps = [ |
| "//third_party/golibs:go.starlark.net/syntax", |
| ], |
| ) |
| |
| go_binary( |
| name = "cmd", |
| srcs = [ |
| "cmd/main.go", |
| ], |
| deps = [ |
| ":bazel2gn", |
| "//third_party/golibs:go.starlark.net/starlark", |
| "//third_party/golibs:go.starlark.net/syntax", |
| ], |
| ) |
| |
| go_test( |
| name = "bazel2gn_tests", |
| embed = [ ":bazel2gn" ], |
| srcs = [ |
| "bazel2gn_test.go", |
| ], |
| deps = [ |
| "//third_party/golibs:github.com/google/go-cmp/cmp", |
| "//third_party/golibs:go.starlark.net/starlark", |
| "//third_party/golibs:go.starlark.net/syntax", |
| ], |
| )`, |
| wantGN: `go_library("bazel2gn") { |
| sources = [ |
| "bazel2gn.go", |
| ] |
| importpath = "go.fuchsia.dev/fuchsia/build/tools/bazel2gn" |
| deps = [ |
| "//third_party/golibs:go.starlark.net/syntax", |
| ] |
| } |
| go_binary("cmd") { |
| sources = [ |
| "cmd/main.go", |
| ] |
| deps = [ |
| ":bazel2gn", |
| "//third_party/golibs:go.starlark.net/starlark", |
| "//third_party/golibs:go.starlark.net/syntax", |
| ] |
| } |
| go_test("bazel2gn_tests") { |
| embed = [ |
| ":bazel2gn", |
| ] |
| sources = [ |
| "bazel2gn_test.go", |
| ] |
| deps = [ |
| "//third_party/golibs:github.com/google/go-cmp/cmp", |
| "//third_party/golibs:go.starlark.net/starlark", |
| "//third_party/golibs:go.starlark.net/syntax", |
| ] |
| }`, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| f := toSyntaxFile(t, tc.bazel) |
| gotGN, err := bazelToGN(f) |
| if err != nil { |
| t.Fatalf("Unexpected failure converting Bazel build targets: %v", err) |
| } |
| if diff := cmp.Diff(gotGN, tc.wantGN); diff != "" { |
| t.Errorf("Diff found after GN conversion (-got +want):\n%s\nBazel source:\n%s", diff, tc.bazel) |
| } |
| }) |
| } |
| } |
| |
| func TestTargetCompatibleWith(t *testing.T) { |
| for _, tc := range []struct { |
| name string |
| bazel string |
| wantGN string |
| }{ |
| { |
| name: "success", |
| bazel: ` |
| load("@platforms//host:constraints.bzl", "HOST_CONSTRAINTS") |
| |
| go_binary( |
| name = "host_tool", |
| srcs = [ |
| "main.go", |
| ], |
| target_compatible_with = HOST_CONSTRAINTS, |
| )`, |
| wantGN: `if (is_host) { |
| go_binary("host_tool") { |
| sources = [ |
| "main.go", |
| ] |
| } |
| }`, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| f := toSyntaxFile(t, tc.bazel) |
| gotGN, err := bazelToGN(f) |
| if err != nil { |
| t.Fatalf("Unexpected failure converting Bazel build targets: %v", err) |
| } |
| if diff := cmp.Diff(gotGN, tc.wantGN); diff != "" { |
| t.Errorf("Diff found after GN conversion (-got +want):\n%s\nBazel source:\n%s", diff, tc.bazel) |
| } |
| }) |
| } |
| } |
| |
| func TestTargetCompatibleWithErrors(t *testing.T) { |
| for _, tc := range []struct { |
| name string |
| bazel string |
| }{ |
| { |
| name: "unexpected target_compatible_with variable", |
| bazel: ` |
| go_binary( |
| name = "host_tool", |
| srcs = [ |
| "main.go", |
| ], |
| target_compatible_with = UNSUPPORTED_CONSTRAINTS, |
| )`, |
| }, |
| { |
| name: "list of constraints not supported yet", |
| bazel: ` |
| go_binary( |
| name = "host_tool", |
| srcs = [ |
| "main.go", |
| ], |
| target_compatible_with = [ |
| "@platforms//os:linux", |
| "@platforms//cpu:x86_64", |
| ], |
| )`, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| f := toSyntaxFile(t, tc.bazel) |
| _, err := bazelToGN(f) |
| if err == nil { |
| t.Fatal("Expecting failure converting Bazel targets, got nil") |
| } |
| }) |
| } |
| } |
| |
| func TestVisibilityConversion(t *testing.T) { |
| for _, tc := range []struct { |
| name string |
| bazel string |
| wantGN string |
| }{ |
| { |
| name: "public", |
| bazel: `go_library( |
| name = "test", |
| visibility = [ |
| "//visibility:public", |
| ], |
| )`, |
| wantGN: `go_library("test") { |
| visibility = [ |
| "*", |
| ] |
| }`, |
| }, |
| { |
| name: "private", |
| bazel: `go_library( |
| name = "test", |
| visibility = [ |
| "//visibility:private", |
| ], |
| )`, |
| wantGN: `go_library("test") { |
| visibility = [ |
| ":*", |
| ] |
| }`, |
| }, |
| { |
| name: "pkg and subpackages", |
| bazel: `go_library( |
| name = "test", |
| visibility = [ |
| "//path/to/foo:__pkg__", |
| "//path/to/bar:__subpackages__", |
| ], |
| )`, |
| wantGN: `go_library("test") { |
| visibility = [ |
| "//path/to/foo:*", |
| "//path/to/bar/*", |
| ] |
| }`, |
| }, |
| { |
| name: "package group is unchanged", |
| bazel: `go_library( |
| name = "test", |
| visibility = [ |
| "//path/to/foo:__pkg__", |
| "//path/to/bar:bar", |
| ], |
| )`, |
| wantGN: `go_library("test") { |
| visibility = [ |
| "//path/to/foo:*", |
| "//path/to/bar:bar", |
| ] |
| }`, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| f := toSyntaxFile(t, tc.bazel) |
| gotGN, err := bazelToGN(f) |
| if err != nil { |
| t.Fatalf("Unexpected failure converting Bazel build targets: %v", err) |
| } |
| if diff := cmp.Diff(gotGN, tc.wantGN); diff != "" { |
| t.Errorf("Diff found after GN conversion (-got +want):\n%s\nBazel source:\n%s", diff, tc.bazel) |
| } |
| }) |
| } |
| } |
| |
| func TestDepsConversion(t *testing.T) { |
| for _, tc := range []struct { |
| name string |
| bazel string |
| wantGN string |
| }{ |
| { |
| name: "rust third-party", |
| bazel: `go_library( |
| name = "test", |
| deps = [ |
| "//third_party/rust_crates/vendor:foo", |
| "//third_party/rust_crates/ask2patch:bar", |
| "//third_party/rust_crates/forks/baz-v0.4.2:baz", |
| "//path/to/dep", |
| ], |
| )`, |
| wantGN: `go_library("test") { |
| deps = [ |
| "//third_party/rust_crates:foo", |
| "//third_party/rust_crates:bar", |
| "//third_party/rust_crates:baz", |
| "//path/to/dep", |
| ] |
| }`, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| f := toSyntaxFile(t, tc.bazel) |
| gotGN, err := bazelToGN(f) |
| if err != nil { |
| t.Fatalf("Unexpected failure converting Bazel build targets: %v", err) |
| } |
| if diff := cmp.Diff(gotGN, tc.wantGN); diff != "" { |
| t.Errorf("Diff found after GN conversion (-got +want):\n%s\nBazel source:\n%s", diff, tc.bazel) |
| } |
| }) |
| } |
| } |
| |
| func TestIDKConversion(t *testing.T) { |
| for _, tc := range []struct { |
| name string |
| bazel string |
| wantGN string |
| }{ |
| { |
| name: "IDK C++ source library", |
| bazel: `load("//build/bazel/bazel_idk:defs.bzl", "idk_cc_source_library") |
| |
| idk_cc_source_library( |
| name = "foo", |
| api_area = "Media", |
| category = "partner", |
| idk_name = "foobar", |
| stable = True, |
| hdrs = ["include/lib/foobar/foobar_defs.h"], |
| hdrs_for_internal_use = ["path/to/internal.h"], |
| public_configs = [":foo_include"], |
| deps = ["//path/to/public_deps"], |
| implementation_deps = ["//path/to/implementation_deps"], |
| visibility = [ "//visibility:public" ], |
| ) |
| `, |
| wantGN: `sdk_source_set("foo") { |
| sdk_area = "Media" |
| category = "partner" |
| sdk_name = "foobar" |
| stable = true |
| public = [ |
| "include/lib/foobar/foobar_defs.h", |
| ] |
| sdk_headers_for_internal_use = [ |
| "path/to/internal.h", |
| ] |
| public += sdk_headers_for_internal_use |
| public_configs = [ |
| ":foo_include", |
| ] |
| public_deps = [ |
| "//path/to/public_deps", |
| ] |
| deps = [ |
| "//path/to/implementation_deps", |
| ] |
| visibility = [ |
| "*", |
| ] |
| }`, |
| }, |
| { |
| name: "IDK C++ source library for Zircon library", |
| // This test case should be identical to the one for |
| // `idk_cc_source_library()` except for `sdk_publishable` in the |
| // expectation and `sdk` the input and expectation. |
| bazel: `load("//build/bazel/bazel_idk:defs.bzl", "idk_cc_source_library_zx") |
| |
| idk_cc_source_library_zx( |
| name = "foo", |
| api_area = "Media", |
| category = "partner", |
| idk_name = "foobar", |
| sdk = "source", |
| stable = True, |
| hdrs = ["include/lib/foobar/foobar_defs.h"], |
| hdrs_for_internal_use = ["path/to/internal.h"], |
| public_configs = [":foo_include"], |
| deps = ["//path/to/public_deps"], |
| implementation_deps = ["//path/to/implementation_deps"], |
| visibility = [ "//visibility:public" ], |
| ) |
| `, |
| wantGN: `zx_library("foo") { |
| sdk_area = "Media" |
| sdk_publishable = "partner" |
| sdk_name = "foobar" |
| sdk = "source" |
| stable = true |
| public = [ |
| "include/lib/foobar/foobar_defs.h", |
| ] |
| sdk_headers_for_internal_use = [ |
| "path/to/internal.h", |
| ] |
| public += sdk_headers_for_internal_use |
| public_configs = [ |
| ":foo_include", |
| ] |
| public_deps = [ |
| "//path/to/public_deps", |
| ] |
| deps = [ |
| "//path/to/implementation_deps", |
| ] |
| visibility = [ |
| "*", |
| ] |
| }`, |
| }, |
| { |
| name: "fuchsia_deps", |
| bazel: `idk_cc_source_library( |
| name = "foo", |
| public_deps = ["//sdk/lib/stdcompat"], |
| fuchsia_deps = [ |
| "//zircon/system/ulib/zx", |
| ], |
| ) |
| `, |
| wantGN: `sdk_source_set("foo") { |
| public_deps = [ |
| "//sdk/lib/stdcompat", |
| ] |
| if (is_fuchsia) { |
| public_deps += [ |
| "//zircon/system/ulib/zx", |
| ] |
| } |
| }`, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| f := toSyntaxFile(t, tc.bazel) |
| gotGN, err := bazelToGN(f) |
| if err != nil { |
| t.Fatalf("Unexpected failure converting Bazel build targets: %v", err) |
| } |
| if diff := cmp.Diff(gotGN, tc.wantGN); diff != "" { |
| t.Errorf("Diff found after GN conversion (-got +want):\n%s\nBazel source:\n%s", diff, tc.bazel) |
| } |
| }) |
| } |
| } |
| |
| func TestCCConversion(t *testing.T) { |
| for _, tc := range []struct { |
| name string |
| bazel string |
| wantGN string |
| }{ |
| { |
| name: "Simple C++ targets", |
| bazel: `cc_library( |
| name = "foo", |
| srcs = [ |
| "path/to/bar.cc", |
| "path/to/bar.h", |
| "path/to/baz.cc", |
| "path/to/foo.cc", |
| "yet/another/path/to/foo.cc", |
| ], |
| hdrs = [ |
| "path/to/baz.h", |
| "path/to/foo.h", |
| ], |
| deps = [ |
| "//path/to:foo", |
| "//yet/another/path/to:bar", |
| ], |
| implementation_deps = [ |
| "//path/to:bar", |
| ], |
| copts = [ |
| "-Wno-implicit-fallthrough", |
| ], |
| visibility = [ |
| ":__pkg__", |
| "//path/to/dir:__subpackages__", |
| ], |
| ) |
| `, |
| wantGN: `source_set("foo") { |
| sources = [ |
| "path/to/bar.cc", |
| "path/to/bar.h", |
| "path/to/baz.cc", |
| "path/to/foo.cc", |
| "yet/another/path/to/foo.cc", |
| ] |
| public = [ |
| "path/to/baz.h", |
| "path/to/foo.h", |
| ] |
| public_deps = [ |
| "//path/to:foo", |
| "//yet/another/path/to:bar", |
| ] |
| deps = [ |
| "//path/to:bar", |
| ] |
| configs += [ |
| "//build/config:Wno-implicit-fallthrough", |
| ] |
| visibility = [ |
| ":*", |
| "//path/to/dir/*", |
| ] |
| }`, |
| }, |
| { |
| name: "select in copts to configs", |
| bazel: `cc_library( |
| name = "foo", |
| copts = select({ |
| "@platforms//os:fuchsia": [ "-Wno-implicit-fallthrough" ], |
| "//conditions:default": [], |
| }), |
| ) |
| `, |
| wantGN: `source_set("foo") { |
| if (is_fuchsia) { |
| configs += [ |
| "//build/config:Wno-implicit-fallthrough", |
| ] |
| } else { |
| configs += [ |
| ] |
| } |
| }`, |
| }, |
| { |
| name: "configs append by default", |
| bazel: `cc_library( |
| name = "configs_append", |
| copts = [], |
| ) |
| `, |
| wantGN: `source_set("configs_append") { |
| configs += [ |
| ] |
| }`, |
| }, |
| { |
| name: "explicit config clearing with annotation", |
| bazel: `cc_library( |
| name = "empty_configs", |
| copts = [], # bazel2gn: clear |
| ) |
| `, |
| wantGN: `source_set("empty_configs") { |
| configs = [ |
| ] |
| }`, |
| }, |
| { |
| name: "irrelevant comments are ignored", |
| bazel: `cc_library( |
| name = "empty_configs", |
| copts = [], # this comment does NOT affect bazel2gn |
| ) |
| `, |
| wantGN: `source_set("empty_configs") { |
| configs += [ |
| ] |
| }`, |
| }, |
| { |
| name: "comments above are ignored", |
| bazel: `cc_library( |
| name = "comment_above", |
| # bazel2gn: clear |
| copts = [], |
| ) |
| `, |
| wantGN: `source_set("comment_above") { |
| configs += [ |
| ] |
| }`, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| f := toSyntaxFile(t, tc.bazel) |
| gotGN, err := bazelToGN(f) |
| if err != nil { |
| t.Fatalf("Unexpected failure converting Bazel build targets: %v", err) |
| } |
| if diff := cmp.Diff(gotGN, tc.wantGN); diff != "" { |
| t.Errorf("Diff found after GN conversion (-got +want):\n%s\nBazel source:\n%s", diff, tc.bazel) |
| } |
| }) |
| } |
| } |