blob: 52ca590130e56ead869394ccfc8cfa744f31d884 [file] [log] [blame]
"""This module defines the cipd_install repository rule.
The cipd_install repository rule hermetically installs a CIPD package as an external Bazel
repository.
Files in the CIPD package can be added as dependencies to other Bazel targets in two ways: either
individually via a label such as "@my_cipd_pkg//:path/to/file", or by adding
"@my_cipd_pkg//:all_files" as a dependency, which is a filegroup that includes the entire contents
of the CIPD package. The contents of the generated BUILD.bazel file which facilitates this are
configurable, e.g. multiple smaller packages.
Note: Any files with spaces in their names cannot be used by Bazel and are thus excluded
from the generated Bazel rules.
If a Bazel target adds a CIPD package as a dependency, its contents will appear under the runfiles
directory. Example:
```
# WORKSPACE
cipd_install(
name = "git_linux",
package = "infra/3pp/tools/git/linux-amd64",
version = "version:2.29.2.chromium.6",
)
# BUILD.bazel
go_library(
name = "git_util.go",
srcs = ["git_util.go"],
data = ["@git_linux//:all_files"],
...
)
# git_util.go
import (
"path/filepath"
"go.skia.org/infra/bazel/go/bazel"
)
func FindGitBinary() string {
return filepath.Join(bazel.RunfilesDir(), "external/git_linux/bin/git")
}
```
For Bazel targets that must support multiple operating systems, one can declare OS-specific CIPD
packages in the WORKSPACE file, and select the correct package according to the host OS via a
select[1] statement. Example:
```
# WORKSPACE
cipd_install(
name = "git_linux",
package = "infra/3pp/tools/git/linux-amd64",
version = "version:2.29.2.chromium.6",
)
cipd_install(
name = "git_win",
package = "infra/3pp/tools/git/windows-amd64",
version = "version:2.29.2.chromium.6",
)
# BUILD.bazel
go_library(
name = "git_util.go",
srcs = ["git_util.go"],
data = select({
"@platforms//os:linux": ["@git_linux//:all_files"],
"@platforms//os:windows": ["@git_win//:all_files"],
}),
...
)
```
As an alternative, we could extract any such select statements as Bazel macros, which would keep
BUILD files short. Example:
```
# cipd_packages.bzl
def git():
return select({
"@platforms//os:linux": ["@git_linux//:all_files"],
"@platforms//os:windows": ["@git_win//:all_files"],
})
# BUILD.bazel
load(":cipd_packages.bzl", "git")
go_library(
name = "git_util.go",
srcs = ["git_util.go"],
data = git(),
...
)
```
Note that runfile generation is disabled on Windows by default, and must be enabled with
--enable_runfiles[2] for the above mechanism to work.
[1] https://bazel.build/docs/configurable-attributes
[2] https://bazel.build/reference/command-line-reference#flag--enable_runfiles
"""
load(":common.bzl", "fail_if_nonzero_status")
def _postinstall_script(repository_ctx, script_name, script_content):
repository_ctx.report_progress("Executing postinstall script...")
repository_ctx.file(
script_name,
content = script_content,
executable = True,
)
exec_result = repository_ctx.execute(
[repository_ctx.path(script_name)],
quiet = repository_ctx.attr.quiet,
)
fail_if_nonzero_status(exec_result, "Failed to run postinstall script.")
repository_ctx.delete(repository_ctx.path(script_name))
_DEFAULT_BUILD_FILE_CONTENT = """
# To add a specific file inside this CIPD package as a dependency, use a label such as
# @my_cipd_pkg//:path/to/file.
# The exclude pattern prevents files with spaces in their names from tripping up Bazel.
exports_files(glob(include=["**/*"], exclude=["**/* *"]))
# Convenience filegroup to add all files in this CIPD package as dependencies.
filegroup(
name = "all_files",
# The exclude pattern prevents files with spaces in their names from tripping up Bazel.
srcs = glob(include=["**/*"], exclude=["**/* *"]),
visibility = ["//visibility:public"],
)
"""
def _cipd_install_impl(repository_ctx):
is_windows = "windows" in repository_ctx.os.name.lower()
is_posix = not is_windows # This is a safe assumption given our fleet of test machines.
# Install the CIPD package.
cipd_client = Label("@depot_tools//:cipd.bat" if is_windows else "@depot_tools//:cipd")
repository_ctx.report_progress("Installing CIPD package...")
exec_result = repository_ctx.execute(
[
repository_ctx.path(cipd_client),
"install",
repository_ctx.attr.package,
repository_ctx.attr.version,
"-root",
".",
],
quiet = repository_ctx.attr.quiet,
)
fail_if_nonzero_status(exec_result, "Failed to fetch CIPD package.")
# Generate BUILD.bazel file.
build_file_content = repository_ctx.attr.build_file_content
if not build_file_content:
build_file_content = _DEFAULT_BUILD_FILE_CONTENT
repository_ctx.file("BUILD.bazel", content = build_file_content)
# Optionally run the postinstall script if one was given.
if is_posix and repository_ctx.attr.postinstall_script_posix != "":
_postinstall_script(
repository_ctx,
"postinstall.sh",
repository_ctx.attr.postinstall_script_posix,
)
if is_windows and repository_ctx.attr.postinstall_script_win != "":
_postinstall_script(
repository_ctx,
# The .bat extension is needed under Windows, or the OS won't execute the script.
"postinstall.bat",
repository_ctx.attr.postinstall_script_win,
)
cipd_install = repository_rule(
implementation = _cipd_install_impl,
attrs = {
"package": attr.string(
doc = """CIPD package name, e.g. "infra/3pp/tools/git/linux-amd64".""",
mandatory = True,
),
"version": attr.string(
doc = """CIPD package version, e.g. "version:2.29.2.chromium.6".""",
mandatory = True,
),
"build_file_content": attr.string(
doc = """If set, will be used as the content of the BUILD.bazel file. Otherwise, a
default BUILD.bazel file will be created with an all_files target.""",
),
"postinstall_script_posix": attr.string(
doc = """Contents of post-install script to execute. Ignored if Bazel is running on a
non-POSIX OS. Optional.""",
),
"postinstall_script_win": attr.string(
doc = """Contents of post-install script to execute. Ignored if Bazel is not running on
Windows. Optional.""",
),
"quiet": attr.bool(
default = True,
doc = "Whether stdout and stderr should be printed to the terminal for debugging.",
),
},
doc = "Hermetically installs a CIPD package as an external Bazel repository.",
)