Add DefaultOutputPathInfo provider and update write_source_files to accept it (#48)
Also update write_source_files to accept DirectoryPathInfo
This commit is contained in:
parent
7f2641cd07
commit
3b93ee0baa
|
@ -1,5 +1,5 @@
|
||||||
docs/*.md
|
docs/*.md
|
||||||
lib/tests/jq/*.json
|
lib/tests/jq/*.json
|
||||||
lib/tests/write_source_files/a2.js
|
lib/lib/tests/write_source_files/*.js
|
||||||
lib/tests/write_source_files/b2.js
|
lib/lib/tests/write_source_files/subdir/*.js
|
||||||
lib/tests/write_source_files/e_dir/e.js
|
lib/lib/tests/write_source_files/subdir/subsubdir/*.js
|
|
@ -48,6 +48,9 @@ stardoc_with_diff_test(
|
||||||
bzl_library_target = "//lib:directory_path",
|
bzl_library_target = "//lib:directory_path",
|
||||||
)
|
)
|
||||||
|
|
||||||
update_docs(
|
stardoc_with_diff_test(
|
||||||
name = "update",
|
name = "default_info_files",
|
||||||
|
bzl_library_target = "//lib:default_info_files",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
update_docs()
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
<!-- Generated with Stardoc: http://skydoc.bazel.build -->
|
||||||
|
|
||||||
|
A rule that provides file(s) from a given target's DefaultInfo
|
||||||
|
|
||||||
|
|
||||||
|
<a id="#default_info_files"></a>
|
||||||
|
|
||||||
|
## default_info_files
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
default_info_files(<a href="#default_info_files-name">name</a>, <a href="#default_info_files-paths">paths</a>, <a href="#default_info_files-target">target</a>)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
A rule that provides file(s) from a given target's DefaultInfo
|
||||||
|
|
||||||
|
**ATTRIBUTES**
|
||||||
|
|
||||||
|
|
||||||
|
| Name | Description | Type | Mandatory | Default |
|
||||||
|
| :------------- | :------------- | :------------- | :------------- | :------------- |
|
||||||
|
| <a id="default_info_files-name"></a>name | A unique name for this target. | <a href="https://bazel.build/docs/build-ref.html#name">Name</a> | required | |
|
||||||
|
| <a id="default_info_files-paths"></a>paths | the paths of the files to provide in the DefaultInfo of the target relative to its root | List of strings | required | |
|
||||||
|
| <a id="default_info_files-target"></a>target | the target to look in for requested paths in its' DefaultInfo | <a href="https://bazel.build/docs/build-ref.html#labels">Label</a> | required | |
|
||||||
|
|
||||||
|
|
||||||
|
<a id="#make_default_info_files"></a>
|
||||||
|
|
||||||
|
## make_default_info_files
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
make_default_info_files(<a href="#make_default_info_files-name">name</a>, <a href="#make_default_info_files-target">target</a>, <a href="#make_default_info_files-paths">paths</a>)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
Helper function to generate a default_info_files target and return its label.
|
||||||
|
|
||||||
|
**PARAMETERS**
|
||||||
|
|
||||||
|
|
||||||
|
| Name | Description | Default Value |
|
||||||
|
| :------------- | :------------- | :------------- |
|
||||||
|
| <a id="make_default_info_files-name"></a>name | unique name for the generated <code>default_info_files</code> target. | none |
|
||||||
|
| <a id="make_default_info_files-target"></a>target | the target to look in for requested paths in its' DefaultInfo | none |
|
||||||
|
| <a id="make_default_info_files-paths"></a>paths | the paths of the files to provide in the DefaultInfo of the target relative to its root | none |
|
||||||
|
|
||||||
|
**RETURNS**
|
||||||
|
|
||||||
|
The label `name`
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ Joins a label pointing to a TreeArtifact with a path nested within that director
|
||||||
make_directory_path(<a href="#make_directory_path-name">name</a>, <a href="#make_directory_path-directory">directory</a>, <a href="#make_directory_path-path">path</a>)
|
make_directory_path(<a href="#make_directory_path-name">name</a>, <a href="#make_directory_path-directory">directory</a>, <a href="#make_directory_path-path">path</a>)
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
Helper function to convert generate a directory_path target and return its label.
|
Helper function to generate a directory_path target and return its label.
|
||||||
|
|
||||||
**PARAMETERS**
|
**PARAMETERS**
|
||||||
|
|
||||||
|
|
|
@ -87,8 +87,8 @@ If you have many sources that you want to update as a group, we recommend wrappi
|
||||||
| :------------- | :------------- | :------------- |
|
| :------------- | :------------- | :------------- |
|
||||||
| <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-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 or folders to write to and the values are labels pointing to the desired content. Sources must be within the same bazel package as the target. | <code>{}</code> |
|
| <a id="write_source_files-files"></a>files | A dict where the keys are source files or folders to write to and the values are labels pointing to the desired content. Sources must be within the same bazel package as the target. | <code>{}</code> |
|
||||||
| <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-additional_update_targets"></a>additional_update_targets | (Optional) List of other write_source_file or other executable updater targets to call 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-suggested_update_target"></a>suggested_update_target | (Optional) Label of the write_source_file 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 |
|
| <a id="write_source_files-kwargs"></a>kwargs | Other common named parameters such as <code>tags</code> or <code>visibility</code> | none |
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,13 @@ bzl_library(
|
||||||
deps = ["//lib/private:directory_path"],
|
deps = ["//lib/private:directory_path"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "default_info_files",
|
||||||
|
srcs = ["default_info_files.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//lib/private:default_info_files"],
|
||||||
|
)
|
||||||
|
|
||||||
bzl_library(
|
bzl_library(
|
||||||
name = "copy_to_directory",
|
name = "copy_to_directory",
|
||||||
srcs = ["copy_to_directory.bzl"],
|
srcs = ["copy_to_directory.bzl"],
|
||||||
|
@ -80,9 +87,16 @@ bzl_library(
|
||||||
srcs = ["write_source_files.bzl"],
|
srcs = ["write_source_files.bzl"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
|
":diff_test",
|
||||||
":utils",
|
":utils",
|
||||||
"//lib/private:fail_with_message_test",
|
"//lib/private:fail_with_message_test",
|
||||||
"//lib/private:write_source_files",
|
"//lib/private:write_source_file",
|
||||||
"@bazel_skylib//rules:diff_test",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "diff_test",
|
||||||
|
srcs = ["diff_test.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//lib/private:diff_test"],
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
"""A rule that provides file(s) from a given target's DefaultInfo
|
||||||
|
"""
|
||||||
|
|
||||||
|
load(
|
||||||
|
"//lib/private:default_info_files.bzl",
|
||||||
|
_default_info_files = "default_info_files",
|
||||||
|
_make_default_info_files = "make_default_info_files",
|
||||||
|
)
|
||||||
|
|
||||||
|
default_info_files = _default_info_files
|
||||||
|
make_default_info_files = _make_default_info_files
|
|
@ -1,17 +1,3 @@
|
||||||
# Copyright 2019 The Bazel Authors. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
"""Rule and corresponding provider that joins a label pointing to a TreeArtifact
|
"""Rule and corresponding provider that joins a label pointing to a TreeArtifact
|
||||||
with a path nested within that directory
|
with a path nested within that directory
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -10,6 +10,7 @@ bzl_library(
|
||||||
srcs = ["copy_to_directory.bzl"],
|
srcs = ["copy_to_directory.bzl"],
|
||||||
visibility = ["//lib:__subpackages__"],
|
visibility = ["//lib:__subpackages__"],
|
||||||
deps = [
|
deps = [
|
||||||
|
":default_info_files",
|
||||||
":directory_path",
|
":directory_path",
|
||||||
":paths",
|
":paths",
|
||||||
"@bazel_skylib//lib:paths",
|
"@bazel_skylib//lib:paths",
|
||||||
|
@ -61,10 +62,14 @@ bzl_library(
|
||||||
)
|
)
|
||||||
|
|
||||||
bzl_library(
|
bzl_library(
|
||||||
name = "write_source_files",
|
name = "write_source_file",
|
||||||
srcs = ["write_source_files.bzl"],
|
srcs = ["write_source_file.bzl"],
|
||||||
visibility = ["//lib:__subpackages__"],
|
visibility = ["//lib:__subpackages__"],
|
||||||
deps = ["//lib:utils"],
|
deps = [
|
||||||
|
":default_info_files",
|
||||||
|
":directory_path",
|
||||||
|
"//lib:utils",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
bzl_library(
|
bzl_library(
|
||||||
|
@ -79,3 +84,16 @@ bzl_library(
|
||||||
visibility = ["//lib:__subpackages__"],
|
visibility = ["//lib:__subpackages__"],
|
||||||
deps = ["//lib:utils"],
|
deps = ["//lib:utils"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "default_info_files",
|
||||||
|
srcs = ["default_info_files.bzl"],
|
||||||
|
visibility = ["//lib:__subpackages__"],
|
||||||
|
deps = ["//lib:utils"],
|
||||||
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "diff_test",
|
||||||
|
srcs = ["diff_test.bzl"],
|
||||||
|
visibility = ["//lib:__subpackages__"],
|
||||||
|
)
|
||||||
|
|
|
@ -127,7 +127,7 @@ def _copy_file_impl(ctx):
|
||||||
src_path = "/".join([src_file.path, ctx.attr.src[DirectoryPathInfo].path])
|
src_path = "/".join([src_file.path, ctx.attr.src[DirectoryPathInfo].path])
|
||||||
else:
|
else:
|
||||||
if len(ctx.files.src) != 1:
|
if len(ctx.files.src) != 1:
|
||||||
fail("src must be a single file or a target with a DirectoryPathInfo provider")
|
fail("src must be a single file or a target that provides a DirectoryPathInfo")
|
||||||
src_file = ctx.files.src[0]
|
src_file = ctx.files.src[0]
|
||||||
src_path = src_file.path
|
src_path = src_file.path
|
||||||
if ctx.attr.is_windows:
|
if ctx.attr.is_windows:
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
"""default_info_files implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
load("//lib:utils.bzl", _to_label = "to_label")
|
||||||
|
|
||||||
|
def _default_info_files(ctx):
|
||||||
|
files = []
|
||||||
|
for path in ctx.attr.paths:
|
||||||
|
file = find_short_path_in_default_info(
|
||||||
|
ctx.attr.target,
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
if not file:
|
||||||
|
fail("%s file not found within the DefaultInfo of %s" % (ctx.attr.path, ctx.attr.target))
|
||||||
|
files.append(file)
|
||||||
|
return [DefaultInfo(
|
||||||
|
files = depset(direct = files),
|
||||||
|
runfiles = ctx.runfiles(files = files),
|
||||||
|
)]
|
||||||
|
|
||||||
|
default_info_files = rule(
|
||||||
|
doc = "A rule that provides file(s) from a given target's DefaultInfo",
|
||||||
|
implementation = _default_info_files,
|
||||||
|
attrs = {
|
||||||
|
"target": attr.label(
|
||||||
|
doc = "the target to look in for requested paths in its' DefaultInfo",
|
||||||
|
mandatory = True,
|
||||||
|
),
|
||||||
|
"paths": attr.string_list(
|
||||||
|
doc = "the paths of the files to provide in the DefaultInfo of the target relative to its root",
|
||||||
|
mandatory = True,
|
||||||
|
allow_empty = False,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
provides = [DefaultInfo],
|
||||||
|
)
|
||||||
|
|
||||||
|
def make_default_info_files(name, target, paths):
|
||||||
|
"""Helper function to generate a default_info_files target and return its label.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: unique name for the generated `default_info_files` target.
|
||||||
|
target: the target to look in for requested paths in its' DefaultInfo
|
||||||
|
paths: the paths of the files to provide in the DefaultInfo of the target relative to its root
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The label `name`
|
||||||
|
"""
|
||||||
|
default_info_files(
|
||||||
|
name = name,
|
||||||
|
target = target,
|
||||||
|
paths = paths,
|
||||||
|
)
|
||||||
|
return _to_label(name)
|
||||||
|
|
||||||
|
def find_short_path_in_default_info(default_info, short_path):
|
||||||
|
"""Helper function find a file in a DefaultInfo by short path
|
||||||
|
|
||||||
|
Args:
|
||||||
|
default_info: a DefaultInfo
|
||||||
|
short_path: the short path (path relative to root) to search for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The File if found else None
|
||||||
|
"""
|
||||||
|
if default_info.files:
|
||||||
|
for file in default_info.files.to_list():
|
||||||
|
if file.short_path == short_path:
|
||||||
|
return file
|
||||||
|
return None
|
|
@ -18,6 +18,8 @@ The rule uses a Bash command (diff) on Linux/macOS/non-Windows, and a cmd.exe
|
||||||
command (fc.exe) on Windows (no Bash is required).
|
command (fc.exe) on Windows (no Bash is required).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
load(":directory_path.bzl", "DirectoryPathInfo")
|
||||||
|
|
||||||
def _runfiles_path(f):
|
def _runfiles_path(f):
|
||||||
if f.root.path:
|
if f.root.path:
|
||||||
return f.path[len(f.root.path) + 1:] # generated file
|
return f.path[len(f.root.path) + 1:] # generated file
|
||||||
|
@ -25,6 +27,24 @@ def _runfiles_path(f):
|
||||||
return f.path # source file
|
return f.path # source file
|
||||||
|
|
||||||
def _diff_test_impl(ctx):
|
def _diff_test_impl(ctx):
|
||||||
|
if DirectoryPathInfo in ctx.attr.file1:
|
||||||
|
file1 = ctx.attr.file1[DirectoryPathInfo].directory
|
||||||
|
file1_path = "/".join([_runfiles_path(file1), ctx.attr.file1[DirectoryPathInfo].path])
|
||||||
|
else:
|
||||||
|
if len(ctx.files.file1) != 1:
|
||||||
|
fail("file1 must be a single file or a target that provides a DirectoryPathInfo")
|
||||||
|
file1 = ctx.files.file1[0]
|
||||||
|
file1_path = _runfiles_path(file1)
|
||||||
|
|
||||||
|
if DirectoryPathInfo in ctx.attr.file2:
|
||||||
|
file2 = ctx.attr.file2[DirectoryPathInfo].directory
|
||||||
|
file2_path = "/".join([_runfiles_path(file2), ctx.attr.file2[DirectoryPathInfo].path])
|
||||||
|
else:
|
||||||
|
if len(ctx.files.file2) != 1:
|
||||||
|
fail("file2 must be a single file or a target that provides a DirectoryPathInfo")
|
||||||
|
file2 = ctx.files.file2[0]
|
||||||
|
file2_path = _runfiles_path(file2)
|
||||||
|
|
||||||
if ctx.attr.is_windows:
|
if ctx.attr.is_windows:
|
||||||
test_bin = ctx.actions.declare_file(ctx.label.name + "-test.bat")
|
test_bin = ctx.actions.declare_file(ctx.label.name + "-test.bat")
|
||||||
ctx.actions.write(
|
ctx.actions.write(
|
||||||
|
@ -138,8 +158,8 @@ exit /b 0
|
||||||
exit /b 1
|
exit /b 1
|
||||||
""".format(
|
""".format(
|
||||||
fail_msg = ctx.attr.failure_message,
|
fail_msg = ctx.attr.failure_message,
|
||||||
file1 = _runfiles_path(ctx.file.file1),
|
file1 = file1_path,
|
||||||
file2 = _runfiles_path(ctx.file.file2),
|
file2 = file2_path,
|
||||||
),
|
),
|
||||||
is_executable = True,
|
is_executable = True,
|
||||||
)
|
)
|
||||||
|
@ -191,26 +211,26 @@ else
|
||||||
fi
|
fi
|
||||||
""".format(
|
""".format(
|
||||||
fail_msg = ctx.attr.failure_message,
|
fail_msg = ctx.attr.failure_message,
|
||||||
file1 = _runfiles_path(ctx.file.file1),
|
file1 = file1_path,
|
||||||
file2 = _runfiles_path(ctx.file.file2),
|
file2 = file2_path,
|
||||||
),
|
),
|
||||||
is_executable = True,
|
is_executable = True,
|
||||||
)
|
)
|
||||||
return DefaultInfo(
|
return DefaultInfo(
|
||||||
executable = test_bin,
|
executable = test_bin,
|
||||||
files = depset(direct = [test_bin]),
|
files = depset(direct = [test_bin]),
|
||||||
runfiles = ctx.runfiles(files = [test_bin, ctx.file.file1, ctx.file.file2]),
|
runfiles = ctx.runfiles(files = [test_bin, file1, file2]),
|
||||||
)
|
)
|
||||||
|
|
||||||
_diff_test = rule(
|
_diff_test = rule(
|
||||||
attrs = {
|
attrs = {
|
||||||
"failure_message": attr.string(),
|
"failure_message": attr.string(),
|
||||||
"file1": attr.label(
|
"file1": attr.label(
|
||||||
allow_single_file = True,
|
allow_files = True,
|
||||||
mandatory = True,
|
mandatory = True,
|
||||||
),
|
),
|
||||||
"file2": attr.label(
|
"file2": attr.label(
|
||||||
allow_single_file = True,
|
allow_files = True,
|
||||||
mandatory = True,
|
mandatory = True,
|
||||||
),
|
),
|
||||||
"is_windows": attr.bool(mandatory = True),
|
"is_windows": attr.bool(mandatory = True),
|
||||||
|
|
|
@ -33,10 +33,11 @@ Otherwise there is no way to give a Bazel label for it.""",
|
||||||
mandatory = True,
|
mandatory = True,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
provides = [DirectoryPathInfo],
|
||||||
)
|
)
|
||||||
|
|
||||||
def make_directory_path(name, directory, path):
|
def make_directory_path(name, directory, path):
|
||||||
"""Helper function to convert generate a directory_path target and return its label.
|
"""Helper function to generate a directory_path target and return its label.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: Unique name for the generated `directory_path` target.
|
name: Unique name for the generated `directory_path` target.
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
"write_source_file implementation"
|
||||||
|
|
||||||
|
load("//lib:utils.bzl", "is_external_label")
|
||||||
|
load(":directory_path.bzl", "DirectoryPathInfo")
|
||||||
|
|
||||||
|
_write_source_file_attrs = {
|
||||||
|
"in_file": attr.label(allow_files = True, mandatory = False),
|
||||||
|
"out_file": attr.label(allow_files = True, mandatory = False),
|
||||||
|
"additional_update_targets": attr.label_list(cfg = "host", mandatory = False),
|
||||||
|
"is_windows": attr.bool(mandatory = True),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _write_source_file_sh(ctx, paths):
|
||||||
|
updater = ctx.actions.declare_file(
|
||||||
|
ctx.label.name + "_update.sh",
|
||||||
|
)
|
||||||
|
|
||||||
|
additional_update_scripts = []
|
||||||
|
for target in ctx.attr.additional_update_targets:
|
||||||
|
if target[DefaultInfo].files_to_run and target[DefaultInfo].files_to_run.executable:
|
||||||
|
additional_update_scripts.append(target[DefaultInfo].files_to_run.executable)
|
||||||
|
else:
|
||||||
|
fail("additional_update_targets target %s does not provide an executable")
|
||||||
|
|
||||||
|
contents = ["""#!/usr/bin/env bash
|
||||||
|
set -o errexit -o nounset -o pipefail
|
||||||
|
runfiles_dir=$PWD
|
||||||
|
# BUILD_WORKSPACE_DIRECTORY not set when running as a test, uses the sandbox instead
|
||||||
|
if [[ ! -z "${BUILD_WORKSPACE_DIRECTORY:-}" ]]; then
|
||||||
|
cd "$BUILD_WORKSPACE_DIRECTORY"
|
||||||
|
fi"""]
|
||||||
|
|
||||||
|
for in_path, out_path in paths:
|
||||||
|
contents.append("""
|
||||||
|
in=$runfiles_dir/{in_path}
|
||||||
|
out={out_path}
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$out")"
|
||||||
|
echo "Copying $in to $out in $PWD"
|
||||||
|
|
||||||
|
if [[ -f "$in" ]]; then
|
||||||
|
cp -f "$in" "$out"
|
||||||
|
chmod 664 "$out"
|
||||||
|
else
|
||||||
|
mkdir -p "$out"
|
||||||
|
cp -rf "$in"/* "$out"
|
||||||
|
chmod 664 "$out"/*
|
||||||
|
fi
|
||||||
|
""".format(in_path = in_path, out_path = out_path))
|
||||||
|
|
||||||
|
contents.extend([
|
||||||
|
"cd \"$runfiles_dir\"",
|
||||||
|
"# Run the update scripts for all write_source_file deps",
|
||||||
|
])
|
||||||
|
for update_script in additional_update_scripts:
|
||||||
|
contents.append("\"{update_script}\"".format(update_script = update_script.short_path))
|
||||||
|
|
||||||
|
ctx.actions.write(
|
||||||
|
output = updater,
|
||||||
|
is_executable = True,
|
||||||
|
content = "\n".join(contents),
|
||||||
|
)
|
||||||
|
|
||||||
|
return updater
|
||||||
|
|
||||||
|
def _write_source_file_bat(ctx, paths):
|
||||||
|
updater = ctx.actions.declare_file(
|
||||||
|
ctx.label.name + "_update.bat",
|
||||||
|
)
|
||||||
|
|
||||||
|
additional_update_scripts = []
|
||||||
|
for target in ctx.attr.additional_update_targets:
|
||||||
|
if target[DefaultInfo].files_to_run and target[DefaultInfo].files_to_run.executable:
|
||||||
|
additional_update_scripts.append(target[DefaultInfo].files_to_run.executable)
|
||||||
|
else:
|
||||||
|
fail("additional_update_targets target %s does not provide an executable")
|
||||||
|
|
||||||
|
contents = ["""@rem Generated by write_source_file.bzl, do not edit.
|
||||||
|
@echo off
|
||||||
|
set runfiles_dir=%cd%
|
||||||
|
if defined BUILD_WORKSPACE_DIRECTORY (
|
||||||
|
cd %BUILD_WORKSPACE_DIRECTORY%
|
||||||
|
)"""]
|
||||||
|
|
||||||
|
for in_path, out_path in paths:
|
||||||
|
contents.append("""
|
||||||
|
set in=%runfiles_dir%\\{in_path}
|
||||||
|
set out={out_path}
|
||||||
|
|
||||||
|
if not defined BUILD_WORKSPACE_DIRECTORY (
|
||||||
|
@rem Because there's no sandboxing in windows, if we copy over the target
|
||||||
|
@rem file's symlink it will get copied back into the source directory
|
||||||
|
@rem during tests. Work around this in tests by deleting the target file
|
||||||
|
@rem symlink before copying over it.
|
||||||
|
del %out%
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Copying %in% to %out% in %cd%
|
||||||
|
|
||||||
|
if exist "%in%\\*" (
|
||||||
|
mkdir "%out%" >NUL 2>NUL
|
||||||
|
robocopy "%in%" "%out%" /E >NUL
|
||||||
|
) else (
|
||||||
|
copy %in% %out% >NUL
|
||||||
|
)
|
||||||
|
""".format(in_path = in_path.replace("/", "\\"), out_path = out_path.replace("/", "\\")))
|
||||||
|
|
||||||
|
contents.extend([
|
||||||
|
"cd %runfiles_dir%",
|
||||||
|
"@rem Run the update scripts for all write_source_file deps",
|
||||||
|
])
|
||||||
|
for update_script in additional_update_scripts:
|
||||||
|
contents.append("call {update_script".format(update_script = update_script.short_path))
|
||||||
|
|
||||||
|
ctx.actions.write(
|
||||||
|
output = updater,
|
||||||
|
is_executable = True,
|
||||||
|
context = "\n".join(contents).replace("\n", "\r\n"),
|
||||||
|
)
|
||||||
|
return updater
|
||||||
|
|
||||||
|
def _write_source_file_impl(ctx):
|
||||||
|
if ctx.attr.out_file:
|
||||||
|
if not ctx.attr.in_file:
|
||||||
|
fail("in_file must be specified if out_file is set")
|
||||||
|
if is_external_label(ctx.attr.out_file.label):
|
||||||
|
fail("out file %s must be in the user workspace" % ctx.attr.out_file.label)
|
||||||
|
if ctx.attr.out_file.label.package != ctx.label.package:
|
||||||
|
fail("out file %s (in package '%s') must be a source file within the target's package: '%s'" % (ctx.attr.out_file.label, ctx.attr.out_file.label.package, ctx.label.package))
|
||||||
|
|
||||||
|
if ctx.attr.in_file and not ctx.attr.out_file:
|
||||||
|
if not ctx.attr.in_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 DirectoryPathInfo in ctx.attr.in_file:
|
||||||
|
in_path = "/".join([
|
||||||
|
ctx.attr.in_file[DirectoryPathInfo].directory.short_path,
|
||||||
|
ctx.attr.in_file[DirectoryPathInfo].path,
|
||||||
|
])
|
||||||
|
runfiles.append(ctx.attr.in_file[DirectoryPathInfo].directory)
|
||||||
|
elif len(ctx.files.in_file) == 0:
|
||||||
|
fail("in file %s must provide files" % ctx.attr.in_file.label)
|
||||||
|
elif len(ctx.files.in_file) == 1:
|
||||||
|
in_path = ctx.files.in_file[0].short_path
|
||||||
|
else:
|
||||||
|
fail("in file %s must be a single file or a target that provides DefaultOutputPathInfo or DirectoryPathInfo" % ctx.attr.in_file.label)
|
||||||
|
|
||||||
|
if len(ctx.files.out_file) != 1:
|
||||||
|
fail("out file %s must be a single file or directory" % ctx.attr.out_file.label)
|
||||||
|
elif not ctx.files.out_file[0].is_source:
|
||||||
|
fail("out file %s must be a source file or directory, not a generated file" % ctx.attr.out_file.label)
|
||||||
|
|
||||||
|
out_path = ctx.files.out_file[0].short_path
|
||||||
|
paths.append((in_path, out_path))
|
||||||
|
|
||||||
|
if ctx.attr.is_windows:
|
||||||
|
updater = _write_source_file_bat(ctx, paths)
|
||||||
|
else:
|
||||||
|
updater = _write_source_file_sh(ctx, paths)
|
||||||
|
|
||||||
|
runfiles = ctx.runfiles(
|
||||||
|
files = runfiles,
|
||||||
|
transitive_files = ctx.attr.in_file.files if ctx.attr.in_file else None,
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
write_source_file_lib = struct(
|
||||||
|
attrs = _write_source_file_attrs,
|
||||||
|
implementation = _write_source_file_impl,
|
||||||
|
)
|
|
@ -1,161 +0,0 @@
|
||||||
"write_source_file implementation"
|
|
||||||
|
|
||||||
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 = True, mandatory = False),
|
|
||||||
"out_files": attr.label_list(allow_files = True, allow_empty = True, mandatory = False),
|
|
||||||
"additional_update_targets": attr.label_list(allow_files = False, providers = [_WriteSourceFilesInfo], mandatory = False),
|
|
||||||
"is_windows": attr.bool(mandatory = True),
|
|
||||||
}
|
|
||||||
|
|
||||||
def _write_source_files_sh(ctx):
|
|
||||||
updater = ctx.actions.declare_file(
|
|
||||||
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,
|
|
||||||
content = """
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -o errexit -o nounset -o pipefail
|
|
||||||
runfiles_dir=$PWD
|
|
||||||
# BUILD_WORKSPACE_DIRECTORY not set when running as a test, uses the sandbox instead
|
|
||||||
if [[ ! -z "${BUILD_WORKSPACE_DIRECTORY:-}" ]]; then
|
|
||||||
cd "$BUILD_WORKSPACE_DIRECTORY"
|
|
||||||
fi
|
|
||||||
""" + "\n".join([
|
|
||||||
"""
|
|
||||||
in=$runfiles_dir/{in_file}
|
|
||||||
out={out_file}
|
|
||||||
|
|
||||||
mkdir -p "$(dirname "$out")"
|
|
||||||
echo "Copying $in to $out in $PWD"
|
|
||||||
|
|
||||||
if [[ -f "$in" ]]; then
|
|
||||||
cp -f "$in" "$out"
|
|
||||||
chmod 664 "$out"
|
|
||||||
else
|
|
||||||
mkdir -p "$out"
|
|
||||||
cp -rf "$in"/* "$out"
|
|
||||||
chmod 664 "$out"/*
|
|
||||||
fi
|
|
||||||
""".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
|
|
||||||
|
|
||||||
def _write_source_files_bat(ctx):
|
|
||||||
updater = ctx.actions.declare_file(
|
|
||||||
ctx.label.name + "_update.bat",
|
|
||||||
)
|
|
||||||
|
|
||||||
additional_update_scripts = [target[_WriteSourceFilesInfo].executable for target in ctx.attr.additional_update_targets]
|
|
||||||
|
|
||||||
content = """
|
|
||||||
@rem Generated by write_source_files.bzl, do not edit.
|
|
||||||
@echo off
|
|
||||||
set runfiles_dir=%cd%
|
|
||||||
if defined BUILD_WORKSPACE_DIRECTORY (
|
|
||||||
cd %BUILD_WORKSPACE_DIRECTORY%
|
|
||||||
)
|
|
||||||
""" + "\n".join([
|
|
||||||
"""
|
|
||||||
set in=%runfiles_dir%\\{in_file}
|
|
||||||
set out={out_file}
|
|
||||||
|
|
||||||
if not defined BUILD_WORKSPACE_DIRECTORY (
|
|
||||||
@rem Because there's no sandboxing in windows, if we copy over the target
|
|
||||||
@rem file's symlink it will get copied back into the source directory
|
|
||||||
@rem during tests. Work around this in tests by deleting the target file
|
|
||||||
@rem symlink before copying over it.
|
|
||||||
del %out%
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Copying %in% to %out% in %cd%
|
|
||||||
|
|
||||||
if exist "%in%\\*" (
|
|
||||||
mkdir "%out%" >NUL 2>NUL
|
|
||||||
robocopy "%in%" "%out%" /E >NUL
|
|
||||||
) else (
|
|
||||||
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))
|
|
||||||
]) + """
|
|
||||||
cd %runfiles_dir%
|
|
||||||
|
|
||||||
@rem Run the update scripts for all write_source_file deps
|
|
||||||
""" + "\n".join(["""
|
|
||||||
call {update_script}
|
|
||||||
""".format(update_script = update_script.short_path) for update_script in additional_update_scripts])
|
|
||||||
|
|
||||||
content = content.replace("\n", "\r\n")
|
|
||||||
|
|
||||||
ctx.actions.write(
|
|
||||||
output = updater,
|
|
||||||
is_executable = True,
|
|
||||||
content = content,
|
|
||||||
)
|
|
||||||
return updater
|
|
||||||
|
|
||||||
def _write_source_files_impl(ctx):
|
|
||||||
if (len(ctx.attr.in_files) != len(ctx.attr.out_files)):
|
|
||||||
fail("in_files and out_files must be the same length")
|
|
||||||
|
|
||||||
for i in range(len(ctx.attr.in_files)):
|
|
||||||
out_file_label = ctx.attr.out_files[i].label
|
|
||||||
if is_external_label(out_file_label):
|
|
||||||
fail("out file %s must be a source file in the user workspace" % out_file_label)
|
|
||||||
|
|
||||||
if not ctx.files.out_files[i].is_source:
|
|
||||||
fail("out file %s must be a source file, not a generated file" % out_file_label)
|
|
||||||
|
|
||||||
if out_file_label.package != ctx.label.package:
|
|
||||||
fail("out file %s (in package '%s') must be a source file within the target's package: '%s'" % (out_file_label, out_file_label.package, ctx.label.package))
|
|
||||||
|
|
||||||
if ctx.attr.is_windows:
|
|
||||||
updater = _write_source_files_bat(ctx)
|
|
||||||
else:
|
|
||||||
updater = _write_source_files_sh(ctx)
|
|
||||||
|
|
||||||
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,
|
|
||||||
implementation = _write_source_files_impl,
|
|
||||||
)
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
"""A simple rule that generates provides a DefaultOutput with some files"""
|
||||||
|
|
||||||
|
def _impl(ctx):
|
||||||
|
if len(ctx.attr.out_files) != len(ctx.attr.out_contents):
|
||||||
|
fail("Number of out_files must match number of out_contents")
|
||||||
|
outputs = []
|
||||||
|
for i, file in enumerate(ctx.attr.out_files):
|
||||||
|
content = ctx.attr.out_contents[i]
|
||||||
|
out = ctx.actions.declare_file(file)
|
||||||
|
|
||||||
|
# ctx.actions.write creates a FileWriteAction which uses UTF-8 encoding.
|
||||||
|
ctx.actions.write(
|
||||||
|
output = out,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
outputs.append(out)
|
||||||
|
|
||||||
|
return [DefaultInfo(
|
||||||
|
files = depset(direct = outputs),
|
||||||
|
runfiles = ctx.runfiles(files = outputs),
|
||||||
|
)]
|
||||||
|
|
||||||
|
default_output_gen = rule(
|
||||||
|
implementation = _impl,
|
||||||
|
provides = [DefaultInfo],
|
||||||
|
attrs = {
|
||||||
|
"out_files": attr.string_list(),
|
||||||
|
"out_contents": attr.string_list(),
|
||||||
|
},
|
||||||
|
)
|
|
@ -1,42 +1,84 @@
|
||||||
load("//lib/tests/write_source_files:write_source_files_test.bzl", "write_source_files_test")
|
load("//lib/tests/write_source_files:write_source_file_test.bzl", "write_source_file_test")
|
||||||
|
load("//lib/tests:default_output_gen.bzl", "default_output_gen")
|
||||||
load("//lib:write_source_files.bzl", "write_source_files")
|
load("//lib:write_source_files.bzl", "write_source_files")
|
||||||
load("//lib:copy_to_directory.bzl", "copy_to_directory")
|
load("//lib:copy_to_directory.bzl", "copy_to_directory")
|
||||||
|
load("//lib:directory_path.bzl", "directory_path")
|
||||||
|
load("//lib:default_info_files.bzl", "default_info_files")
|
||||||
|
|
||||||
genrule(
|
genrule(
|
||||||
name = "a-desired",
|
name = "a-desired",
|
||||||
outs = ["a-desired.js"],
|
outs = ["a-desired.js"],
|
||||||
cmd = "echo 'console.log(\"a*\")' > $@",
|
cmd = "echo 'console.log(\"a*\");' > $@",
|
||||||
)
|
)
|
||||||
|
|
||||||
genrule(
|
default_output_gen(
|
||||||
|
name = "b_c-desired",
|
||||||
|
out_contents = [
|
||||||
|
"""console.log(\"b*\");
|
||||||
|
""",
|
||||||
|
"not used!",
|
||||||
|
],
|
||||||
|
out_files = [
|
||||||
|
"b-desired.js",
|
||||||
|
"c-desired.js",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
default_info_files(
|
||||||
name = "b-desired",
|
name = "b-desired",
|
||||||
outs = ["b-desired.js"],
|
paths = ["%s/b-desired.js" % package_name()],
|
||||||
cmd = "echo 'console.log(\"b*\")' > $@",
|
target = ":b_c-desired",
|
||||||
)
|
)
|
||||||
|
|
||||||
genrule(
|
genrule(
|
||||||
name = "e",
|
name = "e-contained",
|
||||||
outs = ["e.js"],
|
outs = ["e-contained.js"],
|
||||||
cmd = "echo 'console.log(\"e*\")' > $@",
|
cmd = "echo 'console.log(\"e*\");' > $@",
|
||||||
)
|
)
|
||||||
|
|
||||||
copy_to_directory(
|
copy_to_directory(
|
||||||
name = "e_dir-desired",
|
name = "e_dir-desired",
|
||||||
srcs = [":e"],
|
srcs = [":e-contained"],
|
||||||
)
|
)
|
||||||
|
|
||||||
write_source_files_test(
|
genrule(
|
||||||
name = "write_to_source_files_test",
|
name = "f-contained",
|
||||||
in_files = [
|
outs = ["f-contained.js"],
|
||||||
":a-desired",
|
cmd = "echo 'console.log(\"f*\");' > $@",
|
||||||
":b-desired",
|
)
|
||||||
],
|
|
||||||
out_files = [
|
copy_to_directory(
|
||||||
"a.js",
|
name = "e_f_dir-desired",
|
||||||
"b.js",
|
srcs = [
|
||||||
|
":e-contained",
|
||||||
|
":f-contained",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
directory_path(
|
||||||
|
name = "f-desired",
|
||||||
|
directory = ":e_f_dir-desired",
|
||||||
|
path = "f-contained.js",
|
||||||
|
)
|
||||||
|
|
||||||
|
write_source_file_test(
|
||||||
|
name = "write_to_source_files_a_test",
|
||||||
|
in_file = ":a-desired",
|
||||||
|
out_file = "a.js",
|
||||||
|
)
|
||||||
|
|
||||||
|
write_source_file_test(
|
||||||
|
name = "write_to_source_files_b_test",
|
||||||
|
in_file = ":b-desired",
|
||||||
|
out_file = "b.js",
|
||||||
|
)
|
||||||
|
|
||||||
|
write_source_file_test(
|
||||||
|
name = "write_to_source_files_f_test",
|
||||||
|
in_file = ":f-desired",
|
||||||
|
out_file = "f.js",
|
||||||
|
)
|
||||||
|
|
||||||
write_source_files(
|
write_source_files(
|
||||||
name = "macro_smoke_test",
|
name = "macro_smoke_test",
|
||||||
additional_update_targets = [
|
additional_update_targets = [
|
||||||
|
@ -45,6 +87,7 @@ write_source_files(
|
||||||
files = {
|
files = {
|
||||||
"a2.js": ":a-desired",
|
"a2.js": ":a-desired",
|
||||||
"b2.js": ":b-desired",
|
"b2.js": ":b-desired",
|
||||||
"e_dir": ":e_dir-desired",
|
"e2_dir": ":e_dir-desired",
|
||||||
|
"f2.js": ":f-desired",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
console.log("a*")
|
console.log("a*");
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
console.log("b*")
|
console.log("b*");
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
console.log("e*");
|
|
@ -0,0 +1 @@
|
||||||
|
console.log("e");
|
|
@ -1 +0,0 @@
|
||||||
console.log("e*")
|
|
|
@ -0,0 +1 @@
|
||||||
|
console.log("f");
|
|
@ -0,0 +1 @@
|
||||||
|
console.log("f*");
|
|
@ -8,12 +8,12 @@ genrule(
|
||||||
|
|
||||||
write_source_files(
|
write_source_files(
|
||||||
name = "macro_smoke_test",
|
name = "macro_smoke_test",
|
||||||
|
additional_update_targets = [
|
||||||
|
"//lib/tests/write_source_files/subdir/subsubdir:macro_smoke_test",
|
||||||
|
],
|
||||||
files = {
|
files = {
|
||||||
"c.js": ":c-desired",
|
"c.js": ":c-desired",
|
||||||
},
|
},
|
||||||
suggested_update_target = "//lib/tests/write_source_files:macro_smoke_test",
|
suggested_update_target = "//lib/tests/write_source_files:macro_smoke_test",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//lib/tests/write_source_files:__pkg__"],
|
||||||
additional_update_targets = [
|
|
||||||
"//lib/tests/write_source_files/subdir/subsubdir:macro_smoke_test",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,5 +12,5 @@ write_source_files(
|
||||||
"d.js": ":d-desired",
|
"d.js": ":d-desired",
|
||||||
},
|
},
|
||||||
suggested_update_target = "//lib/tests/write_source_files:macro_smoke_test",
|
suggested_update_target = "//lib/tests/write_source_files:macro_smoke_test",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//lib/tests/write_source_files/subdir:__pkg__"],
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
"""Tests for write_source_files"""
|
"""Tests for write_source_files"""
|
||||||
# Inspired by https://github.com/cgrindel/bazel-starlib/blob/main/updatesrc/private/updatesrc_update_test.bzl
|
# Inspired by https://github.com/cgrindel/bazel-starlib/blob/main/updatesrc/private/updatesrc_update_test.bzl
|
||||||
|
|
||||||
load("//lib/private:write_source_files.bzl", _lib = "write_source_files_lib")
|
load("//lib/private:write_source_file.bzl", _lib = "write_source_file_lib")
|
||||||
|
load("//lib/private:directory_path.bzl", "DirectoryPathInfo")
|
||||||
|
|
||||||
_write_source_files = rule(
|
_write_source_file = rule(
|
||||||
attrs = _lib.attrs,
|
attrs = _lib.attrs,
|
||||||
implementation = _lib.implementation,
|
implementation = _lib.implementation,
|
||||||
executable = True,
|
executable = True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _impl_sh(ctx):
|
def _impl_sh(ctx, in_file_path, out_file_path):
|
||||||
test = ctx.actions.declare_file(
|
test = ctx.actions.declare_file(
|
||||||
ctx.label.name + "_test.sh",
|
ctx.label.name + "_test.sh",
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx.actions.write(
|
contents = []
|
||||||
output = test,
|
|
||||||
is_executable = True,
|
contents.append("""
|
||||||
content = """
|
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -o errexit -o nounset -o pipefail
|
set -o errexit -o nounset -o pipefail
|
||||||
|
|
||||||
|
@ -32,32 +32,35 @@ assert_same() {
|
||||||
local in_file="${1}"
|
local in_file="${1}"
|
||||||
local out_file="${2}"
|
local out_file="${2}"
|
||||||
diff "${in_file}" "${out_file}" || (echo >&2 "Expected files to be same. in: ${in_file}, out: ${out_file}" && return -1)
|
diff "${in_file}" "${out_file}" || (echo >&2 "Expected files to be same. in: ${in_file}, out: ${out_file}" && return -1)
|
||||||
}
|
}""")
|
||||||
|
|
||||||
|
contents.append("""
|
||||||
# Check that in and out files are different
|
# Check that in and out files are different
|
||||||
""" + "\n".join([
|
assert_different {in_file} {out_file}
|
||||||
"assert_different {in_file} {out_file}".format(
|
""".format(
|
||||||
in_file = ctx.files.in_files[i].short_path,
|
in_file = in_file_path,
|
||||||
out_file = ctx.files.out_files[i].short_path,
|
out_file = out_file_path,
|
||||||
)
|
))
|
||||||
for i in range(len(ctx.files.in_files))
|
|
||||||
]) + """
|
|
||||||
# Write to the source files
|
|
||||||
{write_source_files}
|
|
||||||
|
|
||||||
# Check that in and out files are the same
|
contents.append("""# Write to the source files
|
||||||
""".format(write_source_files = ctx.file.write_source_files_target.short_path) + "\n".join([
|
{write_source_files}
|
||||||
"assert_same {in_file} {out_file}".format(
|
""".format(write_source_files = ctx.file.write_source_file_target.short_path))
|
||||||
in_file = ctx.files.in_files[i].short_path,
|
|
||||||
out_file = ctx.files.out_files[i].short_path,
|
contents.append("""# Check that in and out files are the same
|
||||||
)
|
assert_same {in_file} {out_file}""".format(
|
||||||
for i in range(len(ctx.files.in_files))
|
in_file = in_file_path,
|
||||||
]),
|
out_file = out_file_path,
|
||||||
|
))
|
||||||
|
|
||||||
|
ctx.actions.write(
|
||||||
|
output = test,
|
||||||
|
is_executable = True,
|
||||||
|
content = "\n".join(contents),
|
||||||
)
|
)
|
||||||
|
|
||||||
return test
|
return test
|
||||||
|
|
||||||
def _impl_bat(ctx):
|
def _impl_bat(ctx, in_file_path, out_file_path):
|
||||||
test = ctx.actions.declare_file(
|
test = ctx.actions.declare_file(
|
||||||
ctx.label.name + "_test.bat",
|
ctx.label.name + "_test.bat",
|
||||||
)
|
)
|
||||||
|
@ -66,40 +69,42 @@ def _impl_bat(ctx):
|
||||||
# but it is able to execute the actual output script so point to that for now.
|
# but it is able to execute the actual output script so point to that for now.
|
||||||
#
|
#
|
||||||
# What we would use if this bug didn't exist:
|
# What we would use if this bug didn't exist:
|
||||||
# write_source_files = ctx.executable.write_source_files_target.short_path.replace("/", "\\")
|
# write_source_files = ctx.executable.write_source_file_target.short_path.replace("/", "\\")
|
||||||
#
|
#
|
||||||
# Instead back out of the runfiles execution directory:
|
# Instead back out of the runfiles execution directory:
|
||||||
# write_to_source_files_test_test.bat.runfiles/aspect_bazel_lib
|
# write_to_source_files_test_test.bat.runfiles/aspect_bazel_lib
|
||||||
# And point to the output script.
|
# And point to the output script.
|
||||||
write_source_files = "..\\..\\%s" % ctx.executable.write_source_files_target.basename
|
write_source_files = "..\\..\\%s" % ctx.executable.write_source_file_target.basename
|
||||||
|
|
||||||
content = """
|
contents = []
|
||||||
|
|
||||||
|
contents.append("""
|
||||||
@rem Generated by copy_to_directory.bzl, do not edit.
|
@rem Generated by copy_to_directory.bzl, do not edit.
|
||||||
@echo off
|
@echo off
|
||||||
@rem Check that in and out files are different
|
@rem Check that in and out files are different
|
||||||
""" + "\n".join(["""
|
|
||||||
call :assert_different {in_file}, {out_file}
|
call :assert_different {in_file}, {out_file}
|
||||||
if %errorlevel% neq 0 exit /b 1
|
if %errorlevel% neq 0 exit /b 1
|
||||||
""".format(
|
""".format(
|
||||||
in_file = ctx.files.in_files[i].short_path.replace("/", "\\"),
|
in_file = in_file_path.replace("/", "\\"),
|
||||||
out_file = ctx.files.out_files[i].short_path.replace("/", "\\"),
|
out_file = out_file_path.replace("/", "\\"),
|
||||||
)
|
))
|
||||||
for i in range(len(ctx.files.in_files))
|
|
||||||
]) + """
|
contents.append("""
|
||||||
@rem Write to the source files
|
@rem Write to the source files
|
||||||
call {write_source_files}
|
call {write_source_files}
|
||||||
if %errorlevel% neq 0 exit /b 1
|
if %errorlevel% neq 0 exit /b 1
|
||||||
|
""".format(write_source_files = write_source_files))
|
||||||
|
|
||||||
|
contents.append("""
|
||||||
@rem Check that in and out files are the same
|
@rem Check that in and out files are the same
|
||||||
""".format(write_source_files = write_source_files) + "\n".join(["""
|
|
||||||
call :assert_same {in_file}, {out_file}
|
call :assert_same {in_file}, {out_file}
|
||||||
if %errorlevel% neq 0 exit /b 1
|
if %errorlevel% neq 0 exit /b 1
|
||||||
""".format(
|
""".format(
|
||||||
in_file = ctx.files.in_files[i].short_path.replace("/", "\\"),
|
in_file = in_file_path.replace("/", "\\"),
|
||||||
out_file = ctx.files.out_files[i].short_path.replace("/", "\\"),
|
out_file = out_file_path.replace("/", "\\"),
|
||||||
)
|
))
|
||||||
for i in range(len(ctx.files.in_files))
|
|
||||||
]) + """
|
contents.append("""
|
||||||
exit /b 0
|
exit /b 0
|
||||||
|
|
||||||
:assert_different
|
:assert_different
|
||||||
|
@ -113,48 +118,54 @@ fc /b %~1 %~2 > nul
|
||||||
if %errorlevel% equ 0 exit /b 0
|
if %errorlevel% equ 0 exit /b 0
|
||||||
echo Error: %~1 and %~2 are not the same
|
echo Error: %~1 and %~2 are not the same
|
||||||
exit /b 1
|
exit /b 1
|
||||||
"""
|
""")
|
||||||
content = content.replace("\n", "\r\n")
|
|
||||||
|
|
||||||
ctx.actions.write(
|
ctx.actions.write(
|
||||||
output = test,
|
output = test,
|
||||||
is_executable = True,
|
is_executable = True,
|
||||||
content = content
|
content = "\n".join(contents).replace("\n", "\r\n"),
|
||||||
)
|
)
|
||||||
|
|
||||||
return test
|
return test
|
||||||
|
|
||||||
def _impl(ctx):
|
def _impl(ctx):
|
||||||
if ctx.attr.is_windows:
|
if DirectoryPathInfo in ctx.attr.in_file:
|
||||||
test = _impl_bat(ctx)
|
in_file = ctx.attr.in_file[DirectoryPathInfo].directory
|
||||||
|
in_file_path = "/".join([in_file.short_path, ctx.attr.in_file[DirectoryPathInfo].path])
|
||||||
else:
|
else:
|
||||||
test = _impl_sh(ctx)
|
if len(ctx.files.in_file) != 1:
|
||||||
|
fail("in_file must be a single file or a target that provides a DirectoryPathInfo")
|
||||||
|
in_file = ctx.files.in_file[0]
|
||||||
|
in_file_path = in_file.short_path
|
||||||
|
|
||||||
|
if ctx.attr.is_windows:
|
||||||
|
test = _impl_bat(ctx, in_file_path, ctx.file.out_file.short_path)
|
||||||
|
else:
|
||||||
|
test = _impl_sh(ctx, in_file_path, ctx.file.out_file.short_path)
|
||||||
|
|
||||||
return DefaultInfo(
|
return DefaultInfo(
|
||||||
executable = test,
|
executable = test,
|
||||||
runfiles = ctx.runfiles(
|
runfiles = ctx.runfiles(
|
||||||
files = [ctx.executable.write_source_files_target] + ctx.files.in_files + ctx.files.out_files,
|
files = [ctx.executable.write_source_file_target, in_file, ctx.file.out_file],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
_write_source_files_test = rule(
|
_write_source_file_test = rule(
|
||||||
implementation = _impl,
|
implementation = _impl,
|
||||||
attrs = {
|
attrs = {
|
||||||
"write_source_files_target": attr.label(
|
"write_source_file_target": attr.label(
|
||||||
allow_single_file = True,
|
allow_single_file = True,
|
||||||
executable = True,
|
executable = True,
|
||||||
# Should be cfg = "exec" but a bazel bug causes a wrong executable symlink on windows
|
# Should be cfg = "exec" but a bazel bug causes a wrong executable symlink on windows
|
||||||
cfg = "target",
|
cfg = "target",
|
||||||
mandatory = True,
|
mandatory = True,
|
||||||
),
|
),
|
||||||
"out_files": attr.label_list(
|
"out_file": attr.label(
|
||||||
allow_files = True,
|
allow_single_file = True,
|
||||||
allow_empty = False,
|
|
||||||
mandatory = True,
|
mandatory = True,
|
||||||
),
|
),
|
||||||
"in_files": attr.label_list(
|
"in_file": attr.label(
|
||||||
allow_files = True,
|
allow_files = True,
|
||||||
allow_empty = False,
|
|
||||||
mandatory = True,
|
mandatory = True,
|
||||||
),
|
),
|
||||||
"is_windows": attr.bool(mandatory = True),
|
"is_windows": attr.bool(mandatory = True),
|
||||||
|
@ -162,13 +173,13 @@ _write_source_files_test = rule(
|
||||||
test = True,
|
test = True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def write_source_files_test(name, in_files, out_files):
|
def write_source_file_test(name, in_file, out_file):
|
||||||
"""Stamp a write_source_files executable and a test to run against it"""
|
"""Stamp a write_source_files executable and a test to run against it"""
|
||||||
|
|
||||||
_write_source_files(
|
_write_source_file(
|
||||||
name = name + "_updater",
|
name = name + "_updater",
|
||||||
out_files = out_files,
|
in_file = in_file,
|
||||||
in_files = in_files,
|
out_file = out_file,
|
||||||
is_windows = select({
|
is_windows = select({
|
||||||
"@bazel_tools//src/conditions:host_windows": True,
|
"@bazel_tools//src/conditions:host_windows": True,
|
||||||
"//conditions:default": False,
|
"//conditions:default": False,
|
||||||
|
@ -177,11 +188,11 @@ def write_source_files_test(name, in_files, out_files):
|
||||||
|
|
||||||
# Note that for testing we update the source files in the sandbox,
|
# Note that for testing we update the source files in the sandbox,
|
||||||
# not the actual source tree.
|
# not the actual source tree.
|
||||||
_write_source_files_test(
|
_write_source_file_test(
|
||||||
name = name,
|
name = name,
|
||||||
write_source_files_target = name + "_updater",
|
write_source_file_target = name + "_updater",
|
||||||
out_files = out_files,
|
in_file = in_file,
|
||||||
in_files = in_files,
|
out_file = out_file,
|
||||||
is_windows = select({
|
is_windows = select({
|
||||||
"@bazel_tools//src/conditions:host_windows": True,
|
"@bazel_tools//src/conditions:host_windows": True,
|
||||||
"//conditions:default": False,
|
"//conditions:default": False,
|
|
@ -1,11 +1,14 @@
|
||||||
"Public API for write_source_files"
|
"Public API for write_source_files"
|
||||||
|
|
||||||
load("//lib/private:write_source_files.bzl", _lib = "write_source_files_lib")
|
load(
|
||||||
|
"//lib/private:write_source_file.bzl",
|
||||||
|
_lib = "write_source_file_lib",
|
||||||
|
)
|
||||||
load("//lib:utils.bzl", _to_label = "to_label")
|
load("//lib:utils.bzl", _to_label = "to_label")
|
||||||
load("@bazel_skylib//rules:diff_test.bzl", _diff_test = "diff_test")
|
load("//lib/private:diff_test.bzl", _diff_test = "diff_test")
|
||||||
load("//lib/private:fail_with_message_test.bzl", "fail_with_message_test")
|
load("//lib/private:fail_with_message_test.bzl", "fail_with_message_test")
|
||||||
|
|
||||||
_write_source_files = rule(
|
_write_source_file = rule(
|
||||||
attrs = _lib.attrs,
|
attrs = _lib.attrs,
|
||||||
implementation = _lib.implementation,
|
implementation = _lib.implementation,
|
||||||
executable = True,
|
executable = True,
|
||||||
|
@ -85,41 +88,45 @@ def write_source_files(name, files = {}, additional_update_targets = [], suggest
|
||||||
name: Name of the executable target that creates or updates the source file
|
name: Name of the executable target that creates or updates the source file
|
||||||
files: A dict where the keys are source files or folders to write to and the values are labels pointing to the desired content.
|
files: A dict where the keys are source files or folders to write to and the values are labels pointing to the desired content.
|
||||||
Sources must be within the same bazel package as the target.
|
Sources 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
|
additional_update_targets: (Optional) List of other write_source_file or other executable updater targets to call in the same run
|
||||||
suggested_update_target: (Optional) Label of the write_source_files target to suggest running when files are out of date
|
suggested_update_target: (Optional) Label of the write_source_file target to suggest running when files are out of date
|
||||||
**kwargs: Other common named parameters such as `tags` or `visibility`
|
**kwargs: Other common named parameters such as `tags` or `visibility`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
out_files = files.keys()
|
single_update_target = len(files.keys()) == 1
|
||||||
in_files = [files[f] for f in out_files]
|
update_targets = []
|
||||||
|
for i, pair in enumerate(files.items()):
|
||||||
|
out_file, in_file = pair
|
||||||
|
|
||||||
# Stamp an executable rule that writes to the out file
|
in_file = _to_label(in_file)
|
||||||
_write_source_files(
|
out_file = _to_label(out_file)
|
||||||
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,
|
|
||||||
}),
|
|
||||||
visibility = kwargs.get("visibility"),
|
|
||||||
tags = kwargs.get("tags"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Fail if user passes args that would conflict with stamped out targets below
|
if single_update_target:
|
||||||
if kwargs.pop("file1", None) != None:
|
update_target_name = name
|
||||||
fail("file1 not a valid parameter in write_source_file")
|
else:
|
||||||
if kwargs.pop("file2", None) != None:
|
update_target_name = "%s_%d" % (name, i)
|
||||||
fail("file2 not a valid parameter in write_source_file")
|
update_targets.append(update_target_name)
|
||||||
if kwargs.pop("failure_message", None) != None:
|
|
||||||
fail("failure_message not a valid parameter in write_source_file")
|
# Runnable target that writes to the out file to the source tree
|
||||||
|
_write_source_file(
|
||||||
|
name = update_target_name,
|
||||||
|
in_file = in_file,
|
||||||
|
out_file = out_file,
|
||||||
|
additional_update_targets = additional_update_targets if single_update_target else [],
|
||||||
|
is_windows = select({
|
||||||
|
"@bazel_tools//src/conditions:host_windows": True,
|
||||||
|
"//conditions:default": False,
|
||||||
|
}),
|
||||||
|
visibility = kwargs.get("visibility"),
|
||||||
|
tags = kwargs.get("tags"),
|
||||||
|
)
|
||||||
|
|
||||||
for i in range(len(out_files)):
|
|
||||||
out_file = _to_label(out_files[i])
|
|
||||||
out_file_missing = _is_file_missing(out_file)
|
out_file_missing = _is_file_missing(out_file)
|
||||||
|
|
||||||
name_test = "%s_%d_test" % (name, i)
|
if single_update_target:
|
||||||
|
test_target_name = "%s_test" % name
|
||||||
|
else:
|
||||||
|
test_target_name = "%s_%d_test" % (name, i)
|
||||||
|
|
||||||
if out_file_missing:
|
if out_file_missing:
|
||||||
if suggested_update_target == None:
|
if suggested_update_target == None:
|
||||||
|
@ -147,7 +154,7 @@ To create an update *only* this file, run:
|
||||||
# Note that we cannot simply call fail() here since it will fail during the analysis
|
# 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.
|
# phase and prevent the user from calling bazel run //update/the:file.
|
||||||
fail_with_message_test(
|
fail_with_message_test(
|
||||||
name = name_test,
|
name = test_target_name,
|
||||||
message = message,
|
message = message,
|
||||||
visibility = kwargs.get("visibility"),
|
visibility = kwargs.get("visibility"),
|
||||||
tags = kwargs.get("tags"),
|
tags = kwargs.get("tags"),
|
||||||
|
@ -176,13 +183,25 @@ To update *only* this file, run:
|
||||||
|
|
||||||
# Stamp out a diff test the check that the source file is up to date
|
# Stamp out a diff test the check that the source file is up to date
|
||||||
_diff_test(
|
_diff_test(
|
||||||
name = name_test,
|
name = test_target_name,
|
||||||
file1 = in_files[i],
|
file1 = in_file,
|
||||||
file2 = out_file,
|
file2 = out_file,
|
||||||
failure_message = message,
|
failure_message = message,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not single_update_target:
|
||||||
|
_write_source_file(
|
||||||
|
name = name,
|
||||||
|
additional_update_targets = update_targets + additional_update_targets,
|
||||||
|
is_windows = select({
|
||||||
|
"@bazel_tools//src/conditions:host_windows": True,
|
||||||
|
"//conditions:default": False,
|
||||||
|
}),
|
||||||
|
visibility = kwargs.get("visibility"),
|
||||||
|
tags = kwargs.get("tags"),
|
||||||
|
)
|
||||||
|
|
||||||
def _is_file_missing(label):
|
def _is_file_missing(label):
|
||||||
"""Check if a file is missing by passing its relative path through a glob()
|
"""Check if a file is missing by passing its relative path through a glob()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue