Skip to content

Commit b6442f2

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

6 files changed

Lines changed: 282 additions & 17 deletions

File tree

MODULE.bazel

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ bazel_dep(name = "rules_python", version = "1.9.0")
5151
single_version_override(
5252
module_name = "antlr4-cpp-runtime",
5353
patch_cmds = [
54-
"python -c \"import os; os.rename('VERSION', 'VERSION.txt') if os.path.exists('VERSION') else None\"",
55-
"python -c \"import os; os.rename('version', 'version.txt') if os.path.exists('version') else None\"",
54+
"mv VERSION VERSION.txt || true",
55+
"mv version version.txt || true",
5656
],
5757
)
5858

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: 206 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,207 @@
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 standard Windows TEMP directory for temporary build folders to avoid nested copy recursion.
44+
set "REPO_DIR=%TEMP%\cel-python-repo-%RANDOM%"
45+
set "TMP_DIR=%TEMP%\cel-python-build-%RANDOM%"
46+
echo Created temporary directories: %REPO_DIR%, %TMP_DIR%
47+
48+
mkdir "%TMP_DIR%"
49+
50+
echo --- Resolving Repository Source ---
51+
if "%DRY_RUN%" == "true" (
52+
echo [DRY RUN] Using local Kokoro clone instead of cloning main.
53+
set "SRC_DIR=%~dp0..\.."
54+
pushd "!SRC_DIR!"
55+
for /f "tokens=*" %%i in ('git tag --sort=-v:refname 2^>nul') do (
56+
set "VERSION=%%i"
57+
goto :got_local_tag
58+
)
59+
set "VERSION=0.1.2"
60+
:got_local_tag
61+
popd
62+
) else (
63+
mkdir "%REPO_DIR%"
64+
pushd "%REPO_DIR%"
65+
git clone https://github.com/cel-expr/cel-python.git
66+
if !ERRORLEVEL! NEQ 0 (
67+
echo Failed to clone repository!
68+
set "RELEASE_STATUS=1"
69+
popd
70+
goto cleanup
71+
)
72+
cd cel-python
73+
for /f "tokens=*" %%i in ('git tag --sort=-v:refname') do (
74+
set "VERSION=%%i"
75+
goto :got_tag
76+
)
77+
:got_tag
78+
if "%VERSION%" == "" (
79+
echo Failed to get version tag!
80+
set "RELEASE_STATUS=1"
81+
popd
82+
goto cleanup
83+
)
84+
set "SRC_DIR=%REPO_DIR%\cel-python"
85+
popd
86+
)
87+
88+
if "%VERSION:~0,1%" == "v" (
89+
set "VERSION=%VERSION:~1%"
90+
)
91+
echo Building release for version: %VERSION%
92+
93+
echo --- Preparing Release in Temp Directory ---
94+
pushd "%TMP_DIR%"
95+
96+
xcopy /E /I /Y "%SRC_DIR%\*.*" .
97+
if !ERRORLEVEL! NEQ 0 (
98+
echo Failed to copy repo contents!
99+
set "RELEASE_STATUS=1"
100+
popd
101+
goto cleanup
102+
)
103+
104+
xcopy /Y "%SRC_DIR%\release\*.*" .
105+
if !ERRORLEVEL! NEQ 0 (
106+
echo Failed to copy release configs!
107+
set "RELEASE_STATUS=1"
108+
popd
109+
goto cleanup
110+
)
111+
112+
if exist "cel_expr_python\*_test.py" (
113+
del /Q "cel_expr_python\*_test.py"
114+
)
115+
116+
:: Substitute $VERSION in pyproject.toml with the value of VERSION.
117+
!PYTHON_EXE! -c "import sys; content = open('pyproject.toml').read(); open('pyproject.toml', 'w').write(content.replace('$VERSION', sys.argv[1]))" "%VERSION%"
118+
if !ERRORLEVEL! NEQ 0 (
119+
echo Failed to substitute version in pyproject.toml!
120+
set "RELEASE_STATUS=1"
121+
popd
122+
goto cleanup
123+
)
124+
125+
echo --- Creating .bazelrc ---
126+
echo startup --output_user_root=C:/tmp >> .bazelrc
127+
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
128+
echo startup --host_jvm_args=-Djava.net.preferIPv4Stack=true >> .bazelrc
129+
130+
echo --- Pre-fetching Dependencies ---
131+
set ATTEMPTS=0
132+
:fetch_loop
133+
set /a ATTEMPTS+=1
134+
echo Fetch attempt !ATTEMPTS! of %FETCH_RETRIES%...
135+
bazel %STARTUP_FLAGS% fetch //... > fetch.log 2>&1
136+
set FETCH_STATUS=!ERRORLEVEL!
137+
type fetch.log
138+
if !FETCH_STATUS! NEQ 0 (
139+
findstr /i "timeout timed" fetch.log >nul
140+
if !ERRORLEVEL! EQU 0 (
141+
if !ATTEMPTS! LSS %FETCH_RETRIES% (
142+
echo Fetch failed with timeout. Retrying in %FETCH_RETRY_DELAY_S% seconds...
143+
set /a PING_COUNT=%FETCH_RETRY_DELAY_S% + 1
144+
ping -n !PING_COUNT! 127.0.0.1 >nul
145+
goto fetch_loop
146+
)
147+
)
148+
echo Pre-fetch failed permanently or max attempts reached, but continuing...
149+
)
150+
if exist fetch.log del fetch.log
151+
152+
echo --- Applying ANTLR VERSION Collision Fix ---
153+
for /f "tokens=*" %%i in ('bazel info output_base') do set "OUTPUT_BASE=%%i"
154+
set "OUTPUT_BASE=!OUTPUT_BASE:/=\!"
155+
echo Output Base: !OUTPUT_BASE!
156+
157+
set "ANTLR_DIR=!OUTPUT_BASE!\external\antlr4-cpp-runtime+"
158+
if exist "!ANTLR_DIR!\VERSION" (
159+
if not exist "!ANTLR_DIR!\VERSION.txt" (
160+
echo Renaming !ANTLR_DIR!\VERSION to VERSION.txt
161+
ren "!ANTLR_DIR!\VERSION" VERSION.txt
162+
)
163+
)
164+
if exist "!ANTLR_DIR!\version" (
165+
if not exist "!ANTLR_DIR!\version.txt" (
166+
echo Renaming !ANTLR_DIR!\version to version.txt
167+
ren "!ANTLR_DIR!\version" version.txt
168+
)
169+
)
170+
171+
echo --- Running cibuildwheel ---
172+
if "%CIBWHEEL_BIN%" == "" (
173+
set "CIBWHEEL_BIN=!PYTHON_EXE! -m cibuildwheel"
174+
)
175+
echo Running cibuildwheel: %CIBWHEEL_BIN%
176+
%CIBWHEEL_BIN% --platform windows --output-dir dist
177+
if !ERRORLEVEL! NEQ 0 (
178+
echo cibuildwheel failed!
179+
set "RELEASE_STATUS=1"
180+
popd
181+
goto cleanup
182+
)
183+
184+
echo --- Uploading to OSS Exit Gate ---
185+
if "%DRY_RUN%" == "true" (
186+
echo [DRY RUN] Skipping upload to PyPI exit gate.
187+
) else (
188+
!PYTHON_EXE! -m twine upload --repository-url https://us-python.pkg.dev/oss-exit-gate-prod/cel-expr-python--pypi dist/*
189+
if !ERRORLEVEL! NEQ 0 (
190+
echo Twine upload failed!
191+
set "RELEASE_STATUS=1"
192+
popd
193+
goto cleanup
194+
)
195+
)
196+
197+
popd
198+
echo cel-expr-python %VERSION% built and uploaded for release by OSS Exit Gate.
199+
200+
:cleanup
201+
echo Cleaning up directories...
202+
if exist "%REPO_DIR%" rd /S /Q "%REPO_DIR%"
203+
if exist "%TMP_DIR%" rd /S /Q "%TMP_DIR%"
204+
205+
if "%RELEASE_STATUS%" NEQ "0" (
206+
exit /b %RELEASE_STATUS%
207+
)

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: 57 additions & 4 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
@@ -46,6 +47,7 @@ def build_extension(self, ext):
4647
# Thus, we can use the current Python version as the target version for
4748
# the bazel build.
4849
python_version = f'{sys.version_info.major}.{sys.version_info.minor}'
50+
4951
print(f'Building for target Python version: {python_version}')
5052

5153
module_bazel_path = os.path.join(os.path.dirname(__file__), 'MODULE.bazel')
@@ -60,9 +62,7 @@ def build_extension(self, ext):
6062
with open(module_bazel_path, 'w') as f:
6163
f.write(content)
6264
else:
63-
raise RuntimeError(
64-
f'MODULE.bazel not found at {module_bazel_path}'
65-
)
65+
raise RuntimeError(f'MODULE.bazel not found at {module_bazel_path}')
6666

6767
dest_path = self.get_ext_fullpath(ext.name)
6868
dest_dir = os.path.dirname(dest_path)
@@ -71,8 +71,10 @@ def build_extension(self, ext):
7171
# Build with bazel
7272
# Use --compilation_mode=opt for release builds
7373
cmd = ['bazel', 'build', ext.target, '--compilation_mode=opt']
74+
if sys.platform == 'win32':
75+
self.platform_config_windows(cmd, python_version)
7476
if sys.platform == 'darwin':
75-
cmd.extend(['--macos_minimum_os=10.13', '--cxxopt=-faligned-allocation'])
77+
self.platform_config_macos(cmd)
7678
print(f"Building {ext.name} with bazel: {' '.join(cmd)}")
7779
subprocess.check_call(cmd)
7880

@@ -112,6 +114,57 @@ def build_extension(self, ext):
112114
print(f'Copying {found} to {dest_path}')
113115
shutil.copyfile(found, dest_path)
114116

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

116169
setuptools.setup(
117170
name='cel-expr-python',

0 commit comments

Comments
 (0)