|
1 | 1 | #!/bin/bash |
| 2 | +# Copyright 2026 Google LLC |
| 3 | +# |
| 4 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +# you may not use this file except in compliance with the License. |
| 6 | +# You may obtain a copy of the License at |
| 7 | +# |
| 8 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +# |
| 10 | +# Unless required by applicable law or agreed to in writing, software |
| 11 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +# See the License for the specific language governing permissions and |
| 14 | +# limitations under the License. |
| 15 | + |
2 | 16 | set -e |
3 | 17 |
|
| 18 | +# Avoid virtualenv/pip trying to download/upgrade tools from PyPI on host |
| 19 | +export VIRTUALENV_NO_DOWNLOAD=1 |
| 20 | +export PIP_DISABLE_PIP_VERSION_CHECK=1 |
| 21 | + |
| 22 | +# Pass these environment variables to the cibuildwheel Docker container |
| 23 | +export CIBW_ENVIRONMENT="VIRTUALENV_NO_DOWNLOAD=1 PIP_DISABLE_PIP_VERSION_CHECK=1" |
| 24 | +export CIBW_DEPENDENCY_VERSIONS="latest" |
| 25 | + |
4 | 26 | # If running locally (not on Kokoro), authenticate with gcloud. |
5 | 27 | if [ -z "${KOKORO_BUILD_ID}" ]; then |
6 | 28 | if ! gcloud auth application-default print-access-token --quiet > /dev/null; then |
7 | 29 | gcloud auth application-default login |
8 | 30 | fi |
9 | 31 | fi |
10 | 32 |
|
11 | | -pip install -U keyring keyrings.google-artifactregistry-auth twine cibuildwheel |
| 33 | +# We use --no-cache-dir to force pip to download packages fresh and bypass the local |
| 34 | +# cache. In Kokoro/RBE sandboxed environments, writing to the default cache directory |
| 35 | +# (~/.cache/pip) can encounter permission/sandbox restrictions or lead to stale |
| 36 | +# dependency resolution. Disabling the cache ensures a reliable, reproducible install. |
| 37 | +pip install --no-cache-dir -U keyring keyrings.google-artifactregistry-auth twine cibuildwheel |
| 38 | + |
| 39 | +# Patch cibuildwheel at runtime to bypass the RBE stdout buffering deadlock. |
| 40 | +# The RBE proxy buffers the persistent container bash stdout. By appending a 4KB |
| 41 | +# padding line to the end of every command output, we force the proxy to flush the |
| 42 | +# buffer immediately. We then read and discard this padding to keep the stream clean. |
| 43 | +OCI_PATH=$(python3 -c "import cibuildwheel.oci_container; print(cibuildwheel.oci_container.__file__)") |
| 44 | +echo "Patching cibuildwheel at $OCI_PATH..." |
| 45 | + |
| 46 | +cat << 'EOF' > patch_oci.py |
| 47 | +import sys |
| 48 | +
|
| 49 | +path = sys.argv[1] |
| 50 | +with open(path, 'r') as f: |
| 51 | + content = f.read() |
| 52 | +
|
| 53 | +# 1. Force a 32KB flush at the end of every command execution |
| 54 | +target_write = 'printf "%04d%s\\n" $? {end_of_message}' |
| 55 | +replacement_write = 'printf "%04d%s\\n%32768s\\n" $? {end_of_message} " "' |
| 56 | +if target_write in content: |
| 57 | + content = content.replace(target_write, replacement_write) |
| 58 | + print("Patched write loop.") |
| 59 | +
|
| 60 | +# 2. Read and discard the 4KB padding to keep the stream clean |
| 61 | +target_read = """ # add the last line to output, without the footer |
| 62 | + output_io.write(line[0:footer_offset]) |
| 63 | + output_io.flush() |
| 64 | + break""" |
| 65 | +
|
| 66 | +replacement_read = """ # add the last line to output, without the footer |
| 67 | + output_io.write(line[0:footer_offset]) |
| 68 | + output_io.flush() |
| 69 | + # Read and discard the 4KB padding line to clear the stream! |
| 70 | + self.bash_stdout.readline() |
| 71 | + break""" |
| 72 | +
|
| 73 | +if target_read in content: |
| 74 | + content = content.replace(target_read, replacement_read) |
| 75 | + print("Patched read loop.") |
| 76 | +
|
| 77 | +with open(path, 'w') as f: |
| 78 | + f.write(content) |
| 79 | +
|
| 80 | +print("Successfully patched oci_container.py!") |
| 81 | +EOF |
| 82 | + |
| 83 | +python3 patch_oci.py "$OCI_PATH" |
| 84 | +rm patch_oci.py |
| 85 | + |
| 86 | +# Verify that the patched file is syntactically valid Python |
| 87 | +echo "Verifying patched oci_container.py syntax..." |
| 88 | +python3 -m py_compile "$OCI_PATH" || { echo "ERROR: Patched oci_container.py is corrupted!"; exit 1; } |
| 89 | + |
| 90 | +REPO_DIR="" |
| 91 | +TMP_DIR="" |
| 92 | +cleanup() { |
| 93 | + echo "Cleaning up temporary directories..." |
| 94 | + [ -n "${REPO_DIR}" ] && rm -rf "${REPO_DIR}" |
| 95 | + [ -n "${TMP_DIR}" ] && rm -rf "${TMP_DIR}" |
| 96 | +} |
| 97 | +trap cleanup EXIT |
12 | 98 |
|
13 | 99 | REPO_DIR=$(mktemp -d) |
14 | 100 | echo "Created temporary directory: ${REPO_DIR}" |
15 | 101 |
|
16 | | -# Ensure the temporary directory is removed on script exit |
17 | | -trap 'echo "Cleaning up temporary directory: ${REPO_DIR}"; rm -rf "${REPO_DIR}"' EXIT |
18 | | - |
19 | 102 | if [ "${DRY_RUN}" = "true" ]; then |
20 | 103 | echo "[DRY RUN] Using local Kokoro clone instead of cloning main." |
21 | 104 | SRC_DIR="$(cd "$(dirname "$0")/../.." && pwd)" |
|
40 | 123 | VERSION=${VERSION#v} |
41 | 124 | echo "Building release for version: ${VERSION}" |
42 | 125 |
|
43 | | -TMP_DIR=$(mktemp -d) |
| 126 | +# Create the build directory inside the workspace volume (SRC_DIR) |
| 127 | +# instead of the ephemeral /tmp, so that the sibling container can |
| 128 | +# access it natively via volume propagation! |
| 129 | +TMP_DIR="${SRC_DIR}/build_area" |
| 130 | +mkdir -p "${TMP_DIR}" |
44 | 131 | echo "Build directory: ${TMP_DIR}" |
45 | 132 |
|
46 | | -# Add trap cleanup for TMP_DIR as well |
47 | | -trap 'echo "Cleaning up temporary directories: ${REPO_DIR} ${TMP_DIR}"; rm -rf "${REPO_DIR}" "${TMP_DIR}"' EXIT |
| 133 | +# Force cibuildwheel to create all its temporary directories (like test_cwd) |
| 134 | +# inside this volume as well, avoiding any host-mount mismatches! |
| 135 | +export TMPDIR="${TMP_DIR}/tmp" |
| 136 | +mkdir -p "${TMPDIR}" |
48 | 137 |
|
49 | 138 | pushd "${TMP_DIR}" |
50 | 139 |
|
51 | 140 | cp -r "${SRC_DIR}"/{*,.*} . 2>/dev/null || true |
52 | 141 | cp -r "${SRC_DIR}"/release/* . 2>/dev/null || true |
53 | 142 | rm -rf cel_expr_python/*_test.py |
54 | 143 |
|
| 144 | +echo "Downloading bazelisk on host..." |
| 145 | +curl -LO https://github.com/bazelbuild/bazelisk/releases/download/v1.19.0/bazelisk-linux-amd64 |
| 146 | +chmod +x bazelisk-linux-amd64 |
| 147 | + |
55 | 148 | # Check if pyproject.toml exists before running sed |
56 | 149 | if [ -f pyproject.toml ]; then |
57 | 150 | sed -i "" "s/\$VERSION/${VERSION}/g" pyproject.toml || sed -i "s/\$VERSION/${VERSION}/g" pyproject.toml |
58 | 151 | fi |
59 | 152 |
|
60 | | -echo "Running cibuildwheel: ${CIBWHEEL_BIN}" |
| 153 | +# Propagate all volume mounts from the Kokoro host container to the sibling container. |
| 154 | +# This allows cibuildwheel to use standard host mounts in the sandboxed RBE environment! |
| 155 | +export CIBW_CONTAINER_ENGINE_EXTRA_ARGS="--network=host --volumes-from=$(hostname)" |
| 156 | + |
| 157 | +echo "Running cibuildwheel..." |
61 | 158 | # Default CIBWHEEL_BIN if not set |
62 | 159 | if [ -z "${CIBWHEEL_BIN}" ]; then |
63 | 160 | CIBWHEEL_BIN="python3 -m cibuildwheel" |
64 | 161 | fi |
| 162 | + |
| 163 | +# Run cibuildwheel synchronously. Output goes directly to the console in real-time. |
65 | 164 | ${CIBWHEEL_BIN} --platform linux --output-dir dist |
66 | 165 |
|
67 | 166 | if [ "${DRY_RUN}" = "true" ]; then |
|
0 commit comments