Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions .github/workflows/release_build_tools.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Builds `//dev/release_artifacts:artifacts_for_release` natively for each
# platform; see dev/release_artifacts/build.sh.
#
# For now, this only verifies that the artifacts build; they aren't attached to
# releases yet.
name: "Release: Build Tools"

on:
workflow_call:
inputs:
ref:
description: "The git ref (e.g. release tag) to build"
required: true
type: string
workflow_dispatch:
inputs:
ref:
description: "The git ref (e.g. release tag) to build. Defaults to the selected ref."
required: false
type: string

permissions:
contents: read

defaults:
run:
shell: bash

jobs:
build:
name: Build ${{ matrix.triple }}
runs-on: ${{ matrix.runner }}
strategy:
# Report the result of every platform instead of stopping at the first
# failure.
fail-fast: false
matrix:
include:
- triple: aarch64-apple-darwin
runner: macos-15
- triple: aarch64-pc-windows-msvc
runner: windows-11-arm
- triple: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
- triple: x86_64-apple-darwin
runner: macos-15-intel
- triple: x86_64-pc-windows-msvc
runner: windows-2022
- triple: x86_64-unknown-linux-gnu
runner: ubuntu-24.04
env:
REF: ${{ inputs.ref || github.ref_name }}
TRIPLE: ${{ matrix.triple }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}
persist-credentials: false

- name: Setup Bazel
uses: bazel-contrib/setup-bazel@0.19.0
with:
# Windows images also have a fixed-version `bazel` on PATH. 1.20.0,
# used by the other workflows, has no windows-arm64 build.
bazelisk-version: 1.29.0

# Release commands run main's workflows, even for patch releases of older
# release branches, which don't have the script; skip those.
- name: Build artifacts
id: build
if: hashFiles('dev/release_artifacts/build.sh') != ''
run: dev/release_artifacts/build.sh "$REF"

# build.sh only sets `files` if the build succeeded. Otherwise, it adds a
# warning instead of failing, since releases don't use the files yet.
- name: Verify artifacts
if: steps.build.outputs.files != ''
run: dev/release_artifacts/verify.sh "$TRIPLE"

# Stores the files with this workflow run so they can be downloaded from
# the run's page. Nothing is added to the GitHub release.
- name: Upload artifacts
if: steps.build.outputs.files != ''
uses: actions/upload-artifact@v7
with:
name: release-tools-${{ matrix.triple }}
path: ${{ steps.build.outputs.files }}
if-no-files-found: error
9 changes: 9 additions & 0 deletions .github/workflows/release_publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ on:
default: true

jobs:
build_tools:
# For now, this only verifies that the tools build. They aren't attached
# to the release, so no other job waits on this one. A failed build only
# adds a warning to this run; a failed check of the built files fails it.
name: Build tools
uses: ./.github/workflows/release_build_tools.yaml
with:
ref: ${{ inputs.tag_name || github.ref_name }}

release:
name: Release
runs-on: ubuntu-latest
Expand Down
6 changes: 6 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ you can set the `publish_to_pypi` input to `false`:
gh workflow run release_publish.yaml --ref <TAG> -f publish_to_pypi=false
```

### Prebuilt tools

The release workflow also builds `//dev/release_artifacts:artifacts_for_release`
for each platform to verify it builds. For now, the files aren't attached to
the release.

### Manually publishing to PyPI

If PyPI publishing failed or was skipped during the main release, the PyPI
Expand Down
20 changes: 20 additions & 0 deletions dev/release_artifacts/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED") # buildifier: disable=bzl-visibility
load(":release_files.bzl", "release_files")

package(default_visibility = ["//:__subpackages__"])

# Files that the release workflow builds for each platform; see build.sh.
release_files(
name = "artifacts_for_release",
srcs = ["//crates/exe_zip_maker"],
# Under WORKSPACE, rules_rust is a stub without the rustc flags setting
# that release_files sets, so building this would fail.
tags = [] if BZLMOD_ENABLED else ["manual"],
)

bzl_library(
name = "release_files",
srcs = ["release_files.bzl"],
deps = ["//python/private:common_labels"],
)
32 changes: 32 additions & 0 deletions dev/release_artifacts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Builds //dev/release_artifacts:artifacts_for_release for the host platform.
# The target applies the release settings; see release_files.bzl. In GitHub
# Actions, the paths of the built files, relative to the workspace root, are
# set as the step's `files` output, one per line.
#
# Usage: dev/release_artifacts/build.sh EMBED_LABEL
set -euo pipefail

embed_label="$1"

# The paths below are relative to the workspace root.
cd "$(dirname "$0")/../.."

# Keep Git Bash on Windows from rewriting `//foo` labels into paths.
export MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL="*"

if ! bazel build --verbose_failures --stamp --embed_label="$embed_label" \
//dev/release_artifacts:artifacts_for_release; then
# Releases don't use the files yet, so don't fail the release over them.
echo "::warning::Building the release artifacts failed. Releases don't use them yet."
exit 0
fi

# The target lists the paths of its files in a manifest; see release_files.bzl.
if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
{
echo "files<<EOF"
cat bazel-bin/dev/release_artifacts/artifacts_for_release.txt
echo "EOF"
} >> "$GITHUB_OUTPUT"
fi
96 changes: 96 additions & 0 deletions dev/release_artifacts/release_files.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Macro to build the files for a release and list where they are."""

load("//python/private:common_labels.bzl", "labels") # buildifier: disable=bzl-visibility

_FEATURES = "//command_line_option:features"
_RUSTC_FLAGS = "@rules_rust//rust/settings:extra_rustc_flags"

def _release_transition_impl(settings, attr):
features = []
rustc_flags = []
if attr.target_os == "linux":
# Rust links glibc dynamically, so the files would require at least
# the build machine's glibc version. Link it statically so they run on
# older distros too. The gold linker can't link static glibc, so use
# bfd.
rustc_flags = [
"-Ctarget-feature=+crt-static",
"-Clink-arg=-fuse-ld=bfd",
]
elif attr.target_os == "windows":
# Statically link the C runtime so that the VC++ redistributable isn't
# required.
features = ["static_link_msvcrt"]
rustc_flags = ["-Ctarget-feature=+crt-static"]
return {
"//command_line_option:compilation_mode": "opt",
# Only affects macOS. Without it, the minimum OS version is the build
# machine's SDK version.
"//command_line_option:macos_minimum_os": "11.0",
_FEATURES: settings[_FEATURES] + features,
_RUSTC_FLAGS: settings[_RUSTC_FLAGS] + rustc_flags,
}

_release_transition = transition(
implementation = _release_transition_impl,
inputs = [_FEATURES, _RUSTC_FLAGS],
outputs = [
"//command_line_option:compilation_mode",
"//command_line_option:macos_minimum_os",
_FEATURES,
_RUSTC_FLAGS,
],
)

def _release_files_impl(ctx):
manifest = ctx.actions.declare_file(ctx.label.name + ".txt")
ctx.actions.write(
output = manifest,
# End every line with "\n", including the last: `while read` loops skip
# an unterminated last line, and build.sh adds a line after the paths.
content = "".join([file.path + "\n" for file in ctx.files.srcs]),
)
return [DefaultInfo(files = depset([manifest] + ctx.files.srcs))]

_release_files = rule(
implementation = _release_files_impl,
attrs = {
"srcs": attr.label_list(
allow_files = True,
cfg = _release_transition,
doc = "The files to build and list.",
),
"target_os": attr.string(
doc = "The OS that the files are built for. Set by the macro.",
),
},
)

def release_files(name, srcs, **kwargs):
"""Builds files for a release and writes their paths to `<name>.txt`.

The files are built with release settings: optimized, and linked so that
they run on older OS versions than the build machine's, without extra
runtime libraries.

The paths, one per line, are relative to the execroot, e.g.
`bazel-out/k8-opt-ST-1234/bin/foo/foo`. Paths of generated files also
resolve from the workspace root through the `bazel-out` convenience
symlink. This lets scripts find the files without running `bazel cquery`,
which re-analyzes the build.

Args:
name: The target name.
srcs: The files to build and list.
**kwargs: Additional attributes for the rule.
"""
_release_files(
name = name,
srcs = srcs,
target_os = select({
Label("@platforms//os:linux"): "linux",
labels.PLATFORMS_OS_WINDOWS: "windows",
"//conditions:default": "",
}),
**kwargs
)
36 changes: 36 additions & 0 deletions dev/release_artifacts/verify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Checks that the files built by build.sh don't require a newer OS than users
# may have. Only Linux and macOS files are checked.
#
# Usage: dev/release_artifacts/verify.sh TRIPLE
set -euo pipefail

triple="$1"

# The paths below are relative to the workspace root.
cd "$(dirname "$0")/../.."

# The target lists the paths of its files in a manifest; see release_files.bzl.
while IFS= read -r bin; do
case "$triple" in
*-linux-gnu)
linkage="$(file "$bin")"
if [[ "$linkage" != *"statically linked"* &&
"$linkage" != *"static-pie linked"* ]]; then
echo "::error::$bin isn't statically linked: $linkage"
exit 1
fi
;;
*-apple-darwin)
minos="$(otool -l "$bin" | awk '
$2 == "LC_BUILD_VERSION" || $2 == "LC_VERSION_MIN_MACOSX" { found = 1 }
found && minos == "" && ($1 == "minos" || $1 == "version") { minos = $2 }
END { print minos }
')"
if [[ -z "$minos" || "${minos%%.*}" -gt 11 ]]; then
echo "::error::$bin requires macOS ${minos:-<unknown>}; expected 11 or lower"
exit 1
fi
;;
esac
done < bazel-bin/dev/release_artifacts/artifacts_for_release.txt
Loading