diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 4ca65677ade6ef4f48d9ce14021089ff342db18d..877fb1eb8284d24f1318b7b25da66f58360f3249 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -656,7 +656,7 @@ "moduleExtensions": { "//:MODULE.bazel%_repo_rules": { "general": { - "bzlTransitiveDigest": "VGGV2/aS8KP0wlEaxXfdomUzHtZ4Ry+aV/L2Ktp3UCo=", + "bzlTransitiveDigest": "4dF26p1sSSHcbwj60ndza5fdK5QeD94q2c21V9FovL0=", "accumulatedFileDigests": {}, "envVariables": {}, "generatedRepoSpecs": { diff --git a/e2e/MODULE.bazel.lock b/e2e/MODULE.bazel.lock index c918f3952c8d3637111bf36cf8146f5dd0df8bd2..a7fbb5f33f62ca39bf6a477d6889a02138af0ddf 100644 --- a/e2e/MODULE.bazel.lock +++ b/e2e/MODULE.bazel.lock @@ -808,7 +808,7 @@ "moduleExtensions": { "//:MODULE.bazel%_repo_rules": { "general": { - "bzlTransitiveDigest": "/R69egUTLvkHkALvRLGxIJMgJWsQ0N6s2oGkMp4YL30=", + "bzlTransitiveDigest": "L6CiLxS6v0YN9d8yxQi+1oUtoYFLVWWho6J7A4BgHB8=", "accumulatedFileDigests": {}, "envVariables": {}, "generatedRepoSpecs": { @@ -1468,7 +1468,7 @@ }, "@@rules_toolchain~override//:MODULE.bazel%_repo_rules": { "general": { - "bzlTransitiveDigest": "VGGV2/aS8KP0wlEaxXfdomUzHtZ4Ry+aV/L2Ktp3UCo=", + "bzlTransitiveDigest": "4dF26p1sSSHcbwj60ndza5fdK5QeD94q2c21V9FovL0=", "accumulatedFileDigests": {}, "envVariables": {}, "generatedRepoSpecs": { diff --git a/e2e/toolchain/echo/BUILD.bazel b/e2e/toolchain/echo/BUILD.bazel index 5fe717679047457d2cbb1a68fc386eb2cc7f970e..9e560e2050021304b2454ad405d25bc3c0f2ff14 100644 --- a/e2e/toolchain/echo/BUILD.bazel +++ b/e2e/toolchain/echo/BUILD.bazel @@ -6,20 +6,21 @@ toolchain_type( visibility = ["//visibility:public"], ) -toolchain_symlink_target( - name = "hermetic", - target = ":echo.sh", -) - toolchain( name = "local", toolchain = "@echo//:echo", toolchain_type = ":type", ) -alias( - name = "echo", - actual = "hermetic", +toolchain_symlink_target( + name = "script", + target = ":echo.sh", +) + +toolchain( + name = "hermetic", + toolchain = ":script", + toolchain_type = ":type", ) alias( diff --git a/toolchain/constraint/libc/BUILD.bazel b/toolchain/constraint/libc/BUILD.bazel index bddb17a2a083ea4d880a9dc1645a0b05ced48e09..9c54d391bbeab28f5ccbd31fe68496c63989aed4 100644 --- a/toolchain/constraint/libc/BUILD.bazel +++ b/toolchain/constraint/libc/BUILD.bazel @@ -6,3 +6,8 @@ constraint_value( name = "gnu", constraint_setting = ":libc", ) + +constraint_value( + name = "ucrt", + constraint_setting = ":libc", +) diff --git a/toolchain/constraint/libc/ucrt/BUILD.bazel b/toolchain/constraint/libc/ucrt/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..ad0758865fa81afac61a4f4ab09ef390c5fdad5d --- /dev/null +++ b/toolchain/constraint/libc/ucrt/BUILD.bazel @@ -0,0 +1,13 @@ +load(":versions.bzl", "VERSIONS") + +package(default_visibility = ["//visibility:public"]) + +constraint_setting(name = "ucrt") + +[ + constraint_value( + name = version, + constraint_setting = ":ucrt", + ) + for version in VERSIONS +] diff --git a/toolchain/constraint/libc/ucrt/versions.bzl b/toolchain/constraint/libc/ucrt/versions.bzl new file mode 100644 index 0000000000000000000000000000000000000000..6fe77435530fb059f534a53fde3899241b8954f3 --- /dev/null +++ b/toolchain/constraint/libc/ucrt/versions.bzl @@ -0,0 +1,9 @@ +load("@local//:triplet.bzl", "TRIPLET") + +visibility("//toolchain/...") + +LOCAL = TRIPLET.libc.version and TRIPLET.libc.version.value + +# TODO: figure out a way to generate Universal CRT versions + +VERSIONS = tuple([LOCAL] if LOCAL != None else []) diff --git a/toolchain/constraint/os/BUILD.bazel b/toolchain/constraint/os/BUILD.bazel index 642cd8ebfb149eb7a82ec73b22a31038acb31e61..a46a3593891bc83c2ea388d37d8aa0add2588598 100644 --- a/toolchain/constraint/os/BUILD.bazel +++ b/toolchain/constraint/os/BUILD.bazel @@ -1,12 +1,11 @@ package(default_visibility = ["//visibility:public"]) -# TODO: add more OS aliases when we are ready to support them. -[ - alias( - name = v, - actual = "@platforms//os:{}".format(v), - ) - for v in ( - "linux", - ) -] +alias( + name = "linux", + actual = "@platforms//os:linux", +) + +alias( + name = "windows", + actual = "@platforms//os:windows", +) diff --git a/toolchain/constraint/os/windows/BUILD.bazel b/toolchain/constraint/os/windows/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..6860041234dc9eff43b9547a134c5e5684c6a2ee --- /dev/null +++ b/toolchain/constraint/os/windows/BUILD.bazel @@ -0,0 +1,13 @@ +load(":versions.bzl", "VERSIONS") + +package(default_visibility = ["//visibility:public"]) + +constraint_setting(name = "windows") + +[ + constraint_value( + name = version, + constraint_setting = ":windows", + ) + for version in VERSIONS +] diff --git a/toolchain/constraint/os/windows/versions.bzl b/toolchain/constraint/os/windows/versions.bzl new file mode 100644 index 0000000000000000000000000000000000000000..435be9bea80aec05269788881ab0b897f2f54af9 --- /dev/null +++ b/toolchain/constraint/os/windows/versions.bzl @@ -0,0 +1,9 @@ +load("@local//:triplet.bzl", "TRIPLET") + +visibility("//toolchain/...") + +LOCAL = TRIPLET.os.version and TRIPLET.os.version.value + +# TODO: figure out a way to generate Windows versions + +VERSIONS = tuple([LOCAL] if LOCAL != None else []) diff --git a/toolchain/local/triplet/libc.bzl b/toolchain/local/triplet/libc.bzl index d1d5c0b2a26ff7f802ec7ebbd7ddcbba841305b5..a83a64af24a76f58e7b88ffe130aefeef52c6cf1 100644 --- a/toolchain/local/triplet/libc.bzl +++ b/toolchain/local/triplet/libc.bzl @@ -49,6 +49,23 @@ def _ldd(rctx, path): fail("Failed to detect `{}` version:\n{}".format(path, result.stdout)) +def _powershell(rctx, path): + result = rctx.execute([path, "-Command", "Get-Package -Name 'Universal CRT Redistributable'| Format-Wide -Property Version"]) + if result.return_code != 0: + fail("Failed to retrieve `Get-Package` version output:\n{}".format(result.stderr)) + + version = result.stdout.strip() + + major, minor, build, patch = split(version, ".", { + 3: lambda w, x, y, z: (w, x, y, None), + 4: lambda w, x, y, z: (w, x, y, z), + }) + + if patch: + return VersionedInfo("ucrt.{}.{}.{}+{}".format(int(major), int(minor), int(build), int(patch))) + + return VersionedInfo("ucrt.{}.{}.{}".format(int(major), int(minor), int(build))) + def libc(rctx): """ Detects the host C library. @@ -67,4 +84,8 @@ def libc(rctx): if path.exists: return _release(rctx, path) + path = rctx.which("powershell.exe") + if path: + return _powershell(rctx, path) + fail("Failed to detect host C library") diff --git a/toolchain/local/triplet/os.bzl b/toolchain/local/triplet/os.bzl index 4b64c09144d5359d6a875debb7fdbef5895f89b6..e9011b15870db4dda1ea675136926f28cae0463b 100644 --- a/toolchain/local/triplet/os.bzl +++ b/toolchain/local/triplet/os.bzl @@ -68,8 +68,6 @@ def _uname(rctx, path): version, _ = result.stdout.split("-", 1) major, minor, patch = split(version, ".", { - 1: lambda x: (x, None, None), - 2: lambda x, y: (x, y, None), 3: lambda x, y, z: (x, y, z), }) @@ -78,6 +76,35 @@ def _uname(rctx, path): return VersionedInfo("linux.{}.{}.{}".format(int(major), int(minor), int(patch))) +def _cmd(rctx, path): + """ + Determines the operating system version from `ver`, a `cmd` built-in. + + Args: + rctx: The repository context that can execute commands on the host machine. + path: the path to the `cmd` executable. + + Returns: + The `VersionedInfo` provider + """ + result = rctx.execute((path, "/C", "ver")) + if result.return_code != 0: + fail("Failed to get `ver` release: {}".format(result.stderr)) + + version = result.stdout.strip() + sku, version = version.split(" [Version ") + version = version.removesuffix("]") + + major, minor, build, patch = split(version, ".", { + 4: lambda w, x, y, z: (w, x, y, z), + }) + + variant = { + "Microsoft Windows": "windows", + }[sku] + + return VersionedInfo("{}.{}.{}.{}+{}".format(variant, int(major), int(minor), int(build), int(patch))) + def os(rctx): """ Detects the host operating system. @@ -93,9 +120,14 @@ def os(rctx): return _header(rctx, path) path = rctx.which("uname") - if path.exists: + if path: return _uname(rctx, path) + path = rctx.which("cmd.exe") + if path: + return _cmd(rctx, path) + return VersionedInfo({ "linux": "linux", + "windows 10": "windows.10", }[rctx.os.name]) diff --git a/toolchain/local/which/repository.bzl b/toolchain/local/which/repository.bzl index ba190b138d105c5aadbff0fcf83865cbeb0b2907..864f51813835048530468441ac4bff50d8c82505 100644 --- a/toolchain/local/which/repository.bzl +++ b/toolchain/local/which/repository.bzl @@ -61,6 +61,7 @@ ATTRS = _ATTRS | { def implementation(rctx): program = rctx.attr.program or rctx.attr.name.rsplit("~", 1)[1] + basename = rctx.attr.basename or program path = rctx.which(program) if not path: @@ -70,7 +71,7 @@ def implementation(rctx): rctx.template("resolved.bzl", rctx.attr.resolved, { "{{toolchain_type}}": str(rctx.attr.toolchain_type), - "{{basename}}": str(rctx.attr.basename), + "{{basename}}": basename, }, executable = False) rctx.template("entrypoint", rctx.attr.entrypoint, { @@ -80,6 +81,7 @@ def implementation(rctx): rctx.template("BUILD.bazel", rctx.attr.build, { "{{name}}": rctx.attr.target or program, "{{program}}": program, + "{{basename}}": basename, "{{path}}": str(path.realpath), "{{variable}}": rctx.attr.variable or program.upper(), "{{toolchain_type}}": str(rctx.attr.toolchain_type), diff --git a/toolchain/symlink/target/rule.bzl b/toolchain/symlink/target/rule.bzl index 1c4a94c8148d8cc98bb9bf7ae9ca1616dd9878a9..fabca2d4a494a23774a64a5cd198332ed6188a37 100644 --- a/toolchain/symlink/target/rule.bzl +++ b/toolchain/symlink/target/rule.bzl @@ -1,5 +1,41 @@ visibility("//toolchain/...") +DOC = """Creates a executable symlink to a binary target file. + +This rule can be used to symlink a executable target and export the necessary toolchain providers. + +Often used with downloaded binary targets: + +```py +load("@rules_toolchain//toolchain:defs.bzl", "ToolchainTripletInfo") + +toolchain_type( + name = "type", +) + +# Setup a toolchain for each downloaded binary +[ + ( + toolchain_symlink_target( + name = "something-{}".format(triplet.value), + target = "@downloaded-{}//:something".format(triplet), + ), + toolchain( + name = triplet.value, + toolchain = ":something-{}".format(triplet.value), + exec_compatible_with = triplet.constraints, + ) + ) + for triplet in ( + ToolchainTripletInfo("arm64-linux-gnu"), + ToolchainTripletInfo("arm64-linux-musl"), + ) +] +``` + +`rules_download` has a `download.archive` and `download.file` extension that can help with retrieving remote binaries. +""" + ATTRS = { "target": attr.label( doc = "The binary file to symlink.", @@ -50,41 +86,7 @@ def implementation(ctx): return [variables, toolchain, default] target = rule( - doc = """Creates a executable symlink to a binary target file. - -This rule can be used to symlink a executable target and export the necessary toolchain providers. - -Often used with downloaded binary targets: - -```py -load("@rules_toolchain//toolchain:defs.bzl", "ToolchainTripletInfo") - -toolchain_type( - name = "type", -) - -# Setup a toolchain for each downloaded binary -[ - ( - toolchain_symlink_target( - name = "something-{}".format(triplet.value), - target = "@downloaded-{}//:something".format(triplet), - ), - toolchain( - name = triplet.value, - toolchain = ":something-{}".format(triplet.value), - exec_compatible_with = triplet.constraints, - ) - ) - for triplet in ( - ToolchainTripletInfo("arm64-linux-gnu"), - ToolchainTripletInfo("arm64-linux-musl"), - ) -] -``` - -`rules_download` has a `download.archive` and `download.file` extension that can help with retrieving remote binaries. -""", + doc = DOC, attrs = ATTRS, implementation = implementation, provides = [ diff --git a/toolchain/triplet/BUILD.bazel b/toolchain/triplet/BUILD.bazel index 7a7903f25c6fbc1b8b33ee8e6bf3c831464fe767..a178f9e6dd02ab855fa2f0615c5d0d83a798e1fc 100644 --- a/toolchain/triplet/BUILD.bazel +++ b/toolchain/triplet/BUILD.bazel @@ -2,6 +2,11 @@ load(":triplets.bzl", "TRIPLETS") load(":rule.bzl", "triplet") load(":test.bzl", "test") +exports_files([ + "nt.tmpl.bat", + "posix.tmpl.sh", +]) + [ triplet( name = t.value, @@ -18,5 +23,15 @@ alias( test( name = "test", + size = "small", target_under_test = ":amd64-linux-gnu", ) + +alias( + name = "template", + actual = select({ + "//toolchain/constraint/os:windows": ":nt.tmpl.bat", + "//conditions:default": ":posix.tmpl.sh", + }), + visibility = ["//toolchain:__subpackages__"], +) diff --git a/toolchain/triplet/nt.tmpl.bat b/toolchain/triplet/nt.tmpl.bat new file mode 100644 index 0000000000000000000000000000000000000000..7231aad06e6a45a5632ac62464f7cbf33b5d3336 --- /dev/null +++ b/toolchain/triplet/nt.tmpl.bat @@ -0,0 +1,21 @@ +:: Enable Batch extensions +@verify other 2>nul +@setlocal EnableExtensions +@if errorlevel 1 ( + echo "Failed to enable extensions" + exit /b 120 +) + +:: Enable delayed expansion of variables with `!VAR!` +@verify other 2>nul +@setlocal EnableDelayedExpansion +@if errorlevel 1 ( + echo "Failed to enable extensions" + exit /b 120 +) + +:: Bazel substitutions +@set "TRIPLET={{triplet}}" + +:: Execute! +@echo.%TRIPLET% diff --git a/toolchain/triplet/posix.tmpl.sh b/toolchain/triplet/posix.tmpl.sh new file mode 100644 index 0000000000000000000000000000000000000000..63432f1004ef9dbda3b2a36e611b8d581c6f4e5a --- /dev/null +++ b/toolchain/triplet/posix.tmpl.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# Strict shell +set -o errexit -o nounset + +# Bazel substitutions +TRIPLET="{{triplet}}" +readonly TRIPLET + +# Execute! +printf '%s\n' "${TRIPLET}" diff --git a/toolchain/triplet/rule.bzl b/toolchain/triplet/rule.bzl index 2fa03d34c8aed918015c97a00c3e30d74ebff552..c916db8d8974905211b60f164f58b061b2d458dd 100644 --- a/toolchain/triplet/rule.bzl +++ b/toolchain/triplet/rule.bzl @@ -6,6 +6,13 @@ ATTRS = { "value": attr.string( doc = "A triplet value that overrides `name`.", ), + "template": attr.label( + doc = "The executable script template.", + default = ":template", + allow_single_file = True, + executable = False, + cfg = "exec", + ), } def implementation(ctx): @@ -18,10 +25,14 @@ def implementation(ctx): content = value, ) - executable = ctx.actions.declare_file("{}.sh".format(value)) - ctx.actions.write( + substitutions = ctx.actions.template_dict() + substitutions.add("{{triplet}}", value) + + executable = ctx.actions.declare_file("{}.{}".format(value, ctx.file.template.extension)) + ctx.actions.expand_template( output = executable, - content = "#!/bin/sh\nprintf '%s\n' {}".format(value), + template = ctx.file.template, + computed_substitutions = substitutions, is_executable = True, )