blob: 0f654a52788270bed546a8ab82cdb05f23a8bf9e [file] [log] [blame]
# Copyright 2022 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.
"""Helper rules for creating workflow entities."""
load(
":utils.bzl",
"alias",
"collect_runfiles",
"rule_variants",
"with_fuchsia_transition",
"wrap_executable",
)
def workflow_entity_rule(*, implementation, attrs = {}, **kwargs):
def workflow_entity_impl(ctx):
if ctx.attr.args:
fail("Please use the `arguments` attribute instead of `args`.")
def _make_workflow(workflow, *runfiles):
"""Generates a workflow runner invocation given a FuchsiaWorkflowInfo and returns all providers.
Args:
workflow: The workflow to provide the workflow runner.
*runfiles: Additional runfiles dependencies for this workflow entity.
Returns:
A DefaultInfo invocation of the corresponding workflow and the FuchsiaWorkflowInfo.
"""
manifest = ctx.actions.declare_file("%s_workflow.json" % ctx.attr.name.removesuffix("_base"))
ctx.actions.write(manifest, json.encode_indent({
"entities": {
str(label).removesuffix("_base"): {
"type": "task",
"task_runner": entity.task_runner,
"args": entity.args,
"default_argument_scope": entity.default_argument_scope,
} if hasattr(entity, "task_runner") else {
"type": "workflow",
"sequence": [str(step).removesuffix("_base") for step in entity.sequence],
"args": entity.args,
}
for label, entity in workflow.entities.items()
},
"entrypoint": str(workflow.entrypoint).removesuffix("_base"),
}))
invocation, runner_runfiles = wrap_executable(
ctx,
ctx.attr._unbuffer_tool,
ctx.attr._run_workflow,
"--workflow-manifest",
manifest,
script_name = "%s.sh" % ctx.attr.name.removesuffix("_base"),
)
return [
workflow,
DefaultInfo(
executable = invocation,
files = depset([invocation, manifest]),
runfiles = collect_runfiles(
ctx,
runner_runfiles,
ctx.attr.inputs,
*runfiles
),
),
]
def _collect_arguments(prepend_task_args = [], *runfiles):
"""Interpolates task arguments and collects runfiles."""
arguments = prepend_task_args + ctx.attr.arguments
task_runfiles = collect_runfiles(ctx, ctx.attr.inputs, runfiles, arguments, ignore_types = ["string"])
# TODO(fxbug.dev/114470): Interpolate input file locations.
return [
arg.short_path if type(arg) == "File" else arg
for arg in arguments
], task_runfiles
return implementation(ctx, _make_workflow, _collect_arguments)
rules = rule_variants(
implementation = workflow_entity_impl,
variants = ("executable", "test"),
attrs = dict(attrs, **{
"_unbuffer_tool": attr.label(
default = "//fuchsia/tools:unbuffer",
doc = "Pass-through executable that unbuffers command stdout/stderr.",
executable = True,
cfg = "target",
),
"_run_workflow": attr.label(
default = "//fuchsia/tools:run_workflow",
doc = "The workflow runner tool.",
executable = True,
cfg = "target",
),
"arguments": attr.string_list(
doc = "Specify arguments for this workflow entity.",
),
"default_argument_scope": attr.string(
doc = "The scope of arguments to use for the workflow entity.",
default = "explicit",
values = ["explicit", "workflow", "global"],
),
"inputs": attr.label_list(
doc = "Task dependencies. Use `$location(path/to/file)` to reference these in arguments.",
),
}),
**kwargs
)
def macro(
*,
name,
apply_fuchsia_transition = False,
testonly = False,
tags = None,
visibility = None,
**kwargs):
# Switch between the test and non-test workflow variant based on testonly.
rules[1 if testonly else 0](
name = name + "_base",
tags = tags,
visibility = visibility,
**kwargs
)
(with_fuchsia_transition if apply_fuchsia_transition else alias)(
name = name,
actual = name + "_base",
executable = True,
testonly = testonly,
tags = tags,
visibility = visibility,
)
# Because cc_test needs all transitive dependent ancestors to be test rules
# (testonly is not sufficient), we return 3 values to be unpacked:
# 1. The non-test workflow entity rule variant,
# 2. The test workflow entity rule variant, and
# 3. A wrapper macro that invokes either `1` or `2` based on `testonly`.
# The wrapper macro can also optionally apply a fuchsia_transition.
#
# We can't omit `1` or `2`, since bazel needs each rule to be assigned a
# name and exportable by a `.bzl` file.
return rules + [macro]