Skip to content

Commit 5e67f5f

Browse files
Update CEL Python wheel build scripts and configuration for Windows
PiperOrigin-RevId: 907889343
1 parent 70f2925 commit 5e67f5f

5 files changed

Lines changed: 246 additions & 11 deletions

File tree

release/build_wheel.sh

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,33 +59,33 @@ VERSION="0.0.1"
5959
SRC_DIR=$(pwd)
6060
echo "cel-expr-python source directory: ${SRC_DIR}"
6161

62-
TMP_DIR=$(mktemp -d)
62+
TMP_DIR=$(mktemp -d ../cel-python-build-XXXXXX)
6363
echo "Build directory: ${TMP_DIR}"
6464

6565
pushd "${TMP_DIR}"
6666

6767
if [[ "$OSTYPE" == "darwin"* ]]; then
6868
cp -a "${SRC_DIR}"/. .
6969
else
70-
cp -r "${SRC_DIR}"/{*,.*} .
70+
cp -r "${SRC_DIR}/." .
7171
fi
72-
cp "${SRC_DIR}"/release/* .
72+
cp release/* .
7373
rm -rf cel_expr_python/*_test.py
7474

7575
# Substitute $VERSION in pyproject.toml with the value of VERSION.
7676
if [[ "$OSTYPE" == "darwin"* ]]; then
7777
sed -i '' "s/\$VERSION/${VERSION}/g" pyproject.toml
7878
else
79-
sed -i "s/\$VERSION/${VERSION}/g" pyproject.toml
79+
python -c "import sys; content = open('pyproject.toml').read(); open('pyproject.toml', 'w').write(content.replace('\$VERSION', sys.argv[1]))" "${VERSION}"
8080
fi
8181

82-
echo "Running cibuildwheel: ${CIBWHEEL_BIN}"
83-
PYTHON_BIN="python"
82+
echo "Running cibuildwheel: ${CIBWHEEL_BIN:-python -m cibuildwheel}"
83+
${CIBWHEEL_BIN:-PYTHON_BIN="python"
8484
if command -v python3 &> /dev/null; then
8585
PYTHON_BIN="python3"
8686
fi
8787
88-
"$PYTHON_BIN" -m cibuildwheel "$@"
88+
"$PYTHON_BIN" -m cibuildwheel} "$@"
8989

9090
echo "Copying generated wheels to ${SRC_DIR}/wheelhouse"
9191
mkdir -p "${SRC_DIR}"/wheelhouse
@@ -98,3 +98,7 @@ popd
9898

9999
echo "Successfully built cel-expr-python wheels"
100100
ls -l "${SRC_DIR}"/wheelhouse
101+
102+
# Keep the window open if run in a separate terminal.
103+
echo ""
104+
read -p "Press enter to exit..."

release/kokoro/release_windows.bat

Lines changed: 177 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,178 @@
1-
@echo off
2-
echo Cel-expr-python release build on Windows
3-
echo TODO(b/507567432): implement release build for windows.
1+
:: Copyright 2026 Google LLC
2+
::
3+
:: Licensed under the Apache License, Version 2.0 (the "License");
4+
:: you may not use this file except in compliance with the License.
5+
:: You may obtain a copy of the License at
6+
::
7+
:: http://www.apache.org/licenses/LICENSE-2.0
8+
::
9+
:: Unless required by applicable law or agreed to in writing, software
10+
:: distributed under the License is distributed on an "AS IS" BASIS,
11+
:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
:: See the License for the specific language governing permissions and
13+
:: limitations under the License.
14+
::
15+
setlocal enabledelayedexpansion
16+
:: release_windows.bat
17+
:: Kokoro entrypoint for Windows Release builds.
418

5-
exit /b 0
19+
echo === Loading Environment Configuration ===
20+
call "%~dp0set_env_windows.bat"
21+
if !ERRORLEVEL! NEQ 0 (
22+
echo Failed to configure build environment!
23+
exit /b 1
24+
)
25+
26+
set "RELEASE_STATUS=0"
27+
28+
:: If running locally (not on Kokoro), authenticate with gcloud.
29+
if "%KOKORO_BUILD_ID%" == "" (
30+
gcloud auth application-default print-access-token --quiet >nul 2>&1
31+
if !ERRORLEVEL! NEQ 0 (
32+
gcloud auth application-default login
33+
)
34+
)
35+
36+
echo --- Installing Release Dependencies ---
37+
!PYTHON_EXE! -m pip install -U keyring keyrings.google-artifactregistry-auth twine cibuildwheel
38+
if !ERRORLEVEL! NEQ 0 (
39+
echo Failed to install dependencies!
40+
exit /b 1
41+
)
42+
43+
:: Use sibling directories for temporary build folders to avoid issues on Windows.
44+
set "REPO_DIR=..\cel-python-repo-%RANDOM%"
45+
set "TMP_DIR=..\cel-python-build-%RANDOM%"
46+
echo Created temporary directories: %REPO_DIR%, %TMP_DIR%
47+
48+
mkdir "%REPO_DIR%"
49+
mkdir "%TMP_DIR%"
50+
51+
echo --- Cloning Repository ---
52+
pushd "%REPO_DIR%"
53+
git clone https://github.com/cel-expr/cel-python.git
54+
if !ERRORLEVEL! NEQ 0 (
55+
echo Failed to clone repository!
56+
set "RELEASE_STATUS=1"
57+
popd
58+
goto cleanup
59+
)
60+
cd cel-python
61+
62+
:: Get the latest version tag
63+
for /f "tokens=*" %%i in ('git tag --sort=-v:refname') do (
64+
set "VERSION=%%i"
65+
goto :got_tag
66+
)
67+
:got_tag
68+
if "%VERSION%" == "" (
69+
echo Failed to get version tag!
70+
set "RELEASE_STATUS=1"
71+
popd
72+
goto cleanup
73+
)
74+
if "%VERSION:~0,1%" == "v" (
75+
set "VERSION=%VERSION:~1%"
76+
)
77+
echo Building release for version: %VERSION%
78+
popd
79+
80+
echo --- Preparing Release in Temp Directory ---
81+
pushd "%TMP_DIR%"
82+
83+
xcopy /E /I /Y "%REPO_DIR%\cel-python\*.*" .
84+
if !ERRORLEVEL! NEQ 0 (
85+
echo Failed to copy repo contents!
86+
set "RELEASE_STATUS=1"
87+
popd
88+
goto cleanup
89+
)
90+
91+
xcopy /Y "%REPO_DIR%\cel-python\release\*.*" .
92+
if !ERRORLEVEL! NEQ 0 (
93+
echo Failed to copy release configs!
94+
set "RELEASE_STATUS=1"
95+
popd
96+
goto cleanup
97+
)
98+
99+
if exist "cel_expr_python\*_test.py" (
100+
del /Q "cel_expr_python\*_test.py"
101+
)
102+
103+
:: Substitute $VERSION in pyproject.toml with the value of VERSION.
104+
!PYTHON_EXE! -c "import sys; content = open('pyproject.toml').read(); open('pyproject.toml', 'w').write(content.replace('$VERSION', sys.argv[1]))" "%VERSION%"
105+
if !ERRORLEVEL! NEQ 0 (
106+
echo Failed to substitute version in pyproject.toml!
107+
set "RELEASE_STATUS=1"
108+
popd
109+
goto cleanup
110+
)
111+
112+
echo --- Creating .bazelrc ---
113+
echo startup --output_user_root=C:/tmp > .bazelrc
114+
echo startup --host_jvm_args=-Dhttp.nonProxyHosts=bcr.bazel.build^^^|*.bazel.build^^^|storage.googleapis.com^^^|*.googleapis.com^^^|metadata.google.internal^^^|169.254.169.254 >> .bazelrc
115+
echo startup --host_jvm_args=-Djava.net.preferIPv4Stack=true >> .bazelrc
116+
117+
echo --- Pre-fetching Dependencies for Workarounds ---
118+
bazel fetch //...
119+
if !ERRORLEVEL! NEQ 0 (
120+
echo Pre-fetch failed, but continuing...
121+
)
122+
123+
echo --- Applying ANTLR VERSION Collision Fix ---
124+
for /f "tokens=*" %%i in ('bazel info output_base') do set "OUTPUT_BASE=%%i"
125+
set "OUTPUT_BASE=!OUTPUT_BASE:/=\!"
126+
echo Output Base: !OUTPUT_BASE!
127+
128+
set "ANTLR_DIR=!OUTPUT_BASE!\external\antlr4-cpp-runtime+"
129+
if exist "!ANTLR_DIR!\VERSION" (
130+
if not exist "!ANTLR_DIR!\VERSION.txt" (
131+
echo Renaming !ANTLR_DIR!\VERSION to VERSION.txt
132+
ren "!ANTLR_DIR!\VERSION" VERSION.txt
133+
)
134+
)
135+
if exist "!ANTLR_DIR!\version" (
136+
if not exist "!ANTLR_DIR!\version.txt" (
137+
echo Renaming !ANTLR_DIR!\version to version.txt
138+
ren "!ANTLR_DIR!\version" version.txt
139+
)
140+
)
141+
142+
echo --- Running cibuildwheel ---
143+
if "%CIBWHEEL_BIN%" == "" (
144+
set "CIBWHEEL_BIN=!PYTHON_EXE! -m cibuildwheel"
145+
)
146+
echo Running cibuildwheel: %CIBWHEEL_BIN%
147+
%CIBWHEEL_BIN% --platform windows --output-dir dist
148+
if !ERRORLEVEL! NEQ 0 (
149+
echo cibuildwheel failed!
150+
set "RELEASE_STATUS=1"
151+
popd
152+
goto cleanup
153+
)
154+
155+
echo --- Uploading to OSS Exit Gate ---
156+
if "%DRY_RUN%" == "true" (
157+
echo [DRY RUN] Skipping upload to PyPI exit gate.
158+
) else (
159+
!PYTHON_EXE! -m twine upload --repository-url https://us-python.pkg.dev/oss-exit-gate-prod/cel-expr-python--pypi dist/*
160+
if !ERRORLEVEL! NEQ 0 (
161+
echo Twine upload failed!
162+
set "RELEASE_STATUS=1"
163+
popd
164+
goto cleanup
165+
)
166+
)
167+
168+
popd
169+
echo cel-expr-python %VERSION% built and uploaded for release by OSS Exit Gate.
170+
171+
:cleanup
172+
echo Cleaning up directories...
173+
if exist "%REPO_DIR%" rd /S /Q "%REPO_DIR%"
174+
if exist "%TMP_DIR%" rd /S /Q "%TMP_DIR%"
175+
176+
if "%RELEASE_STATUS%" NEQ "0" (
177+
exit /b %RELEASE_STATUS%
178+
)

release/kokoro/set_env_windows.bat

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,4 @@ if !ERRORLEVEL! EQU 0 (
7070
set PYTHON_EXE=C:\Python%PY_VER_NO_DOT%\python.exe
7171
)
7272
echo Python set to %PYTHON_EXE%
73+
exit /b 0

release/pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ classifiers = [
2222
"Topic :: Software Development :: Interpreters",
2323
"Topic :: Software Development :: Libraries :: Python Modules",
2424
"Operating System :: POSIX :: Linux",
25+
"Operating System :: Microsoft :: Windows",
2526
]
2627
license = "Apache-2.0"
2728
readme = "README.md"
@@ -39,9 +40,13 @@ exclude = ["codelab*", "conformance*", "custom_ext*", "release*", "testing*", "w
3940
build = "cp311-* cp312-* cp313-* cp314-*"
4041
skip = "*musllinux*"
4142
test-command = "python {project}/cel_basic_test.py"
43+
build-verbosity = 1
4244

4345
[tool.cibuildwheel.linux]
4446
before-all = "echo 'Installing bazelisk'; curl -LO https://github.com/bazelbuild/bazelisk/releases/download/v1.19.0/bazelisk-linux-amd64 && chmod +x bazelisk-linux-amd64 && mv bazelisk-linux-amd64 /usr/local/bin/bazel"
4547

4648
[tool.cibuildwheel.macos]
4749
before-all = "echo 'Installing bazelisk'; brew install bazelisk"
50+
51+
[tool.cibuildwheel.windows]
52+
# Bazel is expected to be already installed and in the PATH on Windows.

release/setup.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
"""Helper for building py_cel with bazel for PyPI releases."""
1616

17+
import glob
1718
import os
1819
import re
1920
import shutil
@@ -71,6 +72,8 @@ def build_extension(self, ext):
7172
# Build with bazel
7273
# Use --compilation_mode=opt for release builds
7374
cmd = ['bazel', 'build', ext.target, '--compilation_mode=opt']
75+
if sys.platform == 'win32':
76+
self.apply_windows_workarounds(cmd, python_version)
7477
if sys.platform == 'darwin':
7578
cmd.extend(['--macos_minimum_os=10.13', '--cxxopt=-faligned-allocation'])
7679
print(f"Building {ext.name} with bazel: {' '.join(cmd)}")
@@ -112,6 +115,55 @@ def build_extension(self, ext):
112115
print(f'Copying {found} to {dest_path}')
113116
shutil.copyfile(found, dest_path)
114117

118+
def apply_windows_workarounds(self, cmd, python_version):
119+
"""Applies Windows-specific Bazel workarounds for Hermetic Python."""
120+
# 1. Get output base
121+
output_base = subprocess.check_output(
122+
['bazel', '--output_user_root=C:/tmp', 'info', 'output_base'], text=True
123+
).strip()
124+
125+
# 2. Find hermetic python directory
126+
py_ver_underscore = python_version.replace('.', '_')
127+
pattern = os.path.join(
128+
output_base, 'external', f'*python_{py_ver_underscore}_host'
129+
)
130+
matches = glob.glob(pattern)
131+
if not matches:
132+
print(
133+
'Warning: Hermetic Python directory not found with pattern:'
134+
f' {pattern}'
135+
)
136+
return
137+
138+
py_host_dir = matches[0]
139+
print(f'Found Hermetic Python Directory: {py_host_dir}')
140+
141+
# 3. Copy libs to a space-free directory
142+
target_dir = r'C:\tmp\python_libs'
143+
os.makedirs(target_dir, exist_ok=True)
144+
145+
# Copy .lib files
146+
lib_pattern = os.path.join(py_host_dir, 'libs', 'python*.lib')
147+
for lib_file in glob.glob(lib_pattern):
148+
print(f'Copying {lib_file} to {target_dir}')
149+
shutil.copy(lib_file, target_dir)
150+
151+
# Copy .dll files
152+
dll_pattern = os.path.join(py_host_dir, 'python*.dll')
153+
for dll_file in glob.glob(dll_pattern):
154+
print(f'Copying {dll_file} to {target_dir}')
155+
shutil.copy(dll_file, target_dir)
156+
157+
# 4. Add link flags to bazel command
158+
cmd.extend([
159+
'--linkopt=/LIBPATH:C:\\tmp\\python_libs',
160+
'--action_env=PATH',
161+
'--cxxopt=-DANTLR4CPP_STATIC',
162+
])
163+
164+
# 5. Add to PATH in current process so bazel can find the DLLs if it runs them
165+
os.environ['PATH'] = f"{target_dir};{os.environ.get('PATH', '')}"
166+
115167

116168
setuptools.setup(
117169
name='cel-expr-python',

0 commit comments

Comments
 (0)