Skip to content

Commit 6ba817f

Browse files
committed
Rewrite write_source_files into a fast language
1 parent 7393f22 commit 6ba817f

File tree

3 files changed

+223
-167
lines changed

3 files changed

+223
-167
lines changed

lib/private/write_source_file.bzl

Lines changed: 38 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ load(":utils.bzl", "utils")
88
WriteSourceFileInfo = provider(
99
"Provider for write_source_file targets",
1010
fields = {
11-
"executable": "Executable that updates the source files",
11+
"args": "Arguments to pass",
1212
},
1313
)
1414

@@ -213,177 +213,29 @@ _write_source_file_attrs = {
213213
"verbosity": attr.string(
214214
values = ["full", "short", "quiet"],
215215
),
216-
"_windows_constraint": attr.label(default = "@platforms//os:windows"),
217-
"_macos_constraint": attr.label(default = "@platforms//os:macos"),
216+
"_writer_bin": attr.label(
217+
default = "//tools/write_source_files",
218+
executable = True,
219+
# Intentionally use the target platform since the target is always meant to be `bazel run`
220+
# on the host machine but we don't want to transition it to the host platform and have the
221+
# generated file rebuilt in a separate output tree. Target platform should always be equal
222+
# to the host platform when using `write_source_files`.
223+
cfg = "target",
224+
),
218225
}
219226

220-
def _write_source_file_sh(ctx, paths):
221-
is_macos = ctx.target_platform_has_constraint(ctx.attr._macos_constraint[platform_common.ConstraintValueInfo])
222-
223-
updater = ctx.actions.declare_file(
224-
ctx.label.name + "_update.sh",
225-
)
226-
227-
additional_update_scripts = []
228-
for target in ctx.attr.additional_update_targets:
229-
additional_update_scripts.append(target[WriteSourceFileInfo].executable)
230-
231-
contents = ["""#!/usr/bin/env bash
232-
set -o errexit -o nounset -o pipefail
233-
runfiles_dir=$PWD
234-
# BUILD_WORKSPACE_DIRECTORY not set when running as a test, uses the sandbox instead
235-
if [[ ! -z "${BUILD_WORKSPACE_DIRECTORY:-}" ]]; then
236-
cd "$BUILD_WORKSPACE_DIRECTORY"
237-
fi"""]
238-
239-
if ctx.attr.executable:
240-
executable_file = "chmod +x \"$out\""
241-
executable_dir = "chmod -R +x \"$out\""
242-
else:
243-
executable_file = "chmod -x \"$out\""
244-
if is_macos:
245-
# -x+X doesn't work on macos so we have to find files and remove the execute bits only from those
246-
executable_dir = "find \"$out\" -type f | xargs chmod -x"
247-
else:
248-
# Remove execute/search bit recursively from files bit not directories: https://superuser.com/a/434418
249-
executable_dir = "chmod -R -x+X \"$out\""
250-
251-
progress_message_dir = ""
252-
progress_message_file = ""
253-
if ctx.attr.verbosity == "full":
254-
progress_message_dir = "echo \"Copying directory $in to $out in $PWD\""
255-
progress_message_file = "echo \"Copying file $in to $out in $PWD\""
256-
elif ctx.attr.verbosity == "short":
257-
progress_message_dir = "echo \"Updating directory $out\""
258-
progress_message_file = "echo \"Updating file $out\""
259-
260-
for in_path, out_path in paths:
261-
contents.append("""
262-
in=$runfiles_dir/{in_path}
263-
out={out_path}
264-
265-
mkdir -p "$(dirname "$out")"
266-
if [[ -f "$in" ]]; then
267-
{progress_message_file}
268-
# in case `cp` from previous command was terminated midway which can result in read-only files/dirs
269-
chmod -R +w "$out" > /dev/null 2>&1 || true
270-
rm -Rf "$out"
271-
cp -f "$in" "$out"
272-
# cp should make the file writable but call chmod anyway as a defense in depth
273-
chmod +w "$out"
274-
# cp should make the file not-executable but set the desired execute bit in both cases as a defense in depth
275-
{executable_file}
276-
else
277-
{progress_message_dir}
278-
# in case `cp` from previous command was terminated midway which can result in read-only files/dirs
279-
chmod -R +w "$out" > /dev/null 2>&1 || true
280-
rm -Rf "$out"/{{*,.[!.]*}}
281-
mkdir -p "$out"
282-
cp -fRL "$in"/. "$out"
283-
chmod -R +w "$out"
284-
{executable_dir}
285-
fi
286-
""".format(
287-
in_path = in_path,
288-
out_path = out_path,
289-
executable_file = executable_file,
290-
executable_dir = executable_dir,
291-
progress_message_dir = progress_message_dir,
292-
progress_message_file = progress_message_file,
293-
))
294-
295-
contents.extend([
296-
"cd \"$runfiles_dir\"",
297-
"# Run the update scripts for all write_source_file deps",
298-
])
299-
for update_script in additional_update_scripts:
300-
contents.append("./\"{update_script}\"".format(update_script = update_script.short_path))
301-
302-
ctx.actions.write(
303-
output = updater,
304-
is_executable = True,
305-
content = "\n".join(contents),
306-
)
307-
308-
return updater
309-
310-
def _write_source_file_bat(ctx, paths):
311-
updater = ctx.actions.declare_file(
312-
ctx.label.name + "_update.bat",
313-
)
314-
315-
additional_update_scripts = []
316-
for target in ctx.attr.additional_update_targets:
317-
if target[DefaultInfo].files_to_run and target[DefaultInfo].files_to_run.executable:
318-
additional_update_scripts.append(target[DefaultInfo].files_to_run.executable)
319-
else:
320-
fail("additional_update_targets target %s does not provide an executable")
321-
322-
contents = ["""@rem @generated by @aspect_bazel_lib//:lib/private:write_source_file.bzl
323-
@echo off
324-
set runfiles_dir=%cd%
325-
if defined BUILD_WORKSPACE_DIRECTORY (
326-
cd %BUILD_WORKSPACE_DIRECTORY%
327-
)"""]
328-
329-
progress_message = ""
330-
if ctx.attr.verbosity == "full":
331-
progress_message = "echo Copying %in% to %out% in %cd%"
332-
elif ctx.attr.verbosity == "short":
333-
progress_message = "echo Updating %out%"
334-
335-
for in_path, out_path in paths:
336-
contents.append("""
337-
set in=%runfiles_dir%\\{in_path}
338-
set out={out_path}
339-
340-
if not defined BUILD_WORKSPACE_DIRECTORY (
341-
@rem Because there's no sandboxing in windows, if we copy over the target
342-
@rem file's symlink it will get copied back into the source directory
343-
@rem during tests. Work around this in tests by deleting the target file
344-
@rem symlink before copying over it.
345-
del %out%
346-
)
347-
348-
{progress_message}
349-
350-
if exist "%in%\\*" (
351-
mkdir "%out%" >NUL 2>NUL
352-
robocopy "%in%" "%out%" /E >NUL
353-
) else (
354-
copy %in% %out% >NUL
355-
)
356-
""".format(
357-
in_path = in_path.replace("/", "\\"),
358-
out_path = out_path.replace("/", "\\"),
359-
progress_message = progress_message,
360-
))
361-
362-
contents.extend([
363-
"cd %runfiles_dir%",
364-
"@rem Run the update scripts for all write_source_file deps",
365-
])
366-
for update_script in additional_update_scripts:
367-
contents.append("call {update_script}".format(update_script = update_script.short_path))
368-
369-
ctx.actions.write(
370-
output = updater,
371-
is_executable = True,
372-
content = "\n".join(contents).replace("\n", "\r\n"),
373-
)
374-
return updater
227+
def _map_arg(item):
228+
return [item[0], item[1], str(item[2])]
375229

376230
def _write_source_file_impl(ctx):
377-
is_windows = ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo])
378-
379231
out_file = Label(ctx.attr.out_file) if ctx.attr.out_file else None
380232

381233
if out_file and not ctx.attr.in_file:
382234
fail("in_file must be specified if out_file is set")
383235
if ctx.attr.in_file and not out_file:
384236
fail("out_file must be specified if in_file is set")
385237

386-
paths = []
238+
direct_args = []
387239
runfiles = []
388240

389241
if ctx.attr.in_file and out_file:
@@ -403,12 +255,26 @@ def _write_source_file_impl(ctx):
403255
fail(msg)
404256

405257
out_path = "/".join([out_file.package, out_file.name]) if out_file.package else out_file.name
406-
paths.append((in_path, out_path))
258+
direct_args.append((in_path, out_path, ctx.attr.executable))
407259

408-
if is_windows:
409-
updater = _write_source_file_bat(ctx, paths)
410-
else:
411-
updater = _write_source_file_sh(ctx, paths)
260+
arg_depset = depset(
261+
direct_args,
262+
transitive = [dep[WriteSourceFileInfo].args for dep in ctx.attr.additional_update_targets],
263+
)
264+
265+
args = ctx.actions.args()
266+
args.add_all(arg_depset, map_each = _map_arg)
267+
268+
updater = ctx.actions.declare_file(ctx.attr.name + "_updater")
269+
ctx.actions.symlink(
270+
output=updater,
271+
target_file=ctx.executable._writer_bin,
272+
is_executable = True,
273+
)
274+
275+
args_file = ctx.actions.declare_file(ctx.attr.name + "_args")
276+
ctx.actions.write(args_file, args)
277+
runfiles.append(args_file)
412278

413279
runfiles = ctx.runfiles(
414280
files = runfiles,
@@ -424,7 +290,12 @@ def _write_source_file_impl(ctx):
424290
runfiles = runfiles,
425291
),
426292
WriteSourceFileInfo(
427-
executable = updater,
293+
args = arg_depset,
294+
),
295+
RunEnvironmentInfo(
296+
environment = {
297+
"ARGS_PATH": args_file.short_path,
298+
},
428299
),
429300
]
430301

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
2+
3+
go_library(
4+
name = "write_source_files_lib",
5+
srcs = ["main.go"],
6+
importpath = "github.com/bazel-contrib/bazel-lib/tools/write_source_files",
7+
visibility = ["//visibility:public"],
8+
)
9+
10+
go_binary(
11+
name = "write_source_files",
12+
embed = [":write_source_files_lib"],
13+
visibility = ["//visibility:public"],
14+
)

0 commit comments

Comments
 (0)