Make Orchestrator::evaluate take `const Expression * instead of Exp…
#255
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
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| name: wheels | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ['v*'] | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| paths: | |
| - 'pyproject.toml' | |
| - 'src/rebalancer/**' | |
| - 'CMakeLists.txt' | |
| - 'version.txt' | |
| - '.github/workflows/wheels.yml' | |
| - 'tools/wheels/**' | |
| - 'build/fbcode_builder/**' | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| actions: read | |
| concurrency: | |
| group: wheels-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| build: | |
| # Fan out across (os, python ABI) so each cell is its own GHA job and | |
| # the matrix runs in parallel. cibuildwheel inside each cell builds | |
| # exactly one ABI -- without this split, cibuildwheel iterates the | |
| # ABIs sequentially in a single job and the wall-clock cost is N x | |
| # one-wheel-build. | |
| name: wheels-${{ matrix.os }}-${{ matrix.py }} | |
| runs-on: ${{ matrix.runs_on }} | |
| timeout-minutes: ${{ matrix.timeout_minutes }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [linux, macos] | |
| py: [cp312, cp313, cp314] | |
| include: | |
| - os: linux | |
| runs_on: 16-core-ubuntu | |
| cibw_archs: x86_64 | |
| cibw_image: quay.io/pypa/manylinux_2_28_x86_64 | |
| timeout_minutes: 60 | |
| - os: macos | |
| runs_on: macos-14-xlarge | |
| cibw_archs: arm64 | |
| timeout_minutes: 60 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| # cibuildwheel reads version.txt indirectly via scikit-build-core; no | |
| # tags are needed at wheel-build time. | |
| fetch-depth: 1 | |
| - uses: pypa/cibuildwheel@v3.1 | |
| env: | |
| # One Python ABI per matrix cell. | |
| CIBW_BUILD: "${{ matrix.py }}-*" | |
| CIBW_SKIP: "*-musllinux* *_i686 pp*" | |
| CIBW_ARCHS: ${{ matrix.cibw_archs }} | |
| # Linux: build inside the same manylinux container the main C++ build | |
| # uses, so the wheel-bundled .so is binary-identical to the .deb/.rpm | |
| # version. | |
| CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.cibw_image }} | |
| CIBW_CONTAINER_ENGINE: docker | |
| # CLANG19_PREINSTALLED tells the clang19 getdeps manifest a compatible | |
| # clang is already on PATH (before_all_linux.sh dnf-installs clang 20, | |
| # which is forward-compatible with the project's "clang19+" need). | |
| # CC/CXX point scikit-build-core's wheel-build cmake at clang | |
| # (cibuildwheel default is the gcc-toolset gcc). | |
| CIBW_ENVIRONMENT_LINUX: >- | |
| CLANG19_PREINSTALLED=1 | |
| CC=clang | |
| CXX=clang++ | |
| # Repair: bundle getdeps-installed shared libs (libgflags.so, etc.) | |
| # into the wheel via auditwheel. | |
| CIBW_REPAIR_WHEEL_COMMAND_LINUX: bash tools/wheels/repair_linux.sh {dest_dir} {wheel} | |
| # Run getdeps to produce the dep tree, then write .cmake_prefix_path | |
| # for the wheel-build cmake invocation to read via CMakeLists.txt. | |
| CIBW_BEFORE_ALL_LINUX: bash tools/wheels/before_all_linux.sh | |
| # macOS environment: | |
| # * MACOSX_DEPLOYMENT_TARGET=14.0 -- Homebrew on macos-14-xlarge | |
| # ships bottles compiled with target 14.0 (Sonoma). delocate | |
| # refuses to bundle those into a wheel announcing an older target, | |
| # so we pin to the highest target any bundled dylib advertises. | |
| # Decision (2026-05-08, autonomous): | |
| # Rejected -- bumping target down to 11.0 by source-building | |
| # every dep via getdeps. Would require pulling boost/openssl/ | |
| # icu/etc. into the getdeps tree instead of relying on Homebrew | |
| # bottles. Substantial extra compile time and a different | |
| # fragility surface. Not worth it pre-1.0. | |
| # Rejected -- shipping wheels per OS minor (11/12/13/14). No | |
| # other Python project does this for marginal coverage gains. | |
| # Cost: wheel only installs on macOS 14+ (Sonoma, Sept 2023+). | |
| # Older users can build from sdist or wait until source-built | |
| # deps land. | |
| # * CC/CXX -> Homebrew LLVM clang for C++23 features (static_assert | |
| # in templates, lambda capture of structured bindings, ranges::join) | |
| # that AppleClang 15 lacks. | |
| # * LIBRARY_PATH adds the keg-only icu4c@78 lib dir to the linker | |
| # search path so bare -licudata / -licuuc / -licui18n flags pulled | |
| # in transitively by Boost can be resolved. | |
| CIBW_ENVIRONMENT_MACOS: >- | |
| MACOSX_DEPLOYMENT_TARGET=14.0 | |
| CC=/opt/homebrew/opt/llvm/bin/clang | |
| CXX=/opt/homebrew/opt/llvm/bin/clang++ | |
| LIBRARY_PATH=/opt/homebrew/opt/icu4c@78/lib | |
| CIBW_BEFORE_ALL_MACOS: bash tools/wheels/before_all_macos.sh | |
| # Repair: cmake fails to embed LC_RPATH in MODULE/SHARED targets for | |
| # this cmake/scikit-build-core version on macOS arm64. repair_macos.sh | |
| # patches the missing rpaths before handing off to delocate-wheel. | |
| CIBW_REPAIR_WHEEL_COMMAND_MACOS: bash tools/wheels/repair_macos.sh {dest_dir} {wheel} {delocate_archs} | |
| # Smoke-test the installed wheel. The wrapper script runs the | |
| # one-liner test first; on failure it dumps wheel layout, ldd | |
| # output, LD_DEBUG=files, and a gdb backtrace so we can root- | |
| # cause crashes (linux wheels have been segfaulting at import | |
| # with no diagnostic output). | |
| CIBW_TEST_COMMAND: bash {project}/tools/wheels/test_wheel.sh | |
| # Linux wheels are built with cmake.build-type = "RelWithDebInfo" (see | |
| # pyproject.toml), which retains full DWARF debug sections in every | |
| # bundled .so. That produces ~133 MB wheels, exceeding PyPI's 100 MB | |
| # per-file limit. We strip debug symbols here rather than switching to a | |
| # Release build so that the smoke tests above (CIBW_TEST_COMMAND) run | |
| # against the full-symbol binary — preserving crash diagnostic quality | |
| # during CI — while the artifact we upload and eventually publish to PyPI | |
| # is lean (~13 MB). strip --strip-unneeded removes only debug sections; | |
| # it leaves the export symbol table and all executable code untouched, | |
| # which is safe for dynamically-linked .so files. macOS wheels are ~25 MB | |
| # unstripped and do not need this treatment. | |
| - if: matrix.os == 'linux' | |
| name: Strip debug symbols from Linux wheels | |
| run: | | |
| python3 - <<'PYEOF' | |
| import hashlib, os, subprocess, zipfile, tempfile, glob | |
| for whl_path in glob.glob('wheelhouse/*.whl'): | |
| before = os.path.getsize(whl_path) | |
| with tempfile.TemporaryDirectory() as tmpdir: | |
| with zipfile.ZipFile(whl_path) as zf: | |
| zf.extractall(tmpdir) | |
| for root, _, files in os.walk(tmpdir): | |
| for f in files: | |
| if '.so' not in f: | |
| continue | |
| fp = os.path.join(root, f) | |
| rel = os.path.relpath(fp, tmpdir) | |
| # Skip bundled deps (rebalancer.libs/) and the | |
| # internal shared library (_lib/librebalancer.so). | |
| # auditwheel's patchelf extends .dynstr in these files | |
| # by adding a new PT_LOAD segment; strip --strip-unneeded | |
| # then fails to re-place .dynstr in that segment and | |
| # leaves it unmapped, causing glibc 2.39 to find only | |
| # empty strings at DT_STRTAB. These files are already | |
| # pre-stripped in repair_linux.sh before auditwheel runs | |
| # (strip --strip-unneeded on the getdeps sources), so | |
| # stripping them again here is a no-op on content but | |
| # dangerous on ELF structure. | |
| if 'rebalancer.libs' in rel or rel.startswith('rebalancer/_lib/'): | |
| continue | |
| subprocess.run( | |
| ['strip', '--strip-unneeded', fp], | |
| check=True) | |
| # Regenerate RECORD so pip's integrity check passes after we | |
| # modified the .so files. | |
| record_path = next( | |
| os.path.join(r, f) | |
| for r, _, fs in os.walk(tmpdir) | |
| for f in fs if f == 'RECORD') | |
| entries = [] | |
| for root, _, files in os.walk(tmpdir): | |
| for f in files: | |
| fp = os.path.join(root, f) | |
| rel = os.path.relpath(fp, tmpdir) | |
| if fp == record_path: | |
| continue | |
| digest = hashlib.sha256(open(fp, 'rb').read()).hexdigest() | |
| entries.append(f'{rel},sha256={digest},{os.path.getsize(fp)}') | |
| entries.append(f'{os.path.relpath(record_path, tmpdir)},,') | |
| open(record_path, 'w').write('\n'.join(entries) + '\n') | |
| os.remove(whl_path) | |
| with zipfile.ZipFile(whl_path, 'w', zipfile.ZIP_DEFLATED) as zf: | |
| for root, _, files in os.walk(tmpdir): | |
| for f in files: | |
| fp = os.path.join(root, f) | |
| zf.write(fp, os.path.relpath(fp, tmpdir)) | |
| after = os.path.getsize(whl_path) | |
| print(f'{os.path.basename(whl_path)}: {before/1e6:.1f} MB -> {after/1e6:.1f} MB') | |
| PYEOF | |
| - uses: actions/upload-artifact@v6 | |
| with: | |
| name: wheels-${{ matrix.os }}-${{ matrix.py }} | |
| path: wheelhouse/*.whl | |
| if-no-files-found: error | |
| # Cross-glibc smoke test: run the Linux wheel on ubuntu-latest (glibc 2.39) | |
| # to catch patchelf string-table corruption that only manifests on newer | |
| # glibc. The cibuildwheel test (CIBW_TEST_COMMAND) runs inside the | |
| # manylinux_2_28 build container (AlmaLinux 8, glibc 2.28) where the | |
| # corruption silently passes; this job runs on a fresh Ubuntu 24.04 host | |
| # where the dynamic linker reads DT_STRTAB strictly and fails on corrupted | |
| # NEEDED entries such as "ev" or "3.1.5". | |
| test-linux-glibc: | |
| name: test-linux-glibc-${{ matrix.py }} | |
| needs: build | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| py: [cp312, cp313, cp314] | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 1 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: wheels-linux-${{ matrix.py }} | |
| path: wheelhouse | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| # Map cibuildwheel ABI tags to setup-python version strings. | |
| python-version: | | |
| ${{ matrix.py == 'cp312' && '3.12' || | |
| matrix.py == 'cp313' && '3.13' || | |
| '3.14' }} | |
| allow-prereleases: true | |
| - name: Install wheel and run smoke test | |
| run: | | |
| pip install wheelhouse/rebalancer-*-manylinux*.whl | |
| # Minimal import check first so we can distinguish import-time crashes | |
| # (patchelf .init_array corruption) from test-execution crashes. | |
| # On failure, capture a gdb C-level backtrace to identify the exact | |
| # constructor function that segfaults. | |
| if python -X faulthandler -c " | |
| from rebalancer import ProblemSolver | |
| ps = ProblemSolver(service_name='rebalancer', service_scope='glibc-test') | |
| assert ps.ping() == 4, f'ping returned {ps.ping()}' | |
| print('import + ping ok') | |
| "; then | |
| python -X faulthandler tools/wheels/test_bindings.py -v | |
| else | |
| echo "--- import failed; running gdb backtrace ---" | |
| sudo apt-get install -y gdb 2>/dev/null || true | |
| gdb -batch \ | |
| -ex 'set confirm off' \ | |
| -ex 'set pagination off' \ | |
| -ex 'handle SIGSEGV stop print' \ | |
| -ex 'run' \ | |
| -ex 'bt full' \ | |
| -ex 'info sharedlibrary' \ | |
| --args python3 -X faulthandler -c \ | |
| "from rebalancer import ProblemSolver; print('ok')" 2>&1 || true | |
| exit 1 | |
| fi |