Repository: qir-alliance/pyqir (language: python)

Issue:
Add musllinux wheel distribution
Alpine (musl) based distribution is not currently supported. To achieve this, several components must be updated:

- [x] Create Alpine OCI base image (maturin, compiler toolchain, etc)
- [x] Get LLVM building correctly in this environment
- [x] Get PyQIR building correctly in this environment
- [x] Update build to create musllinux wheels
- [x] Update build to test musllinux wheels

Wheel should likely follow [PEP 656](https://peps.python.org/pep-0656/) for naming via Maturin with musl 1.2 for the compatibility.



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 744c5498..f30d1ed5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -26,16 +26,37 @@ jobs:
           - {
               os: "ubuntu-20.04",
               arch: "amd64",
+              target: "checks",
+              env: {},
+            }
+          - {
+              os: "ubuntu-20.04",
+              arch: "amd64",
+              target: "musllinux",
+              env: {},
+            }
+          - {
+              os: "ubuntu-20.04",
+              arch: "amd64",
+              target: "manylinux",
+              env: {},
+            }
+          - {
+              os: "ubuntu-20.04",
+              arch: "amd64",
+              target: "default",
               env: {},
             }
           - {
               os: "windows-2019",
               arch: "amd64",
+              target: "default",
               env: {},
             }
           - {
               os: "macOS-10.15",
               arch: "amd64",
+              target: "default",
               env: {},
             }
     steps:
@@ -63,9 +84,9 @@ jobs:
         with:
           path: |
             ${{ env.CCACHE_DIR }}
-          key: ${{ matrix.config.os }}-ccache-${{ steps.timestamp.outputs.timestamp }}
+          key: ${{ matrix.config.os }}-${{ matrix.config.target }}-ccache-${{ steps.timestamp.outputs.timestamp }}
           restore-keys: |
-              ${{ matrix.config.os }}-ccache-
+              ${{ matrix.config.os }}-${{ matrix.config.target }}-ccache-
         if: ${{ matrix.config.os != 'windows-2019' }}
       - name: SCCACHE cache
         uses: actions/cache@v3
@@ -76,15 +97,21 @@ jobs:
           restore-keys: |
               ${{ matrix.config.os }}-sccache-
         if: ${{ matrix.config.os == 'windows-2019' }}
-      - name: "Build all"
-        run: ./build.ps1
+      - name: "Build ${{ matrix.config.target }}"
+        run: ./build.ps1 -t ${{ matrix.config.target }}
         shell: pwsh
+      - name: Artifacts - manylinux
+        uses: actions/upload-artifact@v2
+        with:
+          name: manylinux-artifacts
+          path: target/wheels/*manylinux*.whl
+        if: ${{ matrix.config.os == 'ubuntu-20.04' }}
       - name: Artifacts
         uses: actions/upload-artifact@v2
         with:
-          name: artifacts
+          name: ${{ matrix.config.os }}-artifacts
           path: target/wheels/*
-
+        if: ${{ matrix.config.os != 'ubuntu-20.04' }}
   docs:
     runs-on: "ubuntu-20.04"
     needs: build
@@ -96,19 +123,12 @@ jobs:
       - name: Download Build Artifacts
         uses: actions/download-artifact@v2
         with:
-          name: artifacts
-          path: wheelhouse
+          name: manylinux-artifacts
+          path: target/wheels
 
       - name: Build Docs
-        run: |
-          $properties = @{
-            "wheelhouse" = (Resolve-Path "wheelhouse/*-linux_x86_64.whl")
-          };
-          ./eng/build.ps1 `
-            -taskList docs `
-            -properties $properties
+        run: ./build.ps1 -t docs
         shell: pwsh
-
       # Always publish docs as an artifact, but only publish docs to GitHub Pages
       # when pushing to main.
       - name: Publish Docs as Artifacts
diff --git a/docs/development-guide/building.md b/docs/development-guide/building.md
index 2ba35727..cf8fa920 100644
--- a/docs/development-guide/building.md
+++ b/docs/development-guide/building.md
@@ -37,7 +37,6 @@ Install Rust from [rustup](https://rustup.rs/).
 Install Python 3.6+ from one of the following and make sure it is added to the
 path.
 
-- [Windows store](https://docs.microsoft.com/en-us/windows/python/beginners#install-python)
 - [Miniconda](https://docs.conda.io/en/latest/miniconda.html#latest-miniconda-installer-links)
 - [Python.org](https://www.python.org/downloads/)
 
diff --git a/eng/manylinux.Dockerfile b/eng/manylinux.Dockerfile
index d6273e14..3923c441 100644
--- a/eng/manylinux.Dockerfile
+++ b/eng/manylinux.Dockerfile
@@ -69,6 +69,9 @@ RUN /bin/bash /tmp/Miniconda3.sh -b -p /usr/local/miniconda3
 
 RUN chown -R ${USER_UID}:${USER_GID} /usr/local/miniconda3
 
+RUN curl https://packages.microsoft.com/config/rhel/7/prod.repo | tee /etc/yum.repos.d/microsoft.repo && \
+    yum install -y powershell
+
 USER $USERNAME
 
 ENV PATH="/usr/local/miniconda3/bin:${PATH}"
@@ -78,5 +81,3 @@ ENV PATH="/usr/lib/ccache:${PATH}"
 RUN conda init && \
     conda install -y -c conda-forge clang-11 libstdcxx-devel_linux-64 libgcc-devel_linux-64 && \
     cp /usr/local/miniconda3/bin/clang-11 /usr/local/miniconda3/bin/clang++-11
-
-RUN conda run python -m pip install -U tox
diff --git a/eng/musllinux.Dockerfile b/eng/musllinux.Dockerfile
new file mode 100644
index 00000000..85849cd4
--- /dev/null
+++ b/eng/musllinux.Dockerfile
@@ -0,0 +1,118 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+# As of 18 April 2022
+# 3.14 has llvm/clang 11.1.0
+# 3.15 has llvm/clang 12.0.1
+# edge has llvm/13.0.1
+
+FROM python:3.6-alpine3.14 as base
+
+ARG USERNAME=runner
+ARG USER_UID=1000
+ARG USER_GID=${USER_UID}
+ARG RUST_VERSION=1.57.0
+
+RUN addgroup -g "${USER_GID}" "${USERNAME}"
+RUN adduser \
+    --disabled-password \
+    --gecos "" \
+    --ingroup "${USERNAME}" \
+    --uid "${USER_GID}" \
+    "${USERNAME}"
+
+RUN apk add sudo
+RUN echo ${USERNAME} ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/${USERNAME}
+RUN chmod 0440 /etc/sudoers.d/${USERNAME}
+
+WORKDIR /oi
+RUN chown ${USER_UID}:${USER_GID} /oi
+
+FROM base as base-with-rust
+
+ARG USERNAME=runner
+ARG RUST_VERSION=1.57.0
+
+ENV RUSTUP_HOME=/usr/local/rustup \
+    CARGO_HOME=/usr/local/cargo \
+    PATH=/usr/local/cargo/bin:${PATH} \
+    RUST_VERSION=${RUST_VERSION}
+
+# install build base deps
+RUN apk add build-base
+
+# Install rust deps
+RUN apk add libcrypto1.1 libcurl libgcc libssl1.1 musl zlib
+
+# install specific rust toolchain
+RUN apk add rustup
+RUN rustup-init --no-modify-path --profile minimal --default-toolchain ${RUST_VERSION} -y
+
+RUN chmod -R a+w ${RUSTUP_HOME} ${CARGO_HOME}; \
+    rustup --version; \
+    cargo --version; \
+    rustc --version;
+
+FROM base-with-rust as builder
+
+ARG USERNAME=runner
+
+USER $USERNAME
+
+# Temporary workaround installing beta for license/notice support
+RUN cargo install maturin --git https://github.com/PyO3/maturin --tag v0.12.12
+
+FROM base-with-rust
+
+ARG USERNAME=runner
+
+USER root
+
+RUN apk add samurai
+
+RUN apk add libffi-dev py3-pip cmake git ccache clang-dev
+
+# install the requirements
+RUN apk add --no-cache \
+    ca-certificates \
+    less \
+    ncurses-terminfo-base \
+    krb5-libs \
+    libgcc \
+    libintl \
+    libssl1.1 \
+    libstdc++ \
+    tzdata \
+    userspace-rcu \
+    zlib \
+    icu-libs \
+    curl
+
+RUN apk -X https://dl-cdn.alpinelinux.org/alpine/edge/main add --no-cache \
+    lttng-ust
+
+# Download the powershell '.tar.gz' archive
+RUN curl -L https://github.com/PowerShell/PowerShell/releases/download/v7.2.2/powershell-7.2.2-linux-alpine-x64.tar.gz -o /tmp/powershell.tar.gz
+
+# Create the target folder where powershell will be placed
+RUN mkdir -p /opt/microsoft/powershell/7
+
+# Expand powershell to the target folder
+RUN tar zxf /tmp/powershell.tar.gz -C /opt/microsoft/powershell/7
+
+# Set execute permissions
+RUN chmod +x /opt/microsoft/powershell/7/pwsh
+
+# Create the symbolic link that points to pwsh
+RUN ln -s /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh
+
+RUN apk add patchelf
+
+USER $USERNAME
+
+RUN python -m pip install -U pip
+RUN python -m pip install --no-cache-dir cffi
+
+COPY --from=builder ${CARGO_HOME}/bin/maturin /usr/bin/maturin
+
+RUN python -m pip install -U tox
diff --git a/eng/musllinuxCI.Dockerfile b/eng/musllinuxCI.Dockerfile
new file mode 100644
index 00000000..1adc1f19
--- /dev/null
+++ b/eng/musllinuxCI.Dockerfile
@@ -0,0 +1,91 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+# As of 18 April 2022
+# 3.14 has llvm/clang 11.1.0
+# 3.15 has llvm/clang 12.0.1
+# edge has llvm/13.0.1
+
+FROM python:3.6-alpine3.14 as base
+
+ARG RUST_VERSION=1.57.0
+
+WORKDIR /oi
+
+FROM base as base-with-rust
+
+ARG RUST_VERSION=1.57.0
+
+ENV RUSTUP_HOME=/usr/local/rustup \
+    CARGO_HOME=/usr/local/cargo \
+    PATH=/usr/local/cargo/bin:${PATH} \
+    RUST_VERSION=${RUST_VERSION}
+
+# install build base deps
+RUN apk add build-base
+
+# Install rust deps
+RUN apk add libcrypto1.1 libcurl libgcc libssl1.1 musl zlib
+
+# install specific rust toolchain
+RUN apk add rustup
+RUN rustup-init --no-modify-path --profile minimal --default-toolchain ${RUST_VERSION} -y
+
+RUN chmod -R a+w ${RUSTUP_HOME} ${CARGO_HOME}; \
+    rustup --version; \
+    cargo --version; \
+    rustc --version;
+
+FROM base-with-rust as builder
+
+# Temporary workaround installing beta for license/notice support
+RUN cargo install maturin --git https://github.com/PyO3/maturin --tag v0.12.12
+
+FROM base-with-rust
+
+RUN apk add samurai
+
+RUN apk add libffi-dev py3-pip cmake git ccache clang-dev
+
+# install the requirements
+RUN apk add --no-cache \
+    ca-certificates \
+    less \
+    ncurses-terminfo-base \
+    krb5-libs \
+    libgcc \
+    libintl \
+    libssl1.1 \
+    libstdc++ \
+    tzdata \
+    userspace-rcu \
+    zlib \
+    icu-libs \
+    curl
+
+RUN apk -X https://dl-cdn.alpinelinux.org/alpine/edge/main add --no-cache \
+    lttng-ust
+
+# Download the powershell '.tar.gz' archive
+RUN curl -L https://github.com/PowerShell/PowerShell/releases/download/v7.2.2/powershell-7.2.2-linux-alpine-x64.tar.gz -o /tmp/powershell.tar.gz
+
+# Create the target folder where powershell will be placed
+RUN mkdir -p /opt/microsoft/powershell/7
+
+# Expand powershell to the target folder
+RUN tar zxf /tmp/powershell.tar.gz -C /opt/microsoft/powershell/7
+
+# Set execute permissions
+RUN chmod +x /opt/microsoft/powershell/7/pwsh
+
+# Create the symbolic link that points to pwsh
+RUN ln -s /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh
+
+RUN apk add patchelf
+
+RUN python -m pip install -U pip
+RUN python -m pip install --no-cache-dir cffi
+
+COPY --from=builder ${CARGO_HOME}/bin/maturin /usr/bin/maturin
+
+RUN python -m pip install -U tox
diff --git a/eng/psakefile.ps1 b/eng/psakefile.ps1
index ebd0f9a6..094f3b2f 100644
--- a/eng/psakefile.ps1
+++ b/eng/psakefile.ps1
@@ -36,87 +36,177 @@ properties {
     $docs.build.dir = Join-Path $docs.root "_build"
     $docs.build.opts = @()
 
+    $rust = @{}
+    $rust.version = "1.57.0"
+
+    $linux = @{}
+    $linux.manylinux_tag = "manylinux2014_x86_64_maturin"
+    $linux.manylinux_root = "/io"
+    $linux.musllinux_tag = "musllinux_1_2_x86_64_maturin"
+    $linux.musllinux_root = "/oi"
+
     $wheelhouse = Join-Path $repo.root "target" "wheels" "*.whl"
 }
 
-Include settings.ps1
-Include utils.ps1
+include settings.ps1
+include utils.ps1
+
+task default -depends qirlib, pyqir-tests, parser, generator, evaluator, metawheel, run-examples
+
+task manylinux -depends build-manylinux-container-image, run-manylinux-container-image, run-examples-in-containers 
+
+task musllinux -depends build-musllinux-container-image, run-musllinux-container-image, run-examples-in-musl-containers
+
+task checks -depends cargo-fmt, cargo-clippy
+
+task rebuild -depends qirlib, generator, evaluator, parser
+
+task run-manylinux-container-image -preaction { Write-CacheStats } -postaction { Write-CacheStats } {
+    $srcPath = $repo.root
+
+    # For any of the volumes mapped, if the dir doesn't exist,
+    # docker will create it and it will be owned by root and
+    # the caching/install breaks with permission errors.
+    # New-Item is idempotent so we don't need to check for existence
 
-Task default -Depends checks, pyqir-tests, parser, generator, evaluator, metawheel, run-examples, run-examples-in-containers
+    $cacheMount, $cacheEnv = Get-CCacheParams
 
-Task checks -Depends cargo-fmt, cargo-clippy
+    Write-BuildLog "Running container image:"
+    $ioVolume = "$($srcPath):$($linux.manylinux_root)"
+    $userName = Get-LinuxContainerUserName
 
-Task rebuild -Depends generator, evaluator, parser
+    Invoke-LoggedCommand {
+        docker run --rm --user $userName -v $ioVolume @cacheMount @cacheEnv -e QIRLIB_CACHE_DIR="/tmp/llvm" -w "$($linux.manylinux_root)" "$($linux.manylinux_tag)" conda run --no-capture-output pwsh build.ps1 -t default
+    }
+}
+
+task run-musllinux-container-image -preaction { Write-CacheStats } -postaction { Write-CacheStats } {
+    $srcPath = $repo.root
+
+    # For any of the volumes mapped, if the dir doesn't exist,
+    # docker will create it and it will be owned by root and
+    # the caching/install breaks with permission errors.
+    # New-Item is idempotent so we don't need to check for existence
+
+    $cacheMount, $cacheEnv = Get-CCacheParams
+
+    Write-BuildLog "Running container image:"
+    $ioVolume = "$($srcPath):$($linux.musllinux_root)"
+    $userName = Get-LinuxContainerUserName
+    if (Test-CI) {
+        $userName = "root"
+    }
+    Invoke-LoggedCommand {
+        docker run --rm --user $userName -v $ioVolume @cacheMount @cacheEnv -e QIRLIB_CACHE_DIR="/tmp/llvm" -w "$($linux.musllinux_root)" "$($linux.musllinux_tag)" pwsh build.ps1
+    }
+}
 
-Task cargo-fmt {
+task cargo-fmt {
     Invoke-LoggedCommand -workingDirectory $repo.root -errorMessage "Please run 'cargo fmt --all' before pushing" {
         cargo fmt --all -- --check
     }
 }
 
-Task cargo-clippy -Depends init {
+task cargo-clippy -depends init {
     Invoke-LoggedCommand -workingDirectory $repo.root -errorMessage "Please fix the above clippy errors" {
         $extraArgs = (Test-CI) ? @("--", "-D", "warnings") : @()
         cargo clippy --workspace --all-targets @extraArgs
     }
 }
 
-Task generator -Depends init {
+task generator -depends init {
     Build-PyQIR($pyqir.generator.name)
 }
 
-Task evaluator -Depends init {
+task evaluator -depends init {
     Build-PyQIR($pyqir.evaluator.name)
 }
 
-Task parser -Depends init {
+task parser -depends init {
     Build-PyQIR($pyqir.parser.name)
 }
 
-Task pyqir-tests -Depends init {
+task pyqir-tests -depends init {
     $srcPath = $repo.root
-    $project = "pyqir-tests"
-    $installationDirectory = Resolve-InstallationDirectory
 
-    if (Test-RunInContainer) {
-        Build-ContainerImage $srcPath
-        Write-BuildLog "Running container image:"
-        $ioVolume = "$($srcPath):/io"
-        $llvmVolume = "$($installationDirectory):/usr/lib/llvm"
-        $userName = [Environment]::UserName
-
-        Invoke-LoggedCommand {
-            docker run --rm --user $userName -v $ioVolume -v $llvmVolume -e LLVM_SYS_110_PREFIX=/usr/lib/llvm -w /io/$project manylinux2014_x86_64_maturin conda run --no-capture-output python -m tox -e test
+    exec -workingDirectory (Join-Path $srcPath "pyqir-tests") {
+        if (Test-InCondaEnvironment) {
+            Invoke-LoggedCommand -wd $pyqir.generator.dir {
+                maturin develop --release --cargo-extra-args="-vv"
+            }
+            Invoke-LoggedCommand -wd $pyqir.evaluator.dir {
+                maturin develop --release --cargo-extra-args="-vv"
+            }
+            & $python -m pip install pytest
+            & $python -m pytest
         }
-    }
-    else {
-        exec -workingDirectory (Join-Path $srcPath $project) {
+        else {
             Invoke-LoggedCommand {
                 & $python -m pip install tox
             }
-
             Invoke-LoggedCommand {
-                & $python -m tox -e test
+                & $python -m tox -v -e (Get-ToxTarget)
+            }
+        }
+    }
+}
+
+task qirlib -depends init {
+    if (Test-MuslLinux) {
+        # https://github.com/rust-lang/rust/issues/71651
+        $old_rustflags = ""
+        $reset_rustflags = $false
+
+        if (Test-Path env:\RUSTFLAGS) {
+            $reset_rustflags = $true
+            $old_rustflags = $env:RUSTFLAGS
+            $env:RUSTFLAGS = "$($old_rustflags) -C target-feature=-crt-static".Trim()
+        }
+        else {
+            $env:RUSTFLAGS = "-C target-feature=-crt-static"
+        }
+        try {
+            Invoke-LoggedCommand -wd $pyqir.qirlib.dir {
+                cargo test --release -vv
+            }
+        }
+        finally {
+            if ($reset_rustflags) {
+                $env:RUSTFLAGS = $old_rustflags
+            }
+            else {
+                Remove-Item env:\RUSTFLAGS
             }
         }
     }
+    else {
+        Invoke-LoggedCommand -wd $pyqir.qirlib.dir {
+            cargo test --release -vv
+        }
+    }
+    Invoke-LoggedCommand -wd $pyqir.qirlib.dir {
+        cargo build --release -vv
+    }
 }
 
-Task metawheel {
+task metawheel {
     $wheelDir = Split-Path -Parent $wheelhouse
     if (!(Test-Path $wheelDir)) {
         New-Item -Path $wheelDir -ItemType Directory | Out-Null
     }
     Invoke-LoggedCommand {
-        & $python -m pip wheel --no-deps --wheel-dir $wheelDir $pyqir.meta.dir
+        & $python -m pip wheel --no-deps --wheel-dir $wheelDir "$($pyqir.meta.dir)"
     }
 }
 
-Task wheelhouse `
-    -Precondition { -not (Test-Path $wheelhouse -ErrorAction SilentlyContinue) } `
+task wheelhouse `
+    -precondition { -not (Test-Path $wheelhouse -ErrorAction SilentlyContinue) } `
 { Invoke-Task rebuild }
 
-Task docs -Depends wheelhouse {
+task docs -depends wheelhouse {
+    # Write out the wheels available
+    Write-Host (Get-ChildItem $wheelhouse -Include *.whl)
+
     # - Install artifacts into new venv along with sphinx.
     # - Run sphinx from within new venv.
     $envPath = Join-Path $repo.root ".docs-venv"
@@ -134,20 +224,8 @@ Task docs -Depends wheelhouse {
     }
 }
 
-function Use-ExternalLlvmInstallation {
-    Write-BuildLog "Using LLVM installation specified by QIRLIB_LLVM_EXTERNAL_DIR"
-    Assert (Test-Path $env:QIRLIB_LLVM_EXTERNAL_DIR) "QIRLIB_LLVM_EXTERNAL_DIR folder does not exist"
-    Use-LlvmInstallation $env:QIRLIB_LLVM_EXTERNAL_DIR
-}
-
-function Test-AllowedToDownloadLlvm {
-    # If QIRLIB_DOWNLOAD_LLVM isn't set, we allow for download
-    # If it is set, then we use its value
-    !((Test-Path env:\QIRLIB_DOWNLOAD_LLVM) -and ($env:QIRLIB_DOWNLOAD_LLVM -eq $false))
-}
-
 task init {
-    if ((Test-CI) -and !$IsLinux) {
+    if ((Test-CI)) {
         cargo install maturin --git https://github.com/PyO3/maturin --tag v0.12.12
     }
     
@@ -187,138 +265,68 @@ task install-llvm-from-archive {
     install-llvm $pyqir.qirlib.dir "download"
 }
 
-function Write-CacheStats {
-    if (Test-CommandExists("ccache")) {
-        Write-BuildLog "ccache config:"
-        & { ccache --show-config } -ErrorAction SilentlyContinue
-        Write-BuildLog "ccache stats:"
-        & { ccache --show-stats } -ErrorAction SilentlyContinue
-    }
-    if (Test-CommandExists("sccache")) {
-        Write-BuildLog "sccache config/stats:"
-        & { sccache --show-stats } -ErrorAction SilentlyContinue
-    }
-}
 
-task install-llvm-from-source {
-    Write-CacheStats
-    if (Test-CommandExists("sccache")) {
-        Write-BuildLog "Starting sccache server"
-        & { sccache --start-server } -ErrorAction SilentlyContinue
-        Write-BuildLog "Started sccache server"
+task install-llvm-from-source -depends configure-sccache -postaction { Write-CacheStats } {
+    if ($IsWindows) {
+        Include vcvars.ps1
     }
+    install-llvm $pyqir.qirlib.dir "build"
+}
 
-    if (Test-RunInContainer) {
-        $installationDirectory = Resolve-InstallationDirectory
-        Use-LlvmInstallation $installationDirectory
-        Build-ContainerImage $repo.root
+task package-musllinux-llvm -depends build-musllinux-container-image -preaction { Write-CacheStats } -postaction { Write-CacheStats } {
+    if ($IsLinux) {
         $srcPath = $repo.root
+        $ioVolume = "$($srcPath):$($linux.musllinux_root)"
+        $userName = Get-LinuxContainerUserName
 
-        # For any of the volumes mapped, if the dir doesn't exist,
-        # docker will create it and it will be owned by root and
-        # the caching/install breaks with permission errors.
-        # New-Item is idempotent so we don't need to check for existence
-
-        $ioVolume = "$($srcPath):/io"
-        $llvmVolume = "$($installationDirectory):/llvm"
-        New-Item -ItemType Directory -Force $installationDirectory | Out-Null
-        
-        $userName = [Environment]::UserName
-        $cacheMount = ""
-        $cacheEnv = ""
-        # only ccache is supported in the manylinux container for now.
-        # we would need a way to specify which cache is used to
-        # support both.
-        if (Test-CommandExists("ccache")) {
-            # we need to map the local cache dir into the
-            # container. If the env var isn't set, ask ccache
-            $cacheDir = ""
-            if (Test-Path env:\CCACHE_DIR) {
-                $cacheDir = $Env:CCACHE_DIR
-            }
-            else {
-                $cacheDir = exec { ccache -k cache_dir }
-            }
-            if (![string]::IsNullOrWhiteSpace($cacheDir)) {
-                New-Item -ItemType Directory -Force $cacheDir | Out-Null
-                
-                $cacheDir = Resolve-Path $cacheDir
-                # mount the cache outside of any runner mappings
-                $cacheMount = @("-v", "$($cacheDir):/ccache")
-                $cacheEnv = @("-e", "CCACHE_DIR=`"/ccache`"")
-            }
-        }
-        
         Invoke-LoggedCommand {
-            docker run --rm --user $userName -v $ioVolume -v $llvmVolume @cacheMount @cacheEnv -e QIRLIB_CACHE_DIR="$installationDirectory" -e QIRLIB_CACHE_DIR="/llvm" -w /io/qirlib manylinux2014_x86_64_maturin conda run --no-capture-output cargo build --release --no-default-features --features "build-llvm,no-llvm-linking" -vv
-        }
-    }
-    else {
-        if ($IsWindows) {
-            Include vcvars.ps1
+            docker run --rm --user $userName -v $ioVolume -w "$($linux.musllinux_root)/qirlib" -e QIRLIB_PKG_DEST="$($linux.musllinux_root)/target" "$($linux.musllinux_tag)" cargo build --release --no-default-features --features package-llvm -vv
         }
-        
-        install-llvm $pyqir.qirlib.dir "build"
     }
-
-    Write-CacheStats
 }
 
-task package-llvm {
-    if (Test-RunInContainer) {
-        Build-ContainerImage $repo.root
+task package-manylinux-llvm -depends build-manylinux-container-image -preaction { Write-CacheStats } -postaction { Write-CacheStats } {
+    if ($IsLinux) {
         $srcPath = $repo.root
-        $ioVolume = "$($srcPath):/io"
-        $userName = [Environment]::UserName
+        $ioVolume = "$($srcPath):$($linux.manylinux_root)"
+        $userName = Get-LinuxContainerUserName
 
         Invoke-LoggedCommand {
-            docker run --rm --user $userName -v $ioVolume -w /io/qirlib -e QIRLIB_PKG_DEST=/io/target manylinux2014_x86_64_maturin conda run --no-capture-output cargo build --release  --no-default-features --features package-llvm -vv
-        }
-    }
-    else {
-        if ($IsWindows) {
-            Include vcvars.ps1
-        }
-        $clear_pkg_dest_var = $false
-        if (!(Test-Path env:\QIRLIB_PKG_DEST)) {
-            $clear_pkg_dest_var = $true
-            $env:QIRLIB_PKG_DEST = Join-Path $repo.root "target"
-        }
-        try {
-            Invoke-LoggedCommand -wd $pyqir.qirlib.dir {
-                cargo build --release  --no-default-features --features package-llvm -vv
-            }
-        }
-        finally {
-            if ($clear_pkg_dest_var) {
-                Remove-Item -Path Env:QIRLIB_PKG_DEST
-            }
+            docker run --rm --user $userName -v $ioVolume -w "$($linux.manylinux_root)/qirlib" -e QIRLIB_PKG_DEST="$($linux.manylinux_root)/target" "$($linux.manylinux_tag)" conda run --no-capture-output cargo build --release --no-default-features --features package-llvm -vv
         }
     }
 }
 
-# Only run the nested ManyLinux container
-# build on Linux while not in a dev container
-function Test-RunInContainer {
-    if ($IsLinux -and (Test-CI)) {
-        # If we are in a dev container, our workspace is already
-        # mounted into the container. If we try to mount our 'local' workspace
-        # into a nested container it will silently fail to mount.
-        !(Test-InDevContainer)
+task package-llvm {
+    if ($IsWindows) {
+        Include vcvars.ps1
     }
-    else {
-        $false
+    $clear_pkg_dest_var = $false
+    if (!(Test-Path env:\QIRLIB_PKG_DEST)) {
+        $clear_pkg_dest_var = $true
+        $env:QIRLIB_PKG_DEST = Join-Path $repo.root "target"
+    }
+    try {
+        Invoke-LoggedCommand -wd $pyqir.qirlib.dir {
+            cargo build --release  --no-default-features --features package-llvm -vv
+        }
+    }
+    finally {
+        if ($clear_pkg_dest_var) {
+            Remove-Item -Path Env:QIRLIB_PKG_DEST
+        }
     }
 }
 
-function Build-ContainerImage([string]$srcPath) {
+task build-manylinux-container-image {
+    $srcPath = $repo.root
     Write-BuildLog "Building container image manylinux-llvm-builder"
     Invoke-LoggedCommand -workingDirectory (Join-Path $srcPath eng) {
-        $user = [environment]::UserName
-        $uid = "$(id -u)"
-        $gid = "$(id -g)"
-        $rustv = "1.57.0"
-        $tag = "manylinux2014_x86_64_maturin"
+        $user = "$(Get-LinuxContainerUserName)"
+        $uid = "$(Get-LinuxContainerUserId)"
+        $gid = "$(Get-LinuxContainerGroupId)"
+        $rustv = "$($rust.version)"
+        $tag = "$($linux.manylinux_tag)"
         Get-Content manylinux.Dockerfile | docker build `
             --build-arg USERNAME=$user `
             --build-arg USER_UID=$uid `
@@ -328,82 +336,67 @@ function Build-ContainerImage([string]$srcPath) {
     }
 }
 
-function Build-PyQIR([string]$project) {
+task build-musllinux-container-image {
     $srcPath = $repo.root
-    $installationDirectory = Resolve-InstallationDirectory
-
-    if (Test-RunInContainer) {
-        Build-ContainerImage $srcPath
-        function Invoke-ContainerImage {
-            Write-BuildLog "Running container image:"
-            $ioVolume = "$($srcPath):/io"
-            $llvmVolume = "$($installationDirectory):/usr/lib/llvm"
-            $userName = [Environment]::UserName
-
-            Invoke-LoggedCommand {
-                docker run --rm --user $userName -v $ioVolume -v $llvmVolume -e LLVM_SYS_110_PREFIX=/usr/lib/llvm -w /io/$project manylinux2014_x86_64_maturin conda run --no-capture-output cargo test --release --lib -vv -- --nocapture
-            }
-
-            Invoke-LoggedCommand {
-                docker run --rm --user $userName -v $ioVolume -v $llvmVolume -e LLVM_SYS_110_PREFIX=/usr/lib/llvm -w /io/$project manylinux2014_x86_64_maturin conda run --no-capture-output /usr/bin/maturin build --release
-            }
-
-            Invoke-LoggedCommand {
-                docker run --rm --user $userName -v $ioVolume -v $llvmVolume -e LLVM_SYS_110_PREFIX=/usr/lib/llvm -w /io/$project manylinux2014_x86_64_maturin conda run --no-capture-output python -m tox -e test
-            }
-            
-            Invoke-LoggedCommand {
-                docker run --rm --user $userName -v $ioVolume -v $llvmVolume -e LLVM_SYS_110_PREFIX=/usr/lib/llvm -w /io/$project manylinux2014_x86_64_maturin conda run --no-capture-output cargo test --release -vv -- --nocapture
-            }
+    Write-BuildLog "Building container image musllinux-llvm-builder"
+    if (Test-CI) {
+        Invoke-LoggedCommand -workingDirectory (Join-Path $srcPath eng) {
+            $rustv = "$($rust.version)"
+            $tag = "$($linux.musllinux_tag)"
+            Get-Content musllinuxCI.Dockerfile | docker build `
+                --build-arg RUST_VERSION=$rustv `
+                -t $tag -
         }
-
-        Invoke-ContainerImage
     }
     else {
-
-        exec -workingDirectory (Join-Path $srcPath $project) {
-            Invoke-LoggedCommand {
-                & $python -m pip install tox
-            }
-
-            Invoke-LoggedCommand {
-                & $python -m tox -e test
-            }
-
-            Invoke-LoggedCommand {
-                & $python -m tox -e pack
-            }
-        }
-
-        Invoke-LoggedCommand -workingDirectory $srcPath {
-            & cargo test --release -vv -- --nocapture
+        Invoke-LoggedCommand -workingDirectory (Join-Path $srcPath eng) {
+            $user = "$(Get-LinuxContainerUserName)"
+            $uid = "$(Get-LinuxContainerUserId)"
+            $gid = "$(Get-LinuxContainerGroupId)"
+            $rustv = "$($rust.version)"
+            $tag = "$($linux.musllinux_tag)"
+            Get-Content musllinux.Dockerfile | docker build `
+                --build-arg USERNAME=$user `
+                --build-arg USER_UID=$uid `
+                --build-arg USER_GID=$gid `
+                --build-arg RUST_VERSION=$rustv `
+                -t $tag -
         }
     }
 }
 
-task run-examples-in-containers -precondition { Test-RunInContainer } {
-    $userName = [Environment]::UserName
-    $userId = $(id -u)
-    $groupId = $(id -g)
+# This is only usable if building for manylinux
+task run-examples-in-containers {
+    $user = Get-LinuxContainerUserName
+    $uid = "$(Get-LinuxContainerUserId)"
+    $gid = "$(Get-LinuxContainerGroupId)"
     $images = @("buster", "bullseye", "bionic", "focal")
     foreach ($image in $images) {
         exec -workingDirectory (Join-Path $repo.root "eng") {
-            get-content "$($image).Dockerfile" | docker build --build-arg USERNAME=$userName --build-arg USER_UID=$userId --build-arg USER_GID=$groupId -t "$image-samples" -
+            get-content "$($image).Dockerfile" | docker build --build-arg USERNAME=$user --build-arg USER_UID=$uid --build-arg USER_GID=$gid -t "pyqir-$image-examples" -
         }
         exec {
-            docker run --rm --user $userName -v "$($repo.root):/home/$userName" "$image-samples" build.ps1 -t run-examples
+            docker run --rm --user $user -v "$($repo.root):/home/$user" "pyqir-$image-examples" build.ps1 -t run-examples
         }
     }
 }
 
-task run-examples {   
-    exec -workingDirectory $repo.root {
-        $wheels = Join-Path $repo.root "target" "wheels"
-        & $python -m pip install -r requirements.txt --no-index --find-links=$wheels -v
+task run-examples-in-musl-containers {
+    $userName = Get-LinuxContainerUserName
+    if (Test-CI) {
+        $userName = "root"
+    }
+    Invoke-LoggedCommand {
+        docker run --rm --user $userName -w "/home/$user" -v "$($repo.root):/home/$user" "$($linux.musllinux_tag)" pwsh build.ps1 -t run-examples
     }
+}
 
+# run-examples assumes the wheels have already been installed locally
+task run-examples {   
     exec -workingDirectory $pyqir.generator.examples_dir {
+        & $python -m pip install -U pip
         & $python -m pip install -r requirements.txt
+        & $python -m pip install --user -U --no-index --find-links (Join-Path $repo.root "target" "wheels") pyqir-generator
         & $python "bell_pair.py" | Tee-Object -Variable bell_pair_output
         $bell_first_line = $($bell_pair_output | Select-Object -first 1)
         $bell_expected = "; ModuleID = 'Bell'"
@@ -417,6 +410,7 @@ task run-examples {
     }
 
     exec -workingDirectory $pyqir.evaluator.examples_dir {
+        & $python -m pip install --user -U --no-index --find-links (Join-Path $repo.root "target" "wheels") pyqir-evaluator
         & $python "bernstein_vazirani.py" | Tee-Object -Variable bz_output
         $bz_first_lines = @($bz_output | Select-Object -first 5)
         $bz_expected = @(
@@ -443,65 +437,6 @@ task run-examples {
     }
 }
 
-function Create-DocsEnv() {
-    param(
-        [string]
-        $EnvironmentPath,
-        [string]
-        $RequirementsPath,
-        [string[]]
-        $ArtifactPaths
-    )
-
-    Write-Host "##[info]Creating virtual environment for use with docs at $EnvironmentPath..."
-    python -m venv $EnvironmentPath
-
-    $activateScript = (Join-Path $EnvironmentPath "bin" "Activate.ps1")
-    if (-not (Test-Path $activateScript -ErrorAction SilentlyContinue)) {
-        Get-ChildItem $EnvironmentPath | Write-Host
-        throw "No activate script found for virtual environment at $EnvironmentPath; environment creation failed."
-    }
-
-    & $activateScript
-    try {
-        pip install -r $RequirementsPath
-        foreach ($artifact in $ArtifactPaths) {
-            pip install $artifact
-        }
-    }
-    finally {
-        deactivate
-    }
-}
-
-function install-llvm {
-    Param(
-        [Parameter(Mandatory)]
-        [string]$qirlibDir,
-        [Parameter(Mandatory)]
-        [ValidateSet("download", "build")]
-        [string]$operation
-    )
-
-    $installationDirectory = Resolve-InstallationDirectory
-    Use-LlvmInstallation $installationDirectory
-    $clear_cache_var = $false
-    if (!(Test-Path env:\QIRLIB_CACHE_DIR)) {
-        $clear_cache_var = $true
-        $env:QIRLIB_CACHE_DIR = $installationDirectory
-    }
-    try {
-        Invoke-LoggedCommand -wd $qirlibDir {
-            cargo build --release --no-default-features --features "$($operation)-llvm,no-llvm-linking" -vv
-        }
-    }
-    finally {
-        if ($clear_cache_var) {
-            Remove-Item -Path Env:QIRLIB_CACHE_DIR
-        }
-    }   
-}
-
 task check-licenses {
     # Uses cargo-deny to verify that the linked components
     # only use approved licenses
@@ -530,3 +465,11 @@ task update-noticefiles {
         }
     }
 }
+
+task configure-sccache -postaction { Write-CacheStats } {
+    if (Test-CommandExists("sccache")) {
+        Write-BuildLog "Starting sccache server"
+        & { sccache --start-server } -ErrorAction SilentlyContinue
+        Write-BuildLog "Started sccache server"
+    }
+}
diff --git a/eng/utils.ps1 b/eng/utils.ps1
index d3aca521..98689e6f 100644
--- a/eng/utils.ps1
+++ b/eng/utils.ps1
@@ -115,3 +115,197 @@ function Invoke-LoggedCommand {
         -retryTriggerErrorPattern $retryTriggerErrorPattern `
         -workingDirectory $workingDirectory
 }
+
+function Use-ExternalLlvmInstallation {
+    Write-BuildLog "Using LLVM installation specified by QIRLIB_LLVM_EXTERNAL_DIR"
+    Assert (Test-Path $env:QIRLIB_LLVM_EXTERNAL_DIR) "QIRLIB_LLVM_EXTERNAL_DIR folder does not exist"
+    Use-LlvmInstallation $env:QIRLIB_LLVM_EXTERNAL_DIR
+}
+
+function Test-AllowedToDownloadLlvm {
+    # If QIRLIB_DOWNLOAD_LLVM isn't set, we don't allow for download
+    # If it is set, then we use its value
+    ((Test-Path env:\QIRLIB_DOWNLOAD_LLVM) -and ($env:QIRLIB_DOWNLOAD_LLVM -eq $true))
+}
+
+function Test-InCondaEnvironment {
+    (Test-Path env:\CONDA_PREFIX) -or (Test-Path env:\CONDA_ROOT)
+}
+
+function Get-LinuxTargetTriple {
+    $triple = rustc -vV | sed -n 's|host: ||p'
+    $triple
+}
+
+function Get-LinuxContainerUserId {
+    if (Test-Path env:\PYQIR_CONTAINER_USERID) {
+        $env:PYQIR_CONTAINER_USERID
+    }
+    else {
+        "$(id -u)"
+    }
+}
+
+function Get-LinuxContainerGroupId {
+    if (Test-Path env:\PYQIR_CONTAINER_GROUPID) {
+        $env:PYQIR_CONTAINER_GROUPID
+    }
+    else {
+        "$(id -g)"
+    }
+}
+
+function Get-LinuxContainerUserName {
+    if (Test-Path env:\PYQIR_CONTAINER_USERNAME) {
+        $env:PYQIR_CONTAINER_USERNAME
+    }
+    else {
+        [Environment]::UserName
+    }
+}
+
+function Get-ToxTarget {
+    if (Test-MuslLinux) {
+        "allmusl"
+    }
+    else {
+        "all"
+    }
+}
+
+function Test-MuslLinux {
+    if ($IsLinux) {
+        $triple = Get-LinuxTargetTriple
+        $triple -eq "x86_64-unknown-linux-musl"
+    }
+    else {
+        $false
+    }
+}
+
+function Write-CacheStats {
+    if (Test-CommandExists("ccache")) {
+        Write-BuildLog "ccache config:"
+        & { ccache --show-config } -ErrorAction SilentlyContinue
+        Write-BuildLog "ccache stats:"
+        & { ccache --show-stats } -ErrorAction SilentlyContinue
+    }
+    if (Test-CommandExists("sccache")) {
+        Write-BuildLog "sccache config/stats:"
+        & { sccache --show-stats } -ErrorAction SilentlyContinue
+    }
+}
+
+function Build-PyQIR([string]$project) {
+    $srcPath = $repo.root
+
+    exec -workingDirectory (Join-Path $srcPath $project) {
+        if (Test-InCondaEnvironment) {
+            $build_extra_args = ""
+            if (Test-MuslLinux) {
+                $build_extra_args = "--skip-auditwheel"
+            }
+            Invoke-LoggedCommand {
+                maturin build --release $build_extra_args --cargo-extra-args="-vv"
+                maturin develop --release --cargo-extra-args="-vv"
+                & $python -m pip install pytest
+                & $python -m pytest
+            }
+        }
+        else {
+            Invoke-LoggedCommand {
+                & $python -m pip install tox
+            }
+            Invoke-LoggedCommand {
+                & $python -m tox -v -e (Get-ToxTarget)
+            }
+        }
+    }
+}
+
+function Create-DocsEnv() {
+    param(
+        [string]
+        $EnvironmentPath,
+        [string]
+        $RequirementsPath,
+        [string[]]
+        $ArtifactPaths
+    )
+
+    Write-Host "##[info]Creating virtual environment for use with docs at $EnvironmentPath..."
+    python -m venv $EnvironmentPath
+
+    $activateScript = (Join-Path $EnvironmentPath "bin" "Activate.ps1")
+    if (-not (Test-Path $activateScript -ErrorAction SilentlyContinue)) {
+        Get-ChildItem $EnvironmentPath | Write-Host
+        throw "No activate script found for virtual environment at $EnvironmentPath; environment creation failed."
+    }
+
+    & $activateScript
+    try {
+        pip install -r $RequirementsPath
+        foreach ($artifact in $ArtifactPaths) {
+            pip install $artifact
+        }
+    }
+    finally {
+        deactivate
+    }
+}
+
+function install-llvm {
+    Param(
+        [Parameter(Mandatory)]
+        [string]$qirlibDir,
+        [Parameter(Mandatory)]
+        [ValidateSet("download", "build")]
+        [string]$operation
+    )
+
+    $installationDirectory = Resolve-InstallationDirectory
+    New-Item -ItemType Directory -Force $installationDirectory | Out-Null
+    Use-LlvmInstallation $installationDirectory
+    $clear_cache_var = $false
+    if (!(Test-Path env:\QIRLIB_CACHE_DIR)) {
+        $clear_cache_var = $true
+        $env:QIRLIB_CACHE_DIR = $installationDirectory
+    }
+    try {
+        Invoke-LoggedCommand -wd $qirlibDir {
+            cargo build --release --no-default-features --features "$($operation)-llvm,no-llvm-linking" -vv
+        }
+    }
+    finally {
+        if ($clear_cache_var) {
+            Remove-Item -Path Env:QIRLIB_CACHE_DIR
+        }
+    }   
+}
+
+function Get-CCacheParams {
+    # only ccache is supported in the container for now.
+    # we would need a way to specify which cache is used to
+    # support both.
+    if (Test-CommandExists("ccache")) {
+        # we need to map the local cache dir into the
+        # container. If the env var isn't set, ask ccache
+        $cacheDir = ""
+        if (Test-Path env:\CCACHE_DIR) {
+            $cacheDir = $Env:CCACHE_DIR
+        }
+        else {
+            $cacheDir = exec { ccache -k cache_dir }
+        }
+        if (![string]::IsNullOrWhiteSpace($cacheDir)) {
+            New-Item -ItemType Directory -Force $cacheDir | Out-Null
+            
+            $cacheDir = Resolve-Path $cacheDir
+            # mount the cache outside of any runner mappings
+            $cacheMount = @("-v", "$($cacheDir):/ccache")
+            $cacheEnv = @("-e", "CCACHE_DIR=`"/ccache`"")
+            return $cacheMount, $cacheEnv
+        }
+    }
+    return "", ""
+}
diff --git a/examples/generator/requirements.txt b/examples/generator/requirements.txt
index f796e2a0..af69923a 100644
--- a/examples/generator/requirements.txt
+++ b/examples/generator/requirements.txt
@@ -1,1 +1,2 @@
+wheel
 antlr4-python3-runtime==4.9.3
\ No newline at end of file
diff --git a/pyqir-evaluator/tox.ini b/pyqir-evaluator/tox.ini
index 876c125f..03a2d978 100644
--- a/pyqir-evaluator/tox.ini
+++ b/pyqir-evaluator/tox.ini
@@ -16,10 +16,24 @@ deps = -rrequirements-dev.txt
 [testenv:test]
 description = Run the unit tests under {basepython}
 commands =
-    python -m pip install .
+    maturin develop --release --cargo-extra-args="-vv"
     pytest {posargs}
 
 [testenv:pack]
 description = Build the wheels under all installed platforms
 commands =
-    maturin build --release
+    maturin build --release --cargo-extra-args="-vv"
+
+[testenv:all]
+description = Build the wheels under all installed platforms
+commands =
+    maturin build --release --cargo-extra-args="-vv"
+    maturin develop --release --cargo-extra-args="-vv"
+    pytest {posargs}
+
+[testenv:allmusl]
+description = Build the wheels under all installed platforms
+commands =
+    maturin build --skip-auditwheel --release --cargo-extra-args="-vv"
+    maturin develop --release --cargo-extra-args="-vv"
+    pytest {posargs}
diff --git a/pyqir-generator/requirements-dev.txt b/pyqir-generator/requirements-dev.txt
index 64615f71..d0dc48de 100644
--- a/pyqir-generator/requirements-dev.txt
+++ b/pyqir-generator/requirements-dev.txt
@@ -1,4 +1,5 @@
 pip>=21.3
 pytest-subtests>=0.7.0
 pytest>=3.5.0
-maturin>=0.12.12,<0.13
\ No newline at end of file
+maturin>=0.12.12,<0.13
+dataclasses>=0.6; python_version < '3.7'
\ No newline at end of file
diff --git a/pyqir-generator/tox.ini b/pyqir-generator/tox.ini
index 876c125f..03a2d978 100644
--- a/pyqir-generator/tox.ini
+++ b/pyqir-generator/tox.ini
@@ -16,10 +16,24 @@ deps = -rrequirements-dev.txt
 [testenv:test]
 description = Run the unit tests under {basepython}
 commands =
-    python -m pip install .
+    maturin develop --release --cargo-extra-args="-vv"
     pytest {posargs}
 
 [testenv:pack]
 description = Build the wheels under all installed platforms
 commands =
-    maturin build --release
+    maturin build --release --cargo-extra-args="-vv"
+
+[testenv:all]
+description = Build the wheels under all installed platforms
+commands =
+    maturin build --release --cargo-extra-args="-vv"
+    maturin develop --release --cargo-extra-args="-vv"
+    pytest {posargs}
+
+[testenv:allmusl]
+description = Build the wheels under all installed platforms
+commands =
+    maturin build --skip-auditwheel --release --cargo-extra-args="-vv"
+    maturin develop --release --cargo-extra-args="-vv"
+    pytest {posargs}
diff --git a/pyqir-parser/tox.ini b/pyqir-parser/tox.ini
index 876c125f..03a2d978 100644
--- a/pyqir-parser/tox.ini
+++ b/pyqir-parser/tox.ini
@@ -16,10 +16,24 @@ deps = -rrequirements-dev.txt
 [testenv:test]
 description = Run the unit tests under {basepython}
 commands =
-    python -m pip install .
+    maturin develop --release --cargo-extra-args="-vv"
     pytest {posargs}
 
 [testenv:pack]
 description = Build the wheels under all installed platforms
 commands =
-    maturin build --release
+    maturin build --release --cargo-extra-args="-vv"
+
+[testenv:all]
+description = Build the wheels under all installed platforms
+commands =
+    maturin build --release --cargo-extra-args="-vv"
+    maturin develop --release --cargo-extra-args="-vv"
+    pytest {posargs}
+
+[testenv:allmusl]
+description = Build the wheels under all installed platforms
+commands =
+    maturin build --skip-auditwheel --release --cargo-extra-args="-vv"
+    maturin develop --release --cargo-extra-args="-vv"
+    pytest {posargs}
diff --git a/qirlib/CMakeLists.txt b/qirlib/CMakeLists.txt
index 1788a53d..6b2a594d 100644
--- a/qirlib/CMakeLists.txt
+++ b/qirlib/CMakeLists.txt
@@ -17,7 +17,7 @@ include(ExternalProject)
 # Both
 # - CMAKE_INSTALL_PREFIX
 
-if (NOT $ENV{QIRLIB_DOWNLOAD_LLVM} MATCHES "false")
+if ($ENV{QIRLIB_DOWNLOAD_LLVM} MATCHES "true")
   message (STATUS "Install LLVM to ${CMAKE_INSTALL_PREFIX}")
 
   set(SHA256_NAME $ENV{QIRLIB_LLVM_PKG_NAME}.sha256)
diff --git a/qirlib/config.cmake b/qirlib/config.cmake
index fe7c146b..5ff442f8 100644
--- a/qirlib/config.cmake
+++ b/qirlib/config.cmake
@@ -47,12 +47,14 @@ set(LLVM_ENABLE_ASSERTIONS OFF CACHE BOOL "")
 set(LLVM_BUILD_EXAMPLES OFF CACHE BOOL "")
 set(LLVM_ENABLE_RTTI OFF CACHE BOOL "")
 
+# Remove external lib dependencies
 set(LLVM_ENABLE_LIBXML2 OFF CACHE BOOL "")
 set(LLVM_ENABLE_LIBEDIT OFF CACHE BOOL "")
 set(LLVM_ENABLE_LIBPFM OFF CACHE BOOL "")
 set(LLVM_ENABLE_BINDINGS OFF CACHE BOOL "")
 set(LLVM_ENABLE_OCAMLDOC OFF CACHE BOOL "")
 set(LLVM_ENABLE_ZLIB OFF CACHE BOOL "")
+set(LLVM_ENABLE_TERMINFO OFF CACHE BOOL "")
 
 # Packing
 set(CPACK_BINARY_DEB OFF CACHE BOOL "")


Test patch:
diff --git a/pyqir-tests/tox.ini b/pyqir-tests/tox.ini
index c3db4749..96086ace 100644
--- a/pyqir-tests/tox.ini
+++ b/pyqir-tests/tox.ini
@@ -14,3 +14,9 @@ deps =
 
 [testenv:test]
 commands = pytest {posargs}
+
+[testenv:all]
+commands = pytest {posargs}
+
+[testenv:allmusl]
+commands = pytest {posargs}