Skip to content

Commit abbad4d

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

6 files changed

Lines changed: 264 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: 186 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,187 @@
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+
set "CIBW_STATUS=!ERRORLEVEL!"
149+
150+
if exist debug_platform.txt (
151+
echo --- Debug Platform Info from setup.py ---
152+
type debug_platform.txt
153+
) else (
154+
echo --- Debug Platform Info NOT FOUND ---
155+
)
156+
157+
if !CIBW_STATUS! NEQ 0 (
158+
echo cibuildwheel failed!
159+
set "RELEASE_STATUS=1"
160+
popd
161+
goto cleanup
162+
)
163+
164+
echo --- Uploading to OSS Exit Gate ---
165+
if "%DRY_RUN%" == "true" (
166+
echo [DRY RUN] Skipping upload to PyPI exit gate.
167+
) else (
168+
!PYTHON_EXE! -m twine upload --repository-url https://us-python.pkg.dev/oss-exit-gate-prod/cel-expr-python--pypi dist/*
169+
if !ERRORLEVEL! NEQ 0 (
170+
echo Twine upload failed!
171+
set "RELEASE_STATUS=1"
172+
popd
173+
goto cleanup
174+
)
175+
)
176+
177+
popd
178+
echo cel-expr-python %VERSION% built and uploaded for release by OSS Exit Gate.
179+
180+
:cleanup
181+
echo Cleaning up directories...
182+
if exist "%REPO_DIR%" rd /S /Q "%REPO_DIR%"
183+
if exist "%TMP_DIR%" rd /S /Q "%TMP_DIR%"
184+
185+
if "%RELEASE_STATUS%" NEQ "0" (
186+
exit /b %RELEASE_STATUS%
187+
)

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: 59 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,9 @@ 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+
with open('debug_platform.txt', 'w') as f:
51+
f.write(f'sys.platform: {sys.platform}\n')
52+
f.write(f'python_version: {python_version}\n')
4953
print(f'Building for target Python version: {python_version}')
5054

5155
module_bazel_path = os.path.join(os.path.dirname(__file__), 'MODULE.bazel')
@@ -60,9 +64,7 @@ def build_extension(self, ext):
6064
with open(module_bazel_path, 'w') as f:
6165
f.write(content)
6266
else:
63-
raise RuntimeError(
64-
f'MODULE.bazel not found at {module_bazel_path}'
65-
)
67+
raise RuntimeError(f'MODULE.bazel not found at {module_bazel_path}')
6668

6769
dest_path = self.get_ext_fullpath(ext.name)
6870
dest_dir = os.path.dirname(dest_path)
@@ -71,8 +73,10 @@ def build_extension(self, ext):
7173
# Build with bazel
7274
# Use --compilation_mode=opt for release builds
7375
cmd = ['bazel', 'build', ext.target, '--compilation_mode=opt']
76+
if sys.platform == 'win32':
77+
self.platform_config_windows(cmd, python_version)
7478
if sys.platform == 'darwin':
75-
cmd.extend(['--macos_minimum_os=10.13', '--cxxopt=-faligned-allocation'])
79+
self.platform_config_macos(cmd)
7680
print(f"Building {ext.name} with bazel: {' '.join(cmd)}")
7781
subprocess.check_call(cmd)
7882

@@ -112,6 +116,57 @@ def build_extension(self, ext):
112116
print(f'Copying {found} to {dest_path}')
113117
shutil.copyfile(found, dest_path)
114118

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

116171
setuptools.setup(
117172
name='cel-expr-python',

0 commit comments

Comments
 (0)