Repository navigation
v3 installed package validation #29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: v3 installed package validation | |
| on: | |
| push: | |
| branches: [v3, master] | |
| pull_request: | |
| branches: [v3, master] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| linux: | |
| if: github.event_name != 'workflow_dispatch' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 90 | |
| steps: | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 | |
| - name: Prerequisites | |
| run: sudo apt-get update && sudo apt-get install -y clang autoconf automake libtool pkg-config python3 perl libcurl4-openssl-dev binutils python3-venv | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 | |
| with: | |
| node-version: '24.15.0' | |
| - name: Selected client environment and executable Git provenance | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/package-evidence" | |
| exec > >(tee "$RUNNER_TEMP/package-evidence/clients.log") 2>&1 | |
| python3 -c 'import subprocess; subprocess.run(["git", "--version"], check=True)' | |
| python3 -m venv "$RUNNER_TEMP/package-evidence/clients" | |
| "$RUNNER_TEMP/package-evidence/clients/bin/python" -m pip install -r test/integ/http2-client-requirements.txt | |
| - name: Build selected prerequisites | |
| run: python3 scripts/prepare-v3-package-ci.py --output "$RUNNER_TEMP/package-evidence/prerequisites" | |
| - name: Bootstrap | |
| run: ./bootstrap | |
| - name: Installed matrix | |
| run: | | |
| prefix=() | |
| if ! python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",443)); s.close()'; then | |
| prefix=(sudo env "PATH=$PATH" GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=safe.directory "GIT_CONFIG_VALUE_0=$PWD") | |
| fi | |
| "${prefix[@]}" python3 scripts/check-v3-installed-consumer.py --output "$RUNNER_TEMP/package-evidence/matrix" --provider-prefix "$RUNNER_TEMP/package-evidence/prerequisites/provider" --pkg-config-dir /usr/lib/x86_64-linux-gnu/pkgconfig --pkg-config-dir /usr/share/pkgconfig --client-python "$RUNNER_TEMP/package-evidence/clients/bin/python" --node node --compiler clang++ --cc clang --jobs 2 | |
| - if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: v3-package-linux-${{ github.sha }} | |
| path: ${{ runner.temp }}/package-evidence | |
| retention-days: 14 | |
| if-no-files-found: error | |
| macos: | |
| permissions: | |
| contents: read | |
| actions: read | |
| runs-on: macos-15 | |
| timeout-minutes: 90 | |
| steps: | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 | |
| - name: Prerequisites | |
| run: brew install autoconf automake libtool pkg-config python curl llvm | |
| - name: Select complete C++20 compiler and runtime | |
| run: bash scripts/ci-macos-toolchain.sh | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 | |
| with: | |
| node-version: '24.15.0' | |
| - name: Selected client environment and executable Git provenance | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/package-evidence" | |
| exec > >(tee "$RUNNER_TEMP/package-evidence/clients.log") 2>&1 | |
| python3 -c 'import subprocess; subprocess.run(["git", "--version"], check=True)' | |
| python3 -m venv "$RUNNER_TEMP/package-evidence/clients" | |
| "$RUNNER_TEMP/package-evidence/clients/bin/python" -m pip install -r test/integ/http2-client-requirements.txt | |
| - name: Build selected prerequisites | |
| run: python3 scripts/prepare-v3-package-ci.py --output "$RUNNER_TEMP/package-evidence/prerequisites" | |
| - name: Bootstrap | |
| run: ./bootstrap | |
| - name: Installed matrix | |
| if: github.event_name != 'workflow_dispatch' | |
| run: | | |
| prefix=() | |
| if ! python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",443)); s.close()'; then | |
| prefix=(sudo env "PATH=$PATH" GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=safe.directory "GIT_CONFIG_VALUE_0=$PWD") | |
| fi | |
| "${prefix[@]}" python3 scripts/check-v3-installed-consumer.py --output "$RUNNER_TEMP/package-evidence/matrix" --provider-prefix "$RUNNER_TEMP/package-evidence/prerequisites/provider" --pkg-config-dir "$(brew --prefix)/share/pkgconfig" --client-python "$RUNNER_TEMP/package-evidence/clients/bin/python" --node node --compiler "$V3_CXX" --cc "$V3_CC" --toolchain-prefix "$V3_PACKAGE_TOOLCHAIN" --jobs 2 | |
| # Branch-only checkpoint: remove before merging to v3. This is not matrix proof. | |
| - name: Diagnose installed shared TLS-off external HTTP1 on Darwin | |
| if: github.event_name == 'workflow_dispatch' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| python3 - <<'PY_DIAGNOSTIC' | |
| import hashlib, importlib.util, json, os, platform, shlex, shutil, subprocess, sys | |
| from pathlib import Path | |
| checkout = Path.cwd().resolve() | |
| sys.path.insert(0, str(checkout / 'scripts/lib')) | |
| import package_matrix as matrix | |
| from package_commands import Commands, runtime_environment | |
| from package_integration import compile_probe | |
| import release_artifact as identity | |
| from release_checks import extract_archive | |
| from release_ci import verify_download | |
| spec = importlib.util.spec_from_file_location('audit', checkout / 'scripts/audit-v3-installed-package.py') | |
| audit = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(audit) | |
| assert platform.system() == 'Darwin' | |
| output = Path(os.environ['RUNNER_TEMP']) / 'package-evidence/header-deadline-diagnostic' | |
| output.mkdir() | |
| build, prefix, integration = [output / name for name in ('build', 'prefix', 'integration-shared')] | |
| for directory in (build, prefix, integration): | |
| directory.mkdir() | |
| command = Commands(output) | |
| provider = output.parent / 'prerequisites/provider' | |
| compiler, cc = os.environ['V3_CXX'], os.environ['V3_CC'] | |
| toolchain = Path(os.environ['V3_PACKAGE_TOOLCHAIN']) | |
| env = audit.clean_environment() | |
| for key in ('GH_TOKEN', 'GITHUB_TOKEN', 'V3_RELEASE_IDENTITY'): | |
| env.pop(key, None) | |
| env.update(CXX=compiler, CC=cc, CXXFLAGS='-O0 -g', | |
| CPPFLAGS='-DHTTPSERVER_DIAG_HEADER_DEADLINE', | |
| LDFLAGS='-Wl,-rpath,' + str(provider / 'lib')) | |
| receipt = dict(kind='diagnostic-only; not complete package validation', tls='no', linkage='shared', | |
| trace_mode='bounded memory; stderr emitted after all suite fixtures stop', | |
| revision=command(['git', 'rev-parse', 'HEAD'], checkout, env).strip(), | |
| compiler=command([compiler, '--version'], checkout, env), | |
| provider_header_sha256=hashlib.sha256((provider / 'include/openssl/opensslv.h').read_bytes()).hexdigest()) | |
| try: | |
| expected = dict(repository=os.environ['GITHUB_REPOSITORY'], run_id='39969790346', run_attempt='1', | |
| workflow_head='c0cfdabc8040273d60c610269a93d1c687ed8517', artifact_id='12508482722') | |
| endpoint = 'repos/' + expected['repository'] + '/actions/artifacts/' + expected['artifact_id'] | |
| metadata = json.loads(subprocess.check_output(['gh', 'api', endpoint], text=True)) | |
| run = json.loads(subprocess.check_output(['gh', 'api', 'repos/' + expected['repository'] + | |
| '/actions/runs/' + expected['run_id'] + '/attempts/' + expected['run_attempt']], text=True)) | |
| (output / 'selected-artifact-metadata.json').write_text(json.dumps(metadata, indent=2) + '\n') | |
| (output / 'selected-run-metadata.json').write_text(json.dumps(run, indent=2) + '\n') | |
| downloaded = output / 'selected-archive.zip' | |
| with downloaded.open('xb') as destination: | |
| subprocess.run(['gh', 'api', endpoint + '/zip'], stdout=destination, check=True) | |
| zip_digest = 'sha256:a08eb79186b13cf12ac8c639f424e13a7de1ee09df5547aab2b4200a951a5f05' | |
| verify_download(downloaded, metadata, run, expected, zip_digest, output / 'selected') | |
| selected = json.loads((output / 'selected/selected.json').read_text()) | |
| archive = output / 'selected/libhttpserver-3.0.0.tar.gz' | |
| assert selected['commit'] == selected['source']['head'] == expected['workflow_head'] | |
| assert selected['archive'] == archive.name and archive.stat().st_size == 3053890 | |
| assert identity.sha256(archive) == selected['sha256'] == '43124552f20bd00fceddc714856d605ca9caf017a849a991c34f16f437fcab83' | |
| source = extract_archive(archive, output / 'source') | |
| originals = identity.source_files(source) | |
| assert originals and all(selected['files_sha256'].get(name) == digest for name, digest in originals.items()) | |
| (output / 'original-source-hashes.json').write_text(json.dumps(originals, indent=2) + '\n') | |
| overlay = {} | |
| names = ['src/detail/' + name + '.cpp' for name in ('connection_engine', 'connection_engine_websocket', | |
| 'io_external_adapter', 'io_operation', 'io_poll_backend', 'io_poll_sys', 'io_timer_sweep')] | |
| names += ['test/integ/external_http1_e2e_test.cpp', 'src/httpserver/detail/deadline_trace.hpp'] | |
| for name in names: | |
| original = originals.get(name) | |
| if name.endswith('/deadline_trace.hpp'): | |
| assert original is None and name not in selected['source']['files_sha256'] | |
| else: | |
| assert original == selected['source']['files_sha256'][name] | |
| overlay[name] = dict(original_sha256=original, diagnostic_sha256=identity.sha256(checkout / name)) | |
| shutil.copyfile(checkout / name, source / name) | |
| modified = identity.source_files(source) | |
| assert set(modified) - set(originals) == {'src/httpserver/detail/deadline_trace.hpp'} | |
| assert {name for name in originals if modified[name] != originals[name]} == set(names[:-1]) | |
| (output / 'diagnostic-source-hashes.json').write_text(json.dumps(modified, indent=2) + '\n') | |
| receipt['source_archive'] = dict(**expected, artifact_zip_sha256=zip_digest, | |
| archive_sha256=selected['sha256'], source_root=str(source), overlay=overlay, | |
| provenance='verified original archive plus explicit diagnostic overlay; not immutable release proof') | |
| command(['bash', source / 'configure', '--enable-v3-tls=no', '--disable-examples', '--prefix=' + str(prefix)], build, env) | |
| command(['make', '-j2'], build, env) | |
| command(['make', 'install'], build, env) | |
| runtime = runtime_environment({key: value for key, value in audit.clean_environment().items() | |
| if key not in ('GH_TOKEN', 'GITHUB_TOKEN', 'V3_RELEASE_IDENTITY')}, prefix, provider, [], toolchain) | |
| runtime.update(PKG_CONFIG_PATH='', PKG_CONFIG_LIBDIR=str(prefix / 'lib/pkgconfig')) | |
| os.environ.update({key: runtime[key] for key in ('LD_LIBRARY_PATH', 'DYLD_LIBRARY_PATH', 'V3_PACKAGE_TOOLCHAIN')}) | |
| cflags = shlex.split(command(['pkg-config', '--cflags', 'libhttpserver'], integration, runtime)) | |
| libraries = shlex.split(command(['pkg-config', '--libs', 'libhttpserver'], integration, runtime)) | |
| libraries += ['-pthread', '-Wl,-rpath,' + str(prefix / 'lib'), '-Wl,-rpath,' + str(provider / 'lib')] | |
| flags = cflags + ['-DHTTPSERVER_COMPILATION', '-DHTTPSERVER_DIAG_HEADER_DEADLINE', | |
| '-I' + str(source / 'src'), '-I' + str(source / 'test')] | |
| definition = matrix.load()['suites']['external_http1_e2e'] | |
| units = [source / 'test' / name for name in definition['sources']] | |
| executable = integration / 'external_http1_e2e' | |
| receipt['compile'] = compile_probe(compiler, units, flags, libraries, executable, command, runtime, source, build, prefix, True) | |
| graph = audit.dependency_graph([str(executable)]) | |
| matrix.check_linkage(graph, 'shared', prefix) | |
| receipt['dependencies'] = graph | |
| result = command([str(executable)], source / 'test', runtime, timeout=180, allow_failure=True) | |
| print(result, end='', flush=True) | |
| receipt['exit'] = command.records[-1]['exit'] | |
| receipt['suite'] = matrix.suite_result(result, receipt['exit']) | |
| receipt['status'] = 'diagnostic suite passed; full matrix not run' | |
| except Exception as error: | |
| receipt.update(status='diagnostic failed', error=str(error)) | |
| raise | |
| finally: | |
| (output / 'diagnostic.json').write_text(json.dumps(receipt, indent=2) + '\n') | |
| PY_DIAGNOSTIC | |
| - name: Make privileged proof readable by artifact upload | |
| if: always() | |
| run: | | |
| if [ -d "$RUNNER_TEMP/package-evidence" ]; then | |
| sudo chown -R "$(id -u):$(id -g)" "$RUNNER_TEMP/package-evidence" | |
| fi | |
| - if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: v3-package-macos-${{ github.sha }} | |
| path: | | |
| ${{ runner.temp }}/package-evidence | |
| ${{ runner.temp }}/v3-toolchain | |
| retention-days: 14 | |
| if-no-files-found: error | |
| mingw64: | |
| if: github.event_name != 'workflow_dispatch' | |
| runs-on: windows-2022 | |
| timeout-minutes: 90 | |
| defaults: | |
| run: | |
| shell: msys2 {0} | |
| steps: | |
| - name: Keep selected Git source bytes unchanged | |
| shell: pwsh | |
| run: git config --global core.autocrlf false | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 | |
| - uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c | |
| with: | |
| msystem: MINGW64 | |
| path-type: inherit | |
| update: true | |
| install: >- | |
| autotools base-devel perl git | |
| mingw-w64-x86_64-clang mingw-w64-x86_64-gcc mingw-w64-x86_64-binutils | |
| mingw-w64-x86_64-python mingw-w64-x86_64-pkgconf mingw-w64-x86_64-libtool mingw-w64-x86_64-curl mingw-w64-x86_64-libsystre | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 | |
| with: | |
| node-version: '24.15.0' | |
| - name: Selected client environment and executable Git provenance | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/package-evidence" | |
| exec > >(tee "$RUNNER_TEMP/package-evidence/clients.log") 2>&1 | |
| export PATH="/c/Program Files/Git/cmd:$PATH" | |
| /mingw64/bin/python - <<'GIT_PROBE' | |
| import shutil, subprocess | |
| print('selected native Git:', shutil.which('git'), flush=True) | |
| for arguments in (['--version'], ['rev-parse', '--verify', 'HEAD^{commit}']): | |
| result = subprocess.run(['git'] + arguments, capture_output=True, text=True) | |
| print('git', *arguments, 'exit=' + str(result.returncode), flush=True) | |
| print(result.stdout[-2048:], end='', flush=True) | |
| print(result.stderr[-2048:], end='', flush=True) | |
| result.check_returncode() | |
| GIT_PROBE | |
| /mingw64/bin/python -m venv "$RUNNER_TEMP/package-evidence/clients" | |
| "$(cygpath -m "$RUNNER_TEMP/package-evidence/clients/bin/python.exe")" -m pip install -r test/integ/http2-client-requirements.txt | |
| - name: Build selected prerequisites | |
| run: | | |
| export PATH="/c/Program Files/Git/cmd:$PATH" | |
| /mingw64/bin/python scripts/prepare-v3-package-ci.py --output "$RUNNER_TEMP/package-evidence/prerequisites" | |
| - name: Bootstrap | |
| run: ./bootstrap | |
| - name: Installed matrix (native Python; IOCP and WSAPoll) | |
| run: | | |
| export PATH="/c/Program Files/Git/cmd:$PATH" | |
| /mingw64/bin/python scripts/check-v3-installed-consumer.py --output "$RUNNER_TEMP/package-evidence/matrix" \ | |
| --provider-prefix "$(cygpath -m "$RUNNER_TEMP/package-evidence/prerequisites/provider")" --toolchain-prefix "$(cygpath -m /mingw64)" \ | |
| --client-python "$(cygpath -m "$RUNNER_TEMP/package-evidence/clients/bin/python.exe")" --node node --compiler clang++ --cc clang --jobs 2 | |
| - if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: v3-package-mingw64-${{ github.sha }} | |
| path: ${{ runner.temp }}/package-evidence | |
| retention-days: 14 | |
| if-no-files-found: error | |
| freebsd: | |
| if: github.event_name != 'workflow_dispatch' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 180 | |
| steps: | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 | |
| - name: freebsd_instance | |
| uses: vmactions/freebsd-vm@c9f815bc7aa0d34c9fdd0619b034a32d6ca7b57e | |
| with: | |
| release: '14.3' | |
| mem: 8192 | |
| cpu: 2 | |
| prepare: pkg install -y bash autoconf automake libtool pkgconf python3 gmake curl binutils perl5 llvm19 git node24 | |
| run: | | |
| set -eu | |
| export RUNNER_TEMP=/tmp/v3-package-${{ github.run_id }}-${{ github.run_attempt }} | |
| mkdir -p "$RUNNER_TEMP/package-evidence" | |
| trap 'cp -R "$RUNNER_TEMP/package-evidence" ./package-evidence' EXIT | |
| # Guest root must trust only the host checkout, including in Python's Git children. | |
| export GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=safe.directory GIT_CONFIG_VALUE_0="$PWD" | |
| git_log="$RUNNER_TEMP/package-evidence/git-provenance.log" | |
| { | |
| printf 'checkout=%s\n' "$PWD" && command -v git && git --version && | |
| git rev-parse --verify 'HEAD^{commit}' | |
| } > "$git_log" 2>&1 || { cat "$git_log" >&2; exit 1; } | |
| cat "$git_log" | |
| if [ "$(node --version)" != v24.15.0 ]; then | |
| mkdir -p "$RUNNER_TEMP/node-source" | |
| node_logs="$RUNNER_TEMP/package-evidence/node-source" | |
| mkdir -p "$node_logs" | |
| curl --fail --location https://nodejs.org/dist/v24.15.0/node-v24.15.0.tar.gz -o "$RUNNER_TEMP/node-source/node-v24.15.0.tar.gz" | |
| test "$(sha256 -q "$RUNNER_TEMP/node-source/node-v24.15.0.tar.gz")" = 729de494dd2872e5a3a6c32a1cd156a5413d4aca2772b2d873ee86bb5531bcd9 | |
| tar -xzf "$RUNNER_TEMP/node-source/node-v24.15.0.tar.gz" -C "$RUNNER_TEMP/node-source" | |
| ( | |
| cd "$RUNNER_TEMP/node-source/node-v24.15.0" | |
| { | |
| node_llvm_bin="$(llvm-config19 --bindir)" | |
| export CC="$node_llvm_bin/clang" CXX="$node_llvm_bin/clang++" | |
| printf 'CC=%s\nCXX=%s\n' "$CC" "$CXX" | |
| "$CC" --version | |
| "$CXX" --version | |
| } > "$node_logs/compiler.log" 2>&1 | |
| ./configure --prefix="$RUNNER_TEMP/node" > "$node_logs/configure.log" 2>&1 | |
| gmake -j2 > "$node_logs/build.log" 2>&1 | |
| gmake install >> "$node_logs/build.log" 2>&1 | |
| ) | |
| export PATH="$RUNNER_TEMP/node/bin:$PATH" | |
| fi | |
| test "$(node --version)" = v24.15.0 | |
| python3 -c 'import subprocess; subprocess.run(["git", "--version"], check=True)' | |
| python3 -m venv "$RUNNER_TEMP/package-evidence/clients" | |
| "$RUNNER_TEMP/package-evidence/clients/bin/python" -m pip install -r test/integ/http2-client-requirements.txt | |
| python3 scripts/prepare-v3-package-ci.py --output "$RUNNER_TEMP/package-evidence/prerequisites" --make gmake | |
| ./bootstrap | |
| python3 scripts/check-v3-installed-consumer.py --output "$RUNNER_TEMP/package-evidence/matrix" \ | |
| --provider-prefix "$RUNNER_TEMP/package-evidence/prerequisites/provider" \ | |
| --client-python "$RUNNER_TEMP/package-evidence/clients/bin/python" --node node --pkg-config-dir /usr/local/share/pkgconfig --compiler clang++ --cc clang --make gmake --jobs 2 | |
| - name: Recover FreeBSD evidence | |
| if: always() | |
| run: | | |
| mkdir -p package-evidence | |
| scp -O -r "freebsd:/tmp/v3-package-${{ github.run_id }}-${{ github.run_attempt }}/package-evidence" ./ | |
| - if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: v3-package-freebsd-${{ github.sha }} | |
| path: package-evidence | |
| retention-days: 14 | |
| if-no-files-found: error |