feat: bulk updates for write_source_files (#40)

This commit is contained in:
Derek Cormier 2022-02-28 15:42:20 -08:00 committed by GitHub
parent 2195e1c69a
commit 71c1b893d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 217 additions and 50 deletions

View File

@ -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 |

View File

@ -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,

View File

@ -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",
],
)

View File

@ -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",
],
)

View File

@ -0,0 +1 @@
console.log("c*");

View File

@ -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"],
)

View File

@ -0,0 +1 @@
console.log("d*");

View File

@ -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
)