feat: bulk updates for write_source_files (#40)
This commit is contained in:
parent
2195e1c69a
commit
71c1b893d6
|
@ -7,7 +7,7 @@ Public API for write_source_files
|
|||
## write_source_files
|
||||
|
||||
<pre>
|
||||
write_source_files(<a href="#write_source_files-name">name</a>, <a href="#write_source_files-files">files</a>, <a href="#write_source_files-kwargs">kwargs</a>)
|
||||
write_source_files(<a href="#write_source_files-name">name</a>, <a href="#write_source_files-files">files</a>, <a href="#write_source_files-additional_update_targets">additional_update_targets</a>, <a href="#write_source_files-suggested_update_target">suggested_update_target</a>, <a href="#write_source_files-kwargs">kwargs</a>)
|
||||
</pre>
|
||||
|
||||
Write to one or more files in the source tree. Stamp out tests that ensure the files exists and are up to date.
|
||||
|
@ -30,24 +30,55 @@ To update the source file, run:
|
|||
bazel run //:write_foobar
|
||||
```
|
||||
|
||||
A test will fail if the source file doesn't exist
|
||||
```bash
|
||||
bazel test //...
|
||||
A test will fail if the source file doesn't exist or if it's out of date with instructions on how to create/update it.
|
||||
|
||||
//:foobar.json does not exist. To create & update this file, run:
|
||||
You can declare a tree of generated source file targets:
|
||||
|
||||
bazel run //:write_foobar
|
||||
```starlark
|
||||
load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
|
||||
|
||||
write_source_files(
|
||||
name = "write_all",
|
||||
additional_update_targets = [
|
||||
# Other write_source_files targets to run when this target is run
|
||||
"//a/b/c:write_foo",
|
||||
"//a/b:write_bar",
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
...or if it's out of date.
|
||||
And update them with a single run:
|
||||
|
||||
```bash
|
||||
bazel test //...
|
||||
|
||||
//:foobar.json is out-of-date. To update this file, run:
|
||||
|
||||
bazel run //:write_foobar
|
||||
bazel run //:write_all
|
||||
```
|
||||
|
||||
When a file is out of date, you can leave a suggestion to run a target further up in the tree by specifying `suggested_update_target`. E.g.,
|
||||
|
||||
```starlark
|
||||
write_source_files(
|
||||
name = "write_foo",
|
||||
files = {
|
||||
"foo.json": ":generated-foo",
|
||||
},
|
||||
suggested_update_target = "//:write_all"
|
||||
)
|
||||
```
|
||||
|
||||
A test failure from foo.json being out of date will yield the following message:
|
||||
|
||||
```
|
||||
//a/b:c:foo.json is out of date. To update this and other generated files, run:
|
||||
|
||||
bazel run //:write_all
|
||||
|
||||
To update *only* this file, run:
|
||||
|
||||
bazel run //a/b/c:write_foo
|
||||
```
|
||||
|
||||
If you have many sources that you want to update as a group, we recommend wrapping write_source_files in a macro that defaults `suggested_update_target` to the umbrella update target.
|
||||
|
||||
|
||||
**PARAMETERS**
|
||||
|
||||
|
@ -56,6 +87,8 @@ bazel test //...
|
|||
| :------------- | :------------- | :------------- |
|
||||
| <a id="write_source_files-name"></a>name | Name of the executable target that creates or updates the source file | none |
|
||||
| <a id="write_source_files-files"></a>files | A dict where the keys are source files to write to and the values are labels pointing to the desired content. Source files must be within the same bazel package as the target. | none |
|
||||
| <a id="write_source_files-additional_update_targets"></a>additional_update_targets | (Optional) List of other write_source_files targets to update in the same run | <code>[]</code> |
|
||||
| <a id="write_source_files-suggested_update_target"></a>suggested_update_target | (Optional) Label of the write_source_files target to suggest running when files are out of date | <code>None</code> |
|
||||
| <a id="write_source_files-kwargs"></a>kwargs | Other common named parameters such as <code>tags</code> or <code>visibility</code> | none |
|
||||
|
||||
|
||||
|
|
|
@ -2,9 +2,17 @@
|
|||
|
||||
load("//lib:utils.bzl", "is_external_label")
|
||||
|
||||
_WriteSourceFilesInfo = provider(
|
||||
"Provider to enforce deps are other write_source_files targets",
|
||||
fields = {
|
||||
"executable": "Generated update script",
|
||||
},
|
||||
)
|
||||
|
||||
_write_source_files_attrs = {
|
||||
"in_files": attr.label_list(allow_files = True, allow_empty = False, mandatory = True),
|
||||
"out_files": attr.label_list(allow_files = True, allow_empty = False, mandatory = True),
|
||||
"in_files": attr.label_list(allow_files = True, allow_empty = False, mandatory = False),
|
||||
"out_files": attr.label_list(allow_files = True, allow_empty = False, mandatory = False),
|
||||
"additional_update_targets": attr.label_list(allow_files = False, providers = [_WriteSourceFilesInfo], mandatory = False),
|
||||
"is_windows": attr.bool(mandatory = True),
|
||||
}
|
||||
|
||||
|
@ -13,6 +21,8 @@ def _write_source_files_sh(ctx):
|
|||
ctx.label.name + "_update.sh",
|
||||
)
|
||||
|
||||
additional_update_scripts = [target[_WriteSourceFilesInfo].executable for target in ctx.attr.additional_update_targets]
|
||||
|
||||
ctx.actions.write(
|
||||
output = updater,
|
||||
is_executable = True,
|
||||
|
@ -35,7 +45,13 @@ cp -f "$in" "$out"
|
|||
chmod 644 "$out"
|
||||
""".format(in_file = ctx.files.in_files[i].short_path, out_file = ctx.files.out_files[i].short_path)
|
||||
for i in range(len(ctx.attr.in_files))
|
||||
]),
|
||||
]) + """
|
||||
cd "$runfiles_dir"
|
||||
|
||||
# Run the update scripts for all write_source_file deps
|
||||
""" + "\n".join(["""
|
||||
{update_script}
|
||||
""".format(update_script = update_script.short_path) for update_script in additional_update_scripts]),
|
||||
)
|
||||
|
||||
return updater
|
||||
|
@ -52,7 +68,8 @@ set runfiles_dir=%cd%
|
|||
if defined BUILD_WORKSPACE_DIRECTORY (
|
||||
cd %BUILD_WORKSPACE_DIRECTORY%
|
||||
)
|
||||
""" + "\n".join(["""
|
||||
""" + "\n".join([
|
||||
"""
|
||||
set in=%runfiles_dir%\\{in_file}
|
||||
set out={out_file}
|
||||
|
||||
|
@ -67,9 +84,9 @@ if not defined BUILD_WORKSPACE_DIRECTORY (
|
|||
echo Copying %in% to %out% in %cd%
|
||||
copy %in% %out% >NUL
|
||||
""".format(in_file = ctx.files.in_files[i].short_path.replace("/", "\\"), out_file = ctx.files.out_files[i].short_path).replace("/", "\\")
|
||||
for i in range(len(ctx.attr.in_files))
|
||||
])
|
||||
|
||||
for i in range(len(ctx.attr.in_files))
|
||||
])
|
||||
|
||||
content = content.replace("\n", "\r\n")
|
||||
|
||||
ctx.actions.write(
|
||||
|
@ -99,10 +116,23 @@ def _write_source_files_impl(ctx):
|
|||
else:
|
||||
updater = _write_source_files_sh(ctx)
|
||||
|
||||
return DefaultInfo(
|
||||
executable = updater,
|
||||
runfiles = ctx.runfiles(files = ctx.files.in_files),
|
||||
)
|
||||
runfiles = ctx.runfiles(files = ctx.files.in_files)
|
||||
deps_runfiles = [dep[DefaultInfo].default_runfiles for dep in ctx.attr.additional_update_targets]
|
||||
if "merge_all" in dir(runfiles):
|
||||
runfiles = runfiles.merge_all(deps_runfiles)
|
||||
else:
|
||||
for dep in deps_runfiles:
|
||||
runfiles = runfiles.merge(dep)
|
||||
|
||||
return [
|
||||
DefaultInfo(
|
||||
executable = updater,
|
||||
runfiles = runfiles,
|
||||
),
|
||||
_WriteSourceFilesInfo(
|
||||
executable = updater,
|
||||
),
|
||||
]
|
||||
|
||||
write_source_files_lib = struct(
|
||||
attrs = _write_source_files_attrs,
|
||||
|
|
|
@ -31,4 +31,7 @@ write_source_files(
|
|||
"a2.js": ":a-desired",
|
||||
"b2.js": ":b-desired",
|
||||
},
|
||||
)
|
||||
additional_update_targets = [
|
||||
"//lib/tests/write_source_files/subdir:macro_smoke_test",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
load("//lib:write_source_files.bzl", "write_source_files")
|
||||
|
||||
genrule(
|
||||
name = "c-desired",
|
||||
outs = ["c-desired.js"],
|
||||
cmd = "echo 'console.log(\"c*\");' > $@",
|
||||
)
|
||||
|
||||
write_source_files(
|
||||
name = "macro_smoke_test",
|
||||
files = {
|
||||
"c.js": ":c-desired",
|
||||
},
|
||||
suggested_update_target = "//lib/tests/write_source_files:macro_smoke_test",
|
||||
visibility = ["//visibility:public"],
|
||||
additional_update_targets = [
|
||||
"//lib/tests/write_source_files/subdir/subsubdir:macro_smoke_test",
|
||||
],
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
console.log("c*");
|
|
@ -0,0 +1,16 @@
|
|||
load("//lib:write_source_files.bzl", "write_source_files")
|
||||
|
||||
genrule(
|
||||
name = "d-desired",
|
||||
outs = ["d-desired.js"],
|
||||
cmd = "echo 'console.log(\"d*\");' > $@",
|
||||
)
|
||||
|
||||
write_source_files(
|
||||
name = "macro_smoke_test",
|
||||
files = {
|
||||
"d.js": ":d-desired",
|
||||
},
|
||||
suggested_update_target = "//lib/tests/write_source_files:macro_smoke_test",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
console.log("d*");
|
|
@ -11,7 +11,7 @@ _write_source_files = rule(
|
|||
executable = True,
|
||||
)
|
||||
|
||||
def write_source_files(name, files, **kwargs):
|
||||
def write_source_files(name, files, additional_update_targets = [], suggested_update_target = None, **kwargs):
|
||||
"""Write to one or more files in the source tree. Stamp out tests that ensure the files exists and are up to date.
|
||||
|
||||
Usage:
|
||||
|
@ -32,28 +32,61 @@ def write_source_files(name, files, **kwargs):
|
|||
bazel run //:write_foobar
|
||||
```
|
||||
|
||||
A test will fail if the source file doesn't exist
|
||||
```bash
|
||||
bazel test //...
|
||||
A test will fail if the source file doesn't exist or if it's out of date with instructions on how to create/update it.
|
||||
|
||||
//:foobar.json does not exist. To create & update this file, run:
|
||||
You can declare a tree of generated source file targets:
|
||||
|
||||
bazel run //:write_foobar
|
||||
```starlark
|
||||
load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
|
||||
|
||||
write_source_files(
|
||||
name = "write_all",
|
||||
additional_update_targets = [
|
||||
# Other write_source_files targets to run when this target is run
|
||||
"//a/b/c:write_foo",
|
||||
"//a/b:write_bar",
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
...or if it's out of date.
|
||||
And update them with a single run:
|
||||
|
||||
```bash
|
||||
bazel test //...
|
||||
|
||||
//:foobar.json is out-of-date. To update this file, run:
|
||||
|
||||
bazel run //:write_foobar
|
||||
bazel run //:write_all
|
||||
```
|
||||
|
||||
When a file is out of date, you can leave a suggestion to run a target further up in the tree by specifying `suggested_update_target`. E.g.,
|
||||
|
||||
```starlark
|
||||
write_source_files(
|
||||
name = "write_foo",
|
||||
files = {
|
||||
"foo.json": ":generated-foo",
|
||||
},
|
||||
suggested_update_target = "//:write_all"
|
||||
)
|
||||
```
|
||||
|
||||
A test failure from foo.json being out of date will yield the following message:
|
||||
|
||||
```
|
||||
//a/b:c:foo.json is out of date. To update this and other generated files, run:
|
||||
|
||||
bazel run //:write_all
|
||||
|
||||
To update *only* this file, run:
|
||||
|
||||
bazel run //a/b/c:write_foo
|
||||
```
|
||||
|
||||
If you have many sources that you want to update as a group, we recommend wrapping write_source_files in a macro that defaults `suggested_update_target` to the umbrella update target.
|
||||
|
||||
Args:
|
||||
name: Name of the executable target that creates or updates the source file
|
||||
files: A dict where the keys are source files to write to and the values are labels pointing to the desired content.
|
||||
Source files must be within the same bazel package as the target.
|
||||
additional_update_targets: (Optional) List of other write_source_files targets to update in the same run
|
||||
suggested_update_target: (Optional) Label of the write_source_files target to suggest running when files are out of date
|
||||
**kwargs: Other common named parameters such as `tags` or `visibility`
|
||||
"""
|
||||
|
||||
|
@ -65,6 +98,7 @@ def write_source_files(name, files, **kwargs):
|
|||
name = name,
|
||||
in_files = in_files,
|
||||
out_files = out_files,
|
||||
additional_update_targets = additional_update_targets,
|
||||
is_windows = select({
|
||||
"@bazel_tools//src/conditions:host_windows": True,
|
||||
"//conditions:default": False,
|
||||
|
@ -88,34 +122,64 @@ def write_source_files(name, files, **kwargs):
|
|||
name_test = "%s_%d_test" % (name, i)
|
||||
|
||||
if out_file_missing:
|
||||
if suggested_update_target == None:
|
||||
message = """
|
||||
|
||||
%s does not exist. To create & update this file, run:
|
||||
|
||||
bazel run //%s:%s
|
||||
|
||||
""" % (out_file, native.package_name(), name)
|
||||
else:
|
||||
message = """
|
||||
|
||||
%s does not exist. To create & update this and other generated files, run:
|
||||
|
||||
bazel run %s
|
||||
|
||||
To create an update *only* this file, run:
|
||||
|
||||
bazel run //%s:%s
|
||||
|
||||
""" % (out_file, _to_label(suggested_update_target), native.package_name(), name)
|
||||
|
||||
# Stamp out a test that fails with a helpful message when the source file doesn't exist.
|
||||
# Note that we cannot simply call fail() here since it will fail during the analysis
|
||||
# phase and prevent the user from calling bazel run //update/the:file.
|
||||
fail_with_message_test(
|
||||
name = name_test,
|
||||
message = """
|
||||
|
||||
%s does not exist. To create & update this file, run:
|
||||
|
||||
bazel run //%s:%s
|
||||
|
||||
""" % (out_file, native.package_name(), name),
|
||||
message = message,
|
||||
visibility = kwargs.get("visibility"),
|
||||
tags = kwargs.get("tags"),
|
||||
)
|
||||
else:
|
||||
if suggested_update_target == None:
|
||||
message = """
|
||||
|
||||
%s is out of date. To update this file, run:
|
||||
|
||||
bazel run //%s:%s
|
||||
|
||||
""" % (out_file, native.package_name(), name)
|
||||
else:
|
||||
message = """
|
||||
|
||||
%s is out of date. To update this and other generated files, run:
|
||||
|
||||
bazel run %s
|
||||
|
||||
To update *only* this file, run:
|
||||
|
||||
bazel run //%s:%s
|
||||
|
||||
""" % (out_file, _to_label(suggested_update_target), native.package_name(), name)
|
||||
|
||||
# Stamp out a diff test the check that the source file is up to date
|
||||
_diff_test(
|
||||
name = name_test,
|
||||
file1 = in_files[i],
|
||||
file2 = out_file,
|
||||
failure_message = """
|
||||
|
||||
%s is out-of-date. To update this file, run:
|
||||
|
||||
bazel run //%s:%s
|
||||
|
||||
""" % (out_file, native.package_name(), name),
|
||||
failure_message = message,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue