@@ -25,37 +25,221 @@ jobs:
2525 python-version : ${{ matrix.python-version }}
2626 cache : ' pip'
2727
28+ - name : Cache apt packages (Linux)
29+ if : runner.os == 'Linux'
30+ uses : actions/cache@v4
31+ with :
32+ path : /var/cache/apt/archives
33+ key : apt-${{ runner.os }}-${{ hashFiles('.github/workflows/release.yml') }}
34+ restore-keys : |
35+ apt-${{ runner.os }}-
36+
2837 - name : Install system dependencies (Linux)
2938 if : runner.os == 'Linux'
30- run : sudo apt-get update && sudo apt-get install -y cmake ninja-build libssl-dev
39+ run : |
40+ sudo apt-get update
41+ sudo apt-get install -y cmake ninja-build libssl-dev pkg-config autoconf automake libtool
42+
43+ - name : Cache Homebrew packages (macOS)
44+ if : runner.os == 'macOS'
45+ uses : actions/cache@v4
46+ with :
47+ path : |
48+ ~/Library/Caches/Homebrew
49+ /usr/local/Cellar
50+ /usr/local/opt
51+ key : brew-${{ runner.os }}-${{ hashFiles('.github/workflows/release.yml') }}
52+ restore-keys : |
53+ brew-${{ runner.os }}-
3154
3255 - name : Install system dependencies (macOS)
3356 if : runner.os == 'macOS'
34- run : brew install cmake ninja
57+ run : brew install cmake ninja openssl@3 libnghttp2
3558
3659 - name : Install system dependencies (Windows)
3760 if : runner.os == 'Windows'
38- run : choco install cmake -y
61+ run : |
62+ # Check if already installed
63+ if ! command -v cmake &> /dev/null; then
64+ choco install cmake -y
65+ else
66+ echo "cmake already installed"
67+ fi
68+ if ! command -v ninja &> /dev/null; then
69+ choco install ninja -y
70+ else
71+ echo "ninja already installed"
72+ fi
73+ shell : bash
3974
4075 - name : Setup MSVC (Windows)
4176 if : runner.os == 'Windows'
4277 uses : microsoft/setup-msbuild@v2
4378
79+ - name : Setup Go (for BoringSSL build)
80+ uses : actions/setup-go@v5
81+ with :
82+ go-version : ' 1.21'
83+ cache : true
84+
85+ - name : Setup ccache (Unix)
86+ if : runner.os != 'Windows'
87+ uses : hendrikmuhs/ccache-action@v1.2
88+ with :
89+ key : ccache-release-${{ runner.os }}-${{ matrix.python-version }}
90+ max-size : 500M
91+
92+ - name : Configure ccache (Unix)
93+ if : runner.os != 'Windows'
94+ run : |
95+ echo "CC=ccache gcc" >> $GITHUB_ENV
96+ echo "CXX=ccache g++" >> $GITHUB_ENV
97+ shell : bash
98+
99+ - name : Restore vcpkg cache (Windows)
100+ if : runner.os == 'Windows'
101+ id : cache-vcpkg-restore
102+ uses : actions/cache/restore@v4
103+ with :
104+ path : |
105+ C:/vcpkg/installed
106+ C:/vcpkg/packages
107+ key : vcpkg-nghttp2-zlib-${{ runner.os }}-v3
108+ restore-keys : |
109+ vcpkg-nghttp2-zlib-${{ runner.os }}-
110+
111+ - name : Setup vcpkg (Windows)
112+ if : runner.os == 'Windows' && steps.cache-vcpkg-restore.outputs.cache-hit != 'true'
113+ run : |
114+ # Use pre-installed vcpkg on GitHub runners
115+ if [ -d "C:/vcpkg" ]; then
116+ echo "Using pre-installed vcpkg"
117+ VCPKG_ROOT="C:/vcpkg"
118+ else
119+ echo "Installing vcpkg"
120+ git clone https://github.com/microsoft/vcpkg.git C:/vcpkg
121+ cd C:/vcpkg
122+ ./bootstrap-vcpkg.bat
123+ VCPKG_ROOT="C:/vcpkg"
124+ fi
125+ echo "VCPKG_ROOT=$VCPKG_ROOT" >> $GITHUB_ENV
126+ echo "$VCPKG_ROOT" >> $GITHUB_PATH
127+ shell : bash
128+
129+ - name : Install dependencies via vcpkg (Windows)
130+ if : runner.os == 'Windows' && steps.cache-vcpkg-restore.outputs.cache-hit != 'true'
131+ run : |
132+ export VCPKG_ROOT="C:/vcpkg"
133+ export PATH="$VCPKG_ROOT:$PATH"
134+ vcpkg install nghttp2:x64-windows zlib:x64-windows --clean-after-build
135+ shell : bash
136+
137+ - name : Set vcpkg environment (Windows)
138+ if : runner.os == 'Windows'
139+ run : |
140+ echo "VCPKG_ROOT=C:/vcpkg" >> $GITHUB_ENV
141+ echo "C:/vcpkg" >> $GITHUB_PATH
142+ shell : bash
143+
144+ - name : Restore vendor cache
145+ id : cache-vendor
146+ uses : actions/cache/restore@v4
147+ with :
148+ path : vendor
149+ key : vendor-${{ runner.os }}-${{ hashFiles('scripts/setup_vendors.sh') }}-v7
150+ restore-keys : |
151+ vendor-${{ runner.os }}-
152+
44153 - name : Setup vendor dependencies
154+ if : steps.cache-vendor.outputs.cache-hit != 'true'
45155 run : |
46156 chmod +x scripts/setup_vendors.sh
47157 ./scripts/setup_vendors.sh
48158 shell : bash
49159
160+ - name : Verify vendor directory for caching
161+ run : |
162+ echo "Checking vendor directory contents..."
163+ ls -lah vendor/ || echo "vendor directory not found"
164+ du -sh vendor/ 2>/dev/null || echo "Cannot get vendor size"
165+ shell : bash
166+
167+ - name : Restore Python build cache
168+ id : cache-python-build
169+ uses : actions/cache/restore@v4
170+ with :
171+ path : |
172+ build/
173+ *.egg-info/
174+ key : python-build-release-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('setup.py', 'pyproject.toml', 'src/**/*.c', 'src/**/*.cpp', 'src/**/*.h') }}
175+ restore-keys : |
176+ python-build-release-${{ runner.os }}-${{ matrix.python-version }}-
177+
50178 - name : Install package
51179 run : pip install -e ".[dev]"
52180
181+ - name : Add DLL paths to PATH (Windows)
182+ if : runner.os == 'Windows'
183+ run : |
184+ echo "Adding BoringSSL and vcpkg DLL paths to PATH..."
185+ echo "${{ github.workspace }}/vendor/boringssl/build/Release" >> $GITHUB_PATH
186+ echo "C:/vcpkg/installed/x64-windows/bin" >> $GITHUB_PATH
187+ shell : bash
188+
189+ - name : Verify C extension was built
190+ run : |
191+ echo "Checking for built C extensions..."
192+ find . -name "*.pyd" -o -name "*.so" 2>/dev/null || echo "No extensions found"
193+ ls -R build/ 2>/dev/null || echo "No build directory"
194+ echo ""
195+ echo "Checking for DLL files..."
196+ ls -la vendor/boringssl/build/Release/*.dll vendor/boringssl/build/Release/*.lib 2>/dev/null || echo "No DLLs in BoringSSL Release"
197+ ls -la C:/vcpkg/installed/x64-windows/bin/*.dll 2>/dev/null | head -20 || echo "No DLLs in vcpkg"
198+ echo ""
199+ echo "Attempting to load C extension..."
200+ python -c "import sys; sys.path.insert(0, 'src'); import httpmorph._httpmorph; print('SUCCESS: C extension loaded')" 2>&1 || echo "Failed to load extension"
201+ shell : bash
202+
53203 - name : Lint
54204 run : ruff check src/ tests/
55205
56206 - name : Run tests
57207 run : pytest tests/ -v --cov=httpmorph --cov-report=xml
58208
209+ - name : Upload coverage to Codecov
210+ uses : codecov/codecov-action@v4
211+ with :
212+ file : ./coverage.xml
213+ token : ${{ secrets.CODECOV_TOKEN }}
214+ fail_ci_if_error : false
215+ flags : release,${{ matrix.os }},python-${{ matrix.python-version }}
216+
217+ # Save caches even if tests fail (using always())
218+ - name : Save vcpkg cache (Windows)
219+ if : always() && runner.os == 'Windows' && steps.cache-vcpkg-restore.outputs.cache-hit != 'true'
220+ uses : actions/cache/save@v4
221+ with :
222+ path : |
223+ C:/vcpkg/installed
224+ C:/vcpkg/packages
225+ key : vcpkg-nghttp2-zlib-${{ runner.os }}-v3
226+
227+ - name : Save vendor cache
228+ if : always() && steps.cache-vendor.outputs.cache-hit != 'true'
229+ uses : actions/cache/save@v4
230+ with :
231+ path : vendor
232+ key : vendor-${{ runner.os }}-${{ hashFiles('scripts/setup_vendors.sh') }}-v7
233+
234+ - name : Save Python build cache
235+ if : always()
236+ uses : actions/cache/save@v4
237+ with :
238+ path : |
239+ build/
240+ *.egg-info/
241+ key : python-build-release-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('setup.py', 'pyproject.toml', 'src/**/*.c', 'src/**/*.cpp', 'src/**/*.h') }}
242+
59243 build-wheels :
60244 name : Build Wheels (${{ matrix.os }})
61245 needs : test
@@ -86,6 +270,12 @@ jobs:
86270 name : Publish Release
87271 needs : build-wheels
88272 runs-on : ubuntu-latest
273+ environment :
274+ name : PYPI_RELEASE
275+ url : https://pypi.org/project/httpmorph/
276+ permissions :
277+ contents : write
278+ id-token : write
89279
90280 steps :
91281 - uses : actions/checkout@v4
@@ -110,10 +300,16 @@ jobs:
110300 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
111301
112302 - name : Publish to PyPI
113- if : env.PYPI_API_TOKEN != ''
114303 env :
115304 TWINE_USERNAME : __token__
116305 TWINE_PASSWORD : ${{ secrets.PYPI_API_TOKEN }}
117306 run : |
118307 pip install twine
119- twine upload wheels/*.whl --skip-existing
308+ if [ -n "$TWINE_PASSWORD" ]; then
309+ echo "Publishing to PyPI..."
310+ twine upload wheels/*.whl --skip-existing --verbose
311+ else
312+ echo "⚠️ PYPI_API_TOKEN not set - skipping PyPI upload"
313+ echo "To publish to PyPI, add PYPI_API_TOKEN to the PYPI_RELEASE environment secrets"
314+ fi
315+ shell : bash
0 commit comments