summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-03-08 04:22:46 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-03-08 04:22:46 +0000
commit326005d3816cb37dfe418fc5be89d433e529c0c1 (patch)
tree3f1f16ec6e8cf77a12f3fec5a2f5812422d63fce
parent5bc39608281f02f968b0583a460814319e43ba8f (diff)
parent7872cbd89f2620c7188cec6b53009c5726d3dfdf (diff)
downloadwayland-protocols-android14-release.tar.gz
Change-Id: Ieb53e4435f0b611097ceab0d1bbf57bda0c72895
-rw-r--r--Android.bp49
-rw-r--r--bazel/BUILD.bazel20
-rw-r--r--bazel/gensrcs.bzl192
-rw-r--r--bazel/gensrcs_test.bzl244
-rw-r--r--go.mod17
-rw-r--r--locations.go112
-rw-r--r--wayland_protocol_codegen.go1144
-rw-r--r--wayland_protocol_codegen_test.go488
8 files changed, 2021 insertions, 245 deletions
diff --git a/Android.bp b/Android.bp
index ea50526..4a84d24 100644
--- a/Android.bp
+++ b/Android.bp
@@ -54,8 +54,10 @@ bootstrap_go_package {
"soong-genrule",
],
srcs: [
+ "locations.go",
"wayland_protocol_codegen.go",
],
+ testSrcs: ["wayland_protocol_codegen_test.go"],
pluginFor: ["soong_build"],
}
@@ -120,31 +122,60 @@ filegroup {
],
}
+// Common settings for processing these protocols
+wayland_protocol_codegen_defaults {
+ name: "wayland_extension_protocol_defaults",
+
+ // All the protocol files to generate code for.
+ srcs: [":wayland_extension_protocols"],
+
+ // Use "wayland_scanner" out of external/wayland.
+ tools: ["wayland_scanner"],
+}
+
// Generate protocol source files used by both client and server
wayland_protocol_codegen {
name: "wayland_extension_protocol_sources",
+ defaults: ["wayland_extension_protocol_defaults"],
+
+ // Specifies the command to run to generate each output file for each input file.
cmd: "$(location wayland_scanner) private-code < $(in) > $(out)",
- suffix: ".c",
- srcs: [":wayland_extension_protocols"],
- tools: ["wayland_scanner"],
+
+ // There is a 1:1 correspondence between each generated output file and each source file.
+ // The output filename should use the base filename of the protocol file (no extension), and
+ // add a ".c" suffix. For example, "freedesktop.org/stable/xdg-shell/xdg-shell.xml" generates
+ // "xdg-shell.c".
+ output: "$(in).c",
}
// Generate protocol header files used by the client
wayland_protocol_codegen {
name: "wayland_extension_client_protocol_headers",
+ defaults: ["wayland_extension_protocol_defaults"],
+
+ // Specifies the command to run to generate each output file for each input file.
cmd: "$(location wayland_scanner) client-header < $(in) > $(out)",
- suffix: "-client-protocol.h",
- srcs: [":wayland_extension_protocols"],
- tools: ["wayland_scanner"],
+
+ // There is a 1:1 correspondence between each generated output file and each source file.
+ // The output filename should use the base filename of the protocol file (no extension), and
+ // add a "-client-protocol.h" suffix. For example,
+ // "freedesktop.org/stable/xdg-shell/xdg-shell.xml" generates "xdg-shell-client-protocol.h".
+ output: "$(in)-client-protocol.h",
}
// Generate protocol header files used by the server
wayland_protocol_codegen {
name: "wayland_extension_server_protocol_headers",
+ defaults: ["wayland_extension_protocol_defaults"],
+
+ // Specifies the command to run to generate each output file for each input file.
cmd: "$(location wayland_scanner) server-header < $(in) > $(out)",
- suffix: "-server-protocol.h",
- srcs: [":wayland_extension_protocols"],
- tools: ["wayland_scanner"],
+
+ // There is a 1:1 correspondence between each generated output file and each source file.
+ // The output filename should use the base filename of the protocol file (no extension), and
+ // add a "-server-protocol.h" suffix. For example,
+ // "freedesktop.org/stable/xdg-shell/xdg-shell.xml" generates "xdg-shell-server-protocol.h".
+ output: "$(in)-server-protocol.h",
}
// Generate a library with the protocol files, configured to export the client
diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel
new file mode 100644
index 0000000..1b7969d
--- /dev/null
+++ b/bazel/BUILD.bazel
@@ -0,0 +1,20 @@
+"""
+Copyright 2023 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+load(":gensrcs.bzl", "gensrcs")
+load(":gensrcs_test.bzl", "gensrcs_test_suite")
+
+gensrcs_test_suite(name = "gensrcs_tests")
diff --git a/bazel/gensrcs.bzl b/bazel/gensrcs.bzl
new file mode 100644
index 0000000..730922e
--- /dev/null
+++ b/bazel/gensrcs.bzl
@@ -0,0 +1,192 @@
+"""
+Copyright 2023 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+load("@bazel_skylib//lib:paths.bzl", "paths")
+
+def _remove_extension(p):
+ """Removes the extension from the path `p`.
+
+ Leading periods on the basename are ignored, so
+ `_strip_extension(".bashrc")` returns `".bashrc"`.
+
+ Args:
+ p: The path to modify.
+
+ Returns:
+ The path with the extension removed.
+ """
+
+ # paths.split_extension() does all of the work.
+ return paths.split_extension(p)[0]
+
+# Expands an output path template for the given context and input file.
+def _expand_out_path_template(ctx, src_file):
+ # Each src_file has a short_path that looks like:
+ #
+ # <source-package-path>/<source-package-rel-path>/<base.ext>
+ #
+ # For some expansions, we want to strip of the source package path, and
+ # only use the rest for output file path in the expansion.
+ #
+ # There is also an option during expansion to just use the <base> or
+ # <base.ext> portion of the input path.
+ #
+ # This means there can be collisions if input files are taken from
+ # `filegroups` defined in different packages, if they happen to use
+ # the same relative path for that package.
+ #
+ # These conflcits are left to the user of this `gensrcs` rule to resolve for
+ # their use case, as at least Bazel will raise an error when they occur.
+
+ # Try to obtain the path to the package that defines `src_file`. It may or
+ # may not be defined by the same package this `gensrcs` rule is in.
+ # The `owner` label `package` attribute value is the closest we can get
+ # to that path, but it may not be correct in all cases, such as if the
+ # source path is itself for a generated file, where the generated file is
+ # under a build artifact path, and not in the source tree.
+ pkg_dirname = paths.dirname(src_file.short_path)
+ rel_dirname = pkg_dirname
+ if (src_file.is_source and src_file.owner and
+ src_file.short_path.startswith(src_file.owner.package + "/")):
+ rel_dirname = paths.dirname(paths.relativize(
+ src_file.short_path,
+ src_file.owner.package,
+ ))
+
+ base_inc_ext = src_file.basename
+ base_exc_ext = _remove_extension(base_inc_ext)
+ rel_path_base_inc_ext = paths.join(rel_dirname, base_inc_ext)
+ rel_path_base_exc_ext = paths.join(rel_dirname, base_exc_ext)
+ pkg_path_base_inc_ext = paths.join(pkg_dirname, base_inc_ext)
+ pkg_path_base_exc_ext = paths.join(pkg_dirname, base_exc_ext)
+
+ # Expand the output template
+ return ctx.attr.output \
+ .replace("$(SRC:PKG/PATH/BASE.EXT)", pkg_path_base_inc_ext) \
+ .replace("$(SRC:PKG/PATH/BASE)", pkg_path_base_exc_ext) \
+ .replace("$(SRC:PATH/BASE.EXT)", rel_path_base_inc_ext) \
+ .replace("$(SRC:PATH/BASE)", rel_path_base_exc_ext) \
+ .replace("$(SRC:BASE.EXT)", base_inc_ext) \
+ .replace("$(SRC:BASE)", base_exc_ext) \
+ .replace("$(SRC)", rel_path_base_inc_ext)
+
+# A rule to generate files based on provided srcs and tools.
+def _gensrcs_impl(ctx):
+ # The next two assignments can be created by using ctx.resolve_command.
+ # TODO: Switch to using ctx.resolve_command when it is out of
+ # experimental.
+ command = ctx.expand_location(ctx.attr.cmd)
+ tools = [
+ tool[DefaultInfo].files_to_run
+ for tool in ctx.attr.tools
+ ]
+
+ # Expand the shell command by substituting $(RULEDIR), which will be
+ # the same for any source file.
+ command = command.replace(
+ "$(RULEDIR)",
+ paths.join(
+ ctx.var["GENDIR"],
+ ctx.label.package,
+ ),
+ )
+
+ src_files = ctx.files.srcs
+ out_files = []
+ for src_file in src_files:
+ # Expand the output path template for this source file.
+ out_file_path = _expand_out_path_template(ctx, src_file)
+
+ # out_file is at output_file_path that is relative to
+ # <GENDIR>/<gensrc-package-dir>, hence, the fullpath to out_file is
+ # <GENDIR>/<gensrc-package-dir>/<out_file_path>
+ out_file = ctx.actions.declare_file(out_file_path)
+
+ # Expand the command template for this source file by performing
+ # substitution for $(SRC) and $(OUT).
+ shell_command = command \
+ .replace("$(SRC)", src_file.path) \
+ .replace("$(OUT)", out_file.path)
+
+ # Run the shell comand to generate the output from the input.
+ ctx.actions.run_shell(
+ tools = tools,
+ outputs = [out_file],
+ inputs = [src_file],
+ command = shell_command,
+ progress_message = "Generating %s from %s" % (
+ out_file.path,
+ src_file.path,
+ ),
+ )
+ out_files.append(out_file)
+
+ return [DefaultInfo(
+ files = depset(out_files),
+ )]
+
+gensrcs = rule(
+ implementation = _gensrcs_impl,
+ doc = "This rule generates files, where each of the `srcs` files is " +
+ "passed to `cmd` to generate an `output`.",
+ attrs = {
+ "srcs": attr.label_list(
+ # We allow srcs to directly reference files, instead of only
+ # allowing references to other rules such as filegroups.
+ allow_files = True,
+ # An empty srcs is likely an mistake.
+ allow_empty = False,
+ # srcs must be explicitly specified.
+ mandatory = True,
+ doc = "A list of source files to process",
+ ),
+ "output": attr.string(
+ # By default we generate an output filename based on the input
+ # filename (no extension).
+ default = "$(SRC)",
+ doc = "An output path template which is expanded to generate " +
+ "the output path given an source file. Portions " +
+ "of the source filename can be included in the expansion " +
+ "with one of: $(SRC:BASE), $(SRC:BASE.EXT), " +
+ "$(SRC:PATH/BASE), $(SRC:PATH/BASE), " +
+ "$(SRC:PKG/PATH/BASE), or $(SRC:PKG/PATH/BASE.ext). For " +
+ "example, specifying `output = " +
+ "\"includes/lib/$(SRC:BASE).h\"` would mean the input " +
+ "file `some_path/to/a.txt` generates `includes/lib/a.h`, " +
+ "while instead specifying `output = " +
+ "\"includes/lib/$(SRC:PATH/BASE.EXT).h\"` would expand " +
+ "to `includes/lib/some_path/to/a.txt.h`.",
+ ),
+ "cmd": attr.string(
+ # cmd must be explicitly specified.
+ mandatory = True,
+ doc = "The command to run. Subject to $(location) expansion. " +
+ "$(SRC) represents each input file provided in `srcs` " +
+ "while $(OUT) reprensents corresponding output file " +
+ "generated by the rule. $(RULEDIR) is intepreted the same " +
+ "as it is in genrule.",
+ ),
+ "tools": attr.label_list(
+ # We allow tools to directly reference files, as there could be a local script
+ # used as a tool.
+ allow_files = True,
+ doc = "A list of tool dependencies for this rule. " +
+ "The path of an individual `tools` target //x:y can be " +
+ "obtained using `$(location //x:y)`",
+ cfg = "exec",
+ ),
+ },
+)
diff --git a/bazel/gensrcs_test.bzl b/bazel/gensrcs_test.bzl
new file mode 100644
index 0000000..7c85c61
--- /dev/null
+++ b/bazel/gensrcs_test.bzl
@@ -0,0 +1,244 @@
+"""
+Copyright 2023 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+load("@bazel_skylib//lib:new_sets.bzl", "sets")
+load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
+load("@bazel_skylib//lib:paths.bzl", "paths")
+load(":gensrcs.bzl", "gensrcs")
+
+SRCS = [
+ "texts/src1.txt",
+ "texts/src2.txt",
+ "src3.txt",
+]
+
+# ==== Check the output paths created by gensrcs ====
+
+def _test_output_path_expansion_impl(ctx):
+ env = analysistest.begin(ctx)
+ target = analysistest.target_under_test(env)
+ actions = analysistest.target_actions(env)
+
+ # Expect an action for each input/output file pair.
+ asserts.equals(
+ env,
+ expected = len(ctx.attr.expected_outputs),
+ actual = len(actions),
+ )
+
+ # Expect the correct set of output files.
+ asserts.set_equals(
+ env,
+ expected = sets.make([
+ paths.join(
+ ctx.genfiles_dir.path,
+ paths.dirname(ctx.build_file_path),
+ out,
+ )
+ for out in ctx.attr.expected_outputs
+ ]),
+ actual = sets.make(
+ [file.path for file in target.files.to_list()],
+ ),
+ )
+
+ return analysistest.end(env)
+
+output_path_expansion_test = analysistest.make(
+ _test_output_path_expansion_impl,
+ attrs = {
+ "expected_outputs": attr.string_list(
+ doc = "The expected list of output files",
+ ),
+ },
+)
+
+def _test_output_expansion_base():
+ name = "gensrcs_output_expansion_base"
+ test_name = name + "_test"
+
+ gensrcs(
+ name = name,
+ cmd = "cat $(SRC) > $(OUT)",
+ srcs = SRCS,
+ output = "prefix_$(SRC:BASE)_suffix",
+ tags = ["manual"], # make sure it's not built using `:all`
+ )
+
+ output_path_expansion_test(
+ name = test_name,
+ target_under_test = name,
+ expected_outputs = [
+ "prefix_src1_suffix",
+ "prefix_src2_suffix",
+ "prefix_src3_suffix",
+ ],
+ )
+ return test_name
+
+def _test_output_expansion_base_ext():
+ name = "gensrcs_output_expansion_base_ext"
+ test_name = name + "_test"
+
+ gensrcs(
+ name = name,
+ cmd = "cat $(SRC) > $(OUT)",
+ srcs = SRCS,
+ output = "prefix_$(SRC:BASE.EXT)_suffix",
+ tags = ["manual"], # make sure it's not built using `:all`
+ )
+
+ output_path_expansion_test(
+ name = test_name,
+ target_under_test = name,
+ expected_outputs = [
+ "prefix_src1.txt_suffix",
+ "prefix_src2.txt_suffix",
+ "prefix_src3.txt_suffix",
+ ],
+ )
+ return test_name
+
+def _test_output_expansion_path_base():
+ name = "gensrcs_output_expansion_path_base"
+ test_name = name + "_test"
+
+ gensrcs(
+ name = name,
+ cmd = "cat $(SRC) > $(OUT)",
+ srcs = SRCS,
+ output = "prefix_$(SRC:PATH/BASE)_suffix",
+ tags = ["manual"], # make sure it's not built using `:all`
+ )
+
+ output_path_expansion_test(
+ name = test_name,
+ target_under_test = name,
+ expected_outputs = [
+ "prefix_texts/src1_suffix",
+ "prefix_texts/src2_suffix",
+ "prefix_src3_suffix",
+ ],
+ )
+ return test_name
+
+def _test_output_expansion_path_base_ext():
+ name = "gensrcs_output_expansion_path_base_ext"
+ test_name = name + "_test"
+
+ gensrcs(
+ name = name,
+ cmd = "cat $(SRC) > $(OUT)",
+ srcs = SRCS,
+ output = "prefix_$(SRC:PATH/BASE.EXT)_suffix",
+ tags = ["manual"], # make sure it's not built using `:all`
+ )
+
+ output_path_expansion_test(
+ name = test_name,
+ target_under_test = name,
+ expected_outputs = [
+ "prefix_texts/src1.txt_suffix",
+ "prefix_texts/src2.txt_suffix",
+ "prefix_src3.txt_suffix",
+ ],
+ )
+ return test_name
+
+def _test_output_expansion_pkg_path_base():
+ name = "gensrcs_output_expansion_pkg_path_base"
+ test_name = name + "_test"
+
+ gensrcs(
+ name = name,
+ cmd = "cat $(SRC) > $(OUT)",
+ srcs = SRCS,
+ output = "prefix_$(SRC:PKG/PATH/BASE)_suffix",
+ tags = ["manual"], # make sure it's not built using `:all`
+ )
+
+ output_path_expansion_test(
+ name = test_name,
+ target_under_test = name,
+ expected_outputs = [
+ "prefix_external/wayland-protocols/bazel/texts/src1_suffix",
+ "prefix_external/wayland-protocols/bazel/texts/src2_suffix",
+ "prefix_external/wayland-protocols/bazel/src3_suffix",
+ ],
+ )
+ return test_name
+
+def _test_output_expansion_pkg_path_base_ext():
+ name = "gensrcs_output_expansion_pkg_path_base_ext"
+ test_name = name + "_test"
+
+ gensrcs(
+ name = name,
+ cmd = "cat $(SRC) > $(OUT)",
+ srcs = SRCS,
+ output = "prefix_$(SRC:PKG/PATH/BASE.EXT)_suffix",
+ tags = ["manual"], # make sure it's not built using `:all`
+ )
+
+ output_path_expansion_test(
+ name = test_name,
+ target_under_test = name,
+ expected_outputs = [
+ "prefix_external/wayland-protocols/bazel/texts/src1.txt_suffix",
+ "prefix_external/wayland-protocols/bazel/texts/src2.txt_suffix",
+ "prefix_external/wayland-protocols/bazel/src3.txt_suffix",
+ ],
+ )
+ return test_name
+
+def _test_output_expansion_default():
+ name = "gensrcs_output_expansion_default"
+ test_name = name + "_test"
+
+ gensrcs(
+ name = name,
+ cmd = "cat $(SRC) > $(OUT)",
+ srcs = SRCS,
+ tags = ["manual"], # make sure it's not built using `:all`
+ )
+
+ output_path_expansion_test(
+ name = test_name,
+ target_under_test = name,
+ expected_outputs = [
+ "texts/src1.txt",
+ "texts/src2.txt",
+ "src3.txt",
+ ],
+ )
+ return test_name
+
+# ==== test suite ====
+
+def gensrcs_test_suite(name):
+ """Creates test targets for gensrcs.bzl"""
+ native.test_suite(
+ name = name,
+ tests = [
+ _test_output_expansion_base(),
+ _test_output_expansion_base_ext(),
+ _test_output_expansion_path_base(),
+ _test_output_expansion_path_base_ext(),
+ _test_output_expansion_pkg_path_base(),
+ _test_output_expansion_pkg_path_base_ext(),
+ _test_output_expansion_default(),
+ ],
+ )
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..0cb1089
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,17 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+module android.googlesource.com/platform/external/wayland_protocols
+
+go 1.19
diff --git a/locations.go b/locations.go
new file mode 100644
index 0000000..a2d06d9
--- /dev/null
+++ b/locations.go
@@ -0,0 +1,112 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package soong_wayland_protocol_codegen
+
+import (
+ "strings"
+
+ "android/soong/android"
+)
+
+// Note: the types defined here are identical to the types defined in the
+// locations.go file included as "android/soong/genrule" package.
+// Unfortunately all the types defined there are not public, which means we
+// can't reference them from that package.
+
+// location is used to service $(location) and $(locations) entries in
+// wayland_protocol_codegen commands.
+type location interface {
+ Paths(cmd *android.RuleBuilderCommand) []string
+ String() string
+}
+
+// inputLocation is a $(location) result for an entry in the srcs property.
+type inputLocation struct {
+ paths android.Paths
+}
+
+func (l inputLocation) String() string {
+ return strings.Join(l.paths.Strings(), " ")
+}
+
+func (l inputLocation) Paths(cmd *android.RuleBuilderCommand) []string {
+ return cmd.PathsForInputs(l.paths)
+}
+
+var _ location = inputLocation{}
+
+// outputLocation is a $(location) result for an entry in the out property.
+type outputLocation struct {
+ path android.WritablePath
+}
+
+func (l outputLocation) String() string {
+ return l.path.String()
+}
+
+func (l outputLocation) Paths(cmd *android.RuleBuilderCommand) []string {
+ return []string{cmd.PathForOutput(l.path)}
+}
+
+var _ location = outputLocation{}
+
+// toolLocation is a $(location) result for an entry in the tools or
+// tool_files property.
+type toolLocation struct {
+ paths android.Paths
+}
+
+func (l toolLocation) String() string {
+ return strings.Join(l.paths.Strings(), " ")
+}
+
+func (l toolLocation) Paths(cmd *android.RuleBuilderCommand) []string {
+ return cmd.PathsForTools(l.paths)
+}
+
+var _ location = toolLocation{}
+
+// packagedToolLocation is a $(location) result for an entry in the tools or
+// tool_files property that has PackagingSpecs.
+type packagedToolLocation struct {
+ spec android.PackagingSpec
+}
+
+func (l packagedToolLocation) String() string {
+ return l.spec.FileName()
+}
+
+func (l packagedToolLocation) Paths(cmd *android.RuleBuilderCommand) []string {
+ return []string{cmd.PathForPackagedTool(l.spec)}
+}
+
+var _ location = packagedToolLocation{}
+
+// errorLocation is a placeholder for a $(location) result that returns
+// garbage to break the command when error reporting is delayed by
+// ALLOW_MISSING_DEPENDENCIES=true.
+type errorLocation struct {
+ err string
+}
+
+func (l errorLocation) String() string {
+ return l.err
+}
+
+func (l errorLocation) Paths(cmd *android.RuleBuilderCommand) []string {
+ return []string{l.err}
+}
+
+var _ location = errorLocation{}
diff --git a/wayland_protocol_codegen.go b/wayland_protocol_codegen.go
index f9171af..bf7a9af 100644
--- a/wayland_protocol_codegen.go
+++ b/wayland_protocol_codegen.go
@@ -12,354 +12,1026 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// ---------------------------------------------------------------------------
+/*
+Package wayland_protocol defines an plugin module for the Soong build system,
+which makes it easier to generate code from a list of Wayland protocol files.
-// Package wayland_protcool defines extension modules for the Soong build system
-// to make it easier to generate code from a list of Wayland protocol files.
-//
-// The primary extension module is "wayland_protocol_codegen", which applies a
-// code generation tool to a list of source protocol files.
-//
-// Note that the code generation done here is similar to what is done by the
-// base Soong "gensrcs" module, but there are two functional differences:
-//
-// 1) The output filenames are computed from the input filenames, rather
-// than needing to be specified explicitly. An optional prefix as well
-// as a suffix can be added to the protocol filename (without extension).
-//
-// 2) Code generation is done for each file independently by emitting
-// multiple Ninja build commands, rather than one build command which
-// does it all.
-package wayland_protocol
+The primary build module is "wayland_protocol_codegen", which takes a list of
+protocol files, and runs a configurable code-generation tool to generate
+source code for each one. There is also a "wayland_protocol_codegen_defaults"
+for setting common properties.
+
+This package is substantially similar to the base "android/soong/genrule"
+package, which was originally used for inspiration for this one, and has
+been recently restructured so that it can be kept in sync with a tool like
+"vimdiff" to keep things in sync as needed.
+
+However this build system plugin will not be needed in the future, as
+evidenced by some of the other files that have been added as part of the
+last big sync-up. In the future, Android will be built with Bazel, and it
+is much simpler there to define our own local version of "gensrcs" that
+implements the functionality we need See bazel/gensrcs.bzl for it.
+
+Notable differences:
+
+ - This package implements a more powerful template mechanism for specifying
+ what output path/filename should be used for each source filename. The
+ genrule package only allows the extension on each source filename to be
+ replaced.
+
+ - This package drops support for depfiles, after observing comments that
+ they are problematic in the genrule package sources.
+
+ - This package drops "Extra" and "CmdModifier" from the public Module
+ structure, as this module is not expected to be extended.
+
+ - This package drops "rule" from the public Module structure, as it was
+ unused but present in genrule.
+
+# Usage
+
+ wayland_protocol_codegen {
+ // A standard target name.
+ name: "wayland_extension_protocol_sources",
+
+ // A simple template for generating output filenames.
+ output: "$(in).c"
+
+ // The command line template. See "Cmd".
+ cmd: "$(location wayland_scanner) code < $(in) > $(out)",
+
+ // Protocol source files for the expansion.
+ srcs: [":wayland_extension_protocols"],
+
+ // Any buildable binaries to use as tools
+ tools: ["wayland_scanner"],
+
+ // Any source files to be used (scripts, template files)
+ tools_files: [],
+ }
+*/
+package soong_wayland_protocol_codegen
import (
"fmt"
+ "strconv"
"strings"
"github.com/google/blueprint"
+ "github.com/google/blueprint/bootstrap"
"github.com/google/blueprint/proptools"
"android/soong/android"
+ "android/soong/bazel"
+ "android/soong/bazel/cquery"
"android/soong/genrule"
+ "path/filepath"
)
func init() {
- // Register out extension module type name with Soong.
- android.RegisterModuleType(
- "wayland_protocol_codegen", waylandCodegenModuleFactory)
+ registerCodeGenBuildComponents(android.InitRegistrationContext)
+}
+
+func registerCodeGenBuildComponents(ctx android.RegistrationContext) {
+ ctx.RegisterModuleType("wayland_protocol_codegen_defaults", defaultsFactory)
+
+ ctx.RegisterModuleType("wayland_protocol_codegen", codegenFactory)
+
+ ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
+ ctx.BottomUp("wayland_protocol_codegen_tool_deps", toolDepsMutator).Parallel()
+ })
}
var (
- // Create a context for build rule output from this package
- pctx = android.NewPackageContext("android/soong/external/wayland-protocol")
+ pctx = android.NewPackageContext("android/soong/external/wayland_protocol_codegen")
+
+ // Used by wayland_protocol_codegen when there is more than 1 shard to merge the outputs
+ // of each shard into a zip file.
+ gensrcsMerge = pctx.AndroidStaticRule("wayland_protocol_codegenMerge", blueprint.RuleParams{
+ Command: "${soongZip} -o ${tmpZip} @${tmpZip}.rsp && ${zipSync} -d ${genDir} ${tmpZip}",
+ CommandDeps: []string{"${soongZip}", "${zipSync}"},
+ Rspfile: "${tmpZip}.rsp",
+ RspfileContent: "${zipArgs}",
+ }, "tmpZip", "genDir", "zipArgs")
)
+func init() {
+ pctx.Import("android/soong/android")
+
+ pctx.HostBinToolVariable("soongZip", "soong_zip")
+ pctx.HostBinToolVariable("zipSync", "zipsync")
+}
+
type hostToolDependencyTag struct {
blueprint.BaseDependencyTag
+ android.LicenseAnnotationToolchainDependencyTag
+ label string
+}
+
+func (t hostToolDependencyTag) AllowDisabledModuleDependency(target android.Module) bool {
+ // Allow depending on a disabled module if it's replaced by a prebuilt
+ // counterpart. We get the prebuilt through android.PrebuiltGetPreferred in
+ // GenerateAndroidBuildActions.
+ return target.IsReplacedByPrebuilt()
}
-var hostToolDepTag hostToolDependencyTag
+var _ android.AllowDisabledModuleDependency = (*hostToolDependencyTag)(nil)
-// waylandCodegenProperties defines the properties that will be read in from the
-// Android.bp file for each instantiation of the module.
-type waylandCodegenProperties struct {
- // This string gives the command line template to run on each protocol file
- // to wayland_protocol_codegen.
+type generatorProperties struct {
+ // The command to run on one or more input files. Cmd supports
+ // substitution of a few variables (the actual substitution is implemented
+ // in GenerateAndroidBuildActions below)
+ //
+ // Available variables for substitution:
//
- // The string can contain one or more "$" prefixed variable names for
- // values that can vary. At a minimum you need to use ${location}, ${out}
- // and ${in}
+ // - $(location)
+ // the path to the first entry in tools or tool_files
+ // - $(location <label>)
+ // the path to the tool, tool_file, input or output with name <label>. Use
+ // $(location) if <label> refers to a rule that outputs exactly one file.
+ // - $(locations <label>)
+ // the paths to the tools, tool_files, inputs or outputs with name
+ // <label>. Use $(locations) if <label> refers to a rule that outputs two
+ // or more files.
+ // - $(in)
+ // one or more input files
+ // - $(out)
+ // a single output file
+ // - $(genDir)
+ // the sandbox directory for this tool; contains $(out)
+ // - $$
+ // a literal '$'
//
- // $(location): the path to the first entry in tools or tool_files
- // $(location <label>): the path to the tool or tool_file with name <label>
- // $(in): A protocol file from srcs
- // $(out): The constructed output filename from the protocol filename.
- // $$: a literal $
+ // All files used must be declared as inputs (to ensure proper up-to-date
+ // checks). Use "$(in)" directly in Cmd to ensure that all inputs used are
+ // declared.
Cmd *string
- // The string to prepend to every protcol filename to generate the
- // corresponding output filename. The empty string by default.
- Prefix *string
+ // name of the modules (if any) that produces the host executable. Leave
+ // empty for prebuilts or scripts that do not need a module to build them.
+ Tools []string
- // The suffix to append to every protocol filename to generate the
- // corresponding output filename. The empty string by default.
- Suffix *string
+ // Local source files that are used as scripts or other input files needed
+ // by a tool.
+ Tool_files []string `android:"path"`
- // The list of protocol files to process.
- Srcs []string `android:"path"`
+ // List of directories to export generated headers from.
+ Export_include_dirs []string
- // The names of any built host executables to use for code generation. Can
- // be left empty if a local script is used instead (specified in
- // tool_files).
- Tools []string
+ // List of input files.
+ Srcs []string `android:"path,arch_variant"`
- // Local files that are used for code generation. Can be scripts to run, but
- // should also include any other files that the code generation step should
- // depend on that might be used by the code gen tool.
- Tool_files []string
+ // Input files to exclude.
+ Exclude_srcs []string `android:"path,arch_variant"`
}
-// waylandGenModule defines the Soong module for each instance.
-type waylandGenModule struct {
+type Module struct {
android.ModuleBase
+ android.DefaultableModuleBase
+ android.BazelModuleBase
+ android.ApexModuleBase
+
+ android.ImageInterface
- // Store a copy of the parsed properties for easy reference.
- properties waylandCodegenProperties
+ properties generatorProperties
- // Each module emits its own blueprint (Ninja) rule. Store a reference
- // to the one created for this instance.
- rule blueprint.Rule
+ taskGenerator taskFunc
+
+ rawCommands []string
- // Each module exports one or more include directories. Store the paths here
- // here for easy retrieval.
exportedIncludeDirs android.Paths
- // Each module has a list of files it outputs, that can be used by other
- // modules. Store the list of paths here for easy reference.
outputFiles android.Paths
+ outputDeps android.Paths
+
+ subName string
+ subDir string
+
+ // Collect the module directory for IDE info in java/jdeps.go.
+ modulePaths []string
}
-// For the uninitiated, this is an idiom to check that a given object implements
-// an interface. In this case we want to be sure that waylandGenModule
-// implements genrule.SourceFileGenerator
-var _ genrule.SourceFileGenerator = (*waylandGenModule)(nil)
+// Ensure Module implements the android.MixedBuildBuildable interface.
+var _ android.MixedBuildBuildable = (*Module)(nil)
+
+type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
-// Check that we implement android.SourceFileProducer
-var _ android.SourceFileProducer = (*waylandGenModule)(nil)
+type generateTask struct {
+ in android.Paths
+ out android.WritablePaths
+ copyTo android.WritablePaths
+ genDir android.WritablePath
+ cmd string
-// GeneratedSourceFiles implements the genrule.SourceFileGenerator
-// GeneratedSourceFiles method to return the list of generated source files.
-func (g *waylandGenModule) GeneratedSourceFiles() android.Paths {
+ shard int
+ shards int
+}
+
+// Part of genrule.SourceFileGenerator.
+// Returns the list of generated source files.
+func (g *Module) GeneratedSourceFiles() android.Paths {
return g.outputFiles
}
-// GeneratedHeaderDirs implements the genrule.SourceFileGenerator
-// GeneratedHeaderDirs method to return the list of exported include
-// directories.
-func (g *waylandGenModule) GeneratedHeaderDirs() android.Paths {
- return g.exportedIncludeDirs
+// Part of genrule.SourceFileGenerator.
+// Returns the list of input source files.
+func (g *Module) Srcs() android.Paths {
+ return append(android.Paths{}, g.outputFiles...)
}
-// GeneratedDeps implements the genrule.SourceFileGenerator GeneratedDeps
-// method to return the list of files to be used as dependencies when using
-// GeneratedHeaderDirs.
-func (g *waylandGenModule) GeneratedDeps() android.Paths {
- return g.outputFiles
+// Part of genrule.SourceFileGenerator.
+// Returns the list of the list of exported include paths.
+func (g *Module) GeneratedHeaderDirs() android.Paths {
+ return g.exportedIncludeDirs
}
-// Srcs implements the android.SourceFileProducer Srcs method to return the list
-// of source files.
-func (g *waylandGenModule) Srcs() android.Paths {
- return g.outputFiles
+// Part of genrule.SourceFileGenerator.
+// Returns the list of files to be used as dependencies when using
+// GeneratedHeaderDirs
+func (g *Module) GeneratedDeps() android.Paths {
+ return g.outputDeps
}
-// DepsMutator implements the android.Module DepsMutator method to apply a
-// mutator context to the build graph.
-func (g *waylandGenModule) DepsMutator(ctx android.BottomUpMutatorContext) {
- // This implementation duplicates the one from genrule.go, where gensrcs is
- // defined.
- if g, ok := ctx.Module().(*waylandGenModule); ok {
- if len(g.properties.Tools) > 0 {
- ctx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(),
- hostToolDepTag, g.properties.Tools...)
+// Part of android.OutputFileProducer.
+// Returns the list of output files matching a tag, or an error if there is no
+// match.
+func (g *Module) OutputFiles(tag string) (android.Paths, error) {
+ if tag == "" {
+ return append(android.Paths{}, g.outputFiles...), nil
+ }
+ // otherwise, tag should match one of outputs
+ for _, outputFile := range g.outputFiles {
+ if outputFile.Rel() == tag {
+ return android.Paths{outputFile}, nil
}
}
+ return nil, fmt.Errorf("unsupported module reference tag %q", tag)
}
-// GenerateAndroidBuildActions implements the android.Module
-// GenerateAndroidBuildActions method, which generates all the rules and builds
-// commands used by this module instance.
-func (g *waylandGenModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
- if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
- ctx.ModuleErrorf("at least one `tools` or `tool_files` is required")
- return
- }
+// Ensure Module implements the genrule.SourceFileGenerator interface.
+var _ genrule.SourceFileGenerator = (*Module)(nil)
- // Prepare the list of tools that were defined for codegen purposes.
- tools, implicitDeps := g.prepareTools(ctx)
+// Ensure Module implements the android.SourceFileProducer interface.
+var _ android.SourceFileProducer = (*Module)(nil)
- if ctx.Failed() {
- return
+// Ensure Module implements the android.OutputFileProducer interface.
+var _ android.OutputFileProducer = (*Module)(nil)
+
+func toolDepsMutator(ctx android.BottomUpMutatorContext) {
+ if g, ok := ctx.Module().(*Module); ok {
+ for _, tool := range g.properties.Tools {
+ tag := hostToolDependencyTag{label: tool}
+ if m := android.SrcIsModule(tool); m != "" {
+ tool = m
+ }
+ ctx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), tag, tool)
+ }
}
+}
- // Emit the rule for generating for processing each source file
- g.emitRule(ctx, tools)
+// Part of android.MixedBuildBuildable.
+// Allow this module to be a bridge between Bazel and Soong. Fills in Soong
+// properties for the module from the Bazel cquery, so that other Soong
+// modules can depend on the module when it was actually built by Bazel.
+func (g *Module) ProcessBazelQueryResponse(ctx android.ModuleContext) {
+ g.generateCommonBuildActions(ctx)
- if ctx.Failed() {
+ // Get the list of output files that Bazel generates for the target.
+ label := g.GetBazelLabel(ctx, g)
+ bazelCtx := ctx.Config().BazelContext
+ filePaths, err := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
+ if err != nil {
+ ctx.ModuleErrorf(err.Error())
return
}
- generatedFilenamePrefix := proptools.String(g.properties.Prefix)
- generatedFilenameSuffix := proptools.String(g.properties.Suffix)
- for _, src := range android.PathsForModuleSrc(ctx, g.properties.Srcs) {
- out := g.generateOutputPath(ctx, src, generatedFilenamePrefix, generatedFilenameSuffix)
- if out == nil {
- continue
- }
-
- g.emitBuild(ctx, src, out, implicitDeps)
- g.outputFiles = append(g.outputFiles, out)
+ // Convert to android.Paths, and also form the set of include directories
+ // that might be needed for those paths.
+ var bazelOutputFiles android.Paths
+ exportIncludeDirs := map[string]bool{}
+ for _, bazelOutputFile := range filePaths {
+ bazelOutputFiles = append(bazelOutputFiles,
+ android.PathForBazelOutRelative(ctx, ctx.ModuleDir(), bazelOutputFile))
+ exportIncludeDirs[filepath.Dir(bazelOutputFile)] = true
}
- g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx))
-}
-
-// genrateOutputPath takes an source path, a prefix, and a suffix, and use them
-// to generate and return corresponding an output file path.
-func (g *waylandGenModule) generateOutputPath(ctx android.ModuleContext, src android.Path, prefix string, suffix string) android.WritablePath {
- // Construct a new filename by adding the requested prefix and suffix for this
- // code generator instance. If the input file name is "wayland.xml", and the
- // properties specify a prefix of "test-" and a suffix of "-client.cpp", we
- // will end up with a fulename of "test-wayland-client.cpp"
- protocolFilename, protocolExt := splitExt(src.Base())
- if protocolExt != ".xml" {
- ctx.ModuleErrorf("Source file %q does not end with .xml", src)
- return nil
+ // Set the Soong module properties to refer to the Bazel files
+ g.outputFiles = bazelOutputFiles
+ g.outputDeps = bazelOutputFiles
+ for includePath, _ := range exportIncludeDirs {
+ g.exportedIncludeDirs = append(g.exportedIncludeDirs,
+ android.PathForBazelOut(ctx, includePath))
}
- return android.PathForModuleGen(ctx, prefix+protocolFilename+suffix)
}
-// emitRule is an internal function to emit each Ninja rule.
-func (g *waylandGenModule) emitRule(ctx android.ModuleContext, tools map[string]android.Path) {
- // Get the command to run to process each protocol file. Since everything
- // should be templated, we generate a Ninja rule that uses the command,
- // and invoke it from each Ninja build command we emit.
- g.rule = ctx.Rule(pctx, "generator", blueprint.RuleParams{
- Command: g.expandCmd(ctx, tools),
- })
-}
+// Part of android.Module.
+// Generates all the rules and builds commands used by this module instance.
+func (g *Module) generateCommonBuildActions(ctx android.ModuleContext) {
+ g.subName = ctx.ModuleSubDir()
-// emitBuild is an internal function to emit each Build command.
-func (g *waylandGenModule) emitBuild(ctx android.ModuleContext, src android.Path, out android.WritablePath, implicitDeps android.Paths) android.Path {
- ctx.Build(pctx, android.BuildParams{
- Rule: g.rule,
- Description: "generate " + out.Base(),
- Output: out,
- Inputs: android.Paths{src},
- Implicits: implicitDeps,
- })
+ // Collect the module directory for IDE info in java/jdeps.go.
+ g.modulePaths = append(g.modulePaths, ctx.ModuleDir())
- return out
-}
+ if len(g.properties.Export_include_dirs) > 0 {
+ for _, dir := range g.properties.Export_include_dirs {
+ g.exportedIncludeDirs = append(g.exportedIncludeDirs,
+ android.PathForModuleGen(ctx, g.subDir, ctx.ModuleDir(), dir))
+ }
+ } else {
+ g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, g.subDir))
+ }
-// prepareTools is an internal function to prepare a list of tools.
-func (g *waylandGenModule) prepareTools(ctx android.ModuleContext) (tools map[string]android.Path, implicitDeps android.Paths) {
- tools = map[string]android.Path{}
+ locationLabels := map[string]location{}
+ firstLabel := ""
- // This was extracted and slightly simplifed from equivalent code in
- // genrule.go.
+ addLocationLabel := func(label string, loc location) {
+ if firstLabel == "" {
+ firstLabel = label
+ }
+ if _, exists := locationLabels[label]; !exists {
+ locationLabels[label] = loc
+ } else {
+ ctx.ModuleErrorf("multiple locations for label %q: %q and %q (do you have duplicate srcs entries?)",
+ label, locationLabels[label], loc)
+ }
+ }
- // For each entry in "tool", walk the dependency graph to get more
- // information about it.
+ var tools android.Paths
+ var packagedTools []android.PackagingSpec
if len(g.properties.Tools) > 0 {
+ seenTools := make(map[string]bool)
+
ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
- tag := ctx.OtherModuleDependencyTag(module)
- if android.IsSourceDepTagWithOutputTag(tag, "") {
- // Nothing to do
- return
- }
- switch tag {
- case hostToolDepTag:
+ switch tag := ctx.OtherModuleDependencyTag(module).(type) {
+ case hostToolDependencyTag:
tool := ctx.OtherModuleName(module)
- var path android.OptionalPath
+ if m, ok := module.(android.Module); ok {
+ // Necessary to retrieve any prebuilt replacement for the tool, since
+ // toolDepsMutator runs too late for the prebuilt mutators to have
+ // replaced the dependency.
+ module = android.PrebuiltGetPreferred(ctx, m)
+ }
- if t, ok := module.(genrule.HostToolProvider); ok {
+ switch t := module.(type) {
+ case android.HostToolProvider:
+ // A HostToolProvider provides the path to a tool, which will be copied
+ // into the sandbox.
if !t.(android.Module).Enabled() {
if ctx.Config().AllowMissingDependencies() {
ctx.AddMissingDependencies([]string{tool})
} else {
ctx.ModuleErrorf("depends on disabled module %q", tool)
}
- break
+ return
}
- path = t.HostToolPath()
- } else {
+ path := t.HostToolPath()
+ if !path.Valid() {
+ ctx.ModuleErrorf("host tool %q missing output file", tool)
+ return
+ }
+ if specs := t.TransitivePackagingSpecs(); specs != nil {
+ // If the HostToolProvider has PackgingSpecs, which are definitions of the
+ // required relative locations of the tool and its dependencies, use those
+ // instead. They will be copied to those relative locations in the sbox
+ // sandbox.
+ packagedTools = append(packagedTools, specs...)
+ // Assume that the first PackagingSpec of the module is the tool.
+ addLocationLabel(tag.label, packagedToolLocation{specs[0]})
+ } else {
+ tools = append(tools, path.Path())
+ addLocationLabel(tag.label, toolLocation{android.Paths{path.Path()}})
+ }
+ case bootstrap.GoBinaryTool:
+ // A GoBinaryTool provides the install path to a tool, which will be copied.
+ p := android.PathForGoBinary(ctx, t)
+ tools = append(tools, p)
+ addLocationLabel(tag.label, toolLocation{android.Paths{p}})
+ default:
ctx.ModuleErrorf("%q is not a host tool provider", tool)
- break
+ return
+ }
+
+ seenTools[tag.label] = true
+ }
+ })
+
+ // If AllowMissingDependencies is enabled, the build will not have stopped when
+ // AddFarVariationDependencies was called on a missing tool, which will result in nonsensical
+ // "cmd: unknown location label ..." errors later. Add a placeholder file to the local label.
+ // The command that uses this placeholder file will never be executed because the rule will be
+ // replaced with an android.Error rule reporting the missing dependencies.
+ if ctx.Config().AllowMissingDependencies() {
+ for _, tool := range g.properties.Tools {
+ if !seenTools[tool] {
+ addLocationLabel(tool, errorLocation{"***missing tool " + tool + "***"})
}
+ }
+ }
+ }
+
+ if ctx.Failed() {
+ return
+ }
- if path.Valid() {
- implicitDeps = append(implicitDeps, path.Path())
- if _, exists := tools[tool]; !exists {
- tools[tool] = path.Path()
+ for _, toolFile := range g.properties.Tool_files {
+ paths := android.PathsForModuleSrc(ctx, []string{toolFile})
+ tools = append(tools, paths...)
+ addLocationLabel(toolFile, toolLocation{paths})
+ }
+
+ includeDirInPaths := ctx.DeviceConfig().BuildBrokenInputDir(g.Name())
+ var srcFiles android.Paths
+ for _, in := range g.properties.Srcs {
+ paths, missingDeps := android.PathsAndMissingDepsRelativeToModuleSourceDir(android.SourceInput{
+ Context: ctx, Paths: []string{in}, ExcludePaths: g.properties.Exclude_srcs, IncludeDirs: includeDirInPaths,
+ })
+ if len(missingDeps) > 0 {
+ if !ctx.Config().AllowMissingDependencies() {
+ panic(fmt.Errorf("should never get here, the missing dependencies %q should have been reported in DepsMutator",
+ missingDeps))
+ }
+
+ // If AllowMissingDependencies is enabled, the build will not have stopped when
+ // the dependency was added on a missing SourceFileProducer module, which will result in nonsensical
+ // "cmd: label ":..." has no files" errors later. Add a placeholder file to the local label.
+ // The command that uses this placeholder file will never be executed because the rule will be
+ // replaced with an android.Error rule reporting the missing dependencies.
+ ctx.AddMissingDependencies(missingDeps)
+ addLocationLabel(in, errorLocation{"***missing srcs " + in + "***"})
+ } else {
+ srcFiles = append(srcFiles, paths...)
+ addLocationLabel(in, inputLocation{paths})
+ }
+ }
+
+ var copyFrom android.Paths
+ var outputFiles android.WritablePaths
+ var zipArgs strings.Builder
+
+ cmd := proptools.String(g.properties.Cmd)
+
+ tasks := g.taskGenerator(ctx, cmd, srcFiles)
+ if ctx.Failed() {
+ return
+ }
+
+ for _, task := range tasks {
+ if len(task.out) == 0 {
+ ctx.ModuleErrorf("must have at least one output file")
+ return
+ }
+
+ // Pick a unique path outside the task.genDir for the sbox manifest textproto,
+ // a unique rule name, and the user-visible description.
+ manifestName := "wayland_protocol_codegen.sbox.textproto"
+ desc := "generate"
+ name := "generator"
+ if task.shards > 0 {
+ manifestName = "wayland_protocol_codegen_" + strconv.Itoa(task.shard) + ".sbox.textproto"
+ desc += " " + strconv.Itoa(task.shard)
+ name += strconv.Itoa(task.shard)
+ } else if len(task.out) == 1 {
+ desc += " " + task.out[0].Base()
+ }
+
+ manifestPath := android.PathForModuleOut(ctx, manifestName)
+
+ // Use a RuleBuilder to create a rule that runs the command inside an sbox sandbox.
+ rule := android.NewRuleBuilder(pctx, ctx).Sbox(task.genDir, manifestPath).SandboxTools()
+ cmd := rule.Command()
+
+ for _, out := range task.out {
+ addLocationLabel(out.Rel(), outputLocation{out})
+ }
+
+ rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) {
+ // Report the error directly without returning an error to android.Expand to catch multiple errors in a
+ // single run
+ reportError := func(fmt string, args ...interface{}) (string, error) {
+ ctx.PropertyErrorf("cmd", fmt, args...)
+ return "SOONG_ERROR", nil
+ }
+
+ // Apply shell escape to each cases to prevent source file paths containing $ from being evaluated in shell
+ switch name {
+ case "location":
+ if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
+ return reportError("at least one `tools` or `tool_files` is required if $(location) is used")
+ }
+ loc := locationLabels[firstLabel]
+ paths := loc.Paths(cmd)
+ if len(paths) == 0 {
+ return reportError("default label %q has no files", firstLabel)
+ } else if len(paths) > 1 {
+ return reportError("default label %q has multiple files, use $(locations %s) to reference it",
+ firstLabel, firstLabel)
+ }
+ return proptools.ShellEscape(paths[0]), nil
+ case "in":
+ return strings.Join(proptools.ShellEscapeList(cmd.PathsForInputs(srcFiles)), " "), nil
+ case "out":
+ var sandboxOuts []string
+ for _, out := range task.out {
+ sandboxOuts = append(sandboxOuts, cmd.PathForOutput(out))
+ }
+ return strings.Join(proptools.ShellEscapeList(sandboxOuts), " "), nil
+ case "genDir":
+ return proptools.ShellEscape(cmd.PathForOutput(task.genDir)), nil
+ default:
+ if strings.HasPrefix(name, "location ") {
+ label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
+ if loc, ok := locationLabels[label]; ok {
+ paths := loc.Paths(cmd)
+ if len(paths) == 0 {
+ return reportError("label %q has no files", label)
+ } else if len(paths) > 1 {
+ return reportError("label %q has multiple files, use $(locations %s) to reference it",
+ label, label)
+ }
+ return proptools.ShellEscape(paths[0]), nil
} else {
- ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool], path.Path().String())
+ return reportError("unknown location label %q is not in srcs, out, tools or tool_files.", label)
+ }
+ } else if strings.HasPrefix(name, "locations ") {
+ label := strings.TrimSpace(strings.TrimPrefix(name, "locations "))
+ if loc, ok := locationLabels[label]; ok {
+ paths := loc.Paths(cmd)
+ if len(paths) == 0 {
+ return reportError("label %q has no files", label)
+ }
+ return proptools.ShellEscape(strings.Join(paths, " ")), nil
+ } else {
+ return reportError("unknown locations label %q is not in srcs, out, tools or tool_files.", label)
}
} else {
- ctx.ModuleErrorf("host tool %q missing output file", tool)
+ return reportError("unknown variable '$(%s)'", name)
}
}
})
- }
- // Get more information about each entry in "tool_files".
- for _, tool := range g.properties.Tool_files {
- toolPath := android.PathForModuleSrc(ctx, tool)
- implicitDeps = append(implicitDeps, toolPath)
- if _, exists := tools[tool]; !exists {
- tools[tool] = toolPath
+ if err != nil {
+ ctx.PropertyErrorf("cmd", "%s", err.Error())
+ return
+ }
+
+ g.rawCommands = append(g.rawCommands, rawCommand)
+
+ cmd.Text(rawCommand)
+ cmd.ImplicitOutputs(task.out)
+ cmd.Implicits(task.in)
+ cmd.ImplicitTools(tools)
+ cmd.ImplicitPackagedTools(packagedTools)
+
+ // Create the rule to run the genrule command inside sbox.
+ rule.Build(name, desc)
+
+ if len(task.copyTo) > 0 {
+ // If copyTo is set, multiple shards need to be copied into a single directory.
+ // task.out contains the per-shard paths, and copyTo contains the corresponding
+ // final path. The files need to be copied into the final directory by a
+ // single rule so it can remove the directory before it starts to ensure no
+ // old files remain. zipsync already does this, so build up zipArgs that
+ // zip all the per-shard directories into a single zip.
+ outputFiles = append(outputFiles, task.copyTo...)
+ copyFrom = append(copyFrom, task.out.Paths()...)
+ zipArgs.WriteString(" -C " + task.genDir.String())
+ zipArgs.WriteString(android.JoinWithPrefix(task.out.Strings(), " -f "))
} else {
- ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool], toolPath.String())
+ outputFiles = append(outputFiles, task.out...)
}
}
- return
+
+ if len(copyFrom) > 0 {
+ // Create a rule that zips all the per-shard directories into a single zip and then
+ // uses zipsync to unzip it into the final directory.
+ ctx.Build(pctx, android.BuildParams{
+ Rule: gensrcsMerge,
+ Implicits: copyFrom,
+ Outputs: outputFiles,
+ Description: "merge shards",
+ Args: map[string]string{
+ "zipArgs": zipArgs.String(),
+ "tmpZip": android.PathForModuleGen(ctx, g.subDir+".zip").String(),
+ "genDir": android.PathForModuleGen(ctx, g.subDir).String(),
+ },
+ })
+ }
+
+ g.outputFiles = outputFiles.Paths()
}
-// expandCmd is an internal function to do some expansion and any additional
-// wrapping of the generator command line. Returns the command line to use and
-// an error value.
-func (g *waylandGenModule) expandCmd(ctx android.ModuleContext, tools map[string]android.Path) (cmd string) {
- cmd, err := android.Expand(proptools.String(g.properties.Cmd), func(name string) (string, error) {
- switch name {
- case "in":
- return "$in", nil
- case "out":
- // We need to use the sandbox out path instead
- //return "$sandboxOut", nil
- return "$out", nil
- case "location":
- if len(g.properties.Tools) > 0 {
- return tools[g.properties.Tools[0]].String(), nil
- } else {
- return tools[g.properties.Tool_files[0]].String(), nil
+func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ g.generateCommonBuildActions(ctx)
+
+ // When there are less than six outputs, we directly give those as the
+ // output dependency for this module. However, if there are more outputs,
+ // we inject a phony target. This potentially saves space in the generated
+ // ninja file, as well as simplifying any visualizations of the dependency
+ // graph.
+ if len(g.outputFiles) <= 6 {
+ g.outputDeps = g.outputFiles
+ } else {
+ phonyFile := android.PathForModuleGen(ctx, "genrule-phony")
+ ctx.Build(pctx, android.BuildParams{
+ Rule: blueprint.Phony,
+ Output: phonyFile,
+ Inputs: g.outputFiles,
+ })
+ g.outputDeps = android.Paths{phonyFile}
+ }
+}
+
+// Part of android.MixedBuildBuildable.
+// Queues up Bazel cquery requests related to this module.
+func (g *Module) QueueBazelCall(ctx android.BaseModuleContext) {
+ bazelCtx := ctx.Config().BazelContext
+ bazelCtx.QueueBazelRequest(g.GetBazelLabel(ctx, g), cquery.GetOutputFiles,
+ android.GetConfigKey(ctx))
+}
+
+// Part of android.MixedBuildBuildable.
+// Returns true if Bazel can and should build this module in a mixed build.
+func (g *Module) IsMixedBuildSupported(ctx android.BaseModuleContext) bool {
+ return true
+}
+
+// Part of android.IDEInfo.
+// Collect information for opening IDE project files in java/jdeps.go.
+func (g *Module) IDEInfo(dpInfo *android.IdeInfo) {
+ dpInfo.Srcs = append(dpInfo.Srcs, g.Srcs().Strings()...)
+ for _, src := range g.properties.Srcs {
+ if strings.HasPrefix(src, ":") {
+ src = strings.Trim(src, ":")
+ dpInfo.Deps = append(dpInfo.Deps, src)
+ }
+ }
+ dpInfo.Paths = append(dpInfo.Paths, g.modulePaths...)
+}
+
+// Ensure Module implements android.ApexModule
+// Note: gensrcs implements it but it's possible we do not actually need to.
+var _ android.ApexModule = (*Module)(nil)
+
+// Part of android.ApexModule.
+func (g *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext,
+ sdkVersion android.ApiLevel) error {
+ // Because generated outputs are checked by client modules(e.g. cc_library, ...)
+ // we can safely ignore the check here.
+ return nil
+}
+
+func generatorFactory(taskGenerator taskFunc, props ...interface{}) *Module {
+ module := &Module{
+ taskGenerator: taskGenerator,
+ }
+
+ module.AddProperties(props...)
+ module.AddProperties(&module.properties)
+
+ module.ImageInterface = noopImageInterface{}
+
+ return module
+}
+
+type noopImageInterface struct{}
+
+func (x noopImageInterface) ImageMutatorBegin(android.BaseModuleContext) {}
+func (x noopImageInterface) CoreVariantNeeded(android.BaseModuleContext) bool { return false }
+func (x noopImageInterface) RamdiskVariantNeeded(android.BaseModuleContext) bool { return false }
+func (x noopImageInterface) VendorRamdiskVariantNeeded(android.BaseModuleContext) bool { return false }
+func (x noopImageInterface) DebugRamdiskVariantNeeded(android.BaseModuleContext) bool { return false }
+func (x noopImageInterface) RecoveryVariantNeeded(android.BaseModuleContext) bool { return false }
+func (x noopImageInterface) ExtraImageVariations(ctx android.BaseModuleContext) []string { return nil }
+func (x noopImageInterface) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
+}
+
+// Constructs a Module for handling the code generation.
+func newCodegen() *Module {
+ properties := &codegenProperties{}
+
+ // finalSubDir is the name of the subdirectory that output files will be generated into.
+ // It is used so that per-shard directories can be placed alongside it an then finally
+ // merged into it.
+ const finalSubDir = "wayland_protocol_codegen"
+
+ // Code generation commands are sharded so that up to this many files
+ // are generated as part of one sandbox process.
+ const defaultShardSize = 100
+
+ taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
+ shardSize := defaultShardSize
+
+ if len(srcFiles) == 0 {
+ ctx.ModuleErrorf("must have at least one source file")
+ return []generateTask{}
+ }
+
+ // wayland_protocol_codegen rules can easily hit command line limits by
+ // repeating the command for every input file. Shard the input files into
+ // groups.
+ shards := android.ShardPaths(srcFiles, shardSize)
+ var generateTasks []generateTask
+
+ distinctOutputs := make(map[string]android.Path)
+
+ for i, shard := range shards {
+ var commands []string
+ var outFiles android.WritablePaths
+ var copyTo android.WritablePaths
+
+ // When sharding is enabled (i.e. len(shards) > 1), the sbox rules for each
+ // shard will be write to their own directories and then be merged together
+ // into finalSubDir. If sharding is not enabled (i.e. len(shards) == 1),
+ // the sbox rule will write directly to finalSubDir.
+ genSubDir := finalSubDir
+ if len(shards) > 1 {
+ genSubDir = strconv.Itoa(i)
}
- default:
- if strings.HasPrefix(name, "location ") {
- label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
- if tool, ok := tools[label]; ok {
- return tool.String(), nil
- } else {
- return "", fmt.Errorf("unknown location label %q", label)
+
+ genDir := android.PathForModuleGen(ctx, genSubDir)
+ // NOTE: This TODO is copied from gensrcs, as applies here too.
+ // TODO(ccross): this RuleBuilder is a hack to be able to call
+ // rule.Command().PathForOutput. Replace this with passing the rule into the
+ // generator.
+ rule := android.NewRuleBuilder(pctx, ctx).Sbox(genDir, nil).SandboxTools()
+
+ for _, in := range shard {
+ outFileRaw := expandOutputPath(ctx, *properties, in)
+
+ if conflictWith, hasKey := distinctOutputs[outFileRaw]; hasKey {
+ ctx.ModuleErrorf("generation conflict: both '%v' and '%v' generate '%v'",
+ conflictWith.String(), in.String(), outFileRaw)
}
+
+ distinctOutputs[outFileRaw] = in
+
+ outFile := android.PathForModuleGen(ctx, finalSubDir, outFileRaw)
+
+ // If sharding is enabled, then outFile is the path to the output file in
+ // the shard directory, and copyTo is the path to the output file in the
+ // final directory.
+ if len(shards) > 1 {
+ shardFile := android.PathForModuleGen(ctx, genSubDir, outFileRaw)
+ copyTo = append(copyTo, outFile)
+ outFile = shardFile
+ }
+
+ outFiles = append(outFiles, outFile)
+
+ // pre-expand the command line to replace $in and $out with references to
+ // a single input and output file.
+ command, err := android.Expand(rawCommand, func(name string) (string, error) {
+ switch name {
+ case "in":
+ return in.String(), nil
+ case "out":
+ return rule.Command().PathForOutput(outFile), nil
+ default:
+ return "$(" + name + ")", nil
+ }
+ })
+ if err != nil {
+ ctx.PropertyErrorf("cmd", err.Error())
+ }
+
+ // escape the command in case for example it contains '#', an odd number of '"', etc
+ command = fmt.Sprintf("bash -c %v", proptools.ShellEscape(command))
+ commands = append(commands, command)
}
- return "", fmt.Errorf("unknown variable '$(%s)'", name)
+ fullCommand := strings.Join(commands, " && ")
+
+ generateTasks = append(generateTasks, generateTask{
+ in: shard,
+ out: outFiles,
+ copyTo: copyTo,
+ genDir: genDir,
+ cmd: fullCommand,
+ shard: i,
+ shards: len(shards),
+ })
}
- })
- if err != nil {
- ctx.PropertyErrorf("cmd", "%s", err.Error())
+
+ return generateTasks
}
- return
+
+ g := generatorFactory(taskGenerator, properties)
+ g.subDir = finalSubDir
+ return g
}
-// waylandCodegenModuleFactory creates an extension module instance.
-func waylandCodegenModuleFactory() android.Module {
- m := &waylandGenModule{}
- m.AddProperties(&m.properties)
+// Factory for code generation modules
+func codegenFactory() android.Module {
+ m := newCodegen()
android.InitAndroidModule(m)
+ android.InitBazelModule(m)
+ android.InitDefaultableModule(m)
return m
}
-// splitExt splits a base filename into (filename, ext) components, such that
-// input == filename + ext
-func splitExt(input string) (filename string, ext string) {
- // There is no filepath.SplitExt() or equivalent.
- dot := strings.LastIndex(input, ".")
- if dot != -1 {
- ext = input[dot:]
- filename = input[:dot]
- } else {
- ext = ""
- filename = input
+// The custom properties specific to this code generation module.
+type codegenProperties struct {
+ // The string to prepend to every protocol filename to generate the
+ // corresponding output filename. The empty string by default.
+ // Deprecated. Prefer "Output" instead.
+ Prefix *string
+
+ // The suffix to append to every protocol filename to generate the
+ // corresponding output filename. The empty string by default.
+ // Deprecated. Prefer "Output" instead.
+ Suffix *string
+
+ // The output filename template.
+ //
+ // This template string allows the output file name to be generated for
+ // each source file, using some limited properties of the source file.
+ //
+ // $(in:base): The base filename, no path or extension
+ // $(in:base.ext): The filename, no path
+ // $(in:path/base): The filename with path but no extension
+ // $(in:path/base.ext): The full source filename
+ // $(in): An alias for $(in:base) for the base filename, no extension
+ //
+ // Note that the path that is maintained is the relative path used when
+ // including the source in an Android.bp file.
+ //
+ // The template allows arbitrary prefixes and suffixes to be added to the
+ // output filename. For example, "a_$(in).d" would take an source filename
+ // of "b.c" and turn it into "a_b.d".
+ //
+ // The output template does not have to generate a unique filename,
+ // however the implementation will raise an error if the same output file
+ // is generated by more than one source file.
+ Output *string
+}
+
+// Expands the output path pattern to form the output path for the given
+// input path.
+func expandOutputPath(ctx android.ModuleContext, properties codegenProperties, in android.Path) string {
+ template := proptools.String(properties.Output)
+ if len(template) == 0 {
+ prefix := proptools.String(properties.Prefix)
+ suffix := proptools.String(properties.Suffix)
+ return prefix + removeExtension(in.Base()) + suffix
+ }
+
+ outPath, _ := android.Expand(template, func(name string) (string, error) {
+ // Report the error directly without returning an error to
+ // android.Expand to catch multiple errors in a single run.
+ reportError := func(fmt string, args ...interface{}) (string, error) {
+ ctx.PropertyErrorf("output", fmt, args...)
+ return "EXPANSION_ERROR", nil
+ }
+
+ switch name {
+ case "in":
+ return removeExtension(in.Base()), nil
+ case "in:base":
+ return removeExtension(in.Base()), nil
+ case "in:base.ext":
+ return in.Base(), nil
+ case "in:path/base":
+ return removeExtension(in.Rel()), nil
+ case "in:path/base.ext":
+ return in.Rel(), nil
+ default:
+ return reportError("unknown variable '$(%s)'", name)
+ }
+ })
+
+ return outPath
+}
+
+// Removes any extension from the final component of a path.
+func removeExtension(path string) string {
+ // Note: This implementation does not handle files like ".bashrc" correctly.
+ if dot := strings.LastIndex(path, "."); dot != -1 {
+ return path[:dot]
+ }
+ return path
+}
+
+// The attributes for the custom local ./bazel/gensrcs.bzl. See the .bzl file
+// for attribute documentation.
+type bazelGensrcsAttributes struct {
+ Srcs bazel.LabelListAttribute
+ Output string
+ Tools bazel.LabelListAttribute
+ Cmd string
+}
+
+// ConvertWithBp2build converts a Soong module -> Bazel target.
+func (m *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+ // Bazel only has the "tools" attribute.
+ tools_prop := android.BazelLabelForModuleDeps(ctx, m.properties.Tools)
+ tool_files_prop := android.BazelLabelForModuleSrc(ctx, m.properties.Tool_files)
+ tools_prop.Append(tool_files_prop)
+
+ tools := bazel.MakeLabelListAttribute(tools_prop)
+ srcs := bazel.LabelListAttribute{}
+ srcs_labels := bazel.LabelList{}
+ srcs_labels = android.BazelLabelForModuleSrcExcludes(
+ ctx, m.properties.Srcs, m.properties.Exclude_srcs)
+ srcs = bazel.MakeLabelListAttribute(srcs_labels)
+
+ var allReplacements bazel.LabelList
+ allReplacements.Append(tools.Value)
+ allReplacements.Append(bazel.FirstUniqueBazelLabelList(srcs_labels))
+
+ // Convert the command line template.
+ var cmd string
+ if m.properties.Cmd != nil {
+ // $(in) becomes $(SRC) in our custom gensrcs.bzl
+ cmd = strings.ReplaceAll(*m.properties.Cmd, "$(in)", "$(SRC)")
+ // $(out) becomes $(OUT) in our custom gensrcs.bzl
+ cmd = strings.ReplaceAll(cmd, "$(out)", "$(OUT)")
+ // $(gendir) becomes $(RULEDIR) in our custom gensrcs.bzl
+ cmd = strings.Replace(cmd, "$(genDir)", "$(RULEDIR)", -1)
+
+ // $(location) or $(locations) becomes the more explicit
+ // $(location <default-tool-label>) in Bazel.
+ if len(tools.Value.Includes) > 0 {
+ cmd = strings.Replace(cmd, "$(location)",
+ fmt.Sprintf("$(location %s)", tools.Value.Includes[0].Label), -1)
+ cmd = strings.Replace(cmd, "$(locations)",
+ fmt.Sprintf("$(locations %s)", tools.Value.Includes[0].Label), -1)
+ }
+
+ // Translate all the other $(location <name>) and $(locations <name>)
+ // expansion placeholders.
+ for _, l := range allReplacements.Includes {
+ bpLoc := fmt.Sprintf("$(location %s)", l.OriginalModuleName)
+ bpLocs := fmt.Sprintf("$(locations %s)", l.OriginalModuleName)
+ bazelLoc := fmt.Sprintf("$(location %s)", l.Label)
+ bazelLocs := fmt.Sprintf("$(locations %s)", l.Label)
+ cmd = strings.Replace(cmd, bpLoc, bazelLoc, -1)
+ cmd = strings.Replace(cmd, bpLocs, bazelLocs, -1)
+ }
}
- return
+
+ tags := android.ApexAvailableTags(m)
+
+ // The Output_extension prop is not in an immediately accessible field
+ // in the Module struct, so use GetProperties and cast it
+ // to the known struct prop.
+ var outputFileTemplate string
+ for _, propIntf := range m.GetProperties() {
+ if props, ok := propIntf.(*codegenProperties); ok {
+ // Convert the output path template.
+ outputFileTemplate = proptools.String(props.Output)
+ if len(outputFileTemplate) > 0 {
+ outputFileTemplate = strings.Replace(
+ outputFileTemplate, "$(in)", "$(SRC:BASE)", -1)
+ outputFileTemplate = strings.Replace(
+ outputFileTemplate, "$(in:path/base.ext)", "$(SRC:PATH/BASE.EXT)", -1)
+ outputFileTemplate = strings.Replace(
+ outputFileTemplate, "$(in:path/base)", "$(SRC:PATH/BASE)", -1)
+ outputFileTemplate = strings.Replace(
+ outputFileTemplate, "$(in:base.ext)", "$(SRC:BASE.EXT)", -1)
+ outputFileTemplate = strings.Replace(
+ outputFileTemplate, "$(in:base)", "$(SRC:BASE)", -1)
+ } else {
+ outputFileTemplate = proptools.String(props.Prefix) + "$(SRC:BASE)" +
+ proptools.String(props.Suffix)
+ }
+ break
+ }
+ }
+ props := bazel.BazelTargetModuleProperties{
+ Rule_class: "gensrcs",
+ Bzl_load_location: "//external/wayland-protocols/bazel:gensrcs.bzl",
+ }
+ attrs := &bazelGensrcsAttributes{
+ Srcs: srcs,
+ Output: outputFileTemplate,
+ Cmd: cmd,
+ Tools: tools,
+ }
+ ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+ Name: m.Name(),
+ Tags: tags,
+ }, attrs)
+}
+
+// Defaults module.
+type Defaults struct {
+ android.ModuleBase
+ android.DefaultsModuleBase
+}
+
+func defaultsFactory() android.Module {
+ return DefaultsFactory()
+}
+
+func DefaultsFactory(props ...interface{}) android.Module {
+ module := &Defaults{}
+
+ module.AddProperties(props...)
+ module.AddProperties(
+ &generatorProperties{},
+ &codegenProperties{},
+ )
+
+ android.InitDefaultsModule(module)
+
+ return module
}
diff --git a/wayland_protocol_codegen_test.go b/wayland_protocol_codegen_test.go
new file mode 100644
index 0000000..90f1200
--- /dev/null
+++ b/wayland_protocol_codegen_test.go
@@ -0,0 +1,488 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package soong_wayland_protocol_codegen
+
+import (
+ "os"
+ "regexp"
+ "testing"
+
+ "android/soong/android"
+)
+
+func TestMain(m *testing.M) {
+ os.Exit(m.Run())
+}
+
+var prepareForCodeGenTest = android.GroupFixturePreparers(
+ android.PrepareForTestWithArchMutator,
+ android.PrepareForTestWithDefaults,
+ android.PrepareForTestWithFilegroup,
+ android.GroupFixturePreparers(
+ android.FixtureRegisterWithContext(registerCodeGenBuildComponents),
+ ),
+ android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+ android.RegisterPrebuiltMutators(ctx)
+ ctx.RegisterModuleType("fake_android_host_tool", fakeAndroidHostToolFactory)
+ }),
+ android.FixtureMergeMockFs(android.MockFS{
+ "android_host_tool": nil,
+ "tool_src_file": nil,
+ "tool_src_file_1": nil,
+ "tool_src_file_2": nil,
+ "src_file": nil,
+ "src_file_1": nil,
+ "src_file_2": nil,
+ }),
+)
+
+func testCodeGenBp() string {
+ return `
+ fake_android_host_tool {
+ name: "host_tool",
+ }
+
+ filegroup {
+ name: "tool_single_source_file_filegroup",
+ srcs: [
+ "tool_src_file",
+ ],
+ }
+
+ filegroup {
+ name: "tool_multi_source_files_filegroup",
+ srcs: [
+ "tool_src_file_1",
+ "tool_src_file_2",
+ ],
+ }
+
+ filegroup {
+ name: "single_source_filegroup",
+ srcs: [
+ "src_file",
+ ],
+ }
+
+ filegroup {
+ name: "multi_source_filegroup",
+ srcs: [
+ "src_file_1",
+ "src_file_2",
+ ],
+ }
+
+ filegroup {
+ name: "empty_filegroup",
+ }
+ `
+}
+
+func TestWaylandCodeGen(t *testing.T) {
+ testcases := []struct {
+ name string
+ prop string
+
+ err string
+ cmds []string
+ files []string
+ }{
+ {
+ name: "single_source_with_host_tool",
+ prop: `
+ tools: ["host_tool"],
+ srcs: ["src_file"],
+ output: "prefix_$(in)_suffix",
+ cmd: "$(location host_tool) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file > __SBOX_SANDBOX_DIR__/out/prefix_src_file_suffix'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_suffix",
+ },
+ },
+ {
+ name: "multi_source_with_host_tool",
+ prop: `
+ tools: ["host_tool"],
+ srcs: ["src_file_1", "src_file_2"],
+ output: "prefix_$(in)_suffix",
+ cmd: "$(location host_tool) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file_1 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_1_suffix' && bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file_2 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_2_suffix'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_1_suffix",
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_2_suffix",
+ },
+ },
+ {
+ name: "single_source_filegroup_with_host_tool",
+ prop: `
+ tools: ["host_tool"],
+ srcs: [":single_source_filegroup"],
+ output: "prefix_$(in)_suffix",
+ cmd: "$(location host_tool) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file > __SBOX_SANDBOX_DIR__/out/prefix_src_file_suffix'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_suffix",
+ },
+ },
+ {
+ name: "multi_source_filegroup_with_host_tool",
+ prop: `
+ tools: ["host_tool"],
+ srcs: [":multi_source_filegroup"],
+ output: "prefix_$(in)_suffix",
+ cmd: "$(location host_tool) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file_1 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_1_suffix' && bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file_2 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_2_suffix'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_1_suffix",
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_2_suffix",
+ },
+ },
+ {
+ name: "single_source_with_single_tool_file",
+ prop: `
+ tool_files: ["tool_src_file"],
+ srcs: ["src_file"],
+ output: "prefix_$(in)_suffix",
+ cmd: "$(location tool_src_file) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file gen < src_file > __SBOX_SANDBOX_DIR__/out/prefix_src_file_suffix'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_suffix",
+ },
+ },
+ {
+ name: "multi_source_with_single_tool_file",
+ prop: `
+ tool_files: ["tool_src_file"],
+ srcs: ["src_file_1", "src_file_2"],
+ output: "prefix_$(in)_suffix",
+ cmd: "$(location tool_src_file) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file gen < src_file_1 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_1_suffix' && bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file gen < src_file_2 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_2_suffix'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_1_suffix",
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_2_suffix",
+ },
+ },
+ {
+ name: "single_source_filegroup_with_single_tool_file",
+ prop: `
+ tool_files: ["tool_src_file"],
+ srcs: [":single_source_filegroup"],
+ output: "prefix_$(in)_suffix",
+ cmd: "$(location tool_src_file) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file gen < src_file > __SBOX_SANDBOX_DIR__/out/prefix_src_file_suffix'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_suffix",
+ },
+ },
+ {
+ name: "multi_source_filegroup_with_single_tool_file",
+ prop: `
+ tool_files: ["tool_src_file"],
+ srcs: [":multi_source_filegroup"],
+ output: "prefix_$(in)_suffix",
+ cmd: "$(location tool_src_file) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file gen < src_file_1 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_1_suffix' && bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file gen < src_file_2 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_2_suffix'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_1_suffix",
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_2_suffix",
+ },
+ },
+ {
+ name: "multiple_tool_files",
+ prop: `
+ tool_files: ["tool_src_file_1", "tool_src_file_2"],
+ srcs: ["src_file"],
+ output: "prefix_$(in)_suffix",
+ cmd: "$(location tool_src_file_1) $(location tool_src_file_2) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file_1 __SBOX_SANDBOX_DIR__/tools/src/tool_src_file_2 gen < src_file > __SBOX_SANDBOX_DIR__/out/prefix_src_file_suffix'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_suffix",
+ },
+ },
+ {
+ name: "output_template_explicit_base_only",
+ prop: `
+ tools: ["host_tool"],
+ srcs: ["txt/a/file.txt"],
+ output: "$(in:base)",
+ cmd: "$(location host_tool) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < txt/a/file.txt > __SBOX_SANDBOX_DIR__/out/file'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/file",
+ },
+ },
+ {
+ name: "output_template_explicit_base_and_ext",
+ prop: `
+ tools: ["host_tool"],
+ srcs: ["txt/a/file.txt"],
+ output: "$(in:base.ext)",
+ cmd: "$(location host_tool) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < txt/a/file.txt > __SBOX_SANDBOX_DIR__/out/file.txt'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/file.txt",
+ },
+ },
+ {
+ name: "output_template_explicit_path_and_base",
+ prop: `
+ tools: ["host_tool"],
+ srcs: ["txt/a/file.txt"],
+ output: "$(in:path/base)",
+ cmd: "$(location host_tool) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < txt/a/file.txt > __SBOX_SANDBOX_DIR__/out/txt/a/file'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/txt/a/file",
+ },
+ },
+ {
+ name: "output_template_explicit_path_and_base_and_ext",
+ prop: `
+ tools: ["host_tool"],
+ srcs: ["txt/a/file.txt"],
+ output: "$(in:path/base.ext)",
+ cmd: "$(location host_tool) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < txt/a/file.txt > __SBOX_SANDBOX_DIR__/out/txt/a/file.txt'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/txt/a/file.txt",
+ },
+ },
+ {
+ name: "single_source_file_does_not_need_distinct_outputs",
+ prop: `
+ tools: ["host_tool"],
+ srcs: ["src_file"],
+ output: "output",
+ cmd: "$(location host_tool) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file > __SBOX_SANDBOX_DIR__/out/output'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/output",
+ },
+ },
+ {
+ name: "legacy_prefix_suffix",
+ prop: `
+ tools: ["host_tool"],
+ srcs: ["src_file"],
+ prefix: "legacy_prefix_",
+ suffix: "_legacy_suffix",
+ cmd: "$(location host_tool) gen < $(in) > $(out)",
+ `,
+ cmds: []string{
+ "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file > __SBOX_SANDBOX_DIR__/out/legacy_prefix_src_file_legacy_suffix'",
+ },
+ files: []string{
+ "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/legacy_prefix_src_file_legacy_suffix",
+ },
+ },
+ {
+ name: "error_if_no_sources",
+ prop: `
+ tools: ["host_tool"],
+ cmd: "$(location host_tool) gen < $(in) > $(out)",
+ `,
+ err: "must have at least one source file",
+ },
+ {
+ name: "error_if_no_filegroup_sources",
+ prop: `
+ tools: ["host_tool"],
+ srcs: [":empty_filegroup"],
+ cmd: "$(location host_tool) gen < $(in) > $(out)",
+ `,
+ err: "must have at least one source file",
+ },
+ {
+ name: "error_if_in_outputs_are_not_distinct",
+ prop: `
+ tools: ["host_tool"],
+ tool_files: ["tool_src_file"],
+ srcs: ["src_file_1", "src_file_2"],
+ output: "not_unique",
+ cmd: "$(location)"
+ `,
+ err: "Android.bp:39:2: module \"codegen\": generation conflict: both 'src_file_1' and 'src_file_2' generate 'not_unique'",
+ },
+ {
+ name: "error_if_output_expansion_fails",
+ prop: `
+ tools: ["host_tool"],
+ tool_files: ["tool_src_file"],
+ srcs: ["src_file"],
+ output: "prefix_$(bad)_suffix",
+ cmd: "$(location)"
+ `,
+ err: "Android.bp:45:11: module \"codegen\": output: unknown variable '$(bad)'",
+ },
+ {
+ name: "error_if_cmd_expansion_fails",
+ prop: `
+ tools: ["host_tool"],
+ tool_files: ["tool_src_file"],
+ srcs: ["src_file"],
+ output: "prefix_$(in)_suffix",
+ cmd: "$(location bad_name)"
+ `,
+ err: "Android.bp:46:8: module \"codegen\": cmd: unknown location label \"bad_name\"",
+ },
+ }
+
+ for _, test := range testcases {
+ t.Run(test.name, func(t *testing.T) {
+ bp := "wayland_protocol_codegen {\n"
+ bp += `name: "codegen",` + "\n"
+ bp += test.prop
+ bp += "}\n"
+
+ var expectedErrors []string
+ if test.err != "" {
+ expectedErrors = append(expectedErrors, regexp.QuoteMeta(test.err))
+ }
+
+ result := prepareForCodeGenTest.
+ ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)).
+ RunTestWithBp(t, testCodeGenBp()+bp)
+
+ if expectedErrors != nil {
+ return
+ }
+
+ gen := result.Module("codegen", "").(*Module)
+ android.AssertDeepEquals(t, "cmd", test.cmds, gen.rawCommands)
+ android.AssertPathsRelativeToTopEquals(t, "files", test.files, gen.outputFiles)
+ })
+ }
+}
+
+func TestGenruleWithBazel(t *testing.T) {
+ bp := `
+ wayland_protocol_codegen {
+ name: "mixed_codegen",
+ srcs: ["src_file"],
+ bazel_module: { label: "//example:bazel_codegen" },
+ }
+ `
+
+ result := android.GroupFixturePreparers(
+ prepareForCodeGenTest, android.FixtureModifyConfig(func(config android.Config) {
+ config.BazelContext = android.MockBazelContext{
+ OutputBaseDir: "outputbase",
+ LabelToOutputFiles: map[string][]string{
+ "//example:bazel_codegen": {"bazelone.txt", "bazeltwo.txt"}}}
+ })).RunTestWithBp(t, testCodeGenBp()+bp)
+
+ gen := result.Module("mixed_codegen", "").(*Module)
+
+ expectedOutputFiles := []string{"outputbase/execroot/__main__/bazelone.txt",
+ "outputbase/execroot/__main__/bazeltwo.txt"}
+ android.AssertDeepEquals(t, "output files", expectedOutputFiles, gen.outputFiles.Strings())
+ android.AssertDeepEquals(t, "output deps", expectedOutputFiles, gen.outputDeps.Strings())
+}
+
+func TestDefaults(t *testing.T) {
+ bp := `
+ wayland_protocol_codegen_defaults {
+ name: "gen_defaults1",
+ cmd: "cp $(in) $(out)",
+ output: "$(in).h",
+ }
+
+ wayland_protocol_codegen_defaults {
+ name: "gen_defaults2",
+ srcs: ["in1"],
+ }
+
+ wayland_protocol_codegen {
+ name: "codegen",
+ defaults: ["gen_defaults1", "gen_defaults2"],
+ }
+ `
+
+ result := prepareForCodeGenTest.RunTestWithBp(t, testCodeGenBp()+bp)
+
+ gen := result.Module("codegen", "").(*Module)
+
+ expectedCmd := "bash -c cp in1 __SBOX_SANDBOX_DIR__/out/in1.h"
+ android.AssertStringEquals(t, "cmd", expectedCmd, gen.rawCommands[0])
+
+ expectedSrcs := []string{"in1"}
+ android.AssertDeepEquals(t, "srcs", expectedSrcs, gen.properties.Srcs)
+
+ expectedFiles := []string{"out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/in1.h"}
+ android.AssertPathsRelativeToTopEquals(t, "files", expectedFiles, gen.outputFiles)
+}
+
+type fakeAndroidHostTool struct {
+ android.ModuleBase
+ outputFile android.Path
+}
+
+func fakeAndroidHostToolFactory() android.Module {
+ module := &fakeAndroidHostTool{}
+ android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
+ return module
+}
+
+func (t *fakeAndroidHostTool) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ t.outputFile = android.PathForTesting("out", ctx.ModuleName())
+}
+
+func (t *fakeAndroidHostTool) HostToolPath() android.OptionalPath {
+ return android.OptionalPathForPath(t.outputFile)
+}
+
+var _ android.HostToolProvider = (*fakeAndroidHostTool)(nil)