2
0
Fork 0
mirror of https://github.com/bazel-contrib/bazel-lib synced 2024-11-30 01:41:21 +00:00
bazel-lib/lib/write_source_files.bzl

132 lines
4.1 KiB
Python
Raw Normal View History

2022-01-28 02:15:28 +00:00
"Public API for write_source_files"
load("//lib/private:write_source_files.bzl", _lib = "write_source_files_lib")
load("//lib:utils.bzl", _to_label = "to_label")
load("@bazel_skylib//rules:diff_test.bzl", _diff_test = "diff_test")
load("//lib/private:fail_with_message_test.bzl", "fail_with_message_test")
_write_source_files = rule(
attrs = _lib.attrs,
implementation = _lib.implementation,
executable = True,
)
def write_source_files(name, files, **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:
```starlark
load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
write_source_files(
name = "write_foobar",
files = {
"foobar.json": "//some/generated:file",
},
)
```
To update the source file, run:
```bash
bazel run //:write_foobar
```
A test will fail if the source file doesn't exist
```bash
bazel test //...
//:foobar.json does not exist. To create & update this file, run:
bazel run //:write_foobar
```
...or if it's out of date.
```bash
bazel test //...
//:foobar.json is out-of-date. To update this file, run:
bazel run //:write_foobar
```
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.
**kwargs: Other common named parameters such as `tags` or `visibility`
"""
out_files = files.keys()
in_files = [files[f] for f in out_files]
# Stamp an executable rule that writes to the out file
_write_source_files(
name = name,
in_files = in_files,
out_files = out_files,
is_windows = select({
"@bazel_tools//src/conditions:host_windows": True,
"//conditions:default": False,
}),
visibility = kwargs.get("visibility"),
tags = kwargs.get("tags"),
)
# Fail if user passes args that would conflict with stamped out targets below
if kwargs.pop("file1", None) != None:
fail("file1 not a valid parameter in write_source_file")
if kwargs.pop("file2", None) != None:
fail("file2 not a valid parameter in write_source_file")
if kwargs.pop("failure_message", None) != None:
fail("failure_message not a valid parameter in write_source_file")
for i in range(len(out_files)):
out_file = _to_label(out_files[i])
out_file_missing = _is_file_missing(out_file)
name_test = "%s_%d_test" % (name, i)
if out_file_missing:
# 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),
visibility = kwargs.get("visibility"),
tags = kwargs.get("tags"),
)
else:
# 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),
**kwargs
)
def _is_file_missing(label):
"""Check if a file is missing by passing its relative path through a glob()
Args
label: the file's label
"""
file_abs = "%s/%s" % (label.package, label.name)
file_rel = file_abs[len(native.package_name()) + 1:]
file_glob = native.glob([file_rel])
return len(file_glob) == 0