diff --git a/.github/actions/setup-llvm/action.yml b/.github/actions/setup-llvm/action.yml deleted file mode 100644 index 4da7fe668..000000000 --- a/.github/actions/setup-llvm/action.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: 'Setup LLVM 14.0.6' -description: 'Install prebuilt LLVM 14.0.6 binaries for all platforms' -outputs: - llvm-path: - description: 'Path to LLVM installation' - value: ${{ steps.setup.outputs.llvm-path }} - -runs: - using: 'composite' - steps: - - name: Setup LLVM (Linux) - if: runner.os == 'Linux' - shell: bash - id: setup-linux - run: | - mkdir -p /tmp/llvm - if [ "$(uname -m)" = "x86_64" ]; then - echo "Installing LLVM 14.0.6 for x86_64..." - # Use RHEL 8.4 build (Ubuntu 18.04 build doesn't exist for x86_64 in LLVM 14.0.6) - curl -LO https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-x86_64-linux-gnu-rhel-8.4.tar.xz - tar xf clang+llvm-14.0.6-x86_64-linux-gnu-rhel-8.4.tar.xz -C /tmp/llvm --strip-components=1 - else - echo "Installing LLVM 14.0.6 for aarch64..." - curl -LO https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-aarch64-linux-gnu.tar.xz - tar xf clang+llvm-14.0.6-aarch64-linux-gnu.tar.xz -C /tmp/llvm --strip-components=1 - fi - echo "/tmp/llvm/bin" >> $GITHUB_PATH - echo "LLVM_SYS_140_PREFIX=/tmp/llvm" >> $GITHUB_ENV - echo "llvm-path=/tmp/llvm" >> $GITHUB_OUTPUT - - # Verify installation - /tmp/llvm/bin/llc --version - /tmp/llvm/bin/clang --version - - - name: Setup LLVM (macOS) - if: runner.os == 'macOS' - shell: bash - id: setup-macos - run: | - mkdir -p /tmp/llvm - if [ "$(uname -m)" = "arm64" ]; then - echo "Installing LLVM 14.0.6 for arm64..." - ARCH_PREFIX=arm64-apple-darwin22.3.0 - else - echo "Installing LLVM 14.0.6 for x86_64..." - ARCH_PREFIX=x86_64-apple-darwin - fi - curl -LO https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-$ARCH_PREFIX.tar.xz - tar xf clang+llvm-14.0.6-$ARCH_PREFIX.tar.xz -C /tmp/llvm --strip-components=1 - - # FIX: LLVM 14.0.6 libunwind libraries have @rpath references to themselves - # This causes "Library not loaded: @rpath/libunwind.1.dylib" errors at runtime - # Fix by changing the install name to use the absolute path - echo "Fixing LLVM libunwind install names..." - for lib in /tmp/llvm/lib/libunwind*.dylib; do - if [ -f "$lib" ]; then - echo " Fixing $(basename $lib)" - # Change the self-reference from @rpath/libunwind.1.dylib to the absolute path - install_name_tool -id "$lib" "$lib" - fi - done - echo "LLVM libunwind libraries fixed" - - echo "/tmp/llvm/bin" >> $GITHUB_PATH - echo "LLVM_SYS_140_PREFIX=/tmp/llvm" >> $GITHUB_ENV - echo "llvm-path=/tmp/llvm" >> $GITHUB_OUTPUT - - # Verify installation - /tmp/llvm/bin/llc --version - /tmp/llvm/bin/clang --version - - - name: Setup LLVM (Windows) - if: runner.os == 'Windows' - shell: pwsh - id: setup-windows - run: | - Write-Host "Installing LLVM 14.0.6 for Windows..." - curl.exe -LO https://github.com/PLC-lang/llvm-package-windows/releases/download/v14.0.6/LLVM-14.0.6-win64.7z - 7z x LLVM-14.0.6-win64.7z "-oC:\LLVM" -y - - echo "C:\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - echo "LLVM_SYS_140_PREFIX=C:\LLVM" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - echo "llvm-path=C:\LLVM" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append - - # Verify installation - C:\LLVM\bin\llc.exe --version - C:\LLVM\bin\clang.exe --version - - - name: Set output - shell: bash - id: setup - run: | - if [ "$RUNNER_OS" = "Linux" ] || [ "$RUNNER_OS" = "macOS" ]; then - echo "llvm-path=/tmp/llvm" >> $GITHUB_OUTPUT - else - echo "llvm-path=C:/LLVM" >> $GITHUB_OUTPUT - fi diff --git a/.github/workflows/julia-release.yml b/.github/workflows/julia-release.yml index d29148f0a..42edc9940 100644 --- a/.github/workflows/julia-release.yml +++ b/.github/workflows/julia-release.yml @@ -97,8 +97,38 @@ jobs: - name: Set up Rust run: rustup show - - name: Setup LLVM 14.0.6 - uses: ./.github/actions/setup-llvm + - name: Install LLVM 14.0.6 using pecos-llvm (Unix) + if: runner.os != 'Windows' + run: | + echo "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + echo "Setting LLVM environment variables..." + export PECOS_LLVM=$(cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>/dev/null) + export LLVM_SYS_140_PREFIX="$PECOS_LLVM" + + echo "PECOS_LLVM=$PECOS_LLVM" >> $GITHUB_ENV + echo "LLVM_SYS_140_PREFIX=$LLVM_SYS_140_PREFIX" >> $GITHUB_ENV + + echo "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check + + - name: Install LLVM 14.0.6 using pecos-llvm (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + Write-Host "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + Write-Host "Setting LLVM environment variables..." + $env:PECOS_LLVM = (cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>$null) + $env:LLVM_SYS_140_PREFIX = $env:PECOS_LLVM + + "PECOS_LLVM=$env:PECOS_LLVM" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "LLVM_SYS_140_PREFIX=$env:LLVM_SYS_140_PREFIX" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + Write-Host "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check - name: Install Rust target run: | diff --git a/.github/workflows/julia-test.yml b/.github/workflows/julia-test.yml index b18c80e03..9871c6eb2 100644 --- a/.github/workflows/julia-test.yml +++ b/.github/workflows/julia-test.yml @@ -57,8 +57,38 @@ jobs: - name: Set up Rust run: rustup show - - name: Setup LLVM 14.0.6 - uses: ./.github/actions/setup-llvm + - name: Install LLVM 14.0.6 using pecos-llvm (Unix) + if: runner.os != 'Windows' + run: | + echo "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + echo "Setting LLVM environment variables..." + export PECOS_LLVM=$(cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>/dev/null) + export LLVM_SYS_140_PREFIX="$PECOS_LLVM" + + echo "PECOS_LLVM=$PECOS_LLVM" >> $GITHUB_ENV + echo "LLVM_SYS_140_PREFIX=$LLVM_SYS_140_PREFIX" >> $GITHUB_ENV + + echo "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check + + - name: Install LLVM 14.0.6 using pecos-llvm (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + Write-Host "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + Write-Host "Setting LLVM environment variables..." + $env:PECOS_LLVM = (cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>$null) + $env:LLVM_SYS_140_PREFIX = $env:PECOS_LLVM + + "PECOS_LLVM=$env:PECOS_LLVM" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "LLVM_SYS_140_PREFIX=$env:LLVM_SYS_140_PREFIX" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + Write-Host "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check - name: Cache Rust uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 9cf6b2cee..b6491a8ef 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -57,8 +57,38 @@ jobs: with: workspaces: python/pecos-rslib - - name: Setup LLVM 14.0.6 - uses: ./.github/actions/setup-llvm + - name: Install LLVM 14.0.6 using pecos-llvm (Unix) + if: runner.os != 'Windows' + run: | + echo "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + echo "Setting LLVM environment variables..." + export PECOS_LLVM=$(cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>/dev/null) + export LLVM_SYS_140_PREFIX="$PECOS_LLVM" + + echo "PECOS_LLVM=$PECOS_LLVM" >> $GITHUB_ENV + echo "LLVM_SYS_140_PREFIX=$LLVM_SYS_140_PREFIX" >> $GITHUB_ENV + + echo "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check + + - name: Install LLVM 14.0.6 using pecos-llvm (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + Write-Host "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + Write-Host "Setting LLVM environment variables..." + $env:PECOS_LLVM = (cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>$null) + $env:LLVM_SYS_140_PREFIX = $env:PECOS_LLVM + + "PECOS_LLVM=$env:PECOS_LLVM" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "LLVM_SYS_140_PREFIX=$env:LLVM_SYS_140_PREFIX" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + Write-Host "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check - name: Build and test PECOS (Windows) if: runner.os == 'Windows' @@ -227,7 +257,7 @@ jobs: if [ -f "$lib" ]; then libname=$(basename "$lib") if otool -L "$lib" 2>/dev/null | grep -q "libunwind"; then - echo " ⚠️ $libname HAS libunwind reference:" + echo " [WARNING] $libname HAS libunwind reference:" otool -L "$lib" | grep libunwind fi fi @@ -293,10 +323,10 @@ jobs: exit 1 else - echo "✅ SUCCESS: No @rpath/libunwind reference found!" + echo "[OK] No @rpath/libunwind reference found" fi else - echo "⚠️ WARNING: Could not find extension module to check" + echo "[WARNING] Could not find extension module to check" fi fi diff --git a/.github/workflows/rust-test.yml b/.github/workflows/rust-test.yml index 03468fc36..b5ea12a6d 100644 --- a/.github/workflows/rust-test.yml +++ b/.github/workflows/rust-test.yml @@ -49,8 +49,20 @@ jobs: with: save-if: ${{ github.ref_name == 'master' || github.ref_name == 'development' || github.ref_name == 'dev' }} - - name: Setup LLVM 14.0.6 - uses: ./.github/actions/setup-llvm + - name: Install LLVM 14.0.6 using pecos-llvm + run: | + echo "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + echo "Setting LLVM environment variables..." + export PECOS_LLVM=$(cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>/dev/null) + export LLVM_SYS_140_PREFIX="$PECOS_LLVM" + + echo "PECOS_LLVM=$PECOS_LLVM" >> $GITHUB_ENV + echo "LLVM_SYS_140_PREFIX=$LLVM_SYS_140_PREFIX" >> $GITHUB_ENV + + echo "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check - name: Install rustfmt run: rustup component add rustfmt @@ -179,8 +191,38 @@ jobs: with: save-if: ${{ github.ref_name == 'master' || github.ref_name == 'development' || github.ref_name == 'dev' }} - - name: Setup LLVM 14.0.6 - uses: ./.github/actions/setup-llvm + - name: Install LLVM 14.0.6 using pecos-llvm (Unix) + if: matrix.os != 'windows-latest' + run: | + echo "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + echo "Setting LLVM environment variables..." + export PECOS_LLVM=$(cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>/dev/null) + export LLVM_SYS_140_PREFIX="$PECOS_LLVM" + + echo "PECOS_LLVM=$PECOS_LLVM" >> $GITHUB_ENV + echo "LLVM_SYS_140_PREFIX=$LLVM_SYS_140_PREFIX" >> $GITHUB_ENV + + echo "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check + + - name: Install LLVM 14.0.6 using pecos-llvm (Windows) + if: matrix.os == 'windows-latest' + shell: pwsh + run: | + Write-Host "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + Write-Host "Setting LLVM environment variables..." + $env:PECOS_LLVM = (cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>$null) + $env:LLVM_SYS_140_PREFIX = $env:PECOS_LLVM + + "PECOS_LLVM=$env:PECOS_LLVM" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "LLVM_SYS_140_PREFIX=$env:LLVM_SYS_140_PREFIX" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + Write-Host "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check - name: Set up Visual Studio environment on Windows if: matrix.os == 'windows-latest' diff --git a/.github/workflows/test-docs-examples.yml b/.github/workflows/test-docs-examples.yml index 25ac254ff..8a18bfc12 100644 --- a/.github/workflows/test-docs-examples.yml +++ b/.github/workflows/test-docs-examples.yml @@ -41,8 +41,20 @@ jobs: with: workspaces: python/pecos-rslib - - name: Setup LLVM 14.0.6 - uses: ./.github/actions/setup-llvm + - name: Install LLVM 14.0.6 using pecos-llvm + run: | + echo "Installing LLVM using pecos-llvm-utils..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install + + echo "Setting LLVM environment variables..." + export PECOS_LLVM=$(cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- find 2>/dev/null) + export LLVM_SYS_140_PREFIX="$PECOS_LLVM" + + echo "PECOS_LLVM=$PECOS_LLVM" >> $GITHUB_ENV + echo "LLVM_SYS_140_PREFIX=$LLVM_SYS_140_PREFIX" >> $GITHUB_ENV + + echo "Verifying LLVM installation..." + cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- check - name: Generate lockfile and install dependencies run: | diff --git a/.gitignore b/.gitignore index ae3edfa9c..2e4c5ead7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ tmp/ # LLVM (extracted from archive for Windows development) llvm/ +# Cargo config with machine-specific LLVM paths +.cargo/config.toml + # pytest results junit/ diff --git a/Cargo.lock b/Cargo.lock index 54545a340..e6fe6c3de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -269,6 +269,21 @@ dependencies = [ "virtue", ] +[[package]] +name = "bit-set" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" + [[package]] name = "bitflags" version = "2.10.0" @@ -736,6 +751,21 @@ version = "0.125.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7a06c330b7994a891ad5b622ebc9aefcd17beae832dd25f577cf60c13426bf" +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.5.0" @@ -1240,6 +1270,17 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "filetime_creation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c25b5d475550e559de5b0c0084761c65325444e3b6c9e298af9cefe7a9ef3a5f" +dependencies = [ + "cfg-if", + "filetime", + "windows-sys 0.52.0", +] + [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -2129,6 +2170,26 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lzma-rust" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baab2bbbd7d75a144d671e9ff79270e903957d92fb7386fd39034c709bd2661" +dependencies = [ + "byteorder", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "mach2" version = "0.4.3" @@ -2208,6 +2269,16 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "nt-time" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2de419e64947cd8830e66beb584acc3fb42ed411d103e3c794dda355d1b374b5" +dependencies = [ + "chrono", + "time", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2514,6 +2585,19 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "pecos-llvm-utils" +version = "0.1.1" +dependencies = [ + "clap", + "dirs", + "flate2", + "reqwest", + "sevenz-rust", + "tar", + "xz2", +] + [[package]] name = "pecos-phir" version = "0.1.1" @@ -2586,6 +2670,7 @@ dependencies = [ "log", "pecos-core", "pecos-engines", + "pecos-llvm-utils", "pecos-programs", "pecos-qis-ffi-types", "pecos-qis-selene", @@ -3654,6 +3739,23 @@ dependencies = [ "syn", ] +[[package]] +name = "sevenz-rust" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26482cf1ecce4540dc782fc70019eba89ffc4d87b3717eb5ec524b5db6fdefef" +dependencies = [ + "bit-set", + "byteorder", + "crc", + "filetime_creation", + "js-sys", + "lzma-rust", + "nt-time", + "sha2", + "wasm-bindgen", +] + [[package]] name = "sha2" version = "0.10.9" @@ -4970,6 +5072,15 @@ dependencies = [ "rustix", ] +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 35440cfdb..693ae274e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,6 +118,7 @@ pecos-qis-core = { version = "0.1.1", path = "crates/pecos-qis-core" } pecos-hugr-qis = { version = "0.1.1", path = "crates/pecos-hugr-qis" } pecos-rslib = { version = "0.1.1", path = "python/pecos-rslib/rust" } pecos-build-utils = { version = "0.1.1", path = "crates/pecos-build-utils" } +pecos-llvm-utils = { version = "0.1.1", path = "crates/pecos-llvm-utils" } # Decoder crates pecos-decoder-core = { version = "0.1.1", path = "crates/pecos-decoder-core" } diff --git a/Makefile b/Makefile index c66afa5b6..a3059899f 100644 --- a/Makefile +++ b/Makefile @@ -4,13 +4,9 @@ PYTHON := $(shell which python 2>/dev/null || which python3 2>/dev/null) SHELL=bash -# Set LLVM path for Windows development builds (only if llvm/ directory exists) -ifdef OS - # Windows - check if local LLVM exists and set path - ifneq ($(wildcard llvm/bin/llvm-config.exe),) - export LLVM_SYS_140_PREFIX := $(CURDIR)/llvm - endif -endif +# LLVM Configuration +# LLVM is automatically detected by build.rs files using pecos-llvm-utils +# No manual configuration needed! # Requirements # ------------ @@ -35,34 +31,48 @@ installreqs: ## Install Python project requirements to root .venv # Building development environments # --------------------------------- -# Helper to unset CONDA_PREFIX and set LLVM path in a cross-platform way +.PHONY: check-llvm +check-llvm: ## Check LLVM 14 installation status + @cargo run -q --release --package pecos-llvm-utils --bin pecos-llvm -- check || true + +# LLVM Detection Helper +# Auto-detect LLVM if not already set +SETUP_LLVM = \ + if [ -z "$$LLVM_SYS_140_PREFIX" ]; then \ + DETECTED_LLVM=$$(cargo run -q --release -p pecos-llvm-utils --bin pecos-llvm -- find 2>/dev/null); \ + if [ -n "$$DETECTED_LLVM" ]; then \ + export PECOS_LLVM="$$DETECTED_LLVM"; \ + export LLVM_SYS_140_PREFIX="$$DETECTED_LLVM"; \ + echo "Auto-detected LLVM at: $$LLVM_SYS_140_PREFIX"; \ + fi; \ + fi + +# Helper to unset CONDA_PREFIX and add LLVM to PATH for runtime tools ifdef OS # Windows (running in Git Bash/MSYS) UNSET_CONDA = set "CONDA_PREFIX=" && - # Set LLVM path if local installation exists - ifneq ($(wildcard llvm/bin/llvm-config.exe),) - SET_LLVM = set "LLVM_SYS_140_PREFIX=$(CURDIR)/llvm" && - # Add LLVM bin to PATH for runtime tools (llvm-as, etc.) - # Use colon separator - Git Bash uses Unix-style paths internally - ADD_LLVM_TO_PATH = export PATH="$(CURDIR)/llvm/bin:$$PATH" && + ifdef LLVM_SYS_140_PREFIX + ADD_LLVM_TO_PATH = export PATH="$(LLVM_SYS_140_PREFIX)/bin:$$PATH" && else - SET_LLVM = ADD_LLVM_TO_PATH = endif else # Unix/Linux/macOS UNSET_CONDA = unset CONDA_PREFIX && - SET_LLVM = - ADD_LLVM_TO_PATH = + ifdef LLVM_SYS_140_PREFIX + ADD_LLVM_TO_PATH = export PATH="$(LLVM_SYS_140_PREFIX)/bin:$$PATH" && + else + ADD_LLVM_TO_PATH = + endif endif .PHONY: build build: installreqs ## Compile and install for development - @$(UNSET_CONDA) $(SET_LLVM) cd python/pecos-rslib/ && uv run maturin develop --uv + @$(SETUP_LLVM); $(UNSET_CONDA) cd python/pecos-rslib/ && uv run maturin develop --uv @$(UNSET_CONDA) uv pip install -e "./python/quantum-pecos[all]" @if command -v julia >/dev/null 2>&1; then \ echo "Julia detected, building Julia FFI library..."; \ - cd julia/pecos-julia-ffi && cargo build; \ + $(SETUP_LLVM); cd julia/pecos-julia-ffi && cargo build; \ echo "Julia FFI library built successfully"; \ else \ echo "Julia not detected, skipping Julia build"; \ @@ -70,12 +80,12 @@ build: installreqs ## Compile and install for development .PHONY: build-basic build-basic: installreqs ## Compile and install for development but do not include install extras - @$(UNSET_CONDA) $(SET_LLVM) cd python/pecos-rslib/ && uv run maturin develop --uv + @$(SETUP_LLVM); $(UNSET_CONDA) cd python/pecos-rslib/ && uv run maturin develop --uv @$(UNSET_CONDA) uv pip install -e ./python/quantum-pecos .PHONY: build-release build-release: installreqs ## Build a faster version of binaries - @$(UNSET_CONDA) $(SET_LLVM) cd python/pecos-rslib/ && uv run maturin develop --uv --release + @$(SETUP_LLVM); $(UNSET_CONDA) cd python/pecos-rslib/ && uv run maturin develop --uv --release @$(UNSET_CONDA) uv pip install -e "./python/quantum-pecos[all]" @if command -v julia >/dev/null 2>&1; then \ echo "Julia detected, building Julia FFI library (release)..."; \ @@ -87,13 +97,13 @@ build-release: installreqs ## Build a faster version of binaries .PHONY: build-native build-native: installreqs ## Build a faster version of binaries with native CPU optimization - @$(UNSET_CONDA) $(SET_LLVM) cd python/pecos-rslib/ && RUSTFLAGS='-C target-cpu=native' \ + @$(UNSET_CONDA) cd python/pecos-rslib/ && RUSTFLAGS='-C target-cpu=native' \ uv run maturin develop --uv --release @$(UNSET_CONDA) uv pip install -e "./python/quantum-pecos[all]" .PHONY: build-cuda build-cuda: installreqs ## Compile and install for development with CUDA support - @$(UNSET_CONDA) $(SET_LLVM) cd python/pecos-rslib/ && uv run maturin develop --uv + @$(UNSET_CONDA) cd python/pecos-rslib/ && uv run maturin develop --uv @$(UNSET_CONDA) uv pip install -e "./python/quantum-pecos[all,cuda]" @if command -v julia >/dev/null 2>&1; then \ echo "Julia detected, building Julia FFI library..."; \ diff --git a/README.md b/README.md index 060c1593f..e28276c53 100644 --- a/README.md +++ b/README.md @@ -115,17 +115,26 @@ pecos = "0.x.x" # Replace with the latest version #### Optional Dependencies -- **LLVM version 14**: Required for LLVM IR execution support - - Linux: `sudo apt install llvm-14` - - macOS: `brew install llvm@14` - - Windows: - - For development builds, use the included setup script: `.\scripts\setup_llvm.ps1` - - This will extract the bundled LLVM 14.0.6 and configure the build and test environment - - Alternatively, download LLVM 14.x installer from [LLVM releases](https://releases.llvm.org/download.html#14.0.0) +- **LLVM version 14**: Required for LLVM IR execution support (optional) - **Note**: Only LLVM version 14.x is compatible. LLVM 15 or later versions will not work with PECOS's LLVM runtime implementation. + PECOS provides an automated installer or you can install manually: - If LLVM 14 is not installed, PECOS will still function normally but LLVM IR execution features will be disabled. + ```sh + # Quick setup with automated installer (recommended): + cargo run -p pecos-llvm-utils --bin pecos-llvm -- install + cargo build + ``` + + The installer automatically configures PECOS after installation. + + For detailed LLVM installation instructions for all platforms (macOS, Linux, Windows), see the [**Getting Started Guide**](docs/user-guide/getting-started.md#llvm-for-qis-support). + + For full development environment setup, see the [**Development Setup Guide**](docs/development/DEVELOPMENT.md). + + **Building without LLVM:** If you don't need LLVM IR support: + ```sh + cargo build --no-default-features + ``` ### Julia Package (Experimental) diff --git a/crates/pecos-cppsparsesim/build.rs b/crates/pecos-cppsparsesim/build.rs index de3bc9c40..d9c2fe54e 100644 --- a/crates/pecos-cppsparsesim/build.rs +++ b/crates/pecos-cppsparsesim/build.rs @@ -44,9 +44,7 @@ fn main() { // On macOS, use the -stdlib=libc++ flag to ensure proper C++ standard library linkage if target.contains("darwin") { bridge.flag("-stdlib=libc++"); - // Prevent opportunistic linking to Homebrew's libunwind (Xcode 15+ issue) - bridge.flag("-L/usr/lib"); - bridge.flag("-Wl,-search_paths_first"); + // Note: Linker-specific flags are passed via cargo:rustc-link-arg below, not here } bridge.compile("cppsparsesim-bridge"); diff --git a/crates/pecos-llvm-utils/Cargo.toml b/crates/pecos-llvm-utils/Cargo.toml new file mode 100644 index 000000000..272378d29 --- /dev/null +++ b/crates/pecos-llvm-utils/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "pecos-llvm-utils" +version.workspace = true +edition.workspace = true +description = "LLVM detection and utilities for PECOS" +readme.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +keywords = ["quantum", "llvm", "build-utils"] +categories = ["development-tools::build-utils"] + +[[bin]] +name = "pecos-llvm" +path = "src/bin/pecos-llvm.rs" + +[dependencies] +clap.workspace = true +reqwest.workspace = true +tar.workspace = true +flate2.workspace = true +dirs.workspace = true +xz2 = "0.1" +sevenz-rust = "0.6" + +[lints] +workspace = true diff --git a/crates/pecos-llvm-utils/README.md b/crates/pecos-llvm-utils/README.md new file mode 100644 index 000000000..acf023acf --- /dev/null +++ b/crates/pecos-llvm-utils/README.md @@ -0,0 +1,119 @@ +# pecos-llvm-utils + +LLVM detection, installation, and management for PECOS. + +This crate provides functionality to locate and install LLVM 14 across different platforms (macOS, Linux, Windows). + +## Features + +- **Automatic LLVM Detection**: Finds LLVM 14 in common system locations +- **LLVM Installation**: Downloads and installs LLVM 14.0.6 to user data directory +- **Cross-platform**: Works on macOS, Linux, and Windows +- **Pure Rust**: No external dependencies (tar, 7zip, etc.) required for installation +- **Build Script Integration**: Can be used in `build.rs` files +- **Command-line Tool**: `pecos-llvm` binary for all LLVM operations + +### Installation Details + +The installer uses pure Rust dependencies for archive extraction: +- **Unix systems (macOS/Linux)**: Uses `xz2` and `tar` crates for .tar.xz extraction +- **Windows**: Uses `sevenz-rust` crate for .7z extraction + +No external tools (tar, 7zip) are required - everything is handled through Rust libraries. + +## Command-line Tool: `pecos-llvm` + +The `pecos-llvm` binary provides several subcommands: + +### Find LLVM + +```bash +# Find and print LLVM path +pecos-llvm find + +# Print export command for shell evaluation +pecos-llvm find --export +``` + +### Check LLVM Availability + +```bash +# Check if LLVM is available (exit code 0 if found, 1 if not) +pecos-llvm check + +# Quiet mode (no output) +pecos-llvm check --quiet +``` + +### Install LLVM + +```bash +# Install LLVM 14.0.6 to ~/.pecos/llvm +pecos-llvm install + +# Force reinstall +pecos-llvm install --force +``` + +### Show Version + +```bash +# Show LLVM version information +pecos-llvm version +``` + +## Usage in build.rs + +```rust +use pecos_llvm_utils::{find_llvm_14, get_repo_root_from_manifest, print_llvm_not_found_error}; + +fn main() { + let repo_root = get_repo_root_from_manifest(); + match find_llvm_14(repo_root) { + Some(path) => { + println!("cargo:warning=Found LLVM 14 at: {}", path.display()); + } + None => { + print_llvm_not_found_error(); + panic!("LLVM 14 required but not found"); + } + } +} +``` + +## Shell Scripts + +The `pecos-llvm` tool can be wrapped in shell scripts: + +### Bash/Zsh +```bash +#!/bin/bash +# Install LLVM +cargo run --release -p pecos-llvm-utils --bin pecos-llvm -- install + +# Set environment variable +export LLVM_SYS_140_PREFIX=$(cargo run --release -p pecos-llvm-utils --bin pecos-llvm -- find 2>/dev/null) +``` + +### PowerShell +```powershell +# Install LLVM +cargo run --release -p pecos-llvm-utils --bin pecos-llvm -- install + +# Set environment variable +$env:LLVM_SYS_140_PREFIX = (cargo run --release -p pecos-llvm-utils --bin pecos-llvm -- find 2>$null) +``` + +## Detection Priority + +The crate searches for LLVM 14 in the following order: + +1. **Home directory**: `~/.pecos/llvm` (where `pecos-llvm install` puts it) +2. **Project-local**: `llvm/` directory (relative to repository root, for backward compatibility) +3. **System installations**: + - **macOS**: Homebrew installations (`/opt/homebrew/opt/llvm@14`, `/usr/local/opt/llvm@14`) + - **Linux**: Package manager installations (`/usr/lib/llvm-14`, `/usr/local/llvm-14`) + +## License + +Apache-2.0 diff --git a/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs new file mode 100644 index 000000000..2b34ed19a --- /dev/null +++ b/crates/pecos-llvm-utils/src/bin/pecos-llvm.rs @@ -0,0 +1,164 @@ +#!/usr/bin/env rust +//! PECOS LLVM management tool +//! +//! Handles LLVM 14 detection, installation, and configuration for PECOS. + +use clap::{Parser, Subcommand}; +use pecos_llvm_utils::{find_llvm_14, get_repo_root_from_manifest, print_llvm_not_found_error}; +use std::process; + +#[derive(Parser)] +#[command(name = "pecos-llvm")] +#[command(about = "PECOS LLVM management tool", long_about = None)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Find LLVM 14 installation and print its path + Find { + /// Print export command for shell evaluation + #[arg(long)] + export: bool, + }, + /// Check if LLVM 14 is available (exit code 0 if found, 1 if not) + Check { + /// Suppress output messages + #[arg(short, long)] + quiet: bool, + }, + /// Install LLVM 14.0.6 to ~/.pecos/llvm/ + Install { + /// Force reinstall even if already present + #[arg(short, long)] + force: bool, + /// Skip automatic configuration after installation + #[arg(long)] + no_configure: bool, + }, + /// Auto-configure LLVM for PECOS (updates .cargo/config.toml) + Configure, + /// Show LLVM version information + Version, +} + +fn main() { + let cli = Cli::parse(); + + match cli.command { + Commands::Find { export } => cmd_find(export), + Commands::Check { quiet } => cmd_check(quiet), + Commands::Install { + force, + no_configure, + } => cmd_install(force, no_configure), + Commands::Configure => cmd_configure(), + Commands::Version => cmd_version(), + } +} + +fn cmd_find(export: bool) { + let repo_root = get_repo_root_from_manifest(); + let llvm_path = find_llvm_14(repo_root); + + if let Some(path) = llvm_path { + let path_str = path.to_string_lossy(); + if export { + println!("export LLVM_SYS_140_PREFIX=\"{path_str}\""); + } else { + println!("{path_str}"); + } + process::exit(0); + } else { + print_llvm_not_found_error(); + process::exit(1); + } +} + +fn cmd_check(quiet: bool) { + let repo_root = get_repo_root_from_manifest(); + let llvm_path = find_llvm_14(repo_root); + + if let Some(path) = llvm_path { + if !quiet { + eprintln!("LLVM 14 found at: {}", path.display()); + } + process::exit(0); + } else { + if !quiet { + eprintln!("LLVM 14 not found"); + } + process::exit(1); + } +} + +fn cmd_install(force: bool, no_configure: bool) { + use pecos_llvm_utils::installer::install_llvm; + + match install_llvm(force, no_configure) { + Ok(_install_path) => { + // Success message is printed by install_llvm + process::exit(0); + } + Err(e) => { + eprintln!("Failed to install LLVM: {e}"); + process::exit(1); + } + } +} + +fn cmd_configure() { + use pecos_llvm_utils::auto_configure_llvm; + + println!("Auto-configuring LLVM for PECOS..."); + println!(); + + match auto_configure_llvm(None) { + Ok(configured_path) => { + println!("Success! LLVM configured at: {}", configured_path.display()); + println!(); + println!("Updated .cargo/config.toml with LLVM configuration."); + println!(); + println!("You can now build PECOS:"); + println!(" cargo build"); + process::exit(0); + } + Err(e) => { + eprintln!("Failed to configure LLVM: {e}"); + eprintln!(); + eprintln!("To install LLVM, run:"); + eprintln!(" pecos-llvm install"); + process::exit(1); + } + } +} + +fn cmd_version() { + let repo_root = get_repo_root_from_manifest(); + let llvm_path = find_llvm_14(repo_root); + + if let Some(path) = llvm_path { + println!("LLVM 14 found at: {}", path.display()); + + // Try to get version from llvm-config + let llvm_config = if cfg!(windows) { + path.join("bin").join("llvm-config.exe") + } else { + path.join("bin").join("llvm-config") + }; + + if let Ok(output) = std::process::Command::new(&llvm_config) + .arg("--version") + .output() + && output.status.success() + { + let version = String::from_utf8_lossy(&output.stdout); + println!("Version: {}", version.trim()); + } + } else { + println!("LLVM 14 not found"); + process::exit(1); + } +} diff --git a/crates/pecos-llvm-utils/src/installer.rs b/crates/pecos-llvm-utils/src/installer.rs new file mode 100644 index 000000000..fd0e64a61 --- /dev/null +++ b/crates/pecos-llvm-utils/src/installer.rs @@ -0,0 +1,449 @@ +//! LLVM 14.0.6 installation functionality +//! +//! Downloads and extracts LLVM 14.0.6 pre-built binaries to a project-local directory. + +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +/// Install LLVM 14.0.6 to user data directory +/// +/// Installs to platform-appropriate location: +/// - macOS: ~/Library/Application Support/pecos/llvm +/// - Linux: ~/.local/share/pecos/llvm +/// - Windows: %LOCALAPPDATA%/pecos/llvm +/// +/// # Arguments +/// * `force` - Force reinstall even if already present +/// * `no_configure` - Skip automatic configuration after installation +/// +/// # Errors +/// Returns an error if: +/// - LLVM is already installed and `force` is false +/// - The download or extraction fails +/// - Platform-specific fixes fail +/// - Installation verification fails +/// +/// # Returns +/// Path to the installed LLVM directory +pub fn install_llvm( + force: bool, + no_configure: bool, +) -> Result> { + let llvm_dir = get_install_location()?; + + // Check if already installed + if !force && llvm_dir.exists() && is_valid_installation(&llvm_dir) { + return Err("LLVM is already installed. Use --force to reinstall.".into()); + } + + // If force is specified and directory exists, remove it first + if force && llvm_dir.exists() { + println!("Removing existing LLVM installation..."); + fs::remove_dir_all(&llvm_dir)?; + } + + println!("Installing LLVM 14.0.6..."); + println!("This will download ~400MB and may take 5-10 minutes."); + println!(); + + // Determine platform and download URL + let (url, archive_name) = get_download_url()?; + + // Create parent directory if it doesn't exist + if let Some(parent) = llvm_dir.parent() { + fs::create_dir_all(parent)?; + } + + // Download to temp directory (use llvm subdirectory to avoid conflicts) + let temp_base = llvm_dir.parent().unwrap_or(&llvm_dir).join("tmp"); + let temp_dir = temp_base.join("llvm"); + fs::create_dir_all(&temp_dir)?; + let archive_path = temp_dir.join(&archive_name); + download_llvm(&url, &archive_path)?; + + // Extract + extract_llvm(&archive_path, &llvm_dir)?; + + // Cleanup LLVM temp directory only (not entire tmp directory) + fs::remove_dir_all(&temp_dir)?; + + // Apply platform-specific fixes + apply_platform_fixes(&llvm_dir)?; + + // Verify + if !is_valid_installation(&llvm_dir) { + return Err("Installation completed but verification failed".into()); + } + + println!(); + println!("Installation complete!"); + println!("LLVM 14.0.6 installed to: {}", llvm_dir.display()); + + // Auto-configure LLVM for PECOS (unless --no-configure is specified) + if no_configure { + println!(); + println!("Skipping automatic configuration (--no-configure specified)."); + println!(); + println!("To configure PECOS, run:"); + println!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + } else { + println!(); + println!("Configuring PECOS to use this LLVM installation..."); + match crate::auto_configure_llvm(None) { + Ok(configured_path) => { + println!("Updated .cargo/config.toml with LLVM configuration"); + println!("Configured LLVM path: {}", configured_path.display()); + println!(); + println!("You can now build PECOS:"); + println!(" cargo build"); + } + Err(e) => { + eprintln!("Warning: Could not auto-configure LLVM: {e}"); + println!(); + println!("Please run configuration manually:"); + println!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + } + } + } + + Ok(llvm_dir) +} + +fn get_install_location() -> Result> { + let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?; + + Ok(home_dir.join(".pecos").join("llvm")) +} + +fn get_download_url() -> Result<(String, String), Box> { + let os = std::env::consts::OS; + let arch = std::env::consts::ARCH; + + match os { + "macos" => { + if arch == "aarch64" { + Ok(( + "https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-arm64-apple-darwin22.3.0.tar.xz".to_string(), + "clang+llvm-14.0.6-arm64-apple-darwin22.3.0.tar.xz".to_string(), + )) + } else { + Ok(( + "https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-x86_64-apple-darwin.tar.xz".to_string(), + "clang+llvm-14.0.6-x86_64-apple-darwin.tar.xz".to_string(), + )) + } + } + "linux" => { + if arch == "x86_64" { + Ok(( + "https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-x86_64-linux-gnu-rhel-8.4.tar.xz".to_string(), + "clang+llvm-14.0.6-x86_64-linux-gnu-rhel-8.4.tar.xz".to_string(), + )) + } else if arch == "aarch64" { + Ok(( + "https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/clang+llvm-14.0.6-aarch64-linux-gnu.tar.xz".to_string(), + "clang+llvm-14.0.6-aarch64-linux-gnu.tar.xz".to_string(), + )) + } else { + Err(format!("Unsupported Linux architecture: {arch}").into()) + } + } + "windows" => { + Ok(( + "https://github.com/PLC-lang/llvm-package-windows/releases/download/v14.0.6/LLVM-14.0.6-win64.7z".to_string(), + "LLVM-14.0.6-win64.7z".to_string(), + )) + } + _ => Err(format!("Unsupported operating system: {os}").into()), + } +} + +fn download_llvm(url: &str, dest: &PathBuf) -> Result<(), Box> { + print!("Downloading LLVM... "); + io::Write::flush(&mut io::stdout())?; + + let response = reqwest::blocking::get(url)?; + let total_size = response.content_length().unwrap_or(0); + + let mut file = fs::File::create(dest)?; + let mut downloaded: u64 = 0; + let mut stream = response; + let mut last_print = 0.0; + + loop { + let mut buffer = vec![0; 8192]; + let bytes_read = io::Read::read(&mut stream, &mut buffer)?; + if bytes_read == 0 { + break; + } + + io::Write::write_all(&mut file, &buffer[..bytes_read])?; + downloaded += bytes_read as u64; + + if total_size > 0 { + // Precision loss is acceptable for progress display + #[allow(clippy::cast_precision_loss)] + let progress = (downloaded as f64 / total_size as f64) * 100.0; + // Only update display every 1% + if progress - last_print >= 1.0 { + print!("\rDownloading LLVM... {progress:.0}%"); + io::Write::flush(&mut io::stdout())?; + last_print = progress; + } + } + } + + println!("\rDownloading LLVM... Done"); + Ok(()) +} + +fn extract_llvm(archive: &PathBuf, dest: &PathBuf) -> Result<(), Box> { + print!("Extracting LLVM... "); + io::Write::flush(&mut io::stdout())?; + + // Determine archive type using Path::extension() for case-insensitive comparison + let file_name = archive + .file_name() + .and_then(|n| n.to_str()) + .ok_or("Could not determine archive name")?; + + // Check for .tar.xz (compound extension) + if file_name.ends_with(".tar.xz") || file_name.ends_with(".tar.XZ") { + extract_tar_xz(archive, dest)?; + } else if std::path::Path::new(file_name) + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("7z")) + { + extract_7z(archive, dest)?; + } else { + return Err(format!("Unsupported archive format: {file_name}").into()); + } + + println!("Done"); + Ok(()) +} + +fn extract_tar_xz(archive: &PathBuf, dest: &PathBuf) -> Result<(), Box> { + use tar::Archive; + use xz2::read::XzDecoder; + + // Open the .tar.xz file + let file = fs::File::open(archive)?; + let decompressor = XzDecoder::new(file); + let mut tar_archive = Archive::new(decompressor); + + // Extract to parent directory first + let extract_to = dest.parent().ok_or("Invalid destination path")?; + tar_archive.unpack(extract_to)?; + + // The archive extracts to a directory like clang+llvm-14.0.6-... + // We need to determine the extracted directory name from the archive filename + let archive_name = archive + .file_stem() + .and_then(|s| s.to_str()) + .ok_or("Could not determine archive name")?; + + // For .tar.xz, we need to strip the .tar part too + let archive_path_buf = PathBuf::from(archive_name); + let base_name = if let Some(stem) = archive_path_buf.file_stem() { + stem.to_str().ok_or("Invalid archive name")? + } else { + archive_name + }; + + let extracted_dir = extract_to.join(base_name); + + // If dest doesn't exist, rename extracted_dir to dest + if dest.exists() { + // Move contents + for entry in fs::read_dir(&extracted_dir)? { + let entry = entry?; + let dest_path = dest.join(entry.file_name()); + fs::rename(entry.path(), dest_path)?; + } + fs::remove_dir(&extracted_dir)?; + } else { + fs::rename(&extracted_dir, dest)?; + } + + Ok(()) +} + +fn extract_7z(archive: &PathBuf, dest: &PathBuf) -> Result<(), Box> { + use sevenz_rust::{Password, SevenZReader}; + + // Open the .7z file + let file = fs::File::open(archive)?; + let len = file.metadata()?.len(); + let password = Password::empty(); + let mut reader = SevenZReader::new(file, len, password)?; + + // Extract to parent directory first + let extract_to = dest.parent().ok_or("Invalid destination path")?; + fs::create_dir_all(extract_to)?; + + // Extract all files + reader.for_each_entries(|entry, reader| { + if entry.is_directory() { + let dir_path = extract_to.join(entry.name()); + fs::create_dir_all(&dir_path).ok(); + } else { + let file_path = extract_to.join(entry.name()); + if let Some(parent) = file_path.parent() { + fs::create_dir_all(parent).ok(); + } + let mut output = fs::File::create(&file_path)?; + io::copy(reader, &mut output)?; + } + Ok(true) // Continue extracting + })?; + + // The archive extracts to a directory like LLVM-14.0.6-win64 + // Find the extracted directory + let mut extracted_dir = None; + for entry in fs::read_dir(extract_to)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() && path.file_name().unwrap().to_str().unwrap().contains("LLVM") { + extracted_dir = Some(path); + break; + } + } + + let extracted_dir = extracted_dir.ok_or("Could not find extracted LLVM directory")?; + + // If dest doesn't exist, rename extracted_dir to dest + if dest.exists() { + // Move contents + for entry in fs::read_dir(&extracted_dir)? { + let entry = entry?; + let dest_path = dest.join(entry.file_name()); + fs::rename(entry.path(), dest_path)?; + } + fs::remove_dir(&extracted_dir)?; + } else { + fs::rename(&extracted_dir, dest)?; + } + + Ok(()) +} + +fn is_valid_installation(path: &Path) -> bool { + let llvm_config = if cfg!(windows) { + path.join("bin").join("llvm-config.exe") + } else { + path.join("bin").join("llvm-config") + }; + + llvm_config.exists() +} + +fn apply_platform_fixes(llvm_dir: &Path) -> Result<(), Box> { + #[cfg(target_os = "macos")] + { + print!("Applying macOS fixes... "); + io::Write::flush(&mut io::stdout())?; + + // Fix 1: Configure clang to use macOS SDK + // The pre-built LLVM doesn't know where system libraries are on macOS + configure_macos_sdk(llvm_dir)?; + + // Fix 2: Fix libunwind dylib references + // LLVM 14.0.6 libunwind libraries have @rpath references to themselves + fix_libunwind_references(llvm_dir)?; + + println!("Done"); + } + + Ok(()) +} + +#[cfg(target_os = "macos")] +fn configure_macos_sdk(llvm_dir: &Path) -> Result<(), Box> { + use std::os::unix::fs::PermissionsExt; + use std::process::Command; + + // Get the SDK path from xcrun + let output = Command::new("xcrun").args(["--show-sdk-path"]).output()?; + + if !output.status.success() { + return Err("Failed to find macOS SDK. Is Xcode Command Line Tools installed?".into()); + } + + let sdk_path = String::from_utf8(output.stdout)?.trim().to_string(); + + // Create wrapper scripts for clang and clang-14 that add SDK flags + let bin_dir = llvm_dir.join("bin"); + + // Rename original binaries + let clang_orig = bin_dir.join("clang"); + let clang_real = bin_dir.join("clang-real"); + if clang_orig.exists() && !clang_real.exists() { + fs::rename(&clang_orig, &clang_real)?; + } + + let clang14_orig = bin_dir.join("clang-14"); + let clang14_real = bin_dir.join("clang-14-real"); + if clang14_orig.exists() && !clang14_real.exists() { + fs::rename(&clang14_orig, &clang14_real)?; + } + + // Create wrapper script for clang + let wrapper_content = format!( + "#!/bin/bash\nexec \"$(dirname \"$0\")/clang-real\" -isysroot {sdk_path} -L{sdk_path}/usr/lib \"$@\"\n" + ); + fs::write(&clang_orig, &wrapper_content)?; + fs::set_permissions(&clang_orig, fs::Permissions::from_mode(0o755))?; + + // Create wrapper script for clang-14 + let wrapper14_content = format!( + "#!/bin/bash\nexec \"$(dirname \"$0\")/clang-14-real\" -isysroot {sdk_path} -L{sdk_path}/usr/lib \"$@\"\n" + ); + fs::write(&clang14_orig, &wrapper14_content)?; + fs::set_permissions(&clang14_orig, fs::Permissions::from_mode(0o755))?; + + // Also create wrapper for clang++ if it exists + let clangpp_orig = bin_dir.join("clang++"); + let clangpp_real = bin_dir.join("clang++-real"); + if clangpp_orig.exists() && !clangpp_real.exists() { + fs::rename(&clangpp_orig, &clangpp_real)?; + let wrapperpp_content = format!( + "#!/bin/bash\nexec \"$(dirname \"$0\")/clang++-real\" -isysroot {sdk_path} -L{sdk_path}/usr/lib \"$@\"\n" + ); + fs::write(&clangpp_orig, &wrapperpp_content)?; + fs::set_permissions(&clangpp_orig, fs::Permissions::from_mode(0o755))?; + } + + Ok(()) +} + +#[cfg(target_os = "macos")] +fn fix_libunwind_references(llvm_dir: &Path) -> Result<(), Box> { + use std::process::Command; + + let lib_dir = llvm_dir.join("lib"); + + // Find all libunwind dylibs + if let Ok(entries) = fs::read_dir(&lib_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if let Some(name) = path.file_name().and_then(|n| n.to_str()) { + // Check for libunwind dylib files using case-insensitive extension check + if name.starts_with("libunwind") + && std::path::Path::new(name) + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("dylib")) + { + // Fix the install name to use absolute path instead of @rpath + let abs_path = path.to_string_lossy().to_string(); + Command::new("install_name_tool") + .args(["-id", &abs_path, &abs_path]) + .output()?; + } + } + } + } + + Ok(()) +} diff --git a/crates/pecos-llvm-utils/src/lib.rs b/crates/pecos-llvm-utils/src/lib.rs new file mode 100644 index 000000000..82d5d4cf0 --- /dev/null +++ b/crates/pecos-llvm-utils/src/lib.rs @@ -0,0 +1,443 @@ +//! LLVM detection and utilities for PECOS +//! +//! This crate provides functionality to locate and install LLVM 14 across different platforms. +//! It's primarily used by build scripts but can also be used standalone via the `pecos-llvm` binary. + +pub mod installer; + +use std::path::{Path, PathBuf}; +use std::process::Command; + +/// Find LLVM 14 installation on the system. +/// +/// This function searches for LLVM 14 in the following priority order: +/// 1. Home directory (~/.pecos/llvm) +/// 2. Project-local installation (llvm/ directory relative to repository root) +/// 3. System installations (platform-specific locations) +/// +/// # Returns +/// - `Some(PathBuf)` if LLVM 14 is found and valid +/// - `None` if LLVM 14 is not found +/// +/// # Example +/// ```no_run +/// use pecos_llvm_utils::find_llvm_14; +/// +/// if let Some(llvm_path) = find_llvm_14(None) { +/// println!("Found LLVM 14 at: {}", llvm_path.display()); +/// } else { +/// eprintln!("LLVM 14 not found!"); +/// } +/// ``` +#[must_use] +pub fn find_llvm_14(repo_root: Option) -> Option { + // 1. Check home directory (~/.pecos/llvm) + if let Some(home_dir) = dirs::home_dir() { + let user_llvm = home_dir.join(".pecos").join("llvm"); + if is_valid_llvm_14(&user_llvm) { + return Some(user_llvm); + } + } + + // 2. Check for project-local LLVM (for backward compatibility) + if let Some(root) = repo_root { + let local_llvm = root.join("llvm"); + if is_valid_llvm_14(&local_llvm) { + return Some(local_llvm); + } + } + + // 3. Check system installations + find_system_llvm_14() +} + +/// Find LLVM 14 in system-wide locations (platform-specific) +fn find_system_llvm_14() -> Option { + #[cfg(target_os = "macos")] + { + // Try Homebrew installation via brew command + if let Ok(output) = Command::new("brew").args(["--prefix", "llvm@14"]).output() + && output.status.success() + { + let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let path = PathBuf::from(path_str); + if is_valid_llvm_14(&path) { + return Some(path); + } + } + + // Try common Homebrew paths (in case brew command isn't available) + for path_str in [ + "/opt/homebrew/opt/llvm@14", // Apple Silicon + "/usr/local/opt/llvm@14", // Intel Mac + ] { + let llvm_path = PathBuf::from(path_str); + if is_valid_llvm_14(&llvm_path) { + return Some(llvm_path); + } + } + } + + #[cfg(target_os = "linux")] + { + // Check if llvm-config-14 is in PATH and get its prefix + if let Ok(output) = Command::new("llvm-config-14").arg("--prefix").output() { + if output.status.success() { + let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let path = PathBuf::from(path_str); + if is_valid_llvm_14(&path) { + return Some(path); + } + } + } + + // Try common Linux installation paths + for path_str in [ + "/usr/lib/llvm-14", + "/usr/local/llvm-14", + "/usr/lib/x86_64-linux-gnu/llvm-14", + ] { + let llvm_path = PathBuf::from(path_str); + if is_valid_llvm_14(&llvm_path) { + return Some(llvm_path); + } + } + } + + #[cfg(target_os = "windows")] + { + // Windows typically uses project-local installation + // System-wide LLVM installations on Windows would need to be in PATH + // or have LLVM_SYS_140_PREFIX set + } + + None +} + +/// Check if a given path contains a valid LLVM 14 installation +/// +/// # Arguments +/// * `path` - Path to check for LLVM installation +/// +/// # Returns +/// `true` if the path contains a valid LLVM 14 installation, `false` otherwise +#[must_use] +pub fn is_valid_llvm_14(path: &Path) -> bool { + // Check if the path exists + if !path.exists() { + return false; + } + + // Determine llvm-config path based on platform + #[cfg(target_os = "windows")] + let llvm_config = path.join("bin").join("llvm-config.exe"); + + #[cfg(not(target_os = "windows"))] + let llvm_config = path.join("bin").join("llvm-config"); + + if !llvm_config.exists() { + return false; + } + + // Verify it's LLVM 14 by checking the version + if let Ok(output) = Command::new(&llvm_config).arg("--version").output() + && output.status.success() + { + let version = String::from_utf8_lossy(&output.stdout); + return version.starts_with("14."); + } + + false +} + +/// Print a helpful error message when LLVM 14 is not found +pub fn print_llvm_not_found_error() { + eprintln!("\n═══════════════════════════════════════════════════════════════"); + eprintln!("ERROR: LLVM 14 not found!"); + eprintln!("═══════════════════════════════════════════════════════════════"); + eprintln!(); + eprintln!("PECOS requires LLVM version 14 for LLVM IR/QIR execution features."); + eprintln!(); + eprintln!("To install LLVM 14:"); + eprintln!(); + eprintln!(" Automated installation (all platforms):"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm --release -- install"); + eprintln!(); + + #[cfg(target_os = "macos")] + { + eprintln!(" Or install via Homebrew:"); + eprintln!(" brew install llvm@14"); + eprintln!(); + eprintln!(" Then the build system will auto-detect it, or set:"); + eprintln!(" export LLVM_SYS_140_PREFIX=$(brew --prefix llvm@14)"); + } + + #[cfg(target_os = "linux")] + { + eprintln!(" Or install via package manager:"); + eprintln!(" sudo apt install llvm-14 # Debian/Ubuntu"); + eprintln!(); + eprintln!(" The build system will auto-detect most installations, or set:"); + eprintln!(" export LLVM_SYS_140_PREFIX=/usr/lib/llvm-14"); + } + + #[cfg(target_os = "windows")] + { + eprintln!(" Or download manually:"); + eprintln!(" https://releases.llvm.org/download.html#14.0.0"); + eprintln!(); + eprintln!(" Then set:"); + eprintln!(" set LLVM_SYS_140_PREFIX=C:\\path\\to\\llvm"); + } + + eprintln!(); + eprintln!("Alternatively, you can build without LLVM support:"); + eprintln!(" cargo build --no-default-features"); + eprintln!(); + eprintln!("For more details, see:"); + eprintln!(" https://quantum-pecos.readthedocs.io/"); + eprintln!("═══════════════════════════════════════════════════════════════\n"); +} + +/// Automatically configure LLVM for PECOS +/// +/// This function determines the best LLVM 14 installation to use and writes +/// it to `.cargo/config.toml` with force=true. This is the authoritative +/// configuration function for PECOS. +/// +/// Priority order: +/// 1. ~/.pecos/llvm/ - PECOS-managed LLVM (if it exists) +/// 2. `LLVM_SYS_140_PREFIX` environment variable (if set and valid) +/// 3. System LLVM 14 (Homebrew, system paths, etc.) +/// +/// # Arguments +/// * `project_root` - Optional path to the Cargo project root. If None, attempts to find it. +/// +/// # Errors +/// Returns an error if: +/// - No suitable LLVM 14 installation could be found +/// - The Cargo project root could not be determined +/// - Writing to `.cargo/config.toml` fails +/// +/// # Returns +/// * `Ok(PathBuf)` - The path that was configured +pub fn auto_configure_llvm( + project_root: Option, +) -> Result> { + use std::env; + + // Priority 1: Check ~/.pecos/llvm/ + if let Some(home_dir) = dirs::home_dir() { + let pecos_llvm = home_dir.join(".pecos").join("llvm"); + if is_valid_llvm_14(&pecos_llvm) { + // Found PECOS-managed LLVM, configure it + let project_root = project_root + .or_else(get_repo_root_from_manifest) + .or_else(find_cargo_project_root) + .ok_or("Could not find Cargo project root")?; + + write_cargo_config(&project_root, &pecos_llvm, true)?; + return Ok(pecos_llvm); + } + } + + // Priority 2: Check shell LLVM_SYS_140_PREFIX + if let Ok(sys_prefix) = env::var("LLVM_SYS_140_PREFIX") { + let path = PathBuf::from(&sys_prefix); + if is_valid_llvm_14(&path) { + // Shell env var points to valid LLVM, configure it + let project_root = project_root + .or_else(get_repo_root_from_manifest) + .or_else(find_cargo_project_root) + .ok_or("Could not find Cargo project root")?; + + write_cargo_config(&project_root, &path, true)?; + return Ok(path); + } + } + + // Priority 3: Scan system for LLVM 14 + let repo_root = get_repo_root_from_manifest(); + if let Some(detected_path) = find_llvm_14(repo_root) { + let project_root = project_root + .or_else(get_repo_root_from_manifest) + .or_else(find_cargo_project_root) + .ok_or("Could not find Cargo project root")?; + + write_cargo_config(&project_root, &detected_path, true)?; + return Ok(detected_path); + } + + // No LLVM 14 found anywhere + Err("No suitable LLVM 14 installation found".into()) +} + +/// Get the repository root from `CARGO_MANIFEST_DIR` +/// +/// This assumes the crate is located at `crates/` in the repository +#[must_use] +pub fn get_repo_root_from_manifest() -> Option { + if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") { + let mut path = PathBuf::from(manifest_dir); + // Go up from crates/ to repository root + if path.pop() && path.pop() { + return Some(path); + } + } + None +} + +/// Find the Cargo project root by searching for Cargo.toml +/// +/// Starts from the current directory and walks up the directory tree +/// until it finds a directory containing Cargo.toml or Cargo.lock +#[must_use] +pub fn find_cargo_project_root() -> Option { + let current_dir = std::env::current_dir().ok()?; + let mut path = current_dir.as_path(); + + loop { + if path.join("Cargo.toml").exists() || path.join("Cargo.lock").exists() { + return Some(path.to_path_buf()); + } + + path = path.parent()?; + } +} + +/// Write or update .cargo/config.toml with LLVM configuration +/// +/// # Arguments +/// * `project_root` - Path to the Cargo project root +/// * `llvm_path` - Path to the LLVM installation +/// * `force` - If true, use force=true to override shell environment variables +/// +/// # Errors +/// Returns an error if: +/// - Creating the `.cargo` directory fails +/// - Reading or writing to `.cargo/config.toml` fails +/// +/// # Returns +/// `Ok(())` if successful +pub fn write_cargo_config( + project_root: &Path, + llvm_path: &Path, + force: bool, +) -> Result<(), Box> { + use std::fs; + + let cargo_dir = project_root.join(".cargo"); + let config_path = cargo_dir.join("config.toml"); + + // Create .cargo directory if it doesn't exist + fs::create_dir_all(&cargo_dir)?; + + let llvm_path_str = llvm_path.to_string_lossy(); + + // Format the LLVM_SYS_140_PREFIX line based on force flag + let llvm_line = if force { + format!("LLVM_SYS_140_PREFIX = {{ value = \"{llvm_path_str}\", force = true }}") + } else { + format!("LLVM_SYS_140_PREFIX = \"{llvm_path_str}\"") + }; + + // Read existing config or start with empty string + let existing_content = fs::read_to_string(&config_path).unwrap_or_default(); + + // Check if config already has LLVM_SYS_140_PREFIX + if existing_content.contains("LLVM_SYS_140_PREFIX") { + // Check if it's set to the same value (either simple or force format) + let simple_format = format!("LLVM_SYS_140_PREFIX = \"{llvm_path_str}\""); + let force_format = + format!("LLVM_SYS_140_PREFIX = {{ value = \"{llvm_path_str}\", force = true }}"); + + if existing_content.contains(&simple_format) || existing_content.contains(&force_format) { + // Already configured correctly (might be different format, but same path) + // If force flag changed, we should still update + if (force && existing_content.contains(&force_format)) + || (!force && existing_content.contains(&simple_format)) + { + return Ok(()); + } + } + + // Configuration exists but needs updating - replace it + let lines: Vec<&str> = existing_content.lines().collect(); + let mut new_lines = Vec::new(); + let mut in_env_section = false; + let mut updated = false; + let mut skip_next_lines = 0; + + for (i, line) in lines.iter().enumerate() { + if skip_next_lines > 0 { + skip_next_lines -= 1; + continue; + } + + let trimmed = line.trim(); + + // Track if we're in the [env] section + if trimmed.starts_with('[') { + in_env_section = trimmed == "[env]"; + } + + // Update LLVM_SYS_140_PREFIX if we find it + if in_env_section && trimmed.starts_with("LLVM_SYS_140_PREFIX") { + new_lines.push(llvm_line.clone()); + updated = true; + + // If old format was multi-line (with braces), skip continuation lines + if trimmed.contains('{') && !trimmed.contains('}') { + // Count lines until we find closing brace + for line in lines.iter().skip(i + 1) { + skip_next_lines += 1; + if line.contains('}') { + break; + } + } + } + } else { + new_lines.push((*line).to_string()); + } + } + + if updated { + fs::write(&config_path, new_lines.join("\n"))?; + return Ok(()); + } + } + + // No LLVM configuration exists, append it + let llvm_config = format!( + "\n# LLVM configuration for PECOS\n\ + [env]\n\ + {llvm_line}\n" + ); + + let new_content = if existing_content.is_empty() { + llvm_config.trim_start().to_string() + } else { + format!("{existing_content}{llvm_config}") + }; + + fs::write(&config_path, new_content)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_find_llvm_14() { + // This test will only pass if LLVM 14 is installed on the system + // Skip it in CI if LLVM is not available + if let Some(path) = find_llvm_14(None) { + println!("Found LLVM 14 at: {}", path.display()); + assert!(is_valid_llvm_14(&path)); + } else { + println!("LLVM 14 not found (this is okay for CI)"); + } + } +} diff --git a/crates/pecos-qis-core/Cargo.toml b/crates/pecos-qis-core/Cargo.toml index 667fb61d4..5b64d1cbb 100644 --- a/crates/pecos-qis-core/Cargo.toml +++ b/crates/pecos-qis-core/Cargo.toml @@ -33,6 +33,9 @@ optional = true default = ["llvm"] llvm = ["dep:inkwell"] +[build-dependencies] +pecos-llvm-utils.workspace = true + [dev-dependencies] pecos-qis-selene.workspace = true diff --git a/crates/pecos-qis-core/build.rs b/crates/pecos-qis-core/build.rs new file mode 100644 index 000000000..3eab7e590 --- /dev/null +++ b/crates/pecos-qis-core/build.rs @@ -0,0 +1,120 @@ +fn main() { + // Only run LLVM validation if the llvm feature is enabled + #[cfg(feature = "llvm")] + validate_llvm(); +} + +#[cfg(feature = "llvm")] +fn validate_llvm() { + use pecos_llvm_utils::is_valid_llvm_14; + use std::env; + use std::path::PathBuf; + + // Check if LLVM_SYS_140_PREFIX is already set and valid + if let Ok(sys_prefix) = env::var("LLVM_SYS_140_PREFIX") { + let path = PathBuf::from(&sys_prefix); + if is_valid_llvm_14(&path) { + // LLVM is configured and valid, we're good! + return; + } + eprintln!("\n═══════════════════════════════════════════════════════════════"); + eprintln!("ERROR: Invalid LLVM_SYS_140_PREFIX"); + eprintln!("═══════════════════════════════════════════════════════════════"); + eprintln!(); + eprintln!("LLVM_SYS_140_PREFIX is set to: {sys_prefix}"); + eprintln!("But this is not a valid LLVM 14 installation."); + eprintln!(); + eprintln!("Please either:"); + eprintln!(" 1. Fix the path to point to a valid LLVM 14 installation"); + eprintln!(" 2. Unset it and configure LLVM:"); + eprintln!(" unset LLVM_SYS_140_PREFIX"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!("═══════════════════════════════════════════════════════════════\n"); + panic!("Invalid LLVM_SYS_140_PREFIX. See error message above."); + } + + // LLVM_SYS_140_PREFIX not set - print setup instructions + print_llvm_not_found_error_extended(); + panic!("LLVM 14 not configured. See error message above for setup instructions."); +} + +#[cfg(feature = "llvm")] +fn print_llvm_not_found_error_extended() { + eprintln!("\n═══════════════════════════════════════════════════════════════"); + eprintln!("LLVM 14 Setup Required"); + eprintln!("═══════════════════════════════════════════════════════════════"); + eprintln!(); + eprintln!("PECOS needs LLVM 14. Choose one of these installation methods:"); + eprintln!(); + eprintln!("Option 1: Use pecos-llvm installer (recommended)"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- install"); + eprintln!(" cargo build"); + eprintln!(); + eprintln!(" The installer automatically configures PECOS."); + eprintln!(" (Downloads LLVM 14.0.6 to ~/.pecos/llvm/ - ~400MB, ~5 minutes)"); + eprintln!(); + + #[cfg(target_os = "macos")] + { + eprintln!("Option 2: Install via Homebrew"); + eprintln!(" # Install LLVM 14"); + eprintln!(" brew install llvm@14"); + eprintln!(); + eprintln!(" # Configure PECOS to use it"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!(); + eprintln!(" # Build PECOS"); + eprintln!(" cargo build"); + eprintln!(); + eprintln!(" Note: Works on both Intel and Apple Silicon Macs"); + eprintln!(); + } + + #[cfg(target_os = "linux")] + { + eprintln!("Option 2: Install via system package manager"); + eprintln!(); + eprintln!(" Debian/Ubuntu:"); + eprintln!(" sudo apt update"); + eprintln!(" sudo apt install llvm-14 llvm-14-dev"); + eprintln!(); + eprintln!(" Fedora/RHEL:"); + eprintln!(" sudo dnf install llvm14 llvm14-devel"); + eprintln!(); + eprintln!(" Arch Linux:"); + eprintln!(" # LLVM 14 may need to be built from AUR"); + eprintln!(" yay -S llvm14"); + eprintln!(); + eprintln!(" Then configure and build:"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!(" cargo build"); + eprintln!(); + } + + #[cfg(target_os = "windows")] + { + eprintln!("Option 2: Manual installation"); + eprintln!(); + eprintln!(" 1. Download LLVM 14.0.6 for Windows:"); + eprintln!( + " https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/LLVM-14.0.6-win64.exe" + ); + eprintln!(); + eprintln!(" 2. Run the installer and note the installation directory"); + eprintln!(" (typically C:\\Program Files\\LLVM)"); + eprintln!(); + eprintln!(" 3. Configure PECOS to use it:"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!(); + eprintln!(" 4. Build PECOS:"); + eprintln!(" cargo build"); + eprintln!(); + } + + eprintln!("The 'configure' command will detect your LLVM installation and"); + eprintln!("update .cargo/config.toml automatically."); + eprintln!(); + eprintln!("Don't need LLVM IR support? Build without it:"); + eprintln!(" cargo build --no-default-features"); + eprintln!("═══════════════════════════════════════════════════════════════\n"); +} diff --git a/crates/pecos-qis-core/src/lib.rs b/crates/pecos-qis-core/src/lib.rs index 616ae1f59..f6f01b8dd 100644 --- a/crates/pecos-qis-core/src/lib.rs +++ b/crates/pecos-qis-core/src/lib.rs @@ -6,6 +6,34 @@ //! The reference runtime implementation is: //! - `SeleneRuntime`: Selene-based QIS runtime (in pecos-qis-selene crate) //! +//! # LLVM Setup +//! +//! This crate requires LLVM 14 for QIR (Quantum Intermediate Representation) support. +//! +//! If the build fails, just run the commands shown in the error message. Typically: +//! +//! ```bash +//! cargo install pecos-llvm-utils +//! pecos-llvm install +//! export PECOS_LLVM=$(pecos-llvm find) +//! export LLVM_SYS_140_PREFIX="$PECOS_LLVM" +//! cargo build +//! ``` +//! +//! This takes ~5 minutes, downloads ~400MB, and installs to `~/.pecos/llvm`. +//! +//! **`PECOS_LLVM` vs `LLVM_SYS_140_PREFIX`**: Use `PECOS_LLVM` to specify which LLVM installation +//! PECOS should use. This takes priority over system-wide `LLVM_SYS_140_PREFIX`, allowing +//! you to use a different LLVM for PECOS than other projects. +//! +//! **Don't need QIR?** Disable LLVM: +//! ```toml +//! [dependencies] +//! pecos-qis-core = { version = "0.1", default-features = false } +//! ``` +//! +//! See the [Getting Started guide](https://quantum-pecos.readthedocs.io/) for more details. +//! //! # Example Usage //! //! This crate provides the core builder API for QIS engines. Specific runtime diff --git a/crates/pecos-qis-selene/build.rs b/crates/pecos-qis-selene/build.rs index 7683accda..3370ab505 100644 --- a/crates/pecos-qis-selene/build.rs +++ b/crates/pecos-qis-selene/build.rs @@ -8,6 +8,12 @@ fn main() { env_logger::init(); let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + // Embed LLVM bin path at compile time for runtime use + if let Ok(llvm_prefix) = env::var("LLVM_SYS_140_PREFIX") { + let llvm_bin = PathBuf::from(&llvm_prefix).join("bin"); + println!("cargo:rustc-env=PECOS_LLVM_BIN_PATH={}", llvm_bin.display()); + } + // Find or build libhelios_selene_interface.a find_or_build_helios_lib(&out_dir); diff --git a/crates/pecos-qis-selene/src/executor.rs b/crates/pecos-qis-selene/src/executor.rs index 2a90b9a90..3e2757575 100644 --- a/crates/pecos-qis-selene/src/executor.rs +++ b/crates/pecos-qis-selene/src/executor.rs @@ -17,6 +17,54 @@ type ResetInterfaceFn = unsafe extern "C" fn(); type GetOperationsFn = unsafe extern "C" fn() -> *mut OperationCollector; type CallQmainFn = unsafe extern "C" fn(extern "C" fn(u64) -> u64) -> u64; +/// Find an LLVM tool with the following priority: +/// 1. Embedded path from build time (`PECOS_LLVM_BIN_PATH`) +/// 2. Runtime `LLVM_SYS_140_PREFIX` environment variable +/// 3. Fall back to PATH +fn find_llvm_tool(tool_name: &str) -> PathBuf { + let tool_exe = if cfg!(windows) { + format!("{tool_name}.exe") + } else { + tool_name.to_string() + }; + + option_env!("PECOS_LLVM_BIN_PATH") + .and_then(|bin_path| { + let path = PathBuf::from(bin_path).join(&tool_exe); + if path.exists() { + debug!( + "Using {} from embedded PECOS_LLVM_BIN_PATH: {}", + tool_name, + path.display() + ); + Some(path) + } else { + None + } + }) + .or_else(|| { + std::env::var("LLVM_SYS_140_PREFIX") + .ok() + .and_then(|prefix| { + let path = PathBuf::from(prefix).join("bin").join(&tool_exe); + if path.exists() { + debug!( + "Using {} from LLVM_SYS_140_PREFIX: {}", + tool_name, + path.display() + ); + Some(path) + } else { + None + } + }) + }) + .unwrap_or_else(|| { + debug!("Using {tool_name} from PATH"); + PathBuf::from(tool_name) + }) +} + /// Helios interface implementation /// /// This interface: @@ -343,28 +391,7 @@ impl QisHeliosInterface { InterfaceError::LoadError(format!("Failed to create bitcode file: {e}")) })?; - // Try to find llvm-as: first check LLVM_SYS_140_PREFIX, then fall back to PATH - let llvm_as_cmd = std::env::var("LLVM_SYS_140_PREFIX") - .ok() - .and_then(|prefix| { - let mut path = PathBuf::from(prefix); - path.push("bin"); - path.push(if cfg!(windows) { - "llvm-as.exe" - } else { - "llvm-as" - }); - if path.exists() { - debug!("Using llvm-as from LLVM_SYS_140_PREFIX: {}", path.display()); - Some(path) - } else { - None - } - }) - .unwrap_or_else(|| { - debug!("Using llvm-as from PATH"); - PathBuf::from("llvm-as") - }); + let llvm_as_cmd = find_llvm_tool("llvm-as"); let output = Command::new(&llvm_as_cmd) .arg("-o") @@ -396,15 +423,7 @@ impl QisHeliosInterface { #[cfg(target_os = "windows")] let program_temp_path = { // Use llvm-nm to check which symbols exist in the bitcode - let llvm_nm_cmd = std::env::var("LLVM_SYS_140_PREFIX") - .ok() - .and_then(|prefix| { - let mut path = PathBuf::from(prefix); - path.push("bin"); - path.push("llvm-nm.exe"); - if path.exists() { Some(path) } else { None } - }) - .unwrap_or_else(|| PathBuf::from("llvm-nm")); + let llvm_nm_cmd = find_llvm_tool("llvm-nm"); let nm_output = Command::new(&llvm_nm_cmd) .arg(&program_temp_path) @@ -460,15 +479,7 @@ entry: InterfaceError::LoadError(format!("Failed to create wrapper BC file: {e}")) })?; - let llvm_as_cmd = std::env::var("LLVM_SYS_140_PREFIX") - .ok() - .and_then(|prefix| { - let mut path = PathBuf::from(prefix); - path.push("bin"); - path.push("llvm-as.exe"); - if path.exists() { Some(path) } else { None } - }) - .unwrap_or_else(|| PathBuf::from("llvm-as")); + let llvm_as_cmd = find_llvm_tool("llvm-as"); let as_output = Command::new(&llvm_as_cmd) .arg("-o") @@ -491,15 +502,7 @@ entry: InterfaceError::LoadError(format!("Failed to create linked BC file: {e}")) })?; - let llvm_link_cmd = std::env::var("LLVM_SYS_140_PREFIX") - .ok() - .and_then(|prefix| { - let mut path = PathBuf::from(prefix); - path.push("bin"); - path.push("llvm-link.exe"); - if path.exists() { Some(path) } else { None } - }) - .unwrap_or_else(|| PathBuf::from("llvm-link")); + let llvm_link_cmd = find_llvm_tool("llvm-link"); let link_output = Command::new(&llvm_link_cmd) .arg("-o") diff --git a/crates/pecos-quest/build_quest.rs b/crates/pecos-quest/build_quest.rs index 4b50ef6ac..b18d10220 100644 --- a/crates/pecos-quest/build_quest.rs +++ b/crates/pecos-quest/build_quest.rs @@ -575,11 +575,7 @@ fn build_cxx_bridge(quest_dir: &Path, out_dir: &Path) { .contains("darwin") { build.flag("-stdlib=libc++"); - - // Prevent opportunistic linking to Homebrew's libunwind (Xcode 15+ issue) - // Force use of system libraries only by excluding common Homebrew paths - build.flag("-L/usr/lib"); - build.flag("-Wl,-search_paths_first"); + // Note: Linker-specific flags are passed via cargo:rustc-link-arg below, not here } } diff --git a/crates/pecos-qulacs/build.rs b/crates/pecos-qulacs/build.rs index 3821d095f..ec0b2b62a 100644 --- a/crates/pecos-qulacs/build.rs +++ b/crates/pecos-qulacs/build.rs @@ -198,22 +198,40 @@ fn configure_build( build.flag("/bigobj"); // Allow larger object files build.flag("/EHsc"); // Enable exception handling + // Suppress warnings from external headers (Eigen, Boost, Qulacs) + build.flag_if_supported("/external:anglebrackets"); // Treat angle-bracket includes as external + build.flag_if_supported("/external:W0"); // Disable warnings for external headers + // Use standard optimization level - /bigobj should prevent compiler crashes build.opt_level(2); // Maximize speed optimization (/O2) } else { build.flag_if_supported("-std=c++14"); build.flag_if_supported("-O3"); - build.flag_if_supported("-ffast-math"); + + // On macOS with ARM (Apple Silicon), -ffast-math causes issues with Eigen's NEON code + // which uses infinity constants. Use a more targeted optimization instead. + if target.contains("darwin") && target.contains("aarch64") { + // Enable fast math but allow infinity and NaN + build.flag_if_supported("-fno-math-errno"); + build.flag_if_supported("-fno-trapping-math"); + } else { + build.flag_if_supported("-ffast-math"); + } + // Silence OpenMP pragma warnings since we intentionally don't use OpenMP // PECOS uses thread-level parallelism instead of OpenMP's internal parallelism build.flag_if_supported("-Wno-unknown-pragmas"); + // Suppress specific warnings from third-party libraries (Eigen, Boost, Qulacs) + build.flag_if_supported("-Wno-unused-but-set-variable"); // Eigen SparseLU warnings + build.flag_if_supported("-Wno-deprecated-copy-with-user-provided-copy"); // Boost warnings + build.flag_if_supported("-Wno-unqualified-std-cast-call"); // Qulacs move() warnings + build.flag_if_supported("-Wno-inconsistent-missing-override"); // Qulacs override warnings + // On macOS, use the -stdlib=libc++ flag to ensure proper C++ standard library linkage if target.contains("darwin") { build.flag("-stdlib=libc++"); - // Prevent opportunistic linking to Homebrew's libunwind (Xcode 15+ issue) - build.flag("-L/usr/lib"); - build.flag("-Wl,-search_paths_first"); + // Note: Linker flags are passed via cargo:rustc-link-arg below, not here } } diff --git a/docs/development/DEVELOPMENT.md b/docs/development/DEVELOPMENT.md index 545354e4c..dd421c630 100644 --- a/docs/development/DEVELOPMENT.md +++ b/docs/development/DEVELOPMENT.md @@ -16,7 +16,19 @@ For developers who want to contribute or modify PECOS: uv sync ``` -4. You may wish to explicitly activate the environment for development. To do so: +4. **LLVM 14 Setup (Required for LLVM IR/QIS Support)** + + PECOS requires LLVM version 14 for LLVM IR execution features. + + **Quick setup:** + ```sh + cargo run -p pecos-llvm-utils --bin pecos-llvm -- install + cargo build + ``` + + For detailed installation instructions for all platforms (macOS, Linux, Windows), see the [**LLVM Setup Guide**](../user-guide/getting-started.md#llvm-for-qis-support) in the Getting Started documentation. + +5. You may wish to explicitly activate the environment for development. To do so: === "Linux/Mac" ```sh @@ -28,24 +40,24 @@ For developers who want to contribute or modify PECOS: .\.venv\Scripts\activate ``` -5. Build the project in editable mode +6. Build the project in editable mode ```sh make build ``` See other build options in the `Makefile`. -6. Run all Python and Rust tests: +7. Run all Python and Rust tests: ```sh make test ``` Note: Make sure you have run a build command before running tests. -7. Run linters using pre-commit (after [installing it](https://pre-commit.com/)) to make sure all everything is properly linted/formated +8. Run linters using pre-commit (after [installing it](https://pre-commit.com/)) to make sure all everything is properly linted/formated ```sh make lint ``` -8. To deactivate your development venv: +9. To deactivate your development venv: ```sh deactivate ``` diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index be9e59fa6..7da1bfff3 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -43,27 +43,76 @@ This guide will help you get up and running with PECOS quickly, whether you're u ## Optional Dependencies -### LLVM for QIR Support +### LLVM for QIS Support -LLVM version 14 is required for QIR (Quantum Intermediate Representation) support: +LLVM version 14 is required for LLVM IR execution with QIS (Quantum Instruction Set) support. -=== "Linux" - ```bash - sudo apt install llvm-14 - ``` +**Setup Steps:** -=== "macOS" - ```bash - brew install llvm@14 - ``` +**Option 1 - Use pecos-llvm installer (recommended for all platforms):** + +```bash +# Install LLVM 14.0.6 to ~/.pecos/llvm/ (~400MB, ~5 minutes) +cargo run -p pecos-llvm-utils --bin pecos-llvm -- install + +# Build PECOS +cargo build +``` + +The installer automatically configures PECOS after installation. + +**Option 2 - Manual installation:** + +1. **Install LLVM 14** for your platform: + + === "macOS" + ```bash + brew install llvm@14 + ``` + Works on both Intel and Apple Silicon Macs. + + === "Linux (Debian/Ubuntu)" + ```bash + sudo apt update + sudo apt install llvm-14 llvm-14-dev + ``` + + === "Linux (Fedora/RHEL)" + ```bash + sudo dnf install llvm14 llvm14-devel + ``` + + === "Linux (Arch)" + ```bash + yay -S llvm14 # May need to build from AUR + ``` + + === "Windows" + 1. Download: [LLVM-14.0.6-win64.exe](https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/LLVM-14.0.6-win64.exe) + 2. Run the installer (typically installs to `C:\Program Files\LLVM`) + +2. **Configure PECOS** to detect your LLVM installation: + ```bash + cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure + ``` + +3. **Build PECOS**: + ```bash + cargo build + ``` + +**Check LLVM Status:** -=== "Windows" - Download LLVM 14.x installer from [LLVM releases](https://releases.llvm.org/download.html#14.0.0) +```bash +cargo run -p pecos-llvm-utils --bin pecos-llvm -- check +cargo run -p pecos-llvm-utils --bin pecos-llvm -- version +``` !!! warning - PECOS's QIR implementation is currently only compatible with LLVM version 14.x. + PECOS's LLVM IR implementation is currently only compatible with LLVM version 14.x. -If LLVM 14 is not installed, PECOS will still function normally but QIR-related features will be disabled. +!!! note + The `.cargo/config.toml` file is auto-generated and machine-specific. It's in `.gitignore` and should not be committed. ### Simulators with Special Requirements diff --git a/scripts/setup_cuda.sh b/scripts/setup_cuda.sh index 5f250ab19..ffe5ba54e 100755 --- a/scripts/setup_cuda.sh +++ b/scripts/setup_cuda.sh @@ -99,19 +99,19 @@ fi # Function to print status messages print_status() { - echo -e "${GREEN}✓${NC} $1" + echo -e "${GREEN}[OK]${NC} $1" } print_warning() { - echo -e "${YELLOW}⚠${NC} $1" + echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { - echo -e "${RED}✗${NC} $1" + echo -e "${RED}[ERROR]${NC} $1" } print_info() { - echo -e "${BLUE}ℹ${NC} $1" + echo -e "${BLUE}[INFO]${NC} $1" } # Function to check if command exists diff --git a/scripts/setup_llvm.ps1 b/scripts/setup_llvm.ps1 deleted file mode 100644 index aa5a5b09b..000000000 --- a/scripts/setup_llvm.ps1 +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env pwsh -# Setup script for LLVM 14.0.6 on Windows -# This script extracts LLVM and sets up the required environment variable for building PECOS - -$ErrorActionPreference = "Stop" - -# Get the repository root (parent of scripts directory) -$RepoRoot = Split-Path -Parent $PSScriptRoot -$LLVMDir = Join-Path $RepoRoot "llvm" -$LLVMArchive = Join-Path $RepoRoot "LLVM-14.0.6-win64.7z" -$LLVMConfigPath = Join-Path $LLVMDir "bin\llvm-config.exe" - -Write-Host "PECOS LLVM 14.0.6 Setup for Windows" -ForegroundColor Cyan -Write-Host "====================================" -ForegroundColor Cyan -Write-Host "" - -# Check if LLVM is already extracted -if (Test-Path $LLVMConfigPath) { - Write-Host "[OK] LLVM is already extracted in the repository" -ForegroundColor Green -} else { - # Check if archive exists, if not download it - if (-not (Test-Path $LLVMArchive)) { - Write-Host "[INFO] LLVM archive not found, downloading..." -ForegroundColor Yellow - $DownloadUrl = "https://github.com/PLC-lang/llvm-package-windows/releases/download/v14.0.6/LLVM-14.0.6-win64.7z" - - try { - Write-Host "Downloading from: $DownloadUrl" -ForegroundColor Cyan - Write-Host "This may take several minutes (~450MB download)..." -ForegroundColor Yellow - - # Use Invoke-WebRequest with progress - $ProgressPreference = 'SilentlyContinue' # Faster downloads - Invoke-WebRequest -Uri $DownloadUrl -OutFile $LLVMArchive -UseBasicParsing - $ProgressPreference = 'Continue' - - Write-Host "[OK] Download completed" -ForegroundColor Green - } catch { - Write-Host "[ERROR] Failed to download LLVM archive: $_" -ForegroundColor Red - Write-Host "Please manually download from: $DownloadUrl" -ForegroundColor Yellow - Write-Host "And place it at: $LLVMArchive" -ForegroundColor Yellow - exit 1 - } - } else { - Write-Host "[OK] LLVM archive found" -ForegroundColor Green - } - - Write-Host "[INFO] Extracting LLVM archive..." -ForegroundColor Yellow - Write-Host "This may take a few minutes..." - - # Check if 7z is available - $7zPath = Get-Command "7z" -ErrorAction SilentlyContinue - if (-not $7zPath) { - Write-Host "[ERROR] 7-Zip (7z command) not found in PATH" -ForegroundColor Red - Write-Host "Please install 7-Zip from https://www.7-zip.org/" -ForegroundColor Yellow - exit 1 - } - - # Create llvm directory if it doesn't exist - if (-not (Test-Path $LLVMDir)) { - New-Item -ItemType Directory -Path $LLVMDir | Out-Null - } - - # Extract the archive to llvm directory - Push-Location $RepoRoot - try { - & 7z x $LLVMArchive -o"$LLVMDir" -y | Out-Null - if ($LASTEXITCODE -ne 0) { - throw "7z extraction failed with exit code $LASTEXITCODE" - } - } catch { - Write-Host "[ERROR] Failed to extract LLVM archive: $_" -ForegroundColor Red - Pop-Location - exit 1 - } - Pop-Location - - # Verify extraction - if (Test-Path $LLVMConfigPath) { - Write-Host "[OK] LLVM extracted successfully" -ForegroundColor Green - - # Clean up the archive to save disk space - try { - Remove-Item $LLVMArchive -Force - Write-Host "[OK] Cleaned up archive file (saved ~450MB)" -ForegroundColor Green - } catch { - Write-Host "[WARNING] Failed to delete archive file: $_" -ForegroundColor Yellow - Write-Host "You can manually delete: $LLVMArchive" -ForegroundColor Yellow - } - } else { - Write-Host "[ERROR] LLVM extraction completed but llvm-config.exe not found" -ForegroundColor Red - exit 1 - } -} - -Write-Host "" -Write-Host "Setup complete!" -ForegroundColor Green -Write-Host "" -Write-Host "LLVM has been extracted to: $LLVMDir" -ForegroundColor Cyan -Write-Host "" -Write-Host "The Makefile will automatically use this LLVM installation when building and testing." -ForegroundColor Green -Write-Host "No manual environment variable configuration is needed." -ForegroundColor Green -Write-Host "" -Write-Host "You can now run:" -ForegroundColor Cyan -Write-Host " make dev" -ForegroundColor White -Write-Host "" -Write-Host "Note: This LLVM installation is local to this project and won't interfere" -ForegroundColor Yellow -Write-Host " with other LLVM installations on your system." -ForegroundColor Yellow