diff --git a/.ado/stages/_platform_setup_steps.yaml b/.ado/stages/_platform_setup_steps.yaml index 6543598f..faf9b402 100644 --- a/.ado/stages/_platform_setup_steps.yaml +++ b/.ado/stages/_platform_setup_steps.yaml @@ -8,7 +8,7 @@ parameters: steps: - script: | - sudo tdnf install binutils glibc-devel kernel-headers patchelf gcc python3-3.12.9 python3-pip-24.2 python3-devel-3.12.9 -y + sudo tdnf install binutils glibc-devel kernel-headers patchelf gcc python3-3.12.9 python3-pip-24.2 python3-devel-3.12.9 boost-devel -y displayName: Install build tools and Python on Linux aarch64 condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'), eq(variables['arch'], 'aarch64')) @@ -61,6 +61,17 @@ steps: fi cmake --version + # Boost: header-only library required by the Tesseract decoder C++ bridge. + if ! test -f /usr/include/boost/dynamic_bitset.hpp && ! test -f /usr/local/include/boost/dynamic_bitset.hpp; then + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update && sudo apt-get install -y --no-install-recommends libboost-dev + elif command -v tdnf >/dev/null 2>&1; then + sudo tdnf install -y boost-devel + elif command -v brew >/dev/null 2>&1; then + brew install boost + fi + fi + # protoc: download directly from the protobuf GitHub release so we # get a known version that bundles the well-known proto sources # (google/protobuf/empty.proto and friends). The distro packages @@ -128,6 +139,34 @@ steps: } cmake --version + # Boost: header-only library required by the Tesseract decoder C++ bridge. + # Check well-known pre-installed locations first (some agents ship Boost); + # fall back to downloading from the GitHub release (accessible on 1ES agents). + $boostRoot = $null + foreach ($candidate in @($env:BOOST_ROOT, "C:\local\boost", "C:\Boost", "C:\tools\boost")) { + if ($candidate -and (Test-Path (Join-Path $candidate "boost\dynamic_bitset.hpp"))) { + $boostRoot = $candidate + break + } + } + if (-not $boostRoot) { + $boostVersion = "1.83.0" + $boostUrl = "https://github.com/boostorg/boost/releases/download/boost-$boostVersion/boost-$boostVersion.zip" + $boostZip = Join-Path $tempDir "boost.zip" + $boostExtract = Join-Path $tempDir "boost_extract" + Write-Host "Downloading Boost $boostVersion from $boostUrl" + Invoke-WebRequest -Uri $boostUrl -OutFile $boostZip + Write-Host "Extracting Boost headers..." + Expand-Archive -Path $boostZip -DestinationPath $boostExtract -Force + $boostRoot = Get-ChildItem -Path $boostExtract -Directory | + Where-Object { Test-Path (Join-Path $_.FullName "boost\dynamic_bitset.hpp") } | + Select-Object -First 1 -ExpandProperty FullName + if (-not $boostRoot) { throw "Boost extraction failed: boost/dynamic_bitset.hpp not found" } + } + Write-Host "##vso[task.setvariable variable=DEQ_BOOST_ROOT]$boostRoot" + $env:DEQ_BOOST_ROOT = $boostRoot + Write-Host "Boost configured at: $boostRoot" + # protoc: download directly from the protobuf GitHub release. # The win64.zip is x86_64; Windows ARM64 runs it transparently # under x64 emulation. diff --git a/.ado/stages/build.yaml b/.ado/stages/build.yaml index dc112843..1927a729 100644 --- a/.ado/stages/build.yaml +++ b/.ado/stages/build.yaml @@ -90,17 +90,33 @@ stages: python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip - pip install maturin pytest hypothesis more-itertools numpy + maturin_args=() + case "$(uname -s)" in + Linux) + pip install 'maturin[zig]' pytest hypothesis more-itertools numpy + maturin_args=(--zig --manylinux manylinux_2_34) + ;; + Darwin) + pip install maturin pytest hypothesis more-itertools numpy + ;; + *) + echo "Unsupported Unix platform: $(uname -s)" >&2 + exit 1 + ;; + esac for crate in binar paulimer; do pushd "$crate/bindings/python" - maturin build --release --strip --out ../../../target/wheels + maturin build --release --strip "${maturin_args[@]}" --out ../../../target/wheels popd pip install --force-reinstall --no-deps target/wheels/${crate/-/_}*.whl python -c "import ${crate/-/_}; print('${crate} imported successfully')" python -m pytest "$crate/bindings/python/tests/" -v done + if [[ "$(uname -s)" == "Linux" ]]; then + ls target/wheels/*manylinux_2_34*.whl + fi ls -la target/wheels/ displayName: Build + test binar/paulimer wheels (Unix) condition: ne(variables['Agent.OS'], 'Windows_NT') @@ -197,12 +213,21 @@ stages: python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip - pip install maturin + if [[ "$(uname -s)" == "Linux" ]]; then + pip install 'maturin[zig]' + maturin_args=(--zig --manylinux manylinux_2_34) + else + pip install maturin + maturin_args=() + fi cd deq/deq_runtime - maturin build --release --strip --out ../../target/wheels + maturin build --release --strip "${maturin_args[@]}" --out ../../target/wheels cd ../.. + if [[ "$(uname -s)" == "Linux" ]]; then + ls target/wheels/deq_runtime-*manylinux_2_34*.whl + fi ls -la target/wheels/ displayName: Build deq-runtime wheel (Unix) condition: ne(variables['Agent.OS'], 'Windows_NT') diff --git a/.ado/templates/build-wheels-steps.yaml b/.ado/templates/build-wheels-steps.yaml index 1eb0f3c3..591439ea 100644 --- a/.ado/templates/build-wheels-steps.yaml +++ b/.ado/templates/build-wheels-steps.yaml @@ -12,13 +12,29 @@ steps: displayName: Install Rust toolchain - bash: | - python -m pip install --upgrade maturin + maturin_args=() + case "$(uname -s)" in + Linux) + python -m pip install --upgrade 'maturin[zig]' + maturin_args=(--zig --manylinux manylinux_2_34) + ;; + Darwin) + python -m pip install --upgrade maturin + ;; + *) + echo "Unsupported Unix platform: $(uname -s)" >&2 + exit 1 + ;; + esac mkdir -p target/wheels cd binar/bindings/python - maturin build --release --out ../../../target/wheels + maturin build --release "${maturin_args[@]}" --out ../../../target/wheels cd ../../.. cd paulimer/bindings/python - maturin build --release --out ../../../target/wheels + maturin build --release "${maturin_args[@]}" --out ../../../target/wheels + if [ "$(uname -s)" = "Linux" ]; then + ls ../../../target/wheels/*manylinux_2_34*.whl + fi displayName: Build Python wheels (Linux/Mac) condition: ne( variables['Agent.OS'], 'Windows_NT') diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 989affc0..c4ecad74 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -37,6 +37,16 @@ jobs: - name: Install cmake uses: lukka/get-cmake@latest + - name: Install Boost (Linux/macOS) + if: runner.os != 'Windows' + shell: bash + run: | + if [ "$(uname -s)" = "Linux" ]; then + sudo apt-get update && sudo apt-get install -y --no-install-recommends libboost-dev + elif [ "$(uname -s)" = "Darwin" ]; then + brew install boost + fi + - name: Install cbindgen run: cargo install cbindgen --locked diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f21fbe7..09a61423 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed +- Linux native Python wheels are now built with a `manylinux_2_34` baseline via `maturin --zig`, improving compatibility with glibc 2.35 systems for `binar`, `paulimer`, and `deq-runtime`. + ## [0.1.0] - 2026-01-23 ### Added diff --git a/binar/src/matrix/aligned_bitmatrix.rs b/binar/src/matrix/aligned_bitmatrix.rs index a11a12a1..91f2c971 100644 --- a/binar/src/matrix/aligned_bitmatrix.rs +++ b/binar/src/matrix/aligned_bitmatrix.rs @@ -811,9 +811,9 @@ impl AlignedBitMatrix { /// /// Will panic if matrix is not invertible pub fn inverted(&self) -> AlignedBitMatrix { - assert!(self.column_count() == self.row_count()); + assert_eq!(self.column_count(), self.row_count()); let echelon_form = EchelonForm::new(self.clone()); - assert!(echelon_form.pivots.len() == self.row_count()); + assert_eq!(echelon_form.pivots.len(), self.row_count()); debug_assert_eq!( self * &echelon_form.transform, AlignedBitMatrix::identity(self.row_count()) diff --git a/binar/src/matrix/bitmatrix.rs b/binar/src/matrix/bitmatrix.rs index 4e4f5d1b..b4db4271 100644 --- a/binar/src/matrix/bitmatrix.rs +++ b/binar/src/matrix/bitmatrix.rs @@ -842,7 +842,7 @@ impl BitMatrix { /// /// Panics if `left.len() != self.row_count()`. pub fn right_multiply(&self, left: &BitView) -> BitVec { - assert!(left.len() == self.row_count()); + assert_eq!(left.len(), self.row_count()); BitVec::from_aligned(self.column_count(), self.aligned.right_multiply(&left.bits)) } @@ -988,7 +988,7 @@ impl Mul<&BitView<'_>> for &BitMatrix { type Output = BitVec; fn mul(self, right: &BitView) -> Self::Output { - assert!(right.len() == self.column_count()); + assert_eq!(right.len(), self.column_count()); BitVec::from_aligned(self.row_count(), &self.aligned * &right.bits) } } diff --git a/deq/deq_runtime/build.rs b/deq/deq_runtime/build.rs index 50cd00a2..6cf2515a 100644 --- a/deq/deq_runtime/build.rs +++ b/deq/deq_runtime/build.rs @@ -28,6 +28,26 @@ fn main() -> Result<(), Box> { } } + let mut include_dirs = vec![PROTO_DIR.to_string()]; + // When using a system-installed protoc that does not embed well-known + // proto types (e.g. Ubuntu's `protobuf-compiler`), we need to pass the + // system include directory so that imports like + // `google/protobuf/empty.proto` can be resolved. The release binary + // downloaded by CI embeds these, so no extra path is needed there. + // Skip this probe if the caller already set PROTOC_INCLUDE, which + // prost-build will forward to protoc on its own. + if std::env::var_os("PROTOC_INCLUDE").is_none() { + for candidate in ["/usr/include", "/usr/local/include"] { + if Path::new(candidate) + .join("google/protobuf/empty.proto") + .exists() + { + include_dirs.push(candidate.to_string()); + break; + } + } + } + tonic_prost_build::configure() .build_server(true) .client_mod_attribute(".", "#[cfg(feature = \"cli\")]") @@ -39,7 +59,7 @@ fn main() -> Result<(), Box> { "deq.coordinator.window_coordinator.Event.event", "#[allow(clippy::large_enum_variant)]", ) - .compile_protos(&proto_files, &[PROTO_DIR.to_string()])?; + .compile_protos(&proto_files, &include_dirs)?; Ok(()) } @@ -59,7 +79,7 @@ fn build_tesseract_bridge() { println!("cargo::rerun-if-changed=cpp/tesseract/tesseract_bridge.cc"); println!("cargo::rerun-if-changed=src/decoder/tesseract_ffi.rs"); - let boost_dir = download_boost(&out_dir); + let boost_dir = find_boost(&out_dir); let mut build = cxx_build::bridge("src/decoder/tesseract_ffi.rs"); build @@ -76,6 +96,53 @@ fn build_tesseract_bridge() { build.compile("tesseract-bridge"); } +// ── Boost include path resolution ─────────────────────────────────── +// +// Preference order: +// 1. DEQ_BOOST_ROOT env var (explicit override, e.g. from CI or developer) +// 2. BOOST_ROOT env var (set automatically on GitHub-hosted Windows runners) +// 3. pkg-config (Linux/macOS with libboost-dev / Homebrew boost) +// 4. Well-known system include directories +// 5. Fall back to downloading the headers at build time + +#[cfg(feature = "tesseract")] +fn find_boost(out_dir: &std::path::Path) -> std::path::PathBuf { + use std::path::PathBuf; + + for var in ["DEQ_BOOST_ROOT", "BOOST_ROOT"] { + if let Ok(val) = std::env::var(var) { + let p = PathBuf::from(&val); + if p.join("boost").join("dynamic_bitset.hpp").exists() { + eprintln!("cargo:warning=Using Boost from ${var}: {}", p.display()); + return p; + } + } + } + + if let Ok(output) = std::process::Command::new("pkg-config") + .args(["--variable=includedir", "boost"]) + .output() + && output.status.success() + { + let s = String::from_utf8_lossy(&output.stdout); + let p = PathBuf::from(s.trim()); + if p.join("boost").join("dynamic_bitset.hpp").exists() { + eprintln!("cargo:warning=Using Boost from pkg-config: {}", p.display()); + return p; + } + } + + for candidate in ["/usr/include", "/usr/local/include", "/opt/homebrew/include"] { + let p = PathBuf::from(candidate); + if p.join("boost").join("dynamic_bitset.hpp").exists() { + eprintln!("cargo:warning=Using Boost from system path: {}", p.display()); + return p; + } + } + + download_boost(out_dir) +} + // ── Boost download (header-only, for dynamic_bitset) ──────────────── #[cfg(feature = "tesseract")] diff --git a/paulimer/src/clifford/clifford_impl.rs b/paulimer/src/clifford/clifford_impl.rs index 3beebcf1..f7a7a7c5 100644 --- a/paulimer/src/clifford/clifford_impl.rs +++ b/paulimer/src/clifford/clifford_impl.rs @@ -1375,7 +1375,7 @@ impl MutablePreImages #[allow(clippy::similar_names)] fn preimage_xz_views_mut_distinct(&mut self, index: (usize, usize)) -> crate::Tuple2x2> { - debug_assert!(index.0 != index.1); + debug_assert_ne!(index.0, index.1); unsafe { let (xx, xz, zx, zz) = get_quad_mut_unsafe(&mut self.preimages); let (xx0, xx1) = get_tuple_mut_unsafe(xx, index); diff --git a/paulimer/src/clifford/generic_algos.rs b/paulimer/src/clifford/generic_algos.rs index 759bad29..9da5b1de 100644 --- a/paulimer/src/clifford/generic_algos.rs +++ b/paulimer/src/clifford/generic_algos.rs @@ -303,7 +303,7 @@ where for<'life> ::PreImageViewMut<'life>: PauliBinaryOps<::DensePauli>, { - assert!(left.num_qubits() == right.num_qubits()); + assert_eq!(left.num_qubits(), right.num_qubits()); let mut result = CliffordLike::zero(left.num_qubits()); for qubit_index in 0..left.num_qubits() { result diff --git a/stdin b/stdin new file mode 100755 index 00000000..29a2bb37 Binary files /dev/null and b/stdin differ