Skip to content

Commit

Permalink
feat: allow write_source_file(s) to write source files to bazel packa…
Browse files Browse the repository at this point in the history
…ges outside of the target's package (#717)
  • Loading branch information
gregmagolan authored Jan 10, 2024
1 parent 57bcf28 commit 5b3b7d7
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 16 deletions.
10 changes: 6 additions & 4 deletions docs/write_source_files.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 21 additions & 9 deletions lib/private/write_source_file.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def write_source_file(
additional_update_targets = [],
suggested_update_target = None,
diff_test = True,
check_that_out_file_exists = True,
**kwargs):
"""Write a file or directory to the source tree.
Expand All @@ -34,7 +35,10 @@ def write_source_file(
This is typically a file or directory output of another target. If `in_file` is a directory then entire directory contents are copied.
out_file: The file or directory to write to in the source tree. Must be within the same containing Bazel package as this target.
out_file: The file or directory to write to in the source tree.
The output file or directory must be within the same containing Bazel package as this target if `check_that_out_file_exists` is `True`.
See `check_that_out_file_exists` docstring for more info.
executable: Whether source tree file or files within the source tree directory written should be made executable.
Expand All @@ -44,6 +48,11 @@ def write_source_file(
diff_test: Test that the source tree file or directory exist and is up to date.
check_that_out_file_exists: Test that the output file exists and print a helpful error message if it doesn't.
If `True`, the output file or directory must be in the same containing Bazel package as the target since the underlying mechanism
for this check is limited to files in the same Bazel package.
**kwargs: Other common named parameters such as `tags` or `visibility`
Returns:
Expand All @@ -63,14 +72,15 @@ def write_source_file(
if utils.is_external_label(out_file):
msg = "out file {} must be in the user workspace".format(out_file)
fail(msg)
if out_file.package != native.package_name():
msg = "out file {} (in package '{}') must be a source file within the target's package: '{}'".format(out_file, out_file.package, native.package_name())

if check_that_out_file_exists and out_file.package != native.package_name():
msg = "out file {} (in package '{}') must be a source file within the target's package: '{}'; set check_that_out_file_exists to False to work-around this requirement".format(out_file, out_file.package, native.package_name())
fail(msg)

_write_source_file(
name = name,
in_file = in_file,
out_file = out_file.name if out_file else None,
out_file = str(out_file) if out_file else None,
executable = executable,
additional_update_targets = additional_update_targets,
**kwargs
Expand All @@ -79,7 +89,7 @@ def write_source_file(
if not in_file or not out_file or not diff_test:
return None

out_file_missing = _is_file_missing(out_file)
out_file_missing = check_that_out_file_exists and _is_file_missing(out_file)
test_target_name = "%s_test" % name

if out_file_missing:
Expand Down Expand Up @@ -304,15 +314,17 @@ if exist "%in%\\*" (
def _write_source_file_impl(ctx):
is_windows = ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo])

if ctx.attr.out_file and not ctx.attr.in_file:
out_file = Label(ctx.attr.out_file) if ctx.attr.out_file else None

if out_file and not ctx.attr.in_file:
fail("in_file must be specified if out_file is set")
if ctx.attr.in_file and not ctx.attr.out_file:
if ctx.attr.in_file and not out_file:
fail("out_file must be specified if in_file is set")

paths = []
runfiles = []

if ctx.attr.in_file and ctx.attr.out_file:
if ctx.attr.in_file and out_file:
if DirectoryPathInfo in ctx.attr.in_file:
in_path = "/".join([
ctx.attr.in_file[DirectoryPathInfo].directory.short_path,
Expand All @@ -328,7 +340,7 @@ def _write_source_file_impl(ctx):
msg = "in file {} must be a single file or a target that provides a DirectoryPathInfo".format(ctx.attr.in_file.label)
fail(msg)

out_path = "/".join([ctx.label.package, ctx.attr.out_file]) if ctx.label.package else ctx.attr.out_file
out_path = "/".join([out_file.package, out_file.name]) if out_file.package else out_file.name
paths.append((in_path, out_path))

if is_windows:
Expand Down
2 changes: 2 additions & 0 deletions lib/tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ load(":paths_test.bzl", "paths_test_suite")
load(":strings_tests.bzl", "strings_test_suite")
load(":utils_test.bzl", "utils_test_suite")

exports_files(["a.js"])

config_setting(
name = "allow_unresolved_symlinks",
values = {bazel_features.flags.allow_unresolved_symlinks: "true"},
Expand Down
1 change: 1 addition & 0 deletions lib/tests/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("a");
16 changes: 16 additions & 0 deletions lib/tests/write_source_files/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,19 @@ write_source_files(
"skylib_LICENSE": "@bazel_skylib//:LICENSE",
},
)

# Test that we can write to a sub package
write_source_file_test(
name = "a_subpkg_test",
check_that_out_file_exists = False,
in_file = ":a-desired",
out_file = "//lib/tests/write_source_files/subpkg:a.js",
)

# Test that we can write to a parent package
write_source_file_test(
name = "a_parentpkg_test",
check_that_out_file_exists = False,
in_file = ":a-desired",
out_file = "//lib/tests:a.js",
)
1 change: 1 addition & 0 deletions lib/tests/write_source_files/subpkg/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports_files(["a.js"])
1 change: 1 addition & 0 deletions lib/tests/write_source_files/subpkg/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("a");
3 changes: 2 additions & 1 deletion lib/tests/write_source_files/write_source_file_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,15 @@ _write_source_file_test = rule(
test = True,
)

def write_source_file_test(name, in_file, out_file):
def write_source_file_test(name, in_file, out_file, check_that_out_file_exists = True):
"""Stamp a write_source_files executable and a test to run against it"""

_write_source_file(
name = name + "_updater",
in_file = in_file,
out_file = out_file,
diff_test = False,
check_that_out_file_exists = check_that_out_file_exists,
)

# Note that for testing we update the source files in the sandbox,
Expand Down
11 changes: 9 additions & 2 deletions lib/write_source_files.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def write_source_files(
additional_update_targets = [],
suggested_update_target = None,
diff_test = True,
check_that_out_file_exists = True,
**kwargs):
"""Write one or more files and/or directories to the source tree.
Expand Down Expand Up @@ -102,7 +103,8 @@ def write_source_files(
files: A dict where the keys are files or directories in the source tree to write to and the values are labels
pointing to the desired content, typically file or directory outputs of other targets.
Destination files and directories must be within the same containing Bazel package as this target.
Destination files and directories must be within the same containing Bazel package as this target if
`check_that_out_file_exists` is True. See `check_that_out_file_exists` docstring for more info.
executable: Whether source tree files written should be made executable.
Expand All @@ -116,6 +118,11 @@ def write_source_files(
diff_test: Test that the source tree files and/or directories exist and are up to date.
check_that_out_file_exists: Test that each output file exists and print a helpful error message if it doesn't.
If `True`, destination files and directories must be in the same containing Bazel package as the target since the underlying
mechanism for this check is limited to files in the same Bazel package.
**kwargs: Other common named parameters such as `tags` or `visibility`
"""

Expand Down Expand Up @@ -143,6 +150,7 @@ def write_source_files(
additional_update_targets = additional_update_targets if single_update_target else [],
suggested_update_target = this_suggested_update_target,
diff_test = diff_test,
check_that_out_file_exists = check_that_out_file_exists,
**kwargs
)

Expand All @@ -162,7 +170,6 @@ def write_source_files(
name = name,
additional_update_targets = update_targets + additional_update_targets,
suggested_update_target = suggested_update_target,
diff_test = False,
**kwargs
)

Expand Down

0 comments on commit 5b3b7d7

Please sign in to comment.