Skip to content

Commit bf75339

Browse files
authored
ECC-1972: Python bindings: Support Windows with binary wheel (#108)
* ECC-1972: support for Windows binary wheel
1 parent ac4048c commit bf75339

File tree

8 files changed

+410
-1
lines changed

8 files changed

+410
-1
lines changed
+216
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# (C) Copyright 2024- ECMWF.
2+
#
3+
# This software is licensed under the terms of the Apache Licence Version 2.0
4+
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5+
# In applying this licence, ECMWF does not waive the privileges and immunities
6+
# granted to it by virtue of its status as an intergovernmental organisation
7+
# nor does it submit to any jurisdiction.
8+
9+
10+
name: Build Windows
11+
12+
on:
13+
# Trigger the workflow manually
14+
workflow_dispatch: ~
15+
16+
# Allow to be called from another workflow
17+
workflow_call: ~
18+
19+
push:
20+
tags-ignore:
21+
- '**'
22+
paths:
23+
- 'scripts/common.sh'
24+
- 'scripts/wheel-windows.sh'
25+
- 'scripts/build-windows.sh'
26+
- 'scripts/copy-dlls.py'
27+
- 'scripts/copy-licences.py'
28+
- '.github/workflows/build-wheel-windows.yml'
29+
30+
31+
jobs:
32+
33+
build:
34+
35+
# if: false # for temporarily disabling for debugging
36+
37+
runs-on: windows-latest
38+
39+
strategy:
40+
fail-fast: false
41+
matrix:
42+
architecture: ["x64"]
43+
44+
defaults:
45+
run:
46+
shell: bash
47+
48+
49+
name: Build on ${{ matrix.architecture }}
50+
env:
51+
WINARCH: ${{ matrix.architecture }}
52+
53+
steps:
54+
- uses: actions/checkout@v2
55+
56+
- uses: seanmiddleditch/gha-setup-vsdevenv@master
57+
with:
58+
arch: ${{ matrix.architecture }}
59+
60+
- name: Set up Python
61+
uses: actions/setup-python@v4
62+
with:
63+
python-version: 3.8
64+
architecture: ${{ matrix.architecture }}
65+
66+
- run: ./scripts/build-windows.sh
67+
env:
68+
WINARCH: ${{ matrix.architecture }}
69+
70+
71+
################################################################
72+
- name: Set up Python 3.8
73+
uses: actions/setup-python@v4
74+
with:
75+
python-version: 3.8
76+
architecture: ${{ matrix.architecture }}
77+
78+
- run: ./scripts/wheel-windows.sh 3.8
79+
- uses: actions/upload-artifact@v4
80+
name: Upload wheel 3.8
81+
with:
82+
name: wheel-windows-3.8-${{ matrix.architecture }}
83+
path: wheelhouse/*.whl
84+
85+
################################################################
86+
87+
- name: Set up Python 3.9
88+
uses: actions/setup-python@v4
89+
with:
90+
python-version: 3.9
91+
architecture: ${{ matrix.architecture }}
92+
93+
- run: ./scripts/wheel-windows.sh 3.9
94+
- uses: actions/upload-artifact@v4
95+
name: Upload wheel 3.9
96+
with:
97+
name: wheel-windows-3.9-${{ matrix.architecture }}
98+
path: wheelhouse/*.whl
99+
100+
################################################################
101+
102+
- name: Set up Python 3.10
103+
uses: actions/setup-python@v4
104+
with:
105+
python-version: "3.10"
106+
architecture: ${{ matrix.architecture }}
107+
108+
- run: ./scripts/wheel-windows.sh "3.10"
109+
- uses: actions/upload-artifact@v4
110+
name: Upload wheel 3.10
111+
with:
112+
name: wheel-windows-3.10-${{ matrix.architecture }}
113+
path: wheelhouse/*.whl
114+
115+
################################################################
116+
117+
- name: Set up Python 3.11
118+
uses: actions/setup-python@v4
119+
with:
120+
python-version: "3.11"
121+
architecture: ${{ matrix.architecture }}
122+
123+
- run: ./scripts/wheel-windows.sh "3.11"
124+
- uses: actions/upload-artifact@v4
125+
name: Upload wheel 3.11
126+
with:
127+
name: wheel-windows-3.11-${{ matrix.architecture }}
128+
path: wheelhouse/*.whl
129+
130+
################################################################
131+
132+
- name: Set up Python 3.12
133+
uses: actions/setup-python@v4
134+
with:
135+
python-version: "3.12"
136+
architecture: ${{ matrix.architecture }}
137+
138+
- run: ./scripts/wheel-windows.sh "3.12"
139+
- uses: actions/upload-artifact@v4
140+
name: Upload wheel 3.12
141+
with:
142+
name: wheel-windows-3.12-${{ matrix.architecture }}
143+
path: wheelhouse/*.whl
144+
145+
################################################################
146+
147+
148+
test:
149+
needs: build
150+
runs-on: windows-latest
151+
strategy:
152+
fail-fast: true
153+
matrix:
154+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
155+
architecture: ["x64"]
156+
157+
defaults:
158+
run:
159+
shell: bash
160+
161+
name: Test with Python ${{ matrix.python-version }} ${{ matrix.architecture }}
162+
163+
steps:
164+
- uses: actions/checkout@v2
165+
166+
- name: Set up Python
167+
uses: actions/setup-python@v4
168+
with:
169+
python-version: ${{ matrix.python-version }}
170+
architecture: ${{ matrix.architecture }}
171+
172+
- uses: actions/download-artifact@v4
173+
with:
174+
name: wheel-windows-${{ matrix.python-version }}-${{ matrix.architecture }}
175+
176+
- run: pip install *.whl
177+
178+
- run: pip install -r tests/requirements.txt
179+
180+
- run: pip freeze
181+
182+
- run: ECCODES_PYTHON_TRACE_LIB_SEARCH=1 pytest --verbose -s
183+
working-directory: tests
184+
timeout-minutes: 2
185+
186+
187+
deploy:
188+
if: ${{ github.ref_type == 'tag' || github.event_name == 'release' }}
189+
190+
needs: [test, build]
191+
192+
name: Deploy wheel ${{ matrix.python-version }} ${{ matrix.architecture }}
193+
194+
runs-on: ubuntu-latest
195+
strategy:
196+
fail-fast: true
197+
matrix:
198+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
199+
architecture: ["x64"]
200+
201+
steps:
202+
- name: Set up Python
203+
uses: actions/setup-python@v4
204+
with:
205+
python-version: ${{ matrix.python-version }}
206+
207+
- run: pip install twine
208+
209+
- uses: actions/download-artifact@v4
210+
with:
211+
name: wheel-windows-${{ matrix.python-version }}-${{ matrix.architecture }}
212+
213+
- run: twine upload *.whl
214+
env:
215+
TWINE_USERNAME: __token__
216+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

.github/workflows/cd.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ jobs:
2424
wheel-macos:
2525
uses: ./.github/workflows/build-wheel-macos.yml
2626
secrets: inherit
27+
wheel-windows:
28+
uses: ./.github/workflows/build-wheel-windows.yml
29+
secrets: inherit
2730
pypi:
28-
needs: [wheel-linux, wheel-macos]
31+
needs: [wheel-linux, wheel-macos, wheel-windows]
2932
uses: ecmwf-actions/reusable-workflows/.github/workflows/cd-pypi.yml@v2
3033
secrets: inherit
3134

scripts/build-windows.sh

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
2+
#!/usr/bin/env bash
3+
# (C) Copyright 2024- ECMWF.
4+
#
5+
# This software is licensed under the terms of the Apache Licence Version 2.0
6+
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
7+
# In applying this licence, ECMWF does not waive the privileges and immunities
8+
# granted to it by virtue of its status as an intergovernmental organisation
9+
# nor does it submit to any jurisdiction.
10+
11+
set -eaux
12+
13+
source scripts/common.sh
14+
15+
here=$(pwd)
16+
cd $VCPKG_INSTALLATION_ROOT
17+
url=$(git remote -v | head -1 | awk '{print $2;}')
18+
sha1=$(git rev-parse HEAD)
19+
cd $here
20+
21+
echo git $url $sha1 > versions
22+
23+
# the results of the following suggested that pkg-config.exe is not installed on the latest
24+
# Windows runner
25+
#find /c/ -name pkg-config.exe
26+
#exit 1
27+
28+
# if [[ $WINARCH == "x64" ]]; then
29+
# PKG_CONFIG_EXECUTABLE=/c/rtools43/mingw64/bin/pkg-config.exe
30+
# else
31+
# PKG_CONFIG_EXECUTABLE=/c/rtools43/mingw32/bin/pkg-config.exe
32+
# fi
33+
34+
vcpkg install pkgconf
35+
36+
for p in libpng
37+
do
38+
vcpkg install $p:$WINARCH-windows
39+
n=$(echo $p | sed 's/\[.*//')
40+
v=$(vcpkg list $n | awk '{print $2;}')
41+
echo "vcpkg $n $v" >> versions
42+
done
43+
44+
echo =================================================================
45+
find $VCPKG_INSTALLATION_ROOT -type f -name png.h -print
46+
echo =================================================================
47+
48+
49+
pip install ninja wheel dll-diagnostics
50+
51+
echo "pip $(pip freeze | grep dll-diagnostics | sed 's/==/ /')" >> versions
52+
53+
# Build libaec
54+
git clone $GIT_AEC src/aec
55+
cd src/aec
56+
git checkout $AEC_VERSION
57+
cd $TOPDIR
58+
mkdir -p build-binaries/aec
59+
cd build-binaries/aec
60+
61+
cmake \
62+
$TOPDIR/src/aec -G"NMake Makefiles" \
63+
-DCMAKE_BUILD_TYPE=Release \
64+
-DCMAKE_INSTALL_PREFIX=$TOPDIR/install \
65+
-DCMAKE_TOOLCHAIN_FILE=/c/vcpkg/scripts/buildsystems/vcpkg.cmake \
66+
-DCMAKE_C_COMPILER=cl.exe
67+
68+
cd $TOPDIR
69+
cmake --build build-binaries/aec --target install
70+
71+
72+
73+
# Build eccodes
74+
75+
cd $TOPDIR/build-binaries/eccodes
76+
77+
$TOPDIR/src/ecbuild/bin/ecbuild \
78+
$TOPDIR/src/eccodes \
79+
-G"NMake Makefiles" \
80+
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
81+
-DENABLE_PYTHON=0 \
82+
-DENABLE_FORTRAN=0 \
83+
-DENABLE_BUILD_TOOLS=0 \
84+
-DENABLE_MEMFS=1 \
85+
-DENABLE_INSTALL_ECCODES_DEFINITIONS=0 \
86+
-DENABLE_INSTALL_ECCODES_SAMPLES=0 \
87+
-DCMAKE_INSTALL_PREFIX=$TOPDIR/install \
88+
-DCMAKE_TOOLCHAIN_FILE=/c/vcpkg/scripts/buildsystems/vcpkg.cmake \
89+
-DCMAKE_C_COMPILER=cl.exe $ECCODES_COMMON_CMAKE_OPTIONS
90+
91+
# -DPKG_CONFIG_EXECUTABLE=$PKG_CONFIG_EXECUTABLE
92+
93+
cd $TOPDIR
94+
cmake --build build-binaries/eccodes --target install
95+
96+
97+
# Create wheel
98+
99+
rm -fr dist wheelhouse eccodes/share
100+
python scripts/copy-dlls.py install/bin/eccodes.dll eccodes/
101+
102+
pip install -r scripts/requirements.txt
103+
find eccodes -name '*.dll' > libs
104+
cat libs
105+
python ./scripts/copy-licences.py libs
106+
107+
mkdir -p install/include
108+
109+
./scripts/versions.sh > eccodes/versions.txt

scripts/copy-dlls.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env python
2+
#
3+
# (C) Copyright 2024- ECMWF.
4+
#
5+
# This software is licensed under the terms of the Apache Licence Version 2.0
6+
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
7+
#
8+
# In applying this licence, ECMWF does not waive the privileges and immunities
9+
# granted to it by virtue of its status as an intergovernmental organisation nor
10+
# does it submit to any jurisdiction.
11+
#
12+
13+
import os
14+
import shutil
15+
import sys
16+
17+
from dlldiag.common import ModuleHeader
18+
19+
VCPKG1 = "C:/vcpkg/installed/{}-windows/bin/{}"
20+
VCPKG2 = "C:/vcpkg/installed/{}-windows/debug/bin/{}"
21+
22+
23+
def scan_module(module, depth, seen):
24+
name = os.path.basename(module)
25+
26+
if name in seen:
27+
return
28+
29+
if not os.path.exists(module):
30+
return
31+
32+
print(" " * depth, module)
33+
seen[name] = module
34+
35+
header = ModuleHeader(module)
36+
cwd = os.path.dirname(module)
37+
architecture = header.getArchitecture()
38+
for dll in header.listAllImports():
39+
# print("DEBUG", dll)
40+
scan_module((cwd + "/" + dll), depth + 3, seen)
41+
scan_module(VCPKG1.format(architecture, dll), depth + 3, seen)
42+
scan_module(VCPKG2.format(architecture, dll), depth + 3, seen)
43+
44+
45+
seen = {}
46+
scan_module(sys.argv[1], 0, seen)
47+
48+
for k, v in seen.items():
49+
target = sys.argv[2] + "/" + k
50+
print("Copy", v, "to", target)
51+
shutil.copyfile(v, target)

0 commit comments

Comments
 (0)