sh_binary.bzl•6.28 kB
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:paths.bzl", "paths")
load("@prelude//os_lookup:defs.bzl", "Os", "OsLookup")
def _derive_link(artifact):
if artifact.is_source:
return artifact.short_path
# TODO(cjhopman): Reject cross-repo resources. Buck1 does that. It's probably
# easier for us (compared to v1) to construct a scheme for them that is
# correct, but not necessary yet.
return paths.join(artifact.owner.package, artifact.owner.name)
def _generate_script(
name: str,
main: Artifact,
resources: list[Artifact],
append_script_extension: bool,
actions: AnalysisActions,
is_windows: bool,
copy_resources: bool) -> (Artifact, Artifact):
main_path = main.short_path
if not append_script_extension:
main_link = main_path
elif is_windows:
main_link = main_path if main_path.endswith(".bat") or main_path.endswith(".cmd") else main_path + ".bat"
else:
main_link = main_path if main_path.endswith(".sh") else main_path + ".sh"
resources = {_derive_link(src): src for src in resources}
resources[main_link] = main
# windows isn't stable with resources passed in as symbolic links for
# remote execution. Allow using copies instead.
if copy_resources:
resources_dir = actions.copied_dir("resources", resources)
else:
resources_dir = actions.symlinked_dir("resources", resources)
script_name = name + (".bat" if is_windows else "")
script = actions.declare_output(script_name)
# This is much, much simpler than the buck1 sh_binary template. A couple reasons:
# 1. we don't invoke the script through a symlink and so don't need to use and implement a cross-platform `readlink -e`
# 2. we don't construct an invocation-specific sandbox. The implementation of
# that in buck1 is pretty crazy and it shouldn't actually be necessary.
# 3. we don't construct the cell symlinks. those were also strange. They were
# used for the links in the invocation-specific sandbox (so things would
# point through the cell symlinks to their original locations). Instead we
# construct links directly to things (which buck1 actually also did for its
# BUCK_DEFAULT_RUNTIME_RESOURCES).
if not is_windows:
script_content = cmd_args(
"#!/usr/bin/env bash",
"set -e",
# If we access this sh_binary via a unhashed symlink we need to
# update the relative source.
'__SRC="${BASH_SOURCE[0]}"',
'__SRC="$(realpath "$__SRC")"',
'__SCRIPT_DIR=$(dirname "$__SRC")',
# The format of the directory tree is different in v1 and v2. We
# should unify the two, but prior to doing this we should also
# identify what the right format is. For now, this variable lets
# callees disambiguate (see D28960177 for more context).
"export BUCK_SH_BINARY_VERSION_UNSTABLE=2",
cmd_args("export BUCK_PROJECT_ROOT=\"$__SCRIPT_DIR/", resources_dir, "\"", delimiter = ""),
# In buck1, the paths for resources that are outputs of rules have
# different paths in BUCK_PROJECT_ROOT and
# BUCK_DEFAULT_RUNTIME_RESOURCES, but we use the same paths. buck1's
# BUCK_PROJECT_ROOT paths would use the actual buck-out path rather
# than something derived from the target and so to use that people
# would need to hardcode buck-out paths into their scripts. For repo
# sources, the paths are the same for both.
"export BUCK_DEFAULT_RUNTIME_RESOURCES=\"$BUCK_PROJECT_ROOT\"",
"exec \"$BUCK_PROJECT_ROOT/{}\" \"$@\"".format(main_link),
relative_to = (script, 1),
)
else:
script_content = cmd_args(
"@echo off",
"setlocal EnableDelayedExpansion",
# Fully qualified script path.
"set __SRC=%~f0",
# Symbolic links on windows RE systems may not be stable depending on implementation.
# Don't try to resolve the links when using copies.
'for /f "tokens=2 delims=[]" %%a in (\'dir %__SRC% ^|%SYSTEMROOT%\\System32\\find.exe "<SYMLINK>"\') do set "__SRC=%%a"' if not copy_resources else "",
# Get parent folder.
'for %%a in ("%__SRC%") do set "__SCRIPT_DIR=%%~dpa"',
"set BUCK_SH_BINARY_VERSION_UNSTABLE=2",
cmd_args("set BUCK_PROJECT_ROOT=%__SCRIPT_DIR%\\", resources_dir, delimiter = ""),
"set BUCK_DEFAULT_RUNTIME_RESOURCES=%BUCK_PROJECT_ROOT%",
"%BUCK_PROJECT_ROOT%\\{} %*".format(main_link),
relative_to = (script, 1),
)
actions.write(
script,
script_content,
is_executable = True,
)
return (script, resources_dir)
# Attrs:
# "deps": attrs.list(attrs.dep(), default = []),
# "main": attrs.source(),
# "resources": attrs.list(attrs.source(), default = []),
# "copy_resources": attrs.bool(default = False),
def sh_binary_impl(ctx):
# TODO: implement deps (not sure what those even do, though)
if len(ctx.attrs.deps) > 0:
fail("sh_binary deps unsupported. Got `{}`".format(repr(ctx.attrs)))
is_windows = ctx.attrs._target_os_type[OsLookup].os == Os("windows")
(script, resources_dir) = _generate_script(
ctx.label.name,
ctx.attrs.main,
ctx.attrs.resources,
ctx.attrs.append_script_extension,
ctx.actions,
is_windows,
ctx.attrs.copy_resources,
)
script = script.with_associated_artifacts([resources_dir])
return [
DefaultInfo(default_output = script, other_outputs = [resources_dir]),
RunInfo(
# TODO(cjhopman): Figure out if we need to specify the link targets
# as inputs. We shouldn't need to, but need to verify it.
args = cmd_args(script, hidden = resources_dir),
),
]