diff --git a/.build.yml b/.build.yml index 6aeec50..6144d65 100644 --- a/.build.yml +++ b/.build.yml @@ -5,6 +5,7 @@ packages: - qemu-user-static - binfmt-support - moreutils + - wine64 sources: - https://git.sr.ht/~motiejus/bazel-zig-cc environment: diff --git a/README.md b/README.md index 552a0f5..a709204 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ http_archive( name = "bazel-zig-cc", sha256 = "cd2629843fe4ba20cf29e1d73cc02559afba640f884e519b6a194a35627cbbf3", strip_prefix = "bazel-zig-cc-{}".format(BAZEL_ZIG_CC_VERSION), - urls = ["https://git.sr.ht/~motiejus/bazel-zig-cc/archive/{}.tar.gz".format(BAZEL_ZIG_CC_VERSION)], + urls = ["https://git.sr.ht/~motiejus/bazel-zig-cc/archive/{}.{_ext}".format(BAZEL_ZIG_CC_VERSION)], ) load("@bazel-zig-cc//toolchain:defs.bzl", zig_toolchains = "toolchains") @@ -40,7 +40,7 @@ load("@bazel-zig-cc//toolchain:defs.bzl", zig_toolchains = "toolchains") zig_toolchains( version = "<...>", url_formats = [ - "https://example.org/zig/zig-{host_platform}-{version}.tar.xz", + "https://example.org/zig/zig-{host_platform}-{version}.{_ext}", ], host_platform_sha256 = { ... }, ) @@ -152,6 +152,8 @@ register_toolchains( "@zig_sdk//toolchain:linux_arm64_gnu.2.28", "@zig_sdk//toolchain:darwin_amd64", "@zig_sdk//toolchain:darwin_arm64", + "@zig_sdk//toolchain:windows_amd64", + "@zig_sdk//toolchain:windows_arm64", ) ``` @@ -318,6 +320,17 @@ is currently not implemented. target macos.10 (Catalina), macos.11 (Big Sur) or macos.12 (Monterey). It currently targets the lowest version, without ability to change it. +## Windows only: output file extensions + +For Windows targets Bazel uses Unix extensions for output binaries. Those may +need to be renamed before deploying to the Windows system. Here is a primer: + +| Binary type | Bazel extension | Windows extension | +|----------------|-----------------|-------------------| +| Static library | .a | .lib | +| Shared library | .so | .dll | +| Executable | (no extension) | .exe | + # Known Issues In Upstream This section lists issues that I've stumbled into when using `zig cc`, and is @@ -384,10 +397,12 @@ This repository is used on the following (host) platforms: - `linux_arm64`, a.k.a. `AArch64`. - `darwin_amd64`, the 64-bit post-PowerPC models. - `darwin_arm64`, the M1. +- `windows_amd64`, a.k.a. `x64`. The tests are running (CId) on linux-amd64, and are assuming the kernel is -configured to run arm64 binaries. There are two reasonably convenient ways to -configure arm64 emulation: +configured to run `linux_arm64` and `windows_amd64` binaries. + +There are two reasonably convenient ways to configure `linux_arm64` emulation: 1. Install and configure [`binfmt_misc`][binfmt_misc]: ``` @@ -399,6 +414,11 @@ configure arm64 emulation: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes ``` +In order to install and configure `windows_amd64` emulation: +``` +apt install wine-binfmt +``` + ## Transient docker environment A standalone Docker environment to play with bazel-zig-cc: diff --git a/WORKSPACE b/WORKSPACE index 214b203..b01b785 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -51,11 +51,13 @@ zig_toolchains() register_toolchains( # if no `--platform` is specified, these toolchains will be used for - # (linux,darwin)x(amd64,arm64) + # (linux,darwin,windows)x(amd64,arm64) "@zig_sdk//toolchain:linux_amd64_gnu.2.19", "@zig_sdk//toolchain:linux_arm64_gnu.2.28", "@zig_sdk//toolchain:darwin_amd64", "@zig_sdk//toolchain:darwin_arm64", + "@zig_sdk//toolchain:windows_amd64", + "@zig_sdk//toolchain:windows_arm64", # amd64 toolchains for libc-aware platforms: "@zig_sdk//libc_aware/toolchain:linux_amd64_gnu.2.19", diff --git a/ci/test b/ci/test index debdc0b..269510d 100755 --- a/ci/test +++ b/ci/test @@ -1,3 +1,4 @@ -#!/bin/sh +#!/bin/bash +set -xeuo pipefail -exec bazel test ... +bazel test ... diff --git a/relnotes.awk b/relnotes.awk index 3ac8a2f..a24720a 100755 --- a/relnotes.awk +++ b/relnotes.awk @@ -14,7 +14,7 @@ BEGIN {stage=0}; print " name = \"bazel-zig-cc\"," print " sha256 = \""sha256sum"\"," print " strip_prefix = \"bazel-zig-cc-{}\".format(BAZEL_ZIG_CC_VERSION)," - print " urls = [\"https://git.sr.ht/~motiejus/bazel-zig-cc/archive/{}.tar.gz\".format(BAZEL_ZIG_CC_VERSION)]," + print " urls = [\"https://git.sr.ht/~motiejus/bazel-zig-cc/archive/{}.{_ext}\".format(BAZEL_ZIG_CC_VERSION)]," print ")" stage=1 next diff --git a/rules/platform.bzl b/rules/platform.bzl index aabb328..d179cc4 100644 --- a/rules/platform.bzl +++ b/rules/platform.bzl @@ -1,3 +1,10 @@ +def _vars_script(env, run_under, cmd): + ret = ["#!/bin/sh"] + for k, v in env.items(): + ret += ['export {}="{}"'.format(k, v)] + ret += ['exec {} {} "$@"'.format(run_under, cmd)] + return "\n".join(ret) + "\n" # trailing newline is easier on the eyes + def _platform_transition_impl(settings, attr): _ignore = settings return { @@ -17,16 +24,18 @@ def _platform_binary_impl(ctx): executable = None if source_info.files_to_run and source_info.files_to_run.executable: + command = _vars_script(ctx.attr.env, ctx.attr.run_under, source_info.files_to_run.executable.short_path) executable = ctx.actions.declare_file("{}_{}".format(ctx.file.src.basename, ctx.attr.platform)) - ctx.actions.run_shell( - command = "cp {} {}".format(source_info.files_to_run.executable.path, executable.path), - inputs = [source_info.files_to_run.executable], - outputs = [executable], + ctx.actions.write( + output = executable, + content = command, + is_executable = True, ) return [DefaultInfo( - files = depset(ctx.files.src), executable = executable, + files = depset([executable]), + runfiles = ctx.runfiles(files = ctx.files.src), )] _attrs = { @@ -38,6 +47,12 @@ _attrs = { "platform": attr.string( doc = "The platform to build the target for.", ), + "run_under": attr.string( + doc = "wrapper executable", + ), + "env": attr.string_dict( + doc = "Environment variables for the test", + ), "_allowlist_function_transition": attr.label( default = "@bazel_tools//tools/allowlists/function_transition_allowlist", ), diff --git a/test/c/BUILD b/test/c/BUILD index 99e36c4..c5e6748 100644 --- a/test/c/BUILD +++ b/test/c/BUILD @@ -13,7 +13,9 @@ cc_binary( platform_binary( name = "which_libc_{}".format(name), src = "which_libc", + env = {"QEMU_LD_PREFIX": "/usr/aarch64-linux-gnu"} if is_arm64 else {}, platform = platform, + run_under = "qemu-aarch64-static" if is_arm64 else "", ), sh_test( name = "test_libc_{}".format(name), @@ -25,12 +27,12 @@ cc_binary( }, ), ) - for name, platform, want in [ - ("linux_amd64_musl", "//libc_aware/platform:linux_amd64_musl", "non-glibc"), - ("linux_amd64_gnu.2.19", "//libc_aware/platform:linux_amd64_gnu.2.19", "glibc_2.19"), - ("linux_amd64_gnu.2.28", "//libc_aware/platform:linux_amd64_gnu.2.28", "glibc_2.28"), - ("linux_amd64_gnu.2.31", "//libc_aware/platform:linux_amd64_gnu.2.31", "glibc_2.31"), - ("linux_amd64", "//platform:linux_amd64", "glibc_2.19"), - ("linux_arm64", "//platform:linux_arm64", "glibc_2.28"), + for name, platform, want, is_arm64 in [ + ("linux_amd64_musl", "//libc_aware/platform:linux_amd64_musl", "non-glibc", False), + ("linux_amd64_gnu.2.19", "//libc_aware/platform:linux_amd64_gnu.2.19", "glibc_2.19", False), + ("linux_amd64_gnu.2.28", "//libc_aware/platform:linux_amd64_gnu.2.28", "glibc_2.28", False), + ("linux_amd64_gnu.2.31", "//libc_aware/platform:linux_amd64_gnu.2.31", "glibc_2.31", False), + ("linux_amd64", "//platform:linux_amd64", "glibc_2.19", False), + ("linux_arm64", "//platform:linux_arm64", "glibc_2.28", True), ] ] diff --git a/test/c/test.sh b/test/c/test.sh index d5331ff..ee67dd3 100755 --- a/test/c/test.sh +++ b/test/c/test.sh @@ -1,10 +1,11 @@ -#/bin/bash +#!/bin/bash set -euo pipefail +# shellcheck disable=SC2153 want=$WANT +# shellcheck disable=SC2153 binary=$BINARY - got=$($binary) if [[ "$got" != "$want" ]]; then diff --git a/test/cgo/BUILD b/test/cgo/BUILD index 1e11a22..a65c350 100644 --- a/test/cgo/BUILD +++ b/test/cgo/BUILD @@ -41,12 +41,14 @@ go_binary( platform_test( name = "cgo_test_{}".format(name), src = "cgo_test", + env = {"QEMU_LD_PREFIX": "/usr/aarch64-linux-gnu"} if is_arm64 else {}, platform = platform, + run_under = "qemu-aarch64-static" if is_arm64 else "", ) - for name, platform in [ - ("linux_amd64_musl", "//libc_aware/platform:linux_amd64_musl"), - ("linux_amd64_gnu.2.19", "//libc_aware/platform:linux_amd64_gnu.2.19"), - ("linux_arm64_musl", "//libc_aware/platform:linux_arm64_musl"), - ("linux_arm64_gnu.2.28", "//libc_aware/platform:linux_arm64_gnu.2.28"), + for name, platform, is_arm64 in [ + ("linux_amd64_musl", "//libc_aware/platform:linux_amd64_musl", False), + ("linux_amd64_gnu.2.19", "//libc_aware/platform:linux_amd64_gnu.2.19", False), + ("linux_arm64_musl", "//libc_aware/platform:linux_arm64_musl", True), + ("linux_arm64_gnu.2.28", "//libc_aware/platform:linux_arm64_gnu.2.28", True), ] ] diff --git a/test/windows/BUILD b/test/windows/BUILD new file mode 100644 index 0000000..cb8d17a --- /dev/null +++ b/test/windows/BUILD @@ -0,0 +1,21 @@ +load("@bazel-zig-cc//rules:platform.bzl", "platform_binary", "platform_test") + +cc_binary( + name = "winver", + srcs = ["main.c"], + tags = ["manual"], +) + +platform_test( + name = "winver_windows_amd64", + src = "winver", + platform = "//platform:windows_amd64", + run_under = "wine64-stable", + tags = ["no-sandbox"], +) + +platform_binary( + name = "winver_windows_arm64", + src = "winver", + platform = "//platform:windows_arm64", +) diff --git a/test/windows/main.c b/test/windows/main.c new file mode 100644 index 0000000..b5302af --- /dev/null +++ b/test/windows/main.c @@ -0,0 +1,16 @@ +#include +#include + +int main() { + DWORD version = GetVersion(); + DWORD majorVersion = (DWORD)(LOBYTE(LOWORD(version))); + DWORD minorVersion = (DWORD)(HIBYTE(LOWORD(version))); + + DWORD build = 0; + if (version < 0x80000000) { + build = (DWORD)(HIWORD(version)); + } + + printf("Running Windows version %d.%d (%d).\n", majorVersion, minorVersion, build); + return 0; +} diff --git a/toolchain/BUILD.sdk.bazel b/toolchain/BUILD.sdk.bazel index ada3e5d..e1505b1 100644 --- a/toolchain/BUILD.sdk.bazel +++ b/toolchain/BUILD.sdk.bazel @@ -7,6 +7,7 @@ package( declare_files( + os = {os}, zig_include_root = {zig_include_root}, ) diff --git a/toolchain/defs.bzl b/toolchain/defs.bzl index 9ab4e88..e165a86 100644 --- a/toolchain/defs.bzl +++ b/toolchain/defs.bzl @@ -1,6 +1,6 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("@bazel_tools//tools/build_defs/repo:utils.bzl", "read_user_netrc", "use_netrc") -load("@bazel-zig-cc//toolchain/private:defs.bzl", "DEFAULT_INCLUDE_DIRECTORIES", "ZIG_TOOL_PATH", "target_structs") +load("@bazel-zig-cc//toolchain/private:defs.bzl", "DEFAULT_INCLUDE_DIRECTORIES", "target_structs", "zig_tool_path") _fcntl_map = """ GLIBC_2.2.5 { @@ -16,21 +16,21 @@ __asm__(".symver fcntl64, fcntl@GLIBC_2.2.5"); """ # Official recommended version. Should use this when we have a usable release. -URL_FORMAT_RELEASE = "https://ziglang.org/download/{version}/zig-{host_platform}-{version}.tar.xz" +URL_FORMAT_RELEASE = "https://ziglang.org/download/{version}/zig-{host_platform}-{version}.{_ext}" # Caution: nightly releases are purged from ziglang.org after ~90 days. A real # solution would be to allow the downstream project specify their own mirrors. # This is explained in # https://sr.ht/~motiejus/bazel-zig-cc/#alternative-download-urls and is # awaiting my attention or your contribution. -URL_FORMAT_NIGHTLY = "https://ziglang.org/builds/zig-{host_platform}-{version}.tar.xz" +URL_FORMAT_NIGHTLY = "https://ziglang.org/builds/zig-{host_platform}-{version}.{_ext}" # Author's mirror that doesn't purge the nightlies so aggressively. I will be # cleaning those up manually only after the artifacts are not in use for many # months in bazel-zig-cc. dl.jakstys.lt is a small x86_64 server with an NVMe # drive sitting in my home closet on a 1GB/s symmetric residential connection, # which, as of writing, has been quite reliable. -URL_FORMAT_JAKSTYS = "https://dl.jakstys.lt/zig/zig-{host_platform}-{version}.tar.xz" +URL_FORMAT_JAKSTYS = "https://dl.jakstys.lt/zig/zig-{host_platform}-{version}.{_ext}" _VERSION = "0.10.0-dev.2252+a4369918b" @@ -39,12 +39,22 @@ _HOST_PLATFORM_SHA256 = { "linux-x86_64": "1d3c3769eba85a4334c93a3cfa35ad0ef914dd8cf9fd502802004c6908f5370c", "macos-aarch64": "ab46e7499e5bd7b6d6ff2ac331e1a4aa875a01b270dc40306bc29dbaf216fccf", "macos-x86_64": "fb213f996bcab805839e401292c42a92b63cd97deb1631e31bd61f534b7f6b1c", + "windows-x86_64": "14e43a64026512161f3d6201d8972a28f0508da2782c16e980f2ffa3bb7e6720", +} + +_HOST_PLATFORM_EXT = { + "linux-aarch64": "tar.xz", + "linux-x86_64": "tar.xz", + "macos-aarch64": "tar.xz", + "macos-x86_64": "tar.xz", + "windows-x86_64": "zip", } def toolchains( version = _VERSION, url_formats = [URL_FORMAT_NIGHTLY, URL_FORMAT_JAKSTYS], - host_platform_sha256 = _HOST_PLATFORM_SHA256): + host_platform_sha256 = _HOST_PLATFORM_SHA256, + host_platform_ext = _HOST_PLATFORM_EXT): """ Download zig toolchain and declare bazel toolchains. The platforms are not registered automatically, that should be done by @@ -56,11 +66,13 @@ def toolchains( version = version, url_formats = url_formats, host_platform_sha256 = host_platform_sha256, + host_platform_ext = host_platform_ext, host_platform_include_root = { "linux-aarch64": "lib/zig/", "linux-x86_64": "lib/", "macos-aarch64": "lib/zig/", "macos-x86_64": "lib/zig/", + "windows-x86_64": "lib/", }, ) @@ -81,6 +93,10 @@ export ZIG_GLOBAL_CACHE_DIR=$ZIG_LOCAL_CACHE_DIR exec "{zig}" "{zig_tool}" "$@" """ +ZIG_TOOL_WRAPPER_WINDOWS = """@echo off +"{zig}" "{zig_tool}" %* +""" + _ZIG_TOOLS = [ "c++", "cc", @@ -103,11 +119,16 @@ def _zig_repository_impl(repository_ctx): if os.startswith("mac os"): os = "macos" + if os.startswith("windows"): + os = "windows" + host_platform = "{}-{}".format(os, arch) zig_include_root = repository_ctx.attr.host_platform_include_root[host_platform] zig_sha256 = repository_ctx.attr.host_platform_sha256[host_platform] + zig_ext = repository_ctx.attr.host_platform_ext[host_platform] format_vars = { + "_ext": zig_ext, "version": repository_ctx.attr.version, "host_platform": host_platform, } @@ -121,12 +142,19 @@ def _zig_repository_impl(repository_ctx): ) for zig_tool in _ZIG_TOOLS: - repository_ctx.file( - ZIG_TOOL_PATH.format(zig_tool = zig_tool), - ZIG_TOOL_WRAPPER.format( - zig = str(repository_ctx.path("zig")), + zig_tool_wrapper = ZIG_TOOL_WRAPPER.format( + zig = str(repository_ctx.path("zig")), + zig_tool = zig_tool, + ) + if os == "windows": + zig_tool_wrapper = ZIG_TOOL_WRAPPER_WINDOWS.format( + zig = str(repository_ctx.path("zig")).replace("/", "\\") + ".exe", zig_tool = zig_tool, - ), + ) + + repository_ctx.file( + zig_tool_path(os).format(zig_tool = zig_tool), + zig_tool_wrapper, ) repository_ctx.file( @@ -157,6 +185,7 @@ def _zig_repository_impl(repository_ctx): executable = False, substitutions = { "{absolute_path}": _quote(str(repository_ctx.path(""))), + "{os}": _quote(os), "{zig_include_root}": _quote(zig_include_root), }, ) @@ -167,6 +196,7 @@ zig_repository = repository_rule( "host_platform_sha256": attr.string_dict(), "url_formats": attr.string_list(allow_empty = False), "host_platform_include_root": attr.string_dict(), + "host_platform_ext": attr.string_dict(), }, implementation = _zig_repository_impl, ) @@ -175,9 +205,13 @@ def filegroup(name, **kwargs): native.filegroup(name = name, **kwargs) return ":" + name -def declare_files(zig_include_root): +def declare_files(os, zig_include_root): filegroup(name = "empty") - native.exports_files(["zig"], visibility = ["//visibility:public"]) + if os == "windows": + native.exports_files(["zig.exe"], visibility = ["//visibility:public"]) + native.alias(name = "zig", actual = ":zig.exe") + else: + native.exports_files(["zig"], visibility = ["//visibility:public"]) filegroup(name = "lib/std", srcs = native.glob(["lib/std/**"])) lazy_filegroups = {} diff --git a/toolchain/platform/defs.bzl b/toolchain/platform/defs.bzl index cf42191..07e69a6 100644 --- a/toolchain/platform/defs.bzl +++ b/toolchain/platform/defs.bzl @@ -1,11 +1,16 @@ load("@bazel-zig-cc//toolchain/private:defs.bzl", "LIBCS") _CPUS = (("x86_64", "amd64"), ("aarch64", "arm64")) +_OS = { + "linux": ["linux"], + "macos": ["macos", "darwin"], + "windows": ["windows"], +} def declare_platforms(): # create @zig_sdk//{os}_{arch}_platform entries with zig and go conventions for zigcpu, gocpu in _CPUS: - for bzlos, oss in {"linux": ["linux"], "macos": ["macos", "darwin"]}.items(): + for bzlos, oss in _OS.items(): for os in oss: declare_platform(gocpu, zigcpu, bzlos, os) diff --git a/toolchain/private/BUILD.sdk.bazel b/toolchain/private/BUILD.sdk.bazel index 39a6a93..3e7c23e 100644 --- a/toolchain/private/BUILD.sdk.bazel +++ b/toolchain/private/BUILD.sdk.bazel @@ -1,6 +1,7 @@ load("@bazel-zig-cc//toolchain/private:cc_toolchains.bzl", "declare_cc_toolchains") declare_cc_toolchains( + os = {os}, absolute_path = {absolute_path}, zig_include_root = {zig_include_root}, ) diff --git a/toolchain/private/cc_toolchains.bzl b/toolchain/private/cc_toolchains.bzl index 3a1e1cb..724cbda 100644 --- a/toolchain/private/cc_toolchains.bzl +++ b/toolchain/private/cc_toolchains.bzl @@ -1,4 +1,4 @@ -load(":defs.bzl", "DEFAULT_INCLUDE_DIRECTORIES", "ZIG_TOOL_PATH", "target_structs") +load(":defs.bzl", "DEFAULT_INCLUDE_DIRECTORIES", "target_structs", "zig_tool_path") load("@bazel-zig-cc//toolchain:zig_toolchain.bzl", "zig_cc_toolchain_config") DEFAULT_TOOL_PATHS = { @@ -11,7 +11,7 @@ DEFAULT_TOOL_PATHS = { "strip": "/usr/bin/false", }.items() -def declare_cc_toolchains(absolute_path, zig_include_root): +def declare_cc_toolchains(os, absolute_path, zig_include_root): for target_config in target_structs(): gotarget = target_config.gotarget zigtarget = target_config.zigtarget @@ -28,7 +28,7 @@ def declare_cc_toolchains(absolute_path, zig_include_root): if path[0] == "/": absolute_tool_paths[name] = path continue - tool_path = ZIG_TOOL_PATH.format(zig_tool = path) + tool_path = zig_tool_path(os).format(zig_tool = path) absolute_tool_paths[name] = "%s/%s" % (absolute_path, tool_path) linkopts = target_config.linkopts diff --git a/toolchain/private/defs.bzl b/toolchain/private/defs.bzl index 42aaaeb..1aba438 100644 --- a/toolchain/private/defs.bzl +++ b/toolchain/private/defs.bzl @@ -4,7 +4,7 @@ DEFAULT_INCLUDE_DIRECTORIES = [ "libcxxabi/include", ] -ZIG_TOOL_PATH = "tools/{zig_tool}" +_ZIG_TOOL_PATH = "tools/{zig_tool}" # Zig supports even older glibcs than defined below, but we have tested only # down to 2.17. @@ -30,10 +30,17 @@ _GLIBCS = [ LIBCS = ["musl"] + ["gnu.{}".format(glibc) for glibc in _GLIBCS] +def zig_tool_path(os): + if os == "windows": + return _ZIG_TOOL_PATH + ".bat" + else: + return _ZIG_TOOL_PATH + def target_structs(): ret = [] for zigcpu, gocpu in (("x86_64", "amd64"), ("aarch64", "arm64")): ret.append(_target_darwin(gocpu, zigcpu)) + ret.append(_target_windows(gocpu, zigcpu)) ret.append(_target_linux_musl(gocpu, zigcpu)) for glibc in _GLIBCS: ret.append(_target_linux_gnu(gocpu, zigcpu, glibc)) @@ -64,6 +71,25 @@ def _target_darwin(gocpu, zigcpu): tool_paths = {"ld": "ld64.lld"}, ) +def _target_windows(gocpu, zigcpu): + return struct( + gotarget = "windows_{}".format(gocpu), + zigtarget = "{}-windows-gnu".format(zigcpu), + includes = [ + "libunwind/include", + "libc/include/any-windows-any", + ], + linkopts = [], + dynamic_library_linkopts = [], + copts = [], + bazel_target_cpu = "x64_windows", + constraint_values = [ + "@platforms//os:windows", + "@platforms//cpu:{}".format(zigcpu), + ], + tool_paths = {"ld": "ld64.lld"}, + ) + def _target_linux_gnu(gocpu, zigcpu, glibc_version): glibc_suffix = "gnu.{}".format(glibc_version)