diff --git a/MODULE.bazel b/MODULE.bazel index 27c7884fd..2785386d3 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -25,11 +25,13 @@ bazel_lib_toolchains.tar() bazel_lib_toolchains.zstd() bazel_lib_toolchains.expand_template() bazel_lib_toolchains.bats() -use_repo(bazel_lib_toolchains, "bats_toolchains", "bsd_tar_toolchains", "copy_directory_toolchains", "copy_to_directory_toolchains", "coreutils_toolchains", "expand_template_toolchains", "jq", "jq_toolchains", "yq", "yq_toolchains", "zstd_toolchains") +bazel_lib_toolchains.diffutils() +use_repo(bazel_lib_toolchains, "bats_toolchains", "bsd_tar_toolchains", "copy_directory_toolchains", "copy_to_directory_toolchains", "coreutils_toolchains", "diffutils_toolchains", "expand_template_toolchains", "jq", "jq_toolchains", "yq", "yq_toolchains", "zstd_toolchains") register_toolchains( "@copy_directory_toolchains//:all", "@copy_to_directory_toolchains//:all", + "@diffutils_toolchains//:all", "@jq_toolchains//:all", "@yq_toolchains//:all", "@coreutils_toolchains//:all", diff --git a/lib/BUILD.bazel b/lib/BUILD.bazel index 44b312801..4d5e91c13 100644 --- a/lib/BUILD.bazel +++ b/lib/BUILD.bazel @@ -78,6 +78,10 @@ toolchain_type( name = "bats_toolchain_type", ) +toolchain_type( + name = "diffutils_toolchain_type", +) + bzl_library( name = "expand_make_vars", srcs = ["expand_make_vars.bzl"], diff --git a/lib/extensions.bzl b/lib/extensions.bzl index f32c882e7..b1dafd0e5 100644 --- a/lib/extensions.bzl +++ b/lib/extensions.bzl @@ -8,6 +8,7 @@ load( "DEFAULT_COPY_TO_DIRECTORY_REPOSITORY", "DEFAULT_COREUTILS_REPOSITORY", "DEFAULT_COREUTILS_VERSION", + "DEFAULT_DIFFUTILS_REPOSITORY", "DEFAULT_EXPAND_TEMPLATE_REPOSITORY", "DEFAULT_JQ_REPOSITORY", "DEFAULT_JQ_VERSION", @@ -19,6 +20,7 @@ load( "register_copy_directory_toolchains", "register_copy_to_directory_toolchains", "register_coreutils_toolchains", + "register_diffutils_toolchains", "register_expand_template_toolchains", "register_jq_toolchains", "register_tar_toolchains", @@ -118,6 +120,15 @@ def _toolchains_extension_impl(mctx): get_version_fn = lambda attr: attr.core_version, ) + extension_utils.toolchain_repos_bfs( + mctx = mctx, + get_tag_fn = lambda tags: tags.diffutils, + toolchain_name = "diffutils", + default_repository = DEFAULT_DIFFUTILS_REPOSITORY, + toolchain_repos_fn = lambda name, version: register_diffutils_toolchains(name = name, register = False), + get_version_fn = lambda attr: None, + ) + if bazel_features.external_deps.extension_metadata_has_reproducible: return mctx.extension_metadata(reproducible = True) @@ -138,5 +149,6 @@ toolchains = module_extension( "name": attr.string(default = DEFAULT_BATS_REPOSITORY), "core_version": attr.string(default = DEFAULT_BATS_CORE_VERSION), }), + "diffutils": tag_class(attrs = {"name": attr.string(default = DEFAULT_DIFFUTILS_REPOSITORY)}), }, ) diff --git a/lib/private/diff_test.bzl b/lib/private/diff_test.bzl index ffe5d3db3..c7a73ca82 100644 --- a/lib/private/diff_test.bzl +++ b/lib/private/diff_test.bzl @@ -63,6 +63,7 @@ def _diff_test_impl(ctx): template = ctx.file._diff_test_tmpl_sh test_bin = ctx.actions.declare_file(ctx.label.name + test_suffix) + diff_bin = ctx.toolchains["@aspect_bazel_lib//lib:diffutils_toolchain_type"].diffinfo.bin ctx.actions.expand_template( template = template, output = test_bin, @@ -76,6 +77,7 @@ def _diff_test_impl(ctx): shell.quote(arg) for arg in ctx.attr.diff_args ]), + "{diff}": diff_bin.short_path, }, is_executable = True, ) @@ -83,7 +85,7 @@ def _diff_test_impl(ctx): return DefaultInfo( executable = test_bin, files = depset(direct = [test_bin]), - runfiles = ctx.runfiles(files = [test_bin, file1, file2]), + runfiles = ctx.runfiles(files = [test_bin, file1, file2, diff_bin]), ) _diff_test = rule( @@ -110,6 +112,7 @@ _diff_test = rule( }, test = True, implementation = _diff_test_impl, + toolchains = ["@aspect_bazel_lib//lib:diffutils_toolchain_type"], ) def diff_test(name, file1, file2, diff_args = [], size = "small", **kwargs): diff --git a/lib/private/diff_test_tmpl.sh b/lib/private/diff_test_tmpl.sh index d72f04465..13253952f 100644 --- a/lib/private/diff_test_tmpl.sh +++ b/lib/private/diff_test_tmpl.sh @@ -72,11 +72,11 @@ if [[ ! "$DF1" ]] && [[ "$DF2" ]]; then exit 1 fi if [[ "$DF1" ]] || [[ "$DF2" ]]; then - if ! diff {diff_args} -r "$RF1" "$RF2"; then + if ! {diff} {diff_args} -r "$RF1" "$RF2"; then fail "directories \"{file1}\" and \"{file2}\" differ. {fail_msg}" fi else - if ! diff {diff_args} "$RF1" "$RF2"; then + if ! {diff} {diff_args} "$RF1" "$RF2"; then fail "files \"{file1}\" and \"{file2}\" differ. {fail_msg}" fi fi diff --git a/lib/private/diff_test_toolchain.bzl b/lib/private/diff_test_toolchain.bzl new file mode 100644 index 000000000..0cc8b2adc --- /dev/null +++ b/lib/private/diff_test_toolchain.bzl @@ -0,0 +1,213 @@ +"Setup a diffutils toolchain repositories and rules" + +load(":repo_utils.bzl", "repo_utils") + +DIFFUTILS_PLATFORMS = { + "darwin_amd64": struct( + release_platform = "x86_64-apple-darwin", + compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:x86_64", + ], + ), + "darwin_arm64": struct( + release_platform = "aarch64-apple-darwin", + compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:aarch64", + ], + ), + "linux_amd64": struct( + release_platform = "x86_64-unknown-linux-gnu", + compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + ), + "windows_amd64": struct( + release_platform = "x86_64-pc-windows-msvc", + archive_extension = "zip", + compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], + ), +} + +DIFFUTILS_VERSION = "0.4.2" + +# https://github.com/uutils/diffutils/releases +# +# The integrity hashes can be computed with +# shasum -b -a 384 [downloaded file] | awk '{ print $1 }' | xxd -r -p | base64 +DIFFUTILS_INTEGRITIES = { + "aarch64-apple-darwin": "sha384-m5EzRSRFl5NE8bFw+9rV4n06OeMY/tpieZiQepbpDp7/prL2Zr6sw4LWnfhZC+15", + "x86_64-apple-darwin": "sha384-tj3xGzlcdggMrIdrqnrK468cReGkKNQW1ZbKinOHMiMhhILuB1lzyzO2Jmfj9/GM", + "x86_64-pc-windows-msvc": "sha384-kHUBxaHOZPqiXd0exGQsQEPDrDb0O0boAFu6KWSE3K2i1OO5bzwyz8VhmjHlvjGv", + "x86_64-unknown-linux-gnu": "sha384-7qAQT0YR+zaGDPLYkHcBxLzVI+0lwHQx/VMkgEmvgVXkCntM3xaRjUov1Eyz1VBN", +} + +DiffInfo = provider( + doc = "Provide info for executing diff", + fields = { + "bin": "Executable diff binary", + }, +) + +def _diff_toolchain_impl(ctx): + binary = ctx.file.bin + + # Make the $(DIFFUTILS_BIN) variable available in places like genrules. + # See https://docs.bazel.build/versions/main/be/make-variables.html#custom_variables + template_variables = platform_common.TemplateVariableInfo({ + "DIFFUTILS_BIN": binary.path, + }) + default_info = DefaultInfo( + files = depset([binary]), + runfiles = ctx.runfiles(files = [binary]), + ) + diff_info = DiffInfo( + bin = binary, + ) + + # Export all the providers inside our ToolchainInfo + # so the resolved_toolchain rule can grab and re-export them. + toolchain_info = platform_common.ToolchainInfo( + diffinfo = diff_info, + template_variables = template_variables, + default = default_info, + ) + + return [default_info, toolchain_info, template_variables] + +diff_toolchain = rule( + implementation = _diff_toolchain_impl, + attrs = { + "bin": attr.label( + mandatory = True, + allow_single_file = True, + ), + }, +) + +def _diffutils_toolchains_repo_impl(rctx): + # Expose a concrete toolchain which is the result of Bazel resolving the toolchain + # for the execution or target platform. + # Workaround for https://github.com/bazelbuild/bazel/issues/14009 + starlark_content = """# @generated by @aspect_bazel_lib//lib/private:diff_test_toolchain.bzl + +# Forward all the providers +def _resolved_toolchain_impl(ctx): + toolchain_info = ctx.toolchains["@aspect_bazel_lib//lib:diffutils_toolchain_type"] + return [ + toolchain_info, + toolchain_info.default, + toolchain_info.diffinfo, + toolchain_info.template_variables, + ] + +# Copied from java_toolchain_alias +# https://cs.opensource.google/bazel/bazel/+/master:tools/jdk/java_toolchain_alias.bzl +resolved_toolchain = rule( + implementation = _resolved_toolchain_impl, + toolchains = ["@aspect_bazel_lib//lib:diffutils_toolchain_type"], + incompatible_use_toolchain_transition = True, +) +""" + rctx.file("defs.bzl", starlark_content) + + build_content = """# @generated by @aspect_bazel_lib//lib/private:diff_test_toolchain.bzl +# +# These can be registered in the workspace file or passed to --extra_toolchains flag. +# By default all these toolchains are registered by the diff_register_toolchains macro +# so you don't normally need to interact with these targets. + +load(":defs.bzl", "resolved_toolchain") + +resolved_toolchain(name = "resolved_toolchain", visibility = ["//visibility:public"]) + +""" + + for [platform, meta] in DIFFUTILS_PLATFORMS.items(): + build_content += """ +toolchain( + name = "{platform}_toolchain", + exec_compatible_with = {compatible_with}, + toolchain = "@{user_repository_name}_{platform}//:diff_toolchain", + toolchain_type = "@aspect_bazel_lib//lib:diffutils_toolchain_type", +) +""".format( + platform = platform, + user_repository_name = rctx.attr.user_repository_name, + compatible_with = meta.compatible_with, + ) + + # Base BUILD file for this repository + rctx.file("BUILD.bazel", build_content) + +diffutils_toolchains_repo = repository_rule( + _diffutils_toolchains_repo_impl, + doc = """Creates a repository with toolchain definitions for all known platforms + which can be registered or selected.""", + attrs = { + "user_repository_name": attr.string(doc = "Base name for toolchains repository"), + }, +) + +def _diffutils_platform_repo_impl(rctx): + is_windows = rctx.attr.platform.startswith("windows_") + meta = DIFFUTILS_PLATFORMS[rctx.attr.platform] + release_platform = meta.release_platform if hasattr(meta, "release_platform") else rctx.attr.platform + archive_extension = meta.archive_extension if hasattr(meta, "archive_extension") else "tar.xz" + + url = "https://github.com/uutils/diffutils/releases/download/v{0}/diffutils-{1}.{2}".format( + DIFFUTILS_VERSION, + release_platform, + archive_extension, + ) + + rctx.download_and_extract( + url = url, + strip_prefix = "diffutils-{0}".format(release_platform), + integrity = DIFFUTILS_INTEGRITIES[release_platform], + ) + build_content = """# @generated by @aspect_bazel_lib//lib/private:diff_test_toolchain.bzl +load("@aspect_bazel_lib//lib/private:diff_test_toolchain.bzl", "diff_toolchain") +exports_files(["{0}"]) +diff_toolchain(name = "diff_toolchain", bin = "{0}", visibility = ["//visibility:public"]) +""".format("diffutils.exe" if is_windows else "diffutils") + + # Base BUILD file for this repository + rctx.file("BUILD.bazel", build_content) + +diffutils_platform_repo = repository_rule( + implementation = _diffutils_platform_repo_impl, + doc = "Fetch external tools needed for diff toolchain", + attrs = { + "platform": attr.string(mandatory = True, values = DIFFUTILS_PLATFORMS.keys()), + }, +) + +def _diffutils_host_alias_repo(rctx): + ext = ".exe" if repo_utils.is_windows(rctx) else "" + + # Base BUILD file for this repository + rctx.file("BUILD.bazel", """# @generated by @aspect_bazel_lib//lib/private:diff_test_toolchain.bzl +package(default_visibility = ["//visibility:public"]) +exports_files(["diff{ext}"]) +""".format( + ext = ext, + )) + + rctx.symlink("../{name}_{platform}/diff{ext}".format( + name = rctx.attr.name, + platform = repo_utils.platform(rctx), + ext = ext, + ), "diff{ext}".format(ext = ext)) + +diffutils_host_alias_repo = repository_rule( + _diffutils_host_alias_repo, + doc = """Creates a repository with a shorter name meant for the host platform, which contains + a BUILD.bazel file that exports symlinks to the host platform's binaries + """, +) diff --git a/lib/repositories.bzl b/lib/repositories.bzl index 55b80c828..cb5b370a2 100644 --- a/lib/repositories.bzl +++ b/lib/repositories.bzl @@ -5,6 +5,7 @@ load("//lib/private:bats_toolchain.bzl", "BATS_ASSERT_VERSIONS", "BATS_CORE_TEMP load("//lib/private:copy_directory_toolchain.bzl", "COPY_DIRECTORY_PLATFORMS", "copy_directory_platform_repo", "copy_directory_toolchains_repo") load("//lib/private:copy_to_directory_toolchain.bzl", "COPY_TO_DIRECTORY_PLATFORMS", "copy_to_directory_platform_repo", "copy_to_directory_toolchains_repo") load("//lib/private:coreutils_toolchain.bzl", "COREUTILS_PLATFORMS", "coreutils_platform_repo", "coreutils_toolchains_repo", _DEFAULT_COREUTILS_VERSION = "DEFAULT_COREUTILS_VERSION") +load("//lib/private:diff_test_toolchain.bzl", "DIFFUTILS_PLATFORMS", "diffutils_platform_repo", "diffutils_toolchains_repo") load("//lib/private:expand_template_toolchain.bzl", "EXPAND_TEMPLATE_PLATFORMS", "expand_template_platform_repo", "expand_template_toolchains_repo") load("//lib/private:jq_toolchain.bzl", "JQ_PLATFORMS", "jq_host_alias_repo", "jq_platform_repo", "jq_toolchains_repo", _DEFAULT_JQ_VERSION = "DEFAULT_JQ_VERSION") load("//lib/private:source_toolchains_repo.bzl", "source_toolchains_repo") @@ -334,6 +335,29 @@ def register_expand_template_toolchains(name = DEFAULT_EXPAND_TEMPLATE_REPOSITOR user_repository_name = name, ) +DEFAULT_DIFFUTILS_REPOSITORY = "diffutils" + +def register_diffutils_toolchains(name = DEFAULT_DIFFUTILS_REPOSITORY, register = True): + """Registers expand_template toolchain and repositories + + Args: + name: override the prefix for the generated toolchain repositories + register: whether to call through to native.register_toolchains. + Should be True for WORKSPACE users, but false when used under bzlmod extension + """ + for [platform, _] in DIFFUTILS_PLATFORMS.items(): + diffutils_platform_repo( + name = "%s_%s" % (name, platform), + platform = platform, + ) + if register: + native.register_toolchains("@%s_%s//:diffutils_toolchain" % (name, platform)) + + diffutils_toolchains_repo( + name = "%s_toolchains" % name, + user_repository_name = name, + ) + # buildifier: disable=unnamed-macro def aspect_bazel_lib_register_toolchains(): """Register all bazel-lib toolchains at their default versions. @@ -350,3 +374,4 @@ def aspect_bazel_lib_register_toolchains(): register_tar_toolchains() register_zstd_toolchains() register_bats_toolchains() + register_diffutils_toolchains()