From ed2d5e045eae48054fdb667baaefda46e382a674 Mon Sep 17 00:00:00 2001 From: Tony Aiuto Date: Fri, 24 Jul 2026 00:52:40 -0400 Subject: [PATCH 1/6] [ABLD-395] MVP .pkg file rule. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WHAT IT DOE Creates a rule to create Mac PKG files. - bazel/rules/macos/pkg/pkg_mac_pkg.bzl - materialize srcs to diesk via a pkg_install-generated installer. sub-optimal but equivalent to what omnibus does today. Future work will improve that. - bazel/rules/macos/pkg/build_mac_pkg.sh — the pkgbuild wrapper - Adds packages/agent/macos/BUILD.bazel, to create a .pkg file for datadog-agent. This is obviously incomplete because //cmd/agent:agent is not ready yet. - packages/agent/product/BUILD.bazel — fixed a pre-existing bug where //cmd/loader:trace_loader (Linux-only target_compatible_with) was listed unconditionally, breaking any non-Linux consumer of :all_files. TESTING Claude's words: Both targets build successfully and produce well-formed .pkg files. Not yet done: version/package_variables substitution (still a hardcoded "7", matching the same gap that exists in pkg_deb/pkg_rpm today), and productbuild/signing are out of v1 scope as planned. Want me to update ~/mac_package.md to reflect current status, or move on to wiring version substitution next? NEXT - tweak until we have fidelity with the omnibus packager. - replace use of pkg_install to manifest the tree tree with a direct reader/writer. That is probably upstreamable to rules_pkg in a contrib section. - Add signing (blocked on ABLD-386) --- bazel/rules/macos/pkg/BUILD.bazel | 53 +++++++ bazel/rules/macos/pkg/build_mac_pkg.py | 91 ++++++++++++ bazel/rules/macos/pkg/materialize_root.py | 60 ++++++++ bazel/rules/macos/pkg/pkg_mac_pkg.bzl | 163 ++++++++++++++++++++++ packages/agent/macos/BUILD.bazel | 66 +++++++++ packages/agent/product/BUILD.bazel | 7 +- 6 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 bazel/rules/macos/pkg/BUILD.bazel create mode 100755 bazel/rules/macos/pkg/build_mac_pkg.py create mode 100755 bazel/rules/macos/pkg/materialize_root.py create mode 100644 bazel/rules/macos/pkg/pkg_mac_pkg.bzl create mode 100644 packages/agent/macos/BUILD.bazel diff --git a/bazel/rules/macos/pkg/BUILD.bazel b/bazel/rules/macos/pkg/BUILD.bazel new file mode 100644 index 000000000000..65e737e3646d --- /dev/null +++ b/bazel/rules/macos/pkg/BUILD.bazel @@ -0,0 +1,53 @@ +load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup", "pkg_files", "pkg_mklink") +load("@rules_python//python:py_binary.bzl", "py_binary") +load(":pkg_mac_pkg.bzl", "pkg_mac_pkg") + +package(default_visibility = ["//visibility:private"]) + +py_binary( + name = "build_mac_pkg", + srcs = ["build_mac_pkg.py"], + tags = ["manual"], + visibility = ["//visibility:public"], +) + +exports_files([ + "pkg_mac_pkg.bzl", + "materialize_root.py", +]) + +# Manual smoke test: exercises the full materialize+pkgbuild pipeline against +# real pkg_files srcs. Not wired into any real package yet. +pkg_files( + name = "smoketest_files", + srcs = ["build_mac_pkg.py"], + prefix = "bin", + tags = ["manual"], +) + +# Exercises the pkg_mklink path: a real, intentional symlink that +# materialize_root.py must ship as a symlink, not a duplicate file. +pkg_mklink( + name = "smoketest_link", + link_name = "bin/build_mac_pkg_link.sh", + tags = ["manual"], + target = "build_mac_pkg.py", +) + +pkg_filegroup( + name = "smoketest_all", + srcs = [ + ":smoketest_files", + ":smoketest_link", + ], + tags = ["manual"], +) + +pkg_mac_pkg( + name = "smoketest", + srcs = [":smoketest_all"], + identifier = "com.datadoghq.smoketest", + install_location = "/opt/datadog-agent", + tags = ["manual"], + version = "0.0.1", +) diff --git a/bazel/rules/macos/pkg/build_mac_pkg.py b/bazel/rules/macos/pkg/build_mac_pkg.py new file mode 100755 index 000000000000..c6bbe6e00e31 --- /dev/null +++ b/bazel/rules/macos/pkg/build_mac_pkg.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Wraps macOS `pkgbuild` so it can be invoked as a Bazel action. + +Bazel presents action inputs (including declare_directory outputs like +--root) as a symlink farm inside the sandbox, wrapping every payload entry in +a symlink pointing at its real, ephemeral sandbox/exec-root/cache location. +`pkgbuild` faithfully packages whatever it finds under --root, including +symlink-ness, so a naive copy either ships broken symlinks (plain +`cp -R`) or, if symlinks are blindly dereferenced (`cp -RL`), loses two things +pkg_install's NativeInstaller deliberately set: exact permission bits (a plain +`cp` applies umask to the new file's mode instead of copying the source mode) +and intentional payload symlinks (e.g. pkg_mklink entries), which get +flattened into duplicate regular files. + +materialize_root.py resolves exactly the sandbox's own indirection layer +(dereferencing until it hits either real content or the payload's own +intended symlink) and preserves source file modes explicitly, so it is not +subject to the umask under which this action runs. +""" + +import argparse +import os +import shutil +import subprocess +import sys +import tempfile + + +def parse_args(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--materialize-root-py", required=True, help="Path to materialize_root.py.") + parser.add_argument("--root", required=True, help="pkg_install-materialized root directory (symlink farm).") + parser.add_argument("--pkgbuild", required=True, help="Path to the pkgbuild binary.") + parser.add_argument("--identifier", required=True, help="pkgbuild --identifier.") + parser.add_argument("--version", required=True, help="pkgbuild --version.") + parser.add_argument("--install-location", required=True, help="pkgbuild --install-location.") + parser.add_argument("--output", required=True, help="Path to write the built .pkg to.") + parser.add_argument("--preinstall", default="", help="Optional path to a preinstall script.") + parser.add_argument("--postinstall", default="", help="Optional path to a postinstall script.") + parser.add_argument("--signing-identity", default="", help="Optional pkgbuild --sign identity name.") + return parser.parse_args() + + +def _install_script(src, dst): + shutil.copyfile(src, dst) + os.chmod(dst, 0o755) + + +def main(): + args = parse_args() + + real_root_dir = tempfile.mkdtemp() + scripts_dir = tempfile.mkdtemp() + try: + subprocess.run( + [sys.executable, args.materialize_root_py, args.root, real_root_dir], + check=True, + ) + + pkgbuild_args = [ + args.pkgbuild, + "--root", + real_root_dir, + "--identifier", + args.identifier, + "--version", + args.version, + "--install-location", + args.install_location, + ] + + if args.preinstall or args.postinstall: + if args.preinstall: + _install_script(args.preinstall, os.path.join(scripts_dir, "preinstall")) + if args.postinstall: + _install_script(args.postinstall, os.path.join(scripts_dir, "postinstall")) + pkgbuild_args += ["--scripts", scripts_dir] + + if args.signing_identity: + pkgbuild_args += ["--sign", args.signing_identity] + + os.makedirs(os.path.dirname(args.output), exist_ok=True) + pkgbuild_args.append(args.output) + subprocess.run(pkgbuild_args, check=True) + finally: + shutil.rmtree(real_root_dir, ignore_errors=True) + shutil.rmtree(scripts_dir, ignore_errors=True) + + +if __name__ == "__main__": + main() diff --git a/bazel/rules/macos/pkg/materialize_root.py b/bazel/rules/macos/pkg/materialize_root.py new file mode 100755 index 000000000000..71b3912bdc0a --- /dev/null +++ b/bazel/rules/macos/pkg/materialize_root.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Copies a pkg_install-materialized tree out of Bazel's sandbox symlink farm. + +Usage: materialize_root.py + +Bazel presents a declare_directory() action input as a symlink farm: every +entry is (transitively) a symlink to its real, ephemeral location under the +sandbox/exec-root/cache. This walks that farm and, for each entry, resolves +symlinks one hop at a time until it finds either real content (a regular +file/directory) or another symlink -- the latter case means the payload +itself (via pkg_install's NativeInstaller) intentionally created that +symlink, e.g. for a pkg_mklink() entry, so its target text is preserved +verbatim rather than being flattened into a duplicate file. + +File/directory modes are copied explicitly via shutil.copymode()/copystat() +so the result isn't affected by this process's umask. +""" + +import os +import shutil +import sys + + +def _resolve_one_hop(path): + """If path is a symlink, returns (resolved_path, target_is_symlink).""" + if not os.path.islink(path): + return path, False + target = os.readlink(path) + if os.path.isabs(target): + resolved = target + else: + resolved = os.path.join(os.path.dirname(path), target) + resolved = os.path.normpath(resolved) + return resolved, os.path.islink(resolved) + + +def materialize(src, dst): + real_src, is_intentional_symlink = _resolve_one_hop(src) + if is_intentional_symlink: + os.symlink(os.readlink(real_src), dst) + return + + if os.path.isdir(real_src): + os.makedirs(dst, exist_ok=True) + for entry in os.listdir(real_src): + materialize(os.path.join(real_src, entry), os.path.join(dst, entry)) + shutil.copystat(real_src, dst) + else: + shutil.copyfile(real_src, dst) + shutil.copymode(real_src, dst) + + +def main(): + src_dir, dst_dir = sys.argv[1], sys.argv[2] + for entry in os.listdir(src_dir): + materialize(os.path.join(src_dir, entry), os.path.join(dst_dir, entry)) + + +if __name__ == "__main__": + main() diff --git a/bazel/rules/macos/pkg/pkg_mac_pkg.bzl b/bazel/rules/macos/pkg/pkg_mac_pkg.bzl new file mode 100644 index 000000000000..7c607b9d0552 --- /dev/null +++ b/bazel/rules/macos/pkg/pkg_mac_pkg.bzl @@ -0,0 +1,163 @@ +"""pkg_mac_pkg - build a macOS .pkg installer from rules_pkg-style file mappings. + +Consumes the same `srcs` shape as `pkg_deb`/`pkg_rpm` (pkg_files/pkg_filegroup +targets) and calls the macOS `pkgbuild` tool to produce a single component +package. See ABLD-395 (~/mac_package.md has the design writeup). + +v1 scope, deliberately: no `productbuild`/Distribution.xml composition -- a +single flat component package is directly installable via `installer(8)`, +Installer.app, or MDM. v1 also materializes `srcs` to a real directory on disk +before invoking `pkgbuild --root`, reusing rules_pkg's own `pkg_install` +installer (and its codesign-safe atomic file copy) rather than writing a +bespoke manifest interpreter. +""" + +load("@rules_pkg//pkg:install.bzl", "pkg_install") + +def _pkg_mac_pkg_impl(ctx): + # This is a temporary solution. In the future, we'll write the payload + # directly from the source inputs, like pkg_tar + # `pkg_install` produces a `bazel run`-able installer whose CLI insists on + # an absolute --destdir (or BUILD_WORKSPACE_DIRECTORY for resolving a + # relative one), since it's normally invoked interactively. Inside a + # Bazel action the process cwd is already the exec root, so a relative + # destdir resolves correctly on disk -- we just need to satisfy that + # check. Setting BUILD_WORKSPACE_DIRECTORY to "." (a literal we choose, + # not an inherited env var) does that without needing our own installer. + root_dir = ctx.actions.declare_directory(ctx.label.name + "_root") + ctx.actions.run( + mnemonic = "MacPkgRoot", + progress_message = "Materializing pkg root for %s" % ctx.label, + executable = ctx.attr.installer[DefaultInfo].files_to_run, + arguments = ["--destdir", root_dir.path, "--wipe_destdir"], + env = {"BUILD_WORKSPACE_DIRECTORY": "."}, + outputs = [root_dir], + ) + + pkgbuild_toolchain = ctx.toolchains["@macos_pkgbuild//:pkgbuild_toolchain_type"].pkgbuild + if not pkgbuild_toolchain.valid: + fail("No pkgbuild available on this machine.") + + output = ctx.outputs.out + inputs = [root_dir, ctx.file._materialize_root_py] + + args = ctx.actions.args() + args.add("--materialize-root-py", ctx.file._materialize_root_py) + args.add("--root", root_dir.path) + + # pkgbuild_toolchain.path is a plain string pointing at a system binary + # found via `which` (see bazel/toolchains/common/defs.bzl) -- it isn't a + # Bazel-tracked File, so it must not be added to `inputs`. + args.add("--pkgbuild", pkgbuild_toolchain.path) + + args.add("--identifier", ctx.attr.identifier) + args.add("--version", ctx.attr.version) + args.add("--install-location", ctx.attr.install_location) + args.add("--output", output) + + if ctx.file.preinstall: + args.add("--preinstall", ctx.file.preinstall) + inputs.append(ctx.file.preinstall) + + if ctx.file.postinstall: + args.add("--postinstall", ctx.file.postinstall) + inputs.append(ctx.file.postinstall) + + if ctx.attr.signing_identity: + args.add("--signing-identity", ctx.attr.signing_identity) + + ctx.actions.run( + mnemonic = "MacPkgBuild", + progress_message = "Building macOS pkg %s" % ctx.label, + executable = ctx.executable._pkgbuild_wrapper, + arguments = [args], + inputs = inputs, + outputs = [output], + ) + + return [ + DefaultInfo(files = depset([output])), + OutputGroupInfo(pkg = depset([output])), + ] + +_pkg_mac_pkg = rule( + implementation = _pkg_mac_pkg_impl, + attrs = { + "installer": attr.label( + mandatory = True, + executable = True, + cfg = "target", + providers = [DefaultInfo], + doc = "pkg_install target used to materialize srcs onto disk.", + ), + "identifier": attr.string(mandatory = True, doc = "pkgbuild --identifier"), + "version": attr.string(mandatory = True, doc = "pkgbuild --version"), + "install_location": attr.string(default = "/", doc = "pkgbuild --install-location"), + "preinstall": attr.label(allow_single_file = True, doc = "Script run as `preinstall`."), + "postinstall": attr.label(allow_single_file = True, doc = "Script run as `postinstall`."), + "signing_identity": attr.string(doc = "pkgbuild --sign identity name."), + "out": attr.output(mandatory = True), + "_pkgbuild_wrapper": attr.label( + default = Label("//bazel/rules/macos/pkg:build_mac_pkg"), + executable = True, + cfg = "exec", + ), + "_materialize_root_py": attr.label( + default = Label("//bazel/rules/macos/pkg:materialize_root.py"), + allow_single_file = True, + ), + }, + toolchains = ["@macos_pkgbuild//:pkgbuild_toolchain_type"], +) + +def pkg_mac_pkg( + name, + srcs, + identifier, + version, + install_location = "/", + preinstall = None, + postinstall = None, + signing_identity = "", + out = None, + target_compatible_with = None, + **kwargs): + """Builds a macOS .pkg installer from pkg_filegroup/pkg_files srcs. + + Args: + name: rule name. + srcs: pkg_filegroup framework mapping/grouping targets (same shape + accepted by pkg_tar/pkg_install). + identifier: the package identifier, e.g. "com.datadoghq.agent". + version: the package version string. + install_location: root install path baked into the package + (pkgbuild --install-location). Defaults to "/". + preinstall: optional label of a script to run as `preinstall`. + postinstall: optional label of a script to run as `postinstall`. + signing_identity: optional codesigning identity for `pkgbuild --sign`. + Leave unset for unsigned builds; callers that need env-gated + signing (e.g. Omnibus's SIGN_MAC) should select() this at the + call site rather than reading the env from within the rule. + out: output file name. Defaults to "{name}.pkg". + **kwargs: forwarded to the underlying targets (e.g. visibility). + """ + pkg_install( + name = name + "_installer", + srcs = srcs, + tags = ["manual"], + visibility = ["//visibility:private"], + ) + + _pkg_mac_pkg( + name = name, + installer = ":" + name + "_installer", + identifier = identifier, + version = version, + install_location = install_location, + preinstall = preinstall, + postinstall = postinstall, + signing_identity = signing_identity, + out = out or (name + ".pkg"), + target_compatible_with = ["@platforms//os:macos"], + **kwargs + ) diff --git a/packages/agent/macos/BUILD.bazel b/packages/agent/macos/BUILD.bazel new file mode 100644 index 000000000000..3faf6171faea --- /dev/null +++ b/packages/agent/macos/BUILD.bazel @@ -0,0 +1,66 @@ +"""Agent package components specific to macOS.""" + +load( + "@rules_pkg//pkg:mappings.bzl", + "pkg_filegroup", +) +load("//bazel/rules/macos/pkg:pkg_mac_pkg.bzl", "pkg_mac_pkg") +load("//compliance:package_licenses.bzl", "package_licenses") +load("//packages/rules:package_naming.bzl", "package_name_variables") + +package(default_visibility = ["//packages:__subpackages__"]) + +package_name_variables( + name = "variables", + product_name = "datadog-agent", +) + +# This is everything in the distro. All the things that were explicit +# in datadog-agent-dependencies.rb should show up here. Paths already +# include the "/opt/datadog-agent" prefix, so pkg_mac_pkg's +# install_location is "/", same as the deb/rpm whole_distro_tar below. +pkg_filegroup( + name = "agent_components", + srcs = [ + "//packages/agent/dependencies:all_files", + "//packages/agent/product:all_files", + "//packages/install_dir:embedded", + "//packages/install_dir:etc", + ], + prefix = "/opt/datadog-agent", +) + +# Intermediate node so we have a place to collect all the dependencies so we +# can walk the aspect to gather the OSS licenses. See packages/agent/linux. +pkg_filegroup( + name = "everything", + srcs = [ + ":agent_components", + ], +) + +package_licenses( + name = "license_files", + src = ":everything", +) + +pkg_filegroup( + name = "whole_distro", + srcs = [ + ":everything", + ":license_files", + ], +) + +# TODO(ABLD-395): version should come from :variables (PackageVariablesInfo) +# once pkg_mac_pkg supports package_variables-style substitution, same as +# pkg_deb/pkg_rpm's package_file_name. See ~/mac_package.md open questions. +pkg_mac_pkg( + name = "datadog-agent", + srcs = [":whole_distro"], + identifier = "com.datadoghq.agent", + install_location = "/", + postinstall = "//omnibus:package-scripts/agent-dmg/postinst", + preinstall = "//omnibus:package-scripts/agent-dmg/preinst", + version = "7", +) diff --git a/packages/agent/product/BUILD.bazel b/packages/agent/product/BUILD.bazel index cab5f14c89fb..267db42eb8a1 100644 --- a/packages/agent/product/BUILD.bazel +++ b/packages/agent/product/BUILD.bazel @@ -37,7 +37,6 @@ pkg_filegroup( # TODO: sysprobe - windows # TODO: systray - windows # TODO: installer - "//cmd/loader:trace_loader", ":dda_built_trace_agent_binary", ":dda_built_process_agent_binary", ":dda_built_privateactionrunner_binary", @@ -53,6 +52,12 @@ pkg_filegroup( # TODO: cacerts # TODO: dependency 'datadog-agent-integrations-py3' ] + select({ + # //cmd/loader:trace_loader is target_compatible_with linux-only; it + # must be select()-guarded rather than listed unconditionally above, + # or any non-linux consumer of :all_files becomes incompatible too. + "@platforms//os:linux": ["//cmd/loader:trace_loader"], + "//conditions:default": [], + }) + select({ "//packages/agent:linux_default": [ "//pkg/discovery/module/rust:all_files", "//pkg/procmgr/rust:all_files_linux", From 6b1c91c4b748ddb861b8cde9c717d477942aa0fc Mon Sep 17 00:00:00 2001 From: Tony Aiuto Date: Tue, 11 Aug 2026 12:42:13 -0400 Subject: [PATCH 2/6] pretty --- bazel/rules/macos/pkg/BUILD.bazel | 7 ++---- bazel/rules/macos/pkg/build_mac_pkg.py | 30 +++++++++++--------------- bazel/rules/macos/pkg/pkg_mac_pkg.bzl | 3 --- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/bazel/rules/macos/pkg/BUILD.bazel b/bazel/rules/macos/pkg/BUILD.bazel index 65e737e3646d..5d3bf695dc0f 100644 --- a/bazel/rules/macos/pkg/BUILD.bazel +++ b/bazel/rules/macos/pkg/BUILD.bazel @@ -1,3 +1,5 @@ +"""Macos packaging rules.""" + load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup", "pkg_files", "pkg_mklink") load("@rules_python//python:py_binary.bzl", "py_binary") load(":pkg_mac_pkg.bzl", "pkg_mac_pkg") @@ -11,11 +13,6 @@ py_binary( visibility = ["//visibility:public"], ) -exports_files([ - "pkg_mac_pkg.bzl", - "materialize_root.py", -]) - # Manual smoke test: exercises the full materialize+pkgbuild pipeline against # real pkg_files srcs. Not wired into any real package yet. pkg_files( diff --git a/bazel/rules/macos/pkg/build_mac_pkg.py b/bazel/rules/macos/pkg/build_mac_pkg.py index c6bbe6e00e31..193cfb57f253 100755 --- a/bazel/rules/macos/pkg/build_mac_pkg.py +++ b/bazel/rules/macos/pkg/build_mac_pkg.py @@ -26,7 +26,12 @@ import tempfile -def parse_args(): +def _install_script(src, dst): + shutil.copyfile(src, dst) + os.chmod(dst, 0o755) + + +def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--materialize-root-py", required=True, help="Path to materialize_root.py.") parser.add_argument("--root", required=True, help="pkg_install-materialized root directory (symlink farm).") @@ -38,16 +43,8 @@ def parse_args(): parser.add_argument("--preinstall", default="", help="Optional path to a preinstall script.") parser.add_argument("--postinstall", default="", help="Optional path to a postinstall script.") parser.add_argument("--signing-identity", default="", help="Optional pkgbuild --sign identity name.") - return parser.parse_args() - -def _install_script(src, dst): - shutil.copyfile(src, dst) - os.chmod(dst, 0o755) - - -def main(): - args = parse_args() + args = parser.parse_args() real_root_dir = tempfile.mkdtemp() scripts_dir = tempfile.mkdtemp() @@ -57,16 +54,13 @@ def main(): check=True, ) + # fmt: off pkgbuild_args = [ args.pkgbuild, - "--root", - real_root_dir, - "--identifier", - args.identifier, - "--version", - args.version, - "--install-location", - args.install_location, + "--root", real_root_dir, + "--identifier", args.identifier, + "--version", args.version, + "--install-location", args.install_location, ] if args.preinstall or args.postinstall: diff --git a/bazel/rules/macos/pkg/pkg_mac_pkg.bzl b/bazel/rules/macos/pkg/pkg_mac_pkg.bzl index 7c607b9d0552..34569183c121 100644 --- a/bazel/rules/macos/pkg/pkg_mac_pkg.bzl +++ b/bazel/rules/macos/pkg/pkg_mac_pkg.bzl @@ -15,8 +15,6 @@ bespoke manifest interpreter. load("@rules_pkg//pkg:install.bzl", "pkg_install") def _pkg_mac_pkg_impl(ctx): - # This is a temporary solution. In the future, we'll write the payload - # directly from the source inputs, like pkg_tar # `pkg_install` produces a `bazel run`-able installer whose CLI insists on # an absolute --destdir (or BUILD_WORKSPACE_DIRECTORY for resolving a # relative one), since it's normally invoked interactively. Inside a @@ -144,7 +142,6 @@ def pkg_mac_pkg( pkg_install( name = name + "_installer", srcs = srcs, - tags = ["manual"], visibility = ["//visibility:private"], ) From 5433ef99850ca8739fe39158e902349681fa99ca Mon Sep 17 00:00:00 2001 From: Tony Aiuto Date: Tue, 11 Aug 2026 13:30:37 -0400 Subject: [PATCH 3/6] buildify --- bazel/rules/macos/pkg/pkg_mac_pkg.bzl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bazel/rules/macos/pkg/pkg_mac_pkg.bzl b/bazel/rules/macos/pkg/pkg_mac_pkg.bzl index 34569183c121..1c487c27f3ad 100644 --- a/bazel/rules/macos/pkg/pkg_mac_pkg.bzl +++ b/bazel/rules/macos/pkg/pkg_mac_pkg.bzl @@ -118,7 +118,6 @@ def pkg_mac_pkg( postinstall = None, signing_identity = "", out = None, - target_compatible_with = None, **kwargs): """Builds a macOS .pkg installer from pkg_filegroup/pkg_files srcs. @@ -137,7 +136,7 @@ def pkg_mac_pkg( signing (e.g. Omnibus's SIGN_MAC) should select() this at the call site rather than reading the env from within the rule. out: output file name. Defaults to "{name}.pkg". - **kwargs: forwarded to the underlying targets (e.g. visibility). + **kwargs: standard attributes. """ pkg_install( name = name + "_installer", From 1f92770e82606aca6ee8ba2dfa46f725a2a3564f Mon Sep 17 00:00:00 2001 From: Tony Aiuto Date: Wed, 12 Aug 2026 00:26:43 -0400 Subject: [PATCH 4/6] add analysis_test --- bazel/rules/macos/pkg/BUILD.bazel | 39 +----- bazel/rules/macos/pkg/pkg_mac_pkg_test.bzl | 117 ++++++++++++++++++ bazel/rules/macos/pkg/testdata/payload.txt | 1 + bazel/rules/macos/pkg/testdata/postinstall.sh | 3 + bazel/rules/macos/pkg/testdata/preinstall.sh | 3 + 5 files changed, 127 insertions(+), 36 deletions(-) create mode 100644 bazel/rules/macos/pkg/pkg_mac_pkg_test.bzl create mode 100644 bazel/rules/macos/pkg/testdata/payload.txt create mode 100644 bazel/rules/macos/pkg/testdata/postinstall.sh create mode 100644 bazel/rules/macos/pkg/testdata/preinstall.sh diff --git a/bazel/rules/macos/pkg/BUILD.bazel b/bazel/rules/macos/pkg/BUILD.bazel index 5d3bf695dc0f..49619a85d703 100644 --- a/bazel/rules/macos/pkg/BUILD.bazel +++ b/bazel/rules/macos/pkg/BUILD.bazel @@ -3,48 +3,15 @@ load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup", "pkg_files", "pkg_mklink") load("@rules_python//python:py_binary.bzl", "py_binary") load(":pkg_mac_pkg.bzl", "pkg_mac_pkg") +load(":pkg_mac_pkg_test.bzl", "pkg_mac_pkg_test_suite") package(default_visibility = ["//visibility:private"]) +pkg_mac_pkg_test_suite(name = "pkg_mac_pkg_tests") + py_binary( name = "build_mac_pkg", srcs = ["build_mac_pkg.py"], tags = ["manual"], visibility = ["//visibility:public"], ) - -# Manual smoke test: exercises the full materialize+pkgbuild pipeline against -# real pkg_files srcs. Not wired into any real package yet. -pkg_files( - name = "smoketest_files", - srcs = ["build_mac_pkg.py"], - prefix = "bin", - tags = ["manual"], -) - -# Exercises the pkg_mklink path: a real, intentional symlink that -# materialize_root.py must ship as a symlink, not a duplicate file. -pkg_mklink( - name = "smoketest_link", - link_name = "bin/build_mac_pkg_link.sh", - tags = ["manual"], - target = "build_mac_pkg.py", -) - -pkg_filegroup( - name = "smoketest_all", - srcs = [ - ":smoketest_files", - ":smoketest_link", - ], - tags = ["manual"], -) - -pkg_mac_pkg( - name = "smoketest", - srcs = [":smoketest_all"], - identifier = "com.datadoghq.smoketest", - install_location = "/opt/datadog-agent", - tags = ["manual"], - version = "0.0.1", -) diff --git a/bazel/rules/macos/pkg/pkg_mac_pkg_test.bzl b/bazel/rules/macos/pkg/pkg_mac_pkg_test.bzl new file mode 100644 index 000000000000..f2d0434aa727 --- /dev/null +++ b/bazel/rules/macos/pkg/pkg_mac_pkg_test.bzl @@ -0,0 +1,117 @@ +"""Tests for pkg_mac_pkg.""" + +load("@rules_pkg//pkg:mappings.bzl", "pkg_files") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", "util") +load(":pkg_mac_pkg.bzl", "pkg_mac_pkg") + +def _pkgbuild_action(env, target): + return env.expect.that_target(target).action_named("MacPkgBuild") + +# ── Test cases ─────────────────────────────────────────────────────────────── + +# Test 1: identifier, version, and install_location flow through to the +# MacPkgBuild action's argv unchanged. +def _test_identifier_version_install_location(name): + util.helper_target( + pkg_files, + name = name + "_files", + srcs = ["testdata/payload.txt"], + prefix = "bin", + ) + util.helper_target( + pkg_mac_pkg, + name = name + "_subject", + srcs = [":" + name + "_files"], + identifier = "com.datadoghq.test", + install_location = "/opt/datadog-test", + version = "9.8.7", + ) + analysis_test( + name = name, + impl = _test_identifier_version_install_location_impl, + target = name + "_subject", + ) + +def _test_identifier_version_install_location_impl(env, target): + _pkgbuild_action(env, target).contains_flag_values([ + ("--identifier", "com.datadoghq.test"), + ("--version", "9.8.7"), + ("--install-location", "/opt/datadog-test"), + ]) + +# Test 2: when preinstall/postinstall/signing_identity are set, their flags +# and file paths appear in the MacPkgBuild action. +def _test_preinstall_postinstall_signing_identity_present(name): + util.helper_target( + pkg_files, + name = name + "_files", + srcs = ["testdata/payload.txt"], + prefix = "bin", + ) + util.helper_target( + pkg_mac_pkg, + name = name + "_subject", + srcs = [":" + name + "_files"], + identifier = "com.datadoghq.test", + postinstall = "testdata/postinstall.sh", + preinstall = "testdata/preinstall.sh", + signing_identity = "Developer ID Installer: Datadog, Inc.", + version = "1.0.0", + ) + analysis_test( + name = name, + impl = _test_preinstall_postinstall_signing_identity_present_impl, + target = name + "_subject", + ) + +def _test_preinstall_postinstall_signing_identity_present_impl(env, target): + action = _pkgbuild_action(env, target) + action.has_flags_specified(["--preinstall", "--postinstall", "--signing-identity"]) + action.contains_flag_values([ + ("--signing-identity", "Developer ID Installer: Datadog, Inc."), + ]) + action.argv().contains_predicate(matching.str_endswith("testdata/preinstall.sh")) + action.argv().contains_predicate(matching.str_endswith("testdata/postinstall.sh")) + +# Test 3: when preinstall/postinstall/signing_identity are left unset (the +# default), the MacPkgBuild action omits their flags entirely -- pkg_mac_pkg +# must not pass empty placeholders for them. +def _test_preinstall_postinstall_signing_identity_absent_by_default(name): + util.helper_target( + pkg_files, + name = name + "_files", + srcs = ["testdata/payload.txt"], + prefix = "bin", + ) + util.helper_target( + pkg_mac_pkg, + name = name + "_subject", + srcs = [":" + name + "_files"], + identifier = "com.datadoghq.test", + version = "1.0.0", + ) + analysis_test( + name = name, + impl = _test_preinstall_postinstall_signing_identity_absent_by_default_impl, + target = name + "_subject", + ) + +def _test_preinstall_postinstall_signing_identity_absent_by_default_impl(env, target): + action = _pkgbuild_action(env, target) + action.argv().not_contains_predicate(matching.equals_wrapper("--preinstall")) + action.argv().not_contains_predicate(matching.equals_wrapper("--postinstall")) + action.argv().not_contains_predicate(matching.equals_wrapper("--signing-identity")) + +# ── Suite ──────────────────────────────────────────────────────────────────── + +def pkg_mac_pkg_test_suite(name): + test_suite( + name = name, + tests = [ + _test_identifier_version_install_location, + _test_preinstall_postinstall_signing_identity_present, + _test_preinstall_postinstall_signing_identity_absent_by_default, + ], + ) diff --git a/bazel/rules/macos/pkg/testdata/payload.txt b/bazel/rules/macos/pkg/testdata/payload.txt new file mode 100644 index 000000000000..42cabfabb96f --- /dev/null +++ b/bazel/rules/macos/pkg/testdata/payload.txt @@ -0,0 +1 @@ +placeholder payload file used by pkg_mac_pkg_test.bzl diff --git a/bazel/rules/macos/pkg/testdata/postinstall.sh b/bazel/rules/macos/pkg/testdata/postinstall.sh new file mode 100644 index 000000000000..5a9a30fcd015 --- /dev/null +++ b/bazel/rules/macos/pkg/testdata/postinstall.sh @@ -0,0 +1,3 @@ +#!/bin/sh +# placeholder postinstall script used by pkg_mac_pkg_test.bzl +exit 0 diff --git a/bazel/rules/macos/pkg/testdata/preinstall.sh b/bazel/rules/macos/pkg/testdata/preinstall.sh new file mode 100644 index 000000000000..2bb88cf42e64 --- /dev/null +++ b/bazel/rules/macos/pkg/testdata/preinstall.sh @@ -0,0 +1,3 @@ +#!/bin/sh +# placeholder preinstall script used by pkg_mac_pkg_test.bzl +exit 0 From 15c830dd7a2d10c6fcd0624683d047f7e3267358 Mon Sep 17 00:00:00 2001 From: Tony Aiuto Date: Wed, 12 Aug 2026 15:27:17 -0400 Subject: [PATCH 5/6] account for PR #54739 --- packages/agent/product/BUILD.bazel | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/agent/product/BUILD.bazel b/packages/agent/product/BUILD.bazel index 0ecc21d72d5a..2ebd26b145e1 100644 --- a/packages/agent/product/BUILD.bazel +++ b/packages/agent/product/BUILD.bazel @@ -52,12 +52,6 @@ pkg_filegroup( # TODO: cacerts # TODO: dependency 'datadog-agent-integrations-py3' ] + select({ - # //cmd/loader:trace_loader is target_compatible_with linux-only; it - # must be select()-guarded rather than listed unconditionally above, - # or any non-linux consumer of :all_files becomes incompatible too. - "@platforms//os:linux": ["//cmd/loader:trace_loader"], - "//conditions:default": [], - }) + select({ "//packages/agent:linux_default": [ "//pkg/discovery/module/rust:all_files", "//pkg/procmgr/rust:all_files_linux", From e9808be2eacecd552ec790c31f4dba54394ac7ed Mon Sep 17 00:00:00 2001 From: Tony Aiuto Date: Wed, 12 Aug 2026 15:44:40 -0400 Subject: [PATCH 6/6] linty.fresh --- bazel/rules/macos/pkg/BUILD.bazel | 2 -- 1 file changed, 2 deletions(-) diff --git a/bazel/rules/macos/pkg/BUILD.bazel b/bazel/rules/macos/pkg/BUILD.bazel index 49619a85d703..13c1701d72bc 100644 --- a/bazel/rules/macos/pkg/BUILD.bazel +++ b/bazel/rules/macos/pkg/BUILD.bazel @@ -1,8 +1,6 @@ """Macos packaging rules.""" -load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup", "pkg_files", "pkg_mklink") load("@rules_python//python:py_binary.bzl", "py_binary") -load(":pkg_mac_pkg.bzl", "pkg_mac_pkg") load(":pkg_mac_pkg_test.bzl", "pkg_mac_pkg_test_suite") package(default_visibility = ["//visibility:private"])