diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 714e388..8a9bccd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,22 +4,54 @@ on: pull_request: workflow_call: +env: + # SBCL is built from source for its linkable runtime; see scripts/install-sbcl.sh. + SBCL_VERSION: "2.6.7" + # Host used to cross-compile SBCL_VERSION on Linux. It cannot be apt's (22.04 + # ships 2.1.11, too old) nor SBCL_VERSION's own binary release, which is linked + # against glibc 2.38 and will not run on 22.04. 2.5.0's binary needs only glibc + # 2.34 and is recent enough to build 2.6.7. + SBCL_BOOTSTRAP_VERSION: "2.5.0" + # libquil depends on three sbcl-librarian fixes (secondary-system FASL bundles, + # library prefix, configurable core name), which live on a branch of Rigetti's + # fork and are proposed upstream as quil-lang/sbcl-librarian#91. + SBCL_LIBRARIAN_REPO: "https://github.com/rigetti/sbcl-librarian.git" # TODO(rigetti/libquil#60) + SBCL_LIBRARIAN_REF: "fix-secondary-system-bundles" # TODO(rigetti/libquil#60) + # magicl selects a BLAS/LAPACK by searching a fixed list of paths. Until it + # knows about OpenBLAS, that search picks a backend that is wrong on arm64 and + # cannot be redirected without hacks -- so libquil had to preload OpenBLAS and + # symlink it over liblapack.dylib. This branch adds OpenBLAS to the search, a + # MAGICL_LAPACK_PATH override, and a startup check on what got loaded; it is + # proposed upstream as quil-lang/magicl#222. + MAGICL_REPO: "https://github.com/rigetti/magicl.git" # TODO(rigetti/libquil#62) + MAGICL_REF: "backend-selection-and-validation" # TODO(rigetti/libquil#62) + # PROCESS-PROTOQUIL lives in quilc's *application*, which libquil does not + # depend on, so libquil vendored a copy. quil-lang/quilc#933 moves it into the + # library and exports it, which is where it belongs. + QUILC_REPO: "https://github.com/rigetti/quilc.git" # TODO(rigetti/libquil#61) + QUILC_REF: "896-protoquil-transform" # TODO(rigetti/libquil#61) + # The Quicklisp dist supplying the Lisp dependencies. + QUICKLISP_VERSION: "2026-01-01" + jobs: build-linux: name: Build libquil.so - runs-on: ubuntu-latest + # Pinned rather than ubuntu-latest: the artifact links libsbcl, so it inherits + # the glibc of whatever built it, and a build on 24.04 fails to load on 22.04 + # with "version `GLIBC_2.38' not found". + runs-on: ubuntu-22.04 steps: - name: root suid tar run: sudo chown root /bin/tar && sudo chmod u+s /bin/tar - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: path: "libquil" - name: Cache SBCL id: cache-sbcl - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | /usr/local/lib/libsbcl.so @@ -27,73 +59,72 @@ jobs: /usr/local/bin/sbcl ~/quicklisp ~/.sbclrc - key: ${{ runner.os }}-build-${{ hashFiles('**/versions') }} + # env.ImageOS is set by the runner image itself (e.g. "ubuntu22", "macos15"); + # unlike runner.os it changes when the image is upgraded, + # so a new image does not reuse a cache built against the old one. + # Note that this variable is stable but not yet documented: + # https://github.com/github/docs/issues/40108 + key: ${{ env.ImageOS }}-${{ runner.arch }}-build-${{ hashFiles('**/versions') }} - if: steps.cache-sbcl.outputs.cache-hit != 'true' name: Install SBCL, libraries, and quicklisp run: | - sudo apt update && sudo apt install -y sbcl build-essential - sudo git clone --single-branch --branch sbcl-2.2.4 https://git.code.sf.net/p/sbcl/sbcl /usr/src/sbcl - cd /usr/src/sbcl && sudo sh make.sh && sudo sh make-shared-library.sh - sudo apt remove -y sbcl - sudo sh install.sh - sudo cp src/runtime/libsbcl.so /usr/local/lib/libsbcl.so - - sudo apt install -y wget - wget -P /tmp/ 'https://beta.quicklisp.org/quicklisp.lisp' - sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/2022-04-01/distinfo.txt\")" - sbcl --noinform --non-interactive --load ~/quicklisp/setup.lisp --eval '(ql-util:without-prompting (ql:add-to-init-file))' - echo "#+quicklisp(push (truename \"$GITHUB_WORKSPACE\") ql:*local-project-directories*)" >> ~/.sbclrc - rm -f /tmp/quicklisp.lisp - cat ~/.sbclrc + sudo apt update && sudo apt install -y build-essential bzip2 wget + # Cross compiling a current SBCL needs a host of roughly the same + # vintage, and 22.04 ships 2.1.11, which fails in make-host-1 with + # "FAILURE-P was set when creating genesis.fasl". Bootstrap from a + # binary release instead. 22.04 is used deliberately, since the artifact + # inherits the glibc of whatever builds it. + wget -q "https://downloads.sourceforge.net/project/sbcl/sbcl/$SBCL_BOOTSTRAP_VERSION/sbcl-$SBCL_BOOTSTRAP_VERSION-x86-64-linux-binary.tar.bz2" -O /tmp/sbcl-bin.tar.bz2 + mkdir -p /tmp/sbcl-bin && tar xf /tmp/sbcl-bin.tar.bz2 -C /tmp/sbcl-bin --strip-components=1 + cd /tmp/sbcl-bin && sudo sh install.sh + "$GITHUB_WORKSPACE/libquil/scripts/install-sbcl.sh" - name: Pull Lisp dependencies - run: | - git clone https://github.com/notmgsk/quilc.git $GITHUB_WORKSPACE/quilc && cd $GITHUB_WORKSPACE/quilc && git checkout bffea7fdb972cc9b3a50b790246b6a3143c88c7a - git clone https://github.com/quil-lang/qvm.git $GITHUB_WORKSPACE/qvm && cd $GITHUB_WORKSPACE/qvm && git checkout 4617625cb6053b1adfd3f7aea9cd2be328b225f6 - git clone https://github.com/quil-lang/magicl.git $GITHUB_WORKSPACE/magicl - git clone https://github.com/stylewarning/cl-permutation $GITHUB_WORKSPACE/cl-permutation - git clone https://github.com/quil-lang/sbcl-librarian.git $GITHUB_WORKSPACE/sbcl-librarian && cd $GITHUB_WORKSPACE/sbcl-librarian && git checkout 04f7e390c777084ac43b8df9e90593a041da2381 + run: "$GITHUB_WORKSPACE/libquil/scripts/clone-lisp-deps.sh" - name: Install quilc dependencies - run: sudo apt update && sudo apt install -y libblas-dev libffi-dev libffi7 liblapack-dev libz-dev gfortran + # libffi-dev pulls in whichever libffi runtime the image ships; naming a + # version explicitly breaks whenever the runner image moves. + run: sudo apt update && sudo apt install -y libblas-dev libffi-dev liblapack-dev libz-dev gfortran - name: Build libquil.so run: | cd $GITHUB_WORKSPACE/libquil ls - sbcl --noinform --non-interactive --eval '(ql:quickload :sbcl-librarian)' - sbcl --dynamic-space-size 8192 --noinform --non-interactive --eval '(ql:quickload :libquil)' make - name: Test run: | - cd $GITHUB_WORKSPACE/libquil/examples/qvm && LD_LIBRARY_PATH=../.. make test - cd $GITHUB_WORKSPACE/libquil/examples/quilc && LD_LIBRARY_PATH=../.. make test + cd $GITHUB_WORKSPACE/libquil/examples/qvm && LD_LIBRARY_PATH=../..:../../runtime make test + cd $GITHUB_WORKSPACE/libquil/examples/quilc && LD_LIBRARY_PATH=../..:../../runtime make test - name: Package files run: | - mkdir -p $GITHUB_WORKSPACE/artifacts/libquil && cp $GITHUB_WORKSPACE/libquil/libquil.h $GITHUB_WORKSPACE/libquil/libquil.so $GITHUB_WORKSPACE/libquil/libquil.core /usr/local/lib/libsbcl.so $GITHUB_WORKSPACE/artifacts/libquil + "$GITHUB_WORKSPACE/libquil/scripts/package-artifact.sh" \ + "$GITHUB_WORKSPACE/libquil" "$GITHUB_WORKSPACE/artifacts" - name: Store artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: linux-amd64 - path: ${{ github.workspace }}/artifacts/* + # A directory (not a glob): upload-artifact v4 preserves structure + # relative to it, keeping the libquil/ prefix the release job unzips. + path: ${{ github.workspace }}/artifacts - build-macos: - name: Build libquil.dylib + build-macos-arm: + name: Build Apple-Silicon libquil.dylib runs-on: macos-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: path: "libquil" - name: Cache SBCL id: cache-sbcl-macos - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | /usr/local/lib/libsbcl.so @@ -101,46 +132,32 @@ jobs: /usr/local/bin/sbcl ~/quicklisp ~/.sbclrc - key: ${{ runner.os }}-build-${{ hashFiles('**/macos-versions' )}} + # See the Linux job for why this keys on env.ImageOS. + key: ${{ env.ImageOS }}-${{ runner.arch }}-build-${{ hashFiles('**/versions') }} - uses: Homebrew/actions/setup-homebrew@master name: Setup homebrew - if: steps.cache-sbcl-macos.outputs.cache-hit != 'true' name: Install SBCL, libraries, and quicklisp + env: + # Homebrew's sbcl is only the cross-compilation host; drop it once the + # build is done so the one we install is the one on PATH. + SBCL_REMOVE_HOST_CMD: "brew remove -f sbcl" run: | - brew install sbcl git - git clone --branch x86-null-tn https://git.code.sf.net/p/sbcl/sbcl /tmp/sbcl - cd /tmp/sbcl && sudo sh make.sh --without-compact-instance-header --without-immobile-space --without-immobile-code && sudo sh make-shared-library.sh - brew remove -f sbcl - sudo sh install.sh - sudo cp src/runtime/libsbcl.so /usr/local/lib/libsbcl.so - - brew install wget - wget -P /tmp/ 'https://beta.quicklisp.org/quicklisp.lisp' - sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/2022-04-01/distinfo.txt\")" - sbcl --noinform --non-interactive --load ~/quicklisp/setup.lisp --eval '(ql-util:without-prompting (ql:add-to-init-file))' - echo "#+quicklisp(push (truename \"$GITHUB_WORKSPACE\") ql:*local-project-directories*)" >> ~/.sbclrc - rm -f /tmp/quicklisp.lisp - cat ~/.sbclrc + brew install sbcl git wget + "$GITHUB_WORKSPACE/libquil/scripts/install-sbcl.sh" - name: Pull Lisp dependencies - run: | - git clone https://github.com/notmgsk/quilc.git $GITHUB_WORKSPACE/quilc && cd $GITHUB_WORKSPACE/quilc && git checkout bffea7fdb972cc9b3a50b790246b6a3143c88c7a - git clone https://github.com/quil-lang/qvm.git $GITHUB_WORKSPACE/qvm && cd $GITHUB_WORKSPACE/qvm && git checkout 4617625cb6053b1adfd3f7aea9cd2be328b225f6 - git clone https://github.com/quil-lang/magicl.git $GITHUB_WORKSPACE/magicl - git clone https://github.com/stylewarning/cl-permutation $GITHUB_WORKSPACE/cl-permutation - git clone https://github.com/quil-lang/sbcl-librarian.git $GITHUB_WORKSPACE/sbcl-librarian && cd $GITHUB_WORKSPACE/sbcl-librarian && git checkout 04f7e390c777084ac43b8df9e90593a041da2381 + run: "$GITHUB_WORKSPACE/libquil/scripts/clone-lisp-deps.sh" - name: Install quilc dependencies - run: brew install lapack openblas libffi gfortran + run: brew install openblas libffi gfortran - name: Build libquil.dylib run: | cd $GITHUB_WORKSPACE/libquil ls - sbcl --noinform --non-interactive --eval '(ql:quickload :sbcl-librarian)' - sbcl --dynamic-space-size 8192 --noinform --non-interactive --eval '(ql:quickload :libquil)' make - name: ls @@ -149,15 +166,18 @@ jobs: - name: Test run: | - cd $GITHUB_WORKSPACE/libquil/examples/qvm && DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/libquil make test - cd $GITHUB_WORKSPACE/libquil/examples/quilc && DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/libquil make test + cd $GITHUB_WORKSPACE/libquil/examples/qvm && DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/libquil:$GITHUB_WORKSPACE/libquil/runtime make test + cd $GITHUB_WORKSPACE/libquil/examples/quilc && DYLD_LIBRARY_PATH=$GITHUB_WORKSPACE/libquil:$GITHUB_WORKSPACE/libquil/runtime make test - name: Package files run: | - mkdir -p $GITHUB_WORKSPACE/artifacts/libquil && cp $GITHUB_WORKSPACE/libquil/libquil.h $GITHUB_WORKSPACE/libquil/libquil.dylib $GITHUB_WORKSPACE/libquil/libquil.core /usr/local/lib/libsbcl.so $GITHUB_WORKSPACE/artifacts/libquil + "$GITHUB_WORKSPACE/libquil/scripts/package-artifact.sh" \ + "$GITHUB_WORKSPACE/libquil" "$GITHUB_WORKSPACE/artifacts" - name: Store artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: macos - path: ${{ github.workspace }}/artifacts/* + name: macos-arm64 + # A directory (not a glob): upload-artifact v4 preserves structure + # relative to it, keeping the libquil/ prefix the release job unzips. + path: ${{ github.workspace }}/artifacts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1dbdde4..3ba870c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,9 @@ name: Release libquil on: + # Dispatching on main cuts a release; dispatching on any other branch cuts a + # prerelease, so libquil-sys can be built against a libquil that is not yet stable. workflow_dispatch: - inputs: - type: - description: Bump versions and trigger a new release. - required: true - default: release - options: - - release jobs: build: @@ -25,40 +20,61 @@ jobs: git config --global user.name "${{ github.triggering_actor }}" git config --global user.email "${{ github.triggering_actor }}@users.noreply.github.com" - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 + # Release the ref this was dispatched on, not the default branch. + ref: ${{ github.ref }} + # Each artifact is downloaded into its own directory: they contain + # identically named files, so a shared directory would have one overwrite + # the other. - name: Download linux artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-amd64 - path: artifacts/ + path: artifacts/linux-amd64/ - name: Archive linux artifacts run: | - cd artifacts && zip linux-amd64.zip libquil/libsbcl.so libquil/libquil.so libquil/libquil.core libquil/libquil.h + # -r rather than a file list: the build job already assembled exactly + # the right directory, and naming files here means a new one is left out + # of the archive silently, surfacing only when someone installs it. + cd artifacts/linux-amd64 && zip -r ../linux-amd64.zip libquil - - name: Download macos artifacts - uses: actions/download-artifact@v3 + - name: Download macos arm64 artifacts + uses: actions/download-artifact@v4 with: - name: macos - path: artifacts/ + name: macos-arm64 + path: artifacts/macos-arm64/ - - name: Archive linux artifacts + - name: Archive macos arm64 artifacts run: | - cd artifacts && zip macos.zip libquil/libsbcl.so libquil/libquil.dylib libquil/libquil.core libquil/libquil.h + cd artifacts/macos-arm64 && zip -r ../macos-arm64.zip libquil - name: List artifacts run: | ls -R artifacts/ unzip -l artifacts/linux-amd64.zip - unzip -l artifacts/macos.zip + unzip -l artifacts/macos-arm64.zip - name: Install Knope - uses: knope-dev/action@v2.0.0 + uses: knope-dev/action@v2.1.2 with: - version: 0.11.0 + version: 0.23.0 # Test before updating, breaking changes likely: https://github.com/knope-dev/action#install-latest-version - name: Release - run: knope release -v + env: + # The Release step authenticates with this rather than an argument. + GITHUB_TOKEN: ${{ secrets.PAT }} + run: | + set -euo pipefail + + # A release off main is a real one; anywhere else it can only be a + # prerelease, which is how libquil gets published for testing while an ABI + # change is still under review. + if [[ "$GITHUB_REF" == "refs/heads/main" ]]; then + knope release -v + else + knope release -v --prerelease-label=rc + fi diff --git a/.gitignore b/.gitignore index 9e5309c..9fad6e2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ libquil.c libquil.core libquil.h libquil.so +libquil.dylib +libquil.py +build/ +runtime/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 3321f30..8ba731c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +## 0.4.0-rc.1 (2026-08-26) + +### Breaking Changes + +- build against modern sbcl-librarian + +### Features + +- install to any prefix, and need root only when the prefix demands it +- add `make install`, and document installing without root + +### Fixes + +- load OpenBLAS into the image before magicl picks a backend +- check for BLAS and LAPACK, and install without sudo when already root +- say what the SUDO_USER guard in install.sh actually checks + +## 0.4.0-rc.0 (2026-08-13) + +### Breaking Changes + +- build against modern sbcl-librarian + +### Fixes + +- load OpenBLAS into the image before magicl picks a backend +- check for BLAS and LAPACK, and install without sudo when already root + ## 0.3.2 (2023-12-14) ### Fixes diff --git a/Makefile b/Makefile index c2d9223..096ac0f 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,106 @@ -.PHONY: all clean +.PHONY: all clean runtime install + +OS := $(shell uname -s) + +SBCL ?= sbcl +CC ?= cc -OS:=$(shell uname -s) ifeq ($(OS), Darwin) - LIBQUIL_TARGET = libquil.dylib - CCFLAGS = -dynamiclib + SHARED_SUFFIX = .dylib + SHARED_FLAGS = -dynamiclib else - LIBQUIL_TARGET = libquil.so - CCFLAGS = -shared + SHARED_SUFFIX = .so + SHARED_FLAGS = -shared endif +LIBQUIL_TARGET = libquil$(SHARED_SUFFIX) + +# libquil uses sbcl-librarian, which splits the work in two: +# +# runtime/libsbcl_librarian$(SHARED_SUFFIX) the SBCL runtime; a constructor +# initializes Lisp when it is loaded +# runtime/libquil.core the Lisp image, holding libquil and +# its dependencies +# libquil$(SHARED_SUFFIX) the generated C bindings +# +# Both halves are built here, so `make` alone still produces a usable artifact. +RUNTIME_DIR := runtime +RUNTIME_LIB := $(RUNTIME_DIR)/libsbcl_librarian$(SHARED_SUFFIX) +CORE := $(RUNTIME_DIR)/libquil.core + +# Passed to scripts/install.sh by `make install`. Override for a prefix you can +# write without sudo, e.g. make install PREFIX="$HOME/.local" +PREFIX ?= /usr/local + +SBCL_LIBRARIAN_DIR := $(shell $(SBCL) --noinform --non-interactive \ + --eval '(require :asdf)' \ + --eval '(princ (namestring (asdf:system-source-directory "sbcl-librarian")))' 2>/dev/null) + +# The linkable SBCL runtime. `make.sh` does not build one by default, and neither +# Homebrew's nor Ubuntu's sbcl package ships it, so it comes from a source tree +# built with +# `make-shared-library.sh`; `install.sh` puts it in SBCL's home directory. Note +# that SBCL names it libsbcl.so on every platform, including macOS. +SBCL_CORE_DIR := $(dir $(shell $(SBCL) --noinform --no-sysinit --no-userinit --non-interactive \ + --eval '(princ (namestring sb-ext:*core-pathname*))' 2>/dev/null)) +LIBSBCL_SEARCH_DIRS := $(SBCL_HOME) $(SBCL_CORE_DIR) $(SBCL_CORE_DIR).. \ + /usr/local/lib /usr/lib /opt/homebrew/lib +LIBSBCL ?= $(firstword $(wildcard \ + $(foreach dir,$(LIBSBCL_SEARCH_DIRS),$(dir)/libsbcl.so $(dir)/libsbcl.dylib))) + +# libsbcl needs zstd for core compression; pkg-config knows where it is on systems +# that install it outside the default search path (Homebrew, in particular). +ZSTD_LIBS ?= $(shell pkg-config --libs libzstd 2>/dev/null || echo -lzstd) + all: $(LIBQUIL_TARGET) -libquil.core libquil.c libquil.h libquil.py: src/libquil.lisp src/qvm/*.lisp src/quilc/*.lisp - sbcl --dynamic-space-size 8192 --load "src/build-image.lisp" +runtime: $(RUNTIME_LIB) + +# One image produces everything Lisp-side: libquil's bindings, the runtime's +# bindings, and the core that backs both. +$(CORE) libquil.c libquil.h $(RUNTIME_DIR)/sbcl_librarian.c: src/libquil.lisp src/qvm/*.lisp src/quilc/*.lisp src/build-image.lisp + mkdir -p $(RUNTIME_DIR) + $(SBCL) --dynamic-space-size 8192 --non-interactive --load "src/build-image.lisp" + # The core is named after the aggregate library that defines its exports + # (libquil-core); publish it beside the runtime under the name the runtime + # was compiled to look for. + mv libquil_core.core $(CORE) + +# SBCL_LIBRARIAN_CORE_NAME is the core name the runtime is compiled to look for +# next to itself. It defaults to sbcl_librarian.core; libquil never builds a core +# by that name, so the runtime is pointed at libquil.core instead. +$(RUNTIME_LIB): $(RUNTIME_DIR)/sbcl_librarian.c +ifeq ($(LIBSBCL),) + @echo "error: no linkable SBCL runtime (libsbcl.so) found." >&2 + @echo "Searched:" >&2 + @$(foreach dir,$(LIBSBCL_SEARCH_DIRS),echo " $(dir)" >&2;) + @echo "Build one from the SBCL source tree that produced $(SBCL) -- a matching" >&2 + @echo "version is not enough, the build IDs have to agree:" >&2 + @echo " sh make.sh --with-sb-linkable-runtime && sh make-shared-library.sh" >&2 + @echo "then re-run make, or pass LIBSBCL=/path/to/libsbcl.so" >&2 + @exit 1 +endif + mkdir -p $(RUNTIME_DIR) + cp $(LIBSBCL) $(RUNTIME_DIR)/libsbcl.so + cp "$(SBCL_LIBRARIAN_DIR)lib/sbcl_librarian_err.h" $(RUNTIME_DIR)/ + cd $(RUNTIME_DIR) && $(CC) $(SHARED_FLAGS) -fPIC -o libsbcl_librarian$(SHARED_SUFFIX) \ + sbcl_librarian.c \ + "$(SBCL_LIBRARIAN_DIR)lib/entry_point.c" \ + -DLIBSBCL_LIBRARIAN_API_BUILD \ + -DSBCL_LIBRARIAN_CORE_NAME='"libquil.core"' \ + -I. -I"$(SBCL_LIBRARIAN_DIR)lib" -L. -lsbcl $(ZSTD_LIBS) + +$(LIBQUIL_TARGET): libquil.c $(CORE) $(RUNTIME_LIB) + $(CC) $(SHARED_FLAGS) -fPIC -o $@ libquil.c \ + -I. -I$(RUNTIME_DIR) -I"$(SBCL_LIBRARIAN_DIR)lib" \ + -L$(RUNTIME_DIR) -lsbcl_librarian -$(LIBQUIL_TARGET): libquil.core libquil.c - gcc $(CCFLAGS) -o $@ libquil.c -lsbcl +# Delegates to the release installer so the layout, the prefix handling and the +# post-install hint have one implementation. --from makes it install this build +# tree rather than downloading. +install: all + scripts/install.sh --from . --prefix "$(PREFIX)" clean: - rm -f libquil.so libquil.c libquil.h libquil.core libquil.py libquil.dylib example + rm -rf $(RUNTIME_DIR) build + rm -f libquil.so libquil.dylib libquil.h libquil.c libquil.core libquil.py example diff --git a/README.md b/README.md index 703950c..f6d09b0 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,10 @@ We provide pre-built binaries for the following systems - Linux x64 (tested specifically on Ubuntu) -- macOS x64 +- macOS aarch64 (Apple Silicon) -Note that ARM is not yet supported on any of the above. +Other platforms can be built from source — see +[Building from source](#building-from-source). ## Requirements @@ -19,56 +20,138 @@ These libraries are required by `libquil`: - `BLAS`, `LAPACK` for linear algebra routines - `libffi` for cross-language execution -- `libz` for compression + +`magicl` loads `BLAS` and `LAPACK` at runtime under their unversioned names, +`libblas.so` and `liblapack.so` (`.dylib` on macOS). Those names come from the +*development* packages, so the `-dev` packages have to be installed; +a runtime-only package such as Debian's `libblas3` will not work. On systems which use `apt` to install packages (e.g. Ubuntu), these libraries can be installed with the command ``` -sudo apt install libblas-dev libffi-dev libffi7 liblapack-dev libz-dev +sudo apt install libblas-dev libffi-dev liblapack-dev ``` On systems which use `brew` to install packages (e.g macOS), these libraries can be installed with the command ``` -brew install lapack openblas libffi +brew install openblas libffi ``` +On macOS, OpenBLAS supplies both `BLAS` and `LAPACK`. + ## Automated installation A script is provided to automate installation of the library. It will detect the host operating system and install the library to an appropriate location. A version identifier can be provided to install a particular version of the library. If no version is provided, the latest version of the library will be installed. -> Note: the installer script requires `sudo` to install the library into the system. +> Note: by default the library is installed into `/usr/local`, which needs root. Pass +> `--prefix` to install somewhere you can write instead — see [Installing without +> root](#installing-without-root). -> Note: on macOS, `sudo` is also used to mark the library files as trusted. The files themselves are not signed and macOS will, by default, flag them as insecure. +> Note: on macOS the installer also marks the library files as trusted. The files +> themselves are not signed and macOS will, by default, flag them as insecure. Run the following command ``` -curl https://raw.githubusercontent.com/rigetti/libquil/main/install.sh | bash +curl https://raw.githubusercontent.com/rigetti/libquil/main/scripts/install.sh | sudo bash ``` If you would like to install a particular version of the library, run the following command ``` -curl https://raw.githubusercontent.com/rigetti/libquil/main/install.sh | bash -s +curl https://raw.githubusercontent.com/rigetti/libquil/main/scripts/install.sh | sudo bash -s ``` replacing `` with the desired version, e.g. `0.3.0`. +By default the installer checks for the libraries above and stops if any are +missing, leaving it to you to install them. Pass `--install-deps` to have it +install them for you with `apt` or Homebrew, skipping any that are already +present: + +``` +curl https://raw.githubusercontent.com/rigetti/libquil/main/scripts/install.sh | sudo bash -s -- --install-deps +``` + +It requires `apt` on Linux and Homebrew on macOS, and fails if neither is +available. `install.sh --help` lists the options. + +### Installing without root + +`--prefix` chooses where the library goes, and root is needed only when that +location is not writable: + +``` +curl https://raw.githubusercontent.com/rigetti/libquil/main/scripts/install.sh | bash -s -- --prefix "${HOME}/.local" +``` + +A prefix other than `/usr/local` is not on any default search path. The installer +finishes by printing the variables that make it usable — `LIBQUIL_SRC_PATH` and +`LIBQUIL_LIB_PATH` to build against it, and `DYLD_LIBRARY_PATH` (`LD_LIBRARY_PATH` +on Linux) to run. + ## Manual installation If you would like to manually install the library (for example in the case where you want to install the library to a non-standard location), find the appropriate version and operating system from the [releases page](https://github.com/rigetti/libquil/releases). Within the `.zip` archive you will find the library and header files that are required to use the library. Move these into your file system. +## Building from source + +Building requires an SBCL with a *linkable runtime* (`libsbcl.so`). +SBCL does not build one by default, +and neither Homebrew's `sbcl` bottle nor Ubuntu's `sbcl` ships one, +so SBCL itself has to be built from source. + +```bash +git clone --branch sbcl-2.6.7 https://git.code.sf.net/p/sbcl/sbcl +cd sbcl +sh make.sh --with-sb-linkable-runtime && sh make-shared-library.sh && sh install.sh +``` + +SBCL's `install.sh` puts both the `sbcl` binary and `libsbcl.so` in SBCL's home +directory, where libquil's `Makefile` finds the latter automatically. To use a +runtime from elsewhere, pass it explicitly: + +```bash +make LIBSBCL=/path/to/sbcl/src/runtime/libsbcl.so +``` + +> Note: the `sbcl` that builds libquil and the `libsbcl.so` it is linked against +> must come from the *same build*, not merely the same version. SBCL stamps a build +> ID into both, and a mismatch is only caught at runtime, as +> `core was built for runtime "..." but this is "..."`. Installing SBCL from source +> as above satisfies this; leaving a packaged `sbcl` earlier on `PATH` than the one +> just installed does not. + +The Lisp dependencies (`quilc`, `qvm`, `magicl`, `sbcl-librarian`) are expected +in your Quicklisp local-projects directory. Then: + +```bash +make +``` + +To install what you just built, into `/usr/local` or anywhere you can write: + +```bash +make install # needs root +make install PREFIX="${HOME}/.local" # does not +``` + +For a prefix other than `/usr/local` this prints the `LIBQUIL_SRC_PATH`, +`LIBQUIL_LIB_PATH` and library-path variables to build and run against it. + # C API Reference -## Libquil functions and types +These come from `sbcl_librarian_err.h`, which is installed alongside `libquil.h`. -- `libquil_error_t` - Enum which indicates whether a function call was successful (`LIBQUIL_ERROR_SUCCESS`) or not (`LIBQUIL_ERROR_FAIL`). Most functions will have this as their return type. -- `libquil_error_t libquil_error(char** error_msg)` - Used to retrieve the last error message from libquil. +- `lisp_err_t` + Enum which indicates whether a function call was successful (`LISP_ERR_SUCCESS`) or not (`LISP_ERR_FAILURE`, `LISP_ERR_BUG`, `LISP_ERR_FATAL`). Most functions will have this as their return type. +- `lisp_err_t get_error_message(char** error_msg)` + Used to retrieve the last error message. - When any error is encountered by libquil, it will be stored in memory. A subsequent call to `libquil_error` will return that error message. After calling `libquil_error`, the error is cleared from memory such that immediately calling `libquil_error` after a previous call will return an empty string (indicating no errors since the previous error). + When any error is encountered, it will be stored in memory. A subsequent call to `get_error_message` will return that error message. After calling `get_error_message`, the error is cleared from memory such that immediately calling `get_error_message` after a previous call will return an empty string (indicating no errors since the previous error). +- `lisp_err_t enable_backtrace(int enabled)` + Turns backtrace capture on the error message on or off. ## Quilc documentation @@ -97,32 +180,32 @@ If you would like to manually install the library (for example in the case where ### Functions -- `libquil_error quilc_get_version_info(quilc_version_info *version_info)` +- `lisp_err_t quilc_get_version_info(quilc_version_info *version_info)` Allocates a `quilc_version_info` object and stores the pointer to it in `version_info` See [examples/quilc/version.c](examples/quilc/version.c) -- `libquil_error quilc_version_info_version(quilc_version_info version_info, char** version)` +- `lisp_err_t quilc_version_info_version(quilc_version_info version_info, char** version)` Allocates memory which indicates the version string of Quilc and stores the pointer to it in `version` See [examples/quilc/version.c](examples/quilc/version.c) -- `libquil_error quilc_version_info_githash(quilc_version_info version_info, char** githash)` +- `lisp_err_t quilc_version_info_githash(quilc_version_info version_info, char** githash)` Allocates memory which indicates the githash string of Quilc and stores the pointer to it in `version` See [examples/quilc/version.c](examples/quilc/version.c) -- `libquil_error_t quilc_parse_quil(char* program, quil_program *result)` +- `lisp_err_t quilc_parse_quil(char* program, quil_program *result)` Parses the `program` string and stores it in a `quil_program` -- `libquil_error_t quilc_print_program(quil_program program)` +- `lisp_err_t quilc_print_program(quil_program program)` Prints the `program` to stdout -- `libquil_error_t quilc_program_string(quil_program program, char** result)` +- `lisp_err_t quilc_program_string(quil_program program, char** result)` Allocates and populates a `char*` which is the given `program`'s string representation -- `libquil_error_t quilc_compile_quil(quil_program program, chip_specification chip_spec, quil_program* compiled_program)` +- `lisp_err_t quilc_compile_quil(quil_program program, chip_specification chip_spec, quil_program* compiled_program)` Compiles the `program` for the provided chip specification and stores it in a `quil_program` -- `libquil_error_t quilc_compile_protoquil(quil_program program, chip_specification chip_spec, quil_program* compiled_program)` +- `lisp_err_t quilc_compile_protoquil(quil_program program, chip_specification chip_spec, quil_program* compiled_program)` Compiles the (protoquil) `program` for the provided chip specification and stores it in a `quil_program` -- `libquil_error_t quilc_conjugate_pauli_by_clifford(void* pauli_indices, int pauli_indices_len, void* pauli_terms, int pauli_terms_len, quil_program clifford, void* phase, void* pauli)` +- `lisp_err_t quilc_conjugate_pauli_by_clifford(void* pauli_indices, int pauli_indices_len, void* pauli_terms, int pauli_terms_len, quil_program clifford, void* phase, void* pauli)` Conjugates a Pauli operator by a Clifford operator After having called `quilc_conjugate_pauli_by_clifford`: @@ -130,7 +213,7 @@ If you would like to manually install the library (for example in the case where - `phase` will be the encoded global phase factor - `pauli` will be a string description of the resulting encoded Pauli operator -- `libquil_error_t quilc_generate_rb_sequence(int depth, int qubits, void* gateset_ptr, int gateset_len, int seed, void* interleaver, void* results_ptr, void* result_lens_ptr)` +- `lisp_err_t quilc_generate_rb_sequence(int depth, int qubits, void* gateset_ptr, int gateset_len, int seed, void* interleaver, void* results_ptr, void* result_lens_ptr)` Generates a randomized benchmarking sequence After having called `quilc_generate_rb_sequence`: @@ -140,12 +223,12 @@ If you would like to manually install the library (for example in the case where See [examples/quilc/generate-rb-sequence.c](examples/quilc/generate-rb-sequence.c) -- `libquil_error_t quilc_build_nq_linear_chip(int n, chip_specification* chip_spec)` +- `lisp_err_t quilc_build_nq_linear_chip(int n, chip_specification* chip_spec)` Builds a linearly-connected `n`-qubit chip specification and stores it in `chip_spec` -- `libquil_error_t quilc_chip_spec_from_isa_descriptor(char* isa_json, chip_specification* chip_spec)` +- `lisp_err_t quilc_chip_spec_from_isa_descriptor(char* isa_json, chip_specification* chip_spec)` Builds an arbitrary chip specification using the JSON-encoded ISA description -- `libquil_error_t quilc_program_memory_type(quil_program program, char* region_name, program_memory_type* region_type)` +- `lisp_err_t quilc_program_memory_type(quil_program program, char* region_name, program_memory_type* region_type)` Returns the `quilc_program_memory_type` for the given memory region ## QVM documentation @@ -154,67 +237,67 @@ If you would like to manually install the library (for example in the case where - `qvm_multishot_addresses` An opaque pointer to a QVM multishot addresses object -- `libquil_error_t qvm_multishot_result` +- `lisp_err_t qvm_multishot_result` An opaque pointer to a QVM multishot result object -- `libquil_error_t qvm_version_info` +- `lisp_err_t qvm_version_info` An opaque pointer to a QVM version info object ### Functions -- `libquil_error_t qvm_get_version_info(qvm_version_info* version_info)` +- `lisp_err_t qvm_get_version_info(qvm_version_info* version_info)` Get a new `qvm_version_info` -- `libquil_error_t qvm_version_info_version(qvm_version_info version_info, char** version)` +- `lisp_err_t qvm_version_info_version(qvm_version_info version_info, char** version)` Populate a string at `*version` which has the QVM version -- `libquil_error_t qvm_version_info_githash(qvm_version_info version_info, char** version)` +- `lisp_err_t qvm_version_info_githash(qvm_version_info version_info, char** version)` Populate a string at `*githash` which has the QVM githash -- `libquil_error_t qvm_multishot_addresses_new(qvm_multishot_addresses* addresses)` +- `lisp_err_t qvm_multishot_addresses_new(qvm_multishot_addresses* addresses)` Allocate memory for the `qvm_multishot_addresses` object -- `libquil_error_t qvm_multishot_addresses_set(qvm_multishot_addresses addresses, char* name, void* indices, int len)` +- `lisp_err_t qvm_multishot_addresses_set(qvm_multishot_addresses addresses, char* name, void* indices, int len)` Set the indices of a memory region which should be collected when using `qvm_multishot`. For example, if your register was named `ro` and you wanted to get indices 0 and 2, you would provide `"ro"` for `name` and `{0, 2}` for `indices`. (`len` is the length of `indices`.) -- `libquil_error_t qvm_multishot_addresses_get_all(qvm_multishot_addresses addresses, char* name, int shot_index, void\*\* results, int* results_len) +- `lisp_err_t qvm_multishot_addresses_get_all(qvm_multishot_addresses addresses, char* name, int shot_index, void\*\* results, int* results_len) Request all results for the given memory address. On return, `*results` will be an array of length `results_len`. The specific data type contained in the array is to be interpreted by the caller. -- `libquil_error_t qvm_multishot(quil_program program, qvm_multishot_addresses addresses, int trials, double* gate_noise, double* measurement_noise, qvm_multishot_result *result)` +- `lisp_err_t qvm_multishot(quil_program program, qvm_multishot_addresses addresses, int trials, double* gate_noise, double* measurement_noise, qvm_multishot_result *result)` Execute `program` on the QVM `trials`-number of times, collecting the `addresses` into `result`. `gate_noise` and `measurement_noise` are length-3 arrays which affect gate execution and measurement respectively. One or both can be `NULL` which indicates no noise is to be applied. See [examples/qvm/multishot.c](examples/qvm/multishot.c) -- `libquil_error_t qvm_multishot_result_get(qvm_multishot_result qvm_result, char* region_name, int region_index, void* result)` +- `lisp_err_t qvm_multishot_result_get(qvm_multishot_result qvm_result, char* region_name, int region_index, void* result)` Get the measurement results for `region_index` in `region_name`, storing the data in the pre-allocated `result` `result` should be a pointer to memory which has been allocated to store `N` integers, where `N` is the `trials` value used for `qvm_multishot`. See [examples/qvm/multishot.c](examples/qvm/multishot.c) -- `libquil_error_t qvm_multishot_measure(quil_program program, void* qubits, int n_qubits, int trials, void* result)` +- `lisp_err_t qvm_multishot_measure(quil_program program, void* qubits, int n_qubits, int trials, void* result)` Execute `program` on the QVM `trials`-number of times, storing measurement results for the specified `qubits` into `result` `result` should be a pointer to memory which has been allocated to store `n_qubits * trials` integers. This memory should be interpreted as the two-dimensional array whose outer dimension is `trials` and whose inner dimension is `n_qubits`. See [examples/qvm/multishot-measure.c](examples/qvm/multishot-measure.c) -- `libquil_error_t qvm_expectation(quil_program state_prep, void* operators, int n_operators, void* result)` +- `lisp_err_t qvm_expectation(quil_program state_prep, void* operators, int n_operators, void* result)` Calculate the expectation value `` where `P` is the provided state preparation program `state_prep` and `O` is an operator, for each `O` in `operators`. `result` should be a pointer to memory that has been allocated to store `n_operators` double-precision floats. See [examples/qvm/expectation.c](examples/qvm/expectation.c) -- `libquil_error_t qvm_wavefunction(quil_program program, void* result)` +- `lisp_err_t qvm_wavefunction(quil_program program, void* result)` Execute `program` on the QVM and return the associated wavefunction `result` should be a pointer to memory that has been allocated to store `2 * N_q^2` double-precision floats where `N_q` is the number of qubits used in the program. The factor of 2 is here to account for the fact that the wavefunction is a complex-valued vector, thus to represent it in C we need two values: one for the real part and one for the imaginary part. `result` can be interpreted as a two-dimensional array whose outer dimension (size `N_q^2`) is the wavefunction vector index, and whose inner dimension (size `2`) is the pair of values making up the complex value. See [examples/qvm/wavefunction.c](examples/qvm/wavefunction.c) -- `libquil_error_t qvm_probabilities(quil_program program, void* results_ptr)` +- `lisp_err_t qvm_probabilities(quil_program program, void* results_ptr)` Execute `program` on the QVM and return the wavefunction probabilities `result` should be a pointer to memory that has been allocated to store `N_q^2` double-precision floats where `N_q` is the number of qubits used in the program. Each index `i` in `result` is the probability of finding the wavefunction in the `|i>` state. diff --git a/examples/quilc/Makefile b/examples/quilc/Makefile index 8e49983..691694e 100644 --- a/examples/quilc/Makefile +++ b/examples/quilc/Makefile @@ -1,12 +1,10 @@ TEST_SRCS = $(wildcard *.c) TEST_OBJS = $(TEST_SRCS:.c=) BUILD_DIR = ../.. -CCFLAGS = -lsbcl -lquil -L$(BUILD_DIR) -I$(BUILD_DIR) - -OS:=$(shell uname -s) -ifeq ($(OS), Darwin) - CCFLAGS += -pagezero_size 0x100000 -endif +RUNTIME_DIR = $(BUILD_DIR)/runtime +# libquil is the generated bindings; libsbcl_librarian is the runtime that brings up +# the Lisp image (and supplies get_error_message). +CCFLAGS = -lquil -lsbcl_librarian -L$(BUILD_DIR) -L$(RUNTIME_DIR) -I$(BUILD_DIR) -I$(RUNTIME_DIR) .PHONY: all clean diff --git a/examples/quilc/compile-protoquil.c b/examples/quilc/compile-protoquil.c index e8737c0..021bb6a 100644 --- a/examples/quilc/compile-protoquil.c +++ b/examples/quilc/compile-protoquil.c @@ -2,20 +2,19 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" int main() { - init("../../libquil.core"); - quil_program h, result; chip_specification chip; - if (quilc_parse_quil("CNOT 0 1", &h) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("CNOT 0 1", &h) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } - if (quilc_build_nq_linear_chip(2, &chip) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_build_nq_linear_chip(2, &chip) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to build chip"); exit(1); } @@ -23,13 +22,13 @@ int main() { quilc_compilation_metadata metadata; if (quilc_compile_protoquil(h, chip, &metadata, &result) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to compile program"); exit(1); } int *final_rewiring, final_rewiring_len; - if (quilc_compilation_metadata_get_final_rewiring(metadata, &final_rewiring, &final_rewiring_len) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_final_rewiring(metadata, &final_rewiring, &final_rewiring_len) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to final rewiring from metadata"); exit(1); } @@ -41,7 +40,7 @@ int main() { int gate_depth, multiqubit_gate_depth, gate_volume, topological_swaps, present; double program_duration, program_fidelity, qpu_runtime_estimation; - if (quilc_compilation_metadata_get_gate_depth(metadata, &gate_depth, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_gate_depth(metadata, &gate_depth, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get gate depth"); exit(1); } @@ -49,7 +48,7 @@ int main() { printf("gate depth is %d\n", gate_depth); } - if (quilc_compilation_metadata_get_multiqubit_gate_depth(metadata, &multiqubit_gate_depth, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_multiqubit_gate_depth(metadata, &multiqubit_gate_depth, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get multiqubit gate depth"); exit(1); } @@ -57,7 +56,7 @@ int main() { printf("multiqubit gate depth is %d\n", gate_depth); } - if (quilc_compilation_metadata_get_gate_volume(metadata, &gate_volume, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_gate_volume(metadata, &gate_volume, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get gate volume"); exit(1); } @@ -65,7 +64,7 @@ int main() { printf("gate volume is %d\n", gate_volume); } - if (quilc_compilation_metadata_get_topological_swaps(metadata, &topological_swaps, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_topological_swaps(metadata, &topological_swaps, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get topological swaps"); exit(1); } @@ -73,7 +72,7 @@ int main() { printf("topological swaps is %d\n", topological_swaps); } - if (quilc_compilation_metadata_get_program_duration(metadata, &program_duration, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_program_duration(metadata, &program_duration, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get program duration"); exit(1); } @@ -81,7 +80,7 @@ int main() { printf("program duration is %f\n", program_duration); } - if (quilc_compilation_metadata_get_program_fidelity(metadata, &program_fidelity, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_program_fidelity(metadata, &program_fidelity, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get program fidelity "); exit(1); } @@ -89,7 +88,7 @@ int main() { printf("program fidelity is %f\n", program_fidelity); } - if (quilc_compilation_metadata_get_qpu_runtime_estimation(metadata, &qpu_runtime_estimation, &present) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_compilation_metadata_get_qpu_runtime_estimation(metadata, &qpu_runtime_estimation, &present) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to get qpu runtime estimation "); exit(1); } diff --git a/examples/quilc/conjugate-pauli-by-clifford.c b/examples/quilc/conjugate-pauli-by-clifford.c index a037188..cd5bcc9 100644 --- a/examples/quilc/conjugate-pauli-by-clifford.c +++ b/examples/quilc/conjugate-pauli-by-clifford.c @@ -2,13 +2,12 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" int main() { - init("../../libquil.core"); - quil_program clifford; - if (quilc_parse_quil("H 0", &clifford) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("H 0", &clifford) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } @@ -20,7 +19,7 @@ int main() { if (quilc_conjugate_pauli_by_clifford( pauli_indices, 1, pauli_terms, 1, clifford, &result_phase, - &result_pauli) != LIBQUIL_ERROR_SUCCESS) { + &result_pauli) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call quilc_conjugate_by_pauli"); exit(1); } diff --git a/examples/quilc/error.h b/examples/quilc/error.h index 87e0bf5..f20816f 100644 --- a/examples/quilc/error.h +++ b/examples/quilc/error.h @@ -1,5 +1,4 @@ #define LIBQUIL_ERROR(msg) \ char* libquil_err; \ - libquil_error(&libquil_err); \ + get_error_message(&libquil_err); \ printf("%s: %s\n", msg, libquil_err); - diff --git a/examples/quilc/generate-rb-sequence.c b/examples/quilc/generate-rb-sequence.c index e295e67..0ca9555 100644 --- a/examples/quilc/generate-rb-sequence.c +++ b/examples/quilc/generate-rb-sequence.c @@ -2,23 +2,22 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" int main() { - init("../../libquil.core"); - quil_program phase, h, y; int depth = 3, seed = 42, qubits = 1; - if (quilc_parse_quil("PHASE(pi/2) 0", &phase) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("PHASE(pi/2) 0", &phase) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } - if (quilc_parse_quil("H 0", &h) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("H 0", &h) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } - if (quilc_parse_quil("Y 0", &y) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("Y 0", &y) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } @@ -31,7 +30,7 @@ int main() { if (quilc_generate_rb_sequence(depth, qubits, gateset, 3, &seed, &interleaver, &results, - result_lens) != LIBQUIL_ERROR_SUCCESS) { + result_lens) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to generate RB sequence"); exit(1); } diff --git a/examples/quilc/version.c b/examples/quilc/version.c index 5fb4c90..bd5671f 100644 --- a/examples/quilc/version.c +++ b/examples/quilc/version.c @@ -2,6 +2,7 @@ #include #include +#include "sbcl_librarian.h" #include "libquil.h" #include "error.h" @@ -11,22 +12,20 @@ void die(char *msg) { } int main() { - init("../../libquil.core"); - quilc_version_info version_info; - if (quilc_get_version_info(&version_info) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_get_version_info(&version_info) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call quilc_get_version_info"); exit(1); } char* version; char* githash; - if (quilc_version_info_version(version_info, &version) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_version_info_version(version_info, &version) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call quilc_version_info_version"); exit(1); } - if (quilc_version_info_githash(version_info, &githash) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_version_info_githash(version_info, &githash) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call quilc_version_info_githash"); exit(1); } diff --git a/examples/qvm/Makefile b/examples/qvm/Makefile index 8a051b8..c0b3e7d 100644 --- a/examples/qvm/Makefile +++ b/examples/qvm/Makefile @@ -1,12 +1,11 @@ TEST_SRCS = $(wildcard *.c) TEST_OBJS = $(TEST_SRCS:.c=) BUILD_DIR = ../.. -CCFLAGS = -lsbcl -lquil -L$(BUILD_DIR) -I$(BUILD_DIR) - -OS:=$(shell uname -s) -ifeq ($(OS), Darwin) - CCFLAGS += -pagezero_size 0x100000 -endif +RUNTIME_DIR = $(BUILD_DIR)/runtime +# libquil is the generated bindings; libsbcl_librarian is the runtime that brings up +# the Lisp image (and supplies get_error_message). No -pagezero_size: the image is +# mapped by the runtime library, not by this executable. +CCFLAGS = -lquil -lsbcl_librarian -L$(BUILD_DIR) -L$(RUNTIME_DIR) -I$(BUILD_DIR) -I$(RUNTIME_DIR) .PHONY: all clean diff --git a/examples/qvm/error.h b/examples/qvm/error.h index 87e0bf5..f20816f 100644 --- a/examples/qvm/error.h +++ b/examples/qvm/error.h @@ -1,5 +1,4 @@ #define LIBQUIL_ERROR(msg) \ char* libquil_err; \ - libquil_error(&libquil_err); \ + get_error_message(&libquil_err); \ printf("%s: %s\n", msg, libquil_err); - diff --git a/examples/qvm/expectation.c b/examples/qvm/expectation.c index 249976e..3aedd20 100644 --- a/examples/qvm/expectation.c +++ b/examples/qvm/expectation.c @@ -3,6 +3,7 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" void die(char *msg) { @@ -15,7 +16,7 @@ double do_expectation(quil_program state_prep, quil_program operator) { double expectations[1] = {0}; if (qvm_expectation(state_prep, operators, 1, NULL, &expectations) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_expectation"); exit(1); } @@ -24,21 +25,19 @@ double do_expectation(quil_program state_prep, quil_program operator) { } int main(int argc, char **argv) { - init("../../libquil.core"); - quil_program i; quil_program z; quil_program x; - if (quilc_parse_quil("I 0", &i) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("I 0", &i) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } - if (quilc_parse_quil("Z 0", &z) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("Z 0", &z) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } - if (quilc_parse_quil("X 0", &x) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("X 0", &x) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } diff --git a/examples/qvm/multishot-measure.c b/examples/qvm/multishot-measure.c index 36359c8..72f9434 100644 --- a/examples/qvm/multishot-measure.c +++ b/examples/qvm/multishot-measure.c @@ -3,6 +3,7 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" void die(char *msg) { @@ -11,13 +12,11 @@ void die(char *msg) { } int main(int argc, char **argv) { - init("../../libquil.core"); - quil_program program; char *source = "X 0; X 2"; - if (quilc_parse_quil(source, &program) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil(source, &program) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call quilc_parse_quil"); exit(1); } @@ -29,7 +28,7 @@ int main(int argc, char **argv) { int results[num_trials][n_qubits]; memset(results, 0, num_trials * n_qubits * sizeof(int)); if (qvm_multishot_measure(program, qubits, n_qubits, num_trials, NULL, - &results) != LIBQUIL_ERROR_SUCCESS) { + &results) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot_measure"); exit(1); } diff --git a/examples/qvm/multishot.c b/examples/qvm/multishot.c index cd88eac..fd3c804 100644 --- a/examples/qvm/multishot.c +++ b/examples/qvm/multishot.c @@ -3,6 +3,7 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" void die(char *msg) { @@ -26,20 +27,20 @@ void multishot_with_explicit_ro_indices() { "ro[1]; MEASURE %d ro[2]", q0, q0 + 1, q0 + 2, q0, q0 + 1, q0 + 2); - if (quilc_parse_quil(source, &program) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil(source, &program) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } qvm_multishot_addresses addresses; - if (qvm_multishot_addresses_new(&addresses) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_multishot_addresses_new(&addresses) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to create addresses"); exit(1); } int indices[3] = {0, 1, 2}; if (qvm_multishot_addresses_set(addresses, "ro", indices, 3) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to set address indices"); exit(1); } @@ -47,7 +48,7 @@ void multishot_with_explicit_ro_indices() { qvm_multishot_result qvm_res; int num_trials = 10; if (qvm_multishot(program, addresses, num_trials, NULL, NULL, NULL, - &qvm_res) != LIBQUIL_ERROR_SUCCESS) { + &qvm_res) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot"); exit(1); } @@ -55,7 +56,7 @@ void multishot_with_explicit_ro_indices() { for (int i = 0; i < num_trials; i++) { char vals[3]; if (qvm_multishot_result_get(qvm_res, "ro", i, &vals) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot_result_get"); exit(1); } @@ -76,19 +77,19 @@ void multishot_with_implicit_ro_indices() { "ro[1]; MEASURE %d ro[2]", q0, q0 + 1, q0 + 2, q0, q0 + 1, q0 + 2); - if (quilc_parse_quil(source, &program) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil(source, &program) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } qvm_multishot_addresses addresses; - if (qvm_multishot_addresses_new(&addresses) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_multishot_addresses_new(&addresses) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to create addresses"); exit(1); } if (qvm_multishot_addresses_set_all(addresses, "ro") != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to set address indices"); exit(1); } @@ -97,7 +98,7 @@ void multishot_with_implicit_ro_indices() { int num_trials = 10; double gate_noise[] = {0.0, 0.0, 0.0}; if (qvm_multishot(program, addresses, num_trials, NULL, NULL, NULL, - &qvm_res) != LIBQUIL_ERROR_SUCCESS) { + &qvm_res) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot"); exit(1); } @@ -107,7 +108,7 @@ void multishot_with_implicit_ro_indices() { char *vals; if (qvm_multishot_result_get_all(qvm_res, "ro", i, &vals, &len) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot_result_get_all"); exit(1); } @@ -131,20 +132,20 @@ void multishot_with_noise() { "ro[1]; MEASURE %d ro[2]", q0, q0 + 1, q0 + 2, q0, q0 + 1, q0 + 2); - if (quilc_parse_quil(source, &program) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil(source, &program) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } qvm_multishot_addresses addresses; - if (qvm_multishot_addresses_new(&addresses) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_multishot_addresses_new(&addresses) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to create addresses"); exit(1); } int indices[3] = {0, 1, 2}; if (qvm_multishot_addresses_set(addresses, "ro", indices, 3) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to set address indices"); exit(1); } @@ -155,7 +156,7 @@ void multishot_with_noise() { double measurement_noise[] = {0.1, 0.0, 0.0}; if (qvm_multishot(program, addresses, num_trials, gate_noise, measurement_noise, NULL, - &qvm_res) != LIBQUIL_ERROR_SUCCESS) { + &qvm_res) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot"); exit(1); } @@ -163,7 +164,7 @@ void multishot_with_noise() { for (int i = 0; i < num_trials; i++) { char vals[3]; if (qvm_multishot_result_get(qvm_res, "ro", i, &vals) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_multishot_result_get"); exit(1); } @@ -176,8 +177,6 @@ void multishot_with_noise() { } int main(int argc, char **argv) { - init("../../libquil.core"); - multishot_with_explicit_ro_indices(); multishot_with_implicit_ro_indices(); multishot_with_noise(); diff --git a/examples/qvm/probabilities.c b/examples/qvm/probabilities.c index 39f85da..af37995 100644 --- a/examples/qvm/probabilities.c +++ b/examples/qvm/probabilities.c @@ -3,6 +3,7 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" void die(char *msg) { @@ -11,11 +12,9 @@ void die(char *msg) { } int main(int argc, char **argv) { - init("../../libquil.core"); - quil_program program; - if (quilc_parse_quil("H 0; CNOT 0 1", &program) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("H 0; CNOT 0 1", &program) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } @@ -25,7 +24,7 @@ int main(int argc, char **argv) { double wavefunction[n_probabilities]; memset(wavefunction, 0, n_probabilities * sizeof(double)); - if (qvm_probabilities(program, NULL, wavefunction) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_probabilities(program, NULL, wavefunction) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_probabilities"); exit(1); } diff --git a/examples/qvm/version.c b/examples/qvm/version.c index eeba2de..7fbe5af 100644 --- a/examples/qvm/version.c +++ b/examples/qvm/version.c @@ -2,26 +2,25 @@ #include #include +#include "sbcl_librarian.h" #include "libquil.h" #include "error.h" int main(int argc, char **argv) { - init("../../libquil.core"); - qvm_version_info version_info; - if (qvm_get_version_info(&version_info) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_get_version_info(&version_info) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call quilc_get_version_info"); exit(1); } char* version; char* githash; - if (qvm_version_info_version(version_info, &version) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_version_info_version(version_info, &version) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_version_info_version"); exit(1); } - if (qvm_version_info_githash(version_info, &githash) != LIBQUIL_ERROR_SUCCESS) { + if (qvm_version_info_githash(version_info, &githash) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_version_info_githash"); exit(1); } diff --git a/examples/qvm/wavefunction.c b/examples/qvm/wavefunction.c index f59a37c..e618a7d 100644 --- a/examples/qvm/wavefunction.c +++ b/examples/qvm/wavefunction.c @@ -4,6 +4,7 @@ #include #include "error.h" +#include "sbcl_librarian.h" #include "libquil.h" void die(char *msg) { @@ -12,11 +13,9 @@ void die(char *msg) { } int main(int argc, char **argv) { - init("../../libquil.core"); - quil_program program; - if (quilc_parse_quil("X 0; I 1", &program) != LIBQUIL_ERROR_SUCCESS) { + if (quilc_parse_quil("X 0; I 1", &program) != LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to parse quil"); exit(1); } @@ -25,7 +24,7 @@ int main(int argc, char **argv) { double *wavefunction; int seed = 0; if (qvm_wavefunction(program, &seed, &wavefunction, &wavefunction_len) != - LIBQUIL_ERROR_SUCCESS) { + LISP_ERR_SUCCESS) { LIBQUIL_ERROR("failed to call qvm_wavefunction"); exit(1); } diff --git a/install.sh b/install.sh deleted file mode 100755 index 9012dcf..0000000 --- a/install.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash - -set -u - -err() { - printf "%s\n" "$@" >&2 - exit 1 -} - -if [[ "$(uname -p)" =~ "arm" ]]; then - err "Unsupported CPU architecture: $(uname -p)." -fi - -if [[ -n "${1-}" ]] -then - LIBQUIL_URL_PREFIX="https://github.com/rigetti/libquil/releases/download/v${1}" -else - LIBQUIL_URL_PREFIX="https://github.com/rigetti/libquil/releases/latest/download" -fi - -OS="$(uname)" -if [[ "${OS}" == "Linux" ]] -then - IS_LINUX=1 - LIBQUIL_RELEASE_FILE="linux-amd64.zip" -elif [[ "${OS}" == "Darwin" ]] -then - LIBQUIL_RELEASE_FILE="macos.zip" -else - err "Unsupported operating system. Supported operating systems are Linux and macOS." -fi - -LIBQUIL_RELEASE_URL="${LIBQUIL_URL_PREFIX}/${LIBQUIL_RELEASE_FILE}" -LIBQUIL_TEMP_DIR="$(mktemp -d)" -LIBQUIL_LIB_PREFIX="/usr/local/lib" -LIBQUIL_INCLUDE_PREFIX="/usr/local/include/libquil" - -pushd "${LIBQUIL_TEMP_DIR}" || exit -curl -L "${LIBQUIL_RELEASE_URL}" -o "${LIBQUIL_RELEASE_FILE}" -unzip "${LIBQUIL_RELEASE_FILE}" - -if [[ -n "${IS_LINUX-}" ]] -then - sudo cp libquil/libquil.so libquil/libquil.core libquil/libsbcl.so "${LIBQUIL_LIB_PREFIX}" - sudo mkdir -p "${LIBQUIL_INCLUDE_PREFIX}" - sudo cp libquil/libquil.h "${LIBQUIL_INCLUDE_PREFIX}" - sudo ldconfig -else - sudo cp libquil/libquil.dylib libquil/libquil.core libquil/libsbcl.so "${LIBQUIL_LIB_PREFIX}" - sudo mkdir -p "${LIBQUIL_INCLUDE_PREFIX}" - sudo cp libquil/libquil.h "${LIBQUIL_INCLUDE_PREFIX}" - # This disables the "cannot open libquil.dylib from untrusted developer" dialog. - # A better solution for this would be to properly codesign the files, but that - # is a non-trivial amount of work. - sudo xattr -r -d com.apple.quarantine /usr/local/lib/libquil.dylib - sudo xattr -r -d com.apple.quarantine /usr/local/lib/libquil.core - sudo xattr -r -d com.apple.quarantine /usr/local/lib/libsbcl.so -fi diff --git a/knope.toml b/knope.toml index 78193a2..366aa5d 100644 --- a/knope.toml +++ b/knope.toml @@ -7,7 +7,7 @@ changelog = "CHANGELOG.md" path = "artifacts/linux-amd64.zip" [[package.assets]] -path = "artifacts/macos.zip" +path = "artifacts/macos-arm64.zip" [[workflows]] name = "release" diff --git a/scripts/clone-lisp-deps.sh b/scripts/clone-lisp-deps.sh new file mode 100755 index 0000000..077efc2 --- /dev/null +++ b/scripts/clone-lisp-deps.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Clone the Lisp systems libquil is built against into GITHUB_WORKSPACE, which the +# build adds to ql:*local-project-directories*. Shared by the Linux and macOS jobs. + +set -euo pipefail + +: "${GITHUB_WORKSPACE:?GITHUB_WORKSPACE must be set}" +: "${SBCL_LIBRARIAN_REPO:?SBCL_LIBRARIAN_REPO must be set}" +: "${SBCL_LIBRARIAN_REF:?SBCL_LIBRARIAN_REF must be set}" +: "${MAGICL_REPO:?MAGICL_REPO must be set}" +: "${MAGICL_REF:?MAGICL_REF must be set}" +: "${QUILC_REPO:?QUILC_REPO must be set}" +: "${QUILC_REF:?QUILC_REF must be set}" + +# quil-lang/qvm has had no release since 1.17.2 (2021), so this is a commit +# rather than a tag; quil-lang/qvm#330 asks for one, and this pin can become a +# tag once there is one. +QVM_REF="1c4ca60d7912219063e3969be92ec45e12a0798f" # TODO(quil-lang/qvm#330) + +git clone "${QUILC_REPO}" "${GITHUB_WORKSPACE}/quilc" --revision "${QUILC_REF}" + +git clone https://github.com/quil-lang/qvm.git "${GITHUB_WORKSPACE}/qvm" --revision "${QVM_REF}" + +git clone "${MAGICL_REPO}" "${GITHUB_WORKSPACE}/magicl" --revision "${MAGICL_REF}" +git clone https://github.com/stylewarning/cl-permutation "${GITHUB_WORKSPACE}/cl-permutation" + +git clone "${SBCL_LIBRARIAN_REPO}" "${GITHUB_WORKSPACE}/sbcl-librarian" --revision "${SBCL_LIBRARIAN_REF}" diff --git a/scripts/install-sbcl.sh b/scripts/install-sbcl.sh new file mode 100755 index 0000000..e8b7558 --- /dev/null +++ b/scripts/install-sbcl.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Build and install an SBCL with a linkable runtime (libsbcl.so), then install +# Quicklisp. Shared by the Linux and macOS build jobs, which otherwise differ only +# in how the cross-compilation host is provided. +# +# A linkable runtime is required and `make.sh` does not build one by default, nor +# do the binary releases or the apt/Homebrew packages ship one, so SBCL has to be +# built from source. The host SBCL must already be on PATH and of roughly the same +# vintage as SBCL_VERSION. +# +# Required environment: +# SBCL_VERSION SBCL release to build, e.g. 2.6.7 +# QUICKLISP_VERSION Quicklisp dist to pin, e.g. 2026-01-01 +# GITHUB_WORKSPACE added to ql:*local-project-directories* +# Optional environment: +# SBCL_SRC_DIR where to clone SBCL (default /tmp/sbcl) +# SBCL_REMOVE_HOST_CMD run once the build is done and the host is no longer +# needed, to get it out of the way before installing + +set -euo pipefail + +: "${SBCL_VERSION:?SBCL_VERSION must be set}" +: "${QUICKLISP_VERSION:?QUICKLISP_VERSION must be set}" +: "${GITHUB_WORKSPACE:?GITHUB_WORKSPACE must be set}" + +SBCL_SRC_DIR="${SBCL_SRC_DIR:-/tmp/sbcl}" + +sudo git clone --single-branch --branch "sbcl-${SBCL_VERSION}" \ + https://git.code.sf.net/p/sbcl/sbcl "${SBCL_SRC_DIR}" +cd "${SBCL_SRC_DIR}" +sudo sh make.sh --with-sb-linkable-runtime +sudo sh make-shared-library.sh + +if [[ -n "${SBCL_REMOVE_HOST_CMD:-}" ]] +then + eval "${SBCL_REMOVE_HOST_CMD}" +fi + +sudo sh install.sh +sudo mkdir -p /usr/local/lib +sudo cp src/runtime/libsbcl.so /usr/local/lib/libsbcl.so + +if [[ ! -f ~/quicklisp/setup.lisp ]] +then + wget -q -P /tmp/ 'https://beta.quicklisp.org/quicklisp.lisp' + sbcl --noinform --non-interactive --load /tmp/quicklisp.lisp \ + --eval "(quicklisp-quickstart:install :dist-url \"http://beta.quicklisp.org/dist/quicklisp/${QUICKLISP_VERSION}/distinfo.txt\")" + sbcl --noinform --non-interactive --load ~/quicklisp/setup.lisp \ + --eval '(ql-util:without-prompting (ql:add-to-init-file))' + rm -f /tmp/quicklisp.lisp +fi + +echo "#+quicklisp(push (truename \"${GITHUB_WORKSPACE}\") ql:*local-project-directories*)" >> ~/.sbclrc +cat ~/.sbclrc diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..e74caf1 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,405 @@ +#!/usr/bin/env bash + +# -e so a failed download or unpack stops the install rather than falling through +# to copying files that were never extracted; -o pipefail so a failure on the left +# of a pipe is not masked by a successful one on the right. +set -euo pipefail + +err() { + printf "%s\n" "$@" >&2 + exit 1 +} + +usage() { + cat <<'EOF' +Usage: install.sh [--prefix DIR] [--from DIR] [--install-deps] [VERSION] + +Installs libquil into DIR/lib and DIR/include/libquil. + + VERSION release to install, e.g. 0.4.0. Defaults to the latest release. + --prefix DIR install here instead of /usr/local. Root is needed only when the + prefix is not writable, so a prefix under your home directory + installs without sudo. Equivalent to LIBQUIL_PREFIX=DIR. + --from DIR install files already present in DIR instead of downloading a + release. Accepts either an unpacked release or a libquil build + tree, where the runtime lives in a runtime/ subdirectory. This + is what `make install` uses. + --install-deps also install libquil's prerequisites with apt or Homebrew. + Off by default: without it, missing prerequisites are reported + and the install stops. Equivalent to LIBQUIL_INSTALL_DEPS=1. + +Consumers find a non-default prefix through LIBQUIL_SRC_PATH and LIBQUIL_LIB_PATH; +this script prints the values to use when it finishes. + +Environment: + LIBQUIL_RELEASE_REPO repository to fetch releases from (default rigetti/libquil) + LIBQUIL_PREFIX install prefix (default /usr/local) + LIBQUIL_INSTALL_DEPS set to 1 for --install-deps +EOF +} + +# Which repository to fetch releases from. Override to install from a fork, which +# is how a prerelease can be tested before it is published from the main repository. +LIBQUIL_RELEASE_REPO="${LIBQUIL_RELEASE_REPO:-rigetti/libquil}" +LIBQUIL_INSTALL_DEPS="${LIBQUIL_INSTALL_DEPS:-0}" +LIBQUIL_PREFIX="${LIBQUIL_PREFIX:-/usr/local}" +LIBQUIL_FROM="" +LIBQUIL_VERSION="" + +while [[ $# -gt 0 ]] +do + case "${1}" in + --install-deps) LIBQUIL_INSTALL_DEPS=1 ;; + --prefix) + [[ -n "${2-}" ]] || err "--prefix needs a directory" + LIBQUIL_PREFIX="${2}" + shift + ;; + --prefix=*) LIBQUIL_PREFIX="${1#--prefix=}" ;; + --from) + [[ -n "${2-}" ]] || err "--from needs a directory" + LIBQUIL_FROM="${2}" + shift + ;; + --from=*) LIBQUIL_FROM="${1#--from=}" ;; + -h | --help) usage; exit 0 ;; + -*) usage >&2; err "" "Unknown option: ${1}" ;; + *) + if [[ -n "${LIBQUIL_VERSION}" ]] + then + usage >&2 + err "" "Unexpected argument: ${1}" + fi + LIBQUIL_VERSION="${1}" + ;; + esac + shift +done + +if [[ -n "${LIBQUIL_FROM}" && -n "${LIBQUIL_VERSION}" ]] +then + err "--from installs the files in that directory, so a version cannot also be given." +fi + +if [[ -n "${LIBQUIL_VERSION}" ]] +then + LIBQUIL_URL_PREFIX="https://github.com/${LIBQUIL_RELEASE_REPO}/releases/download/v${LIBQUIL_VERSION}" +else + LIBQUIL_URL_PREFIX="https://github.com/${LIBQUIL_RELEASE_REPO}/releases/latest/download" +fi + +OS="$(uname)" +ARCH="$(uname -m)" +if [[ "${OS}" == "Linux" ]] +then + IS_LINUX=1 + case "${ARCH}" in + x86_64 | amd64) + LIBQUIL_RELEASE_FILE="linux-amd64.zip" + ;; + *) + # Only a problem when downloading: --from installs what is already built. + [[ -n "${LIBQUIL_FROM}" ]] || + err "Unsupported CPU architecture for Linux: ${ARCH}. Only x86_64 is supported." \ + "You can build libquil from source; see https://github.com/rigetti/libquil#building-from-source" + ;; + esac +elif [[ "${OS}" == "Darwin" ]] +then + case "${ARCH}" in + arm64 | aarch64) + LIBQUIL_RELEASE_FILE="macos-arm64.zip" + ;; + *) + [[ -n "${LIBQUIL_FROM}" ]] || + err "Unsupported CPU architecture for macOS: ${ARCH}. Only Apple Silicon (arm64)" \ + "has published builds. You can build libquil from source; see" \ + "https://github.com/rigetti/libquil#building-from-source" + ;; + esac +# Windows shells report one of these. libquil publishes no Windows build, so there +# is nothing to install even where the shell would run this script. +elif [[ "${OS}" == CYGWIN* || "${OS}" == MINGW* || "${OS}" == MSYS* || "${OS}" == "Windows_NT" ]] +then + err "Windows is not supported: libquil publishes builds for Linux and macOS only." +else + err "Unsupported operating system: ${OS}. libquil supports Linux and macOS." +fi + +if [[ -z "${LIBQUIL_FROM}" ]] +then + for tool in curl unzip + do + command -v "${tool}" >/dev/null 2>&1 || + err "This installer needs ${tool} to download a release, and it was not found." + done +fi + +LIBQUIL_LIB_PREFIX="${LIBQUIL_PREFIX}/lib" +LIBQUIL_INCLUDE_PREFIX="${LIBQUIL_PREFIX}/include/libquil" +if [[ "${OS}" == "Darwin" ]] +then + LIBQUIL_LD_PATH_VAR="DYLD_LIBRARY_PATH" +else + LIBQUIL_LD_PATH_VAR="LD_LIBRARY_PATH" +fi + +# Root is not a requirement of the install, only of writing to /usr/local. Test +# what is actually needed -- whether the target directories can be created and +# written -- so that `--prefix "${HOME}/.local"` works as an ordinary user. +# Checked before anything else runs so the failure is immediate. +# Create the target and write to it, rather than inspecting permission bits: -w +# disagrees with reality often enough to matter -- it ignores ACLs, reports true +# on a read-only mount, and is always true for root. The directories are needed +# either way, so making them here costs nothing. +directory_is_writable() { + local dir="${1}" probe + mkdir -p "${dir}" 2>/dev/null || return 1 + probe="$(mktemp "${dir}/.libquil-install-probe.XXXXXX" 2>/dev/null)" || return 1 + rm -f "${probe}" +} + +for dir in "${LIBQUIL_LIB_PREFIX}" "${LIBQUIL_INCLUDE_PREFIX}" +do + directory_is_writable "${dir}" || + err "Cannot write to ${dir}." \ + "Re-run with sudo, or choose a writable prefix, e.g." \ + " install.sh --prefix \"\${HOME}/.local\"" +done + +# Installing prerequisites is opt-in. The default is to check and report, because +# this script is commonly run as `curl ... | sudo bash` and a package manager +# invocation there has a much wider blast radius than copying files into +# /usr/local. Consumers that want the one-shot path pass --install-deps. +install_prerequisites() { + if [[ "${OS}" == "Darwin" ]] + then + command -v brew >/dev/null 2>&1 || + err "--install-deps needs Homebrew on macOS, which was not found." \ + "Install libquil's requirements another way and re-run without --install-deps:" \ + "https://github.com/rigetti/libquil#requirements" + + # Homebrew refuses to run as root, so it runs as whoever invoked sudo. + # (`brew --prefix` is the one subcommand it allows as root, which is why the + # search paths above can call it directly.) + # + # SUDO_USER is set only when root was reached through sudo, so this guard is + # about not having a user to drop to -- not about Homebrew's root policy, + # which the line below already handles. It is close to unreachable: brew is + # not on root's default PATH, so a root login normally fails the check above + # instead. It stays because without it `set -u` would abort here with a bare + # "SUDO_USER: unbound variable". + [[ -n "${SUDO_USER-}" ]] || + err "--install-deps needs an unprivileged user to run Homebrew as, and this is" \ + "a root session rather than one entered through sudo." \ + "Re-run it under sudo from your normal account, or install the requirements" \ + "yourself and drop --install-deps." + local brew_cmd=(sudo -u "${SUDO_USER}" brew) + + # OpenBLAS provides both BLAS and LAPACK. + local missing=() + local formula + for formula in openblas libffi + do + "${brew_cmd[@]}" list --formula "${formula}" >/dev/null 2>&1 || missing+=("${formula}") + done + if [[ "${#missing[@]}" -gt 0 ]] + then + echo "Installing prerequisites with Homebrew: ${missing[*]}" + "${brew_cmd[@]}" install "${missing[@]}" + fi + return + fi + + command -v apt-get >/dev/null 2>&1 || + err "--install-deps installs prerequisites with apt, which was not found." \ + "Install libquil's requirements with your package manager and re-run without" \ + "--install-deps: https://github.com/rigetti/libquil#requirements" + + # The -dev packages, not the runtime ones: magicl and CFFI load these under their + # unversioned names, which only the development packages provide. + local missing=() + local package + for package in libblas-dev liblapack-dev libffi-dev + do + if ! dpkg-query -W -f='${Status}' "${package}" 2>/dev/null | grep -q "^install ok installed$" + then + missing+=("${package}") + fi + done + if [[ "${#missing[@]}" -gt 0 ]] + then + echo "Installing prerequisites with apt: ${missing[*]}" + apt-get update + apt-get install -y "${missing[@]}" + fi +} + +if [[ "${LIBQUIL_INSTALL_DEPS}" == "1" ]] +then + install_prerequisites +fi + +# magicl dlopens BLAS and LAPACK under their unversioned names once libquil is in use, +# so a missing one is not a link error at install time but a failure much later, in the +# middle of compiling a program. Check for them up front instead. +if [[ "${OS}" == "Darwin" ]] +then + LIBQUIL_LIB_SUFFIX="dylib" +else + LIBQUIL_LIB_SUFFIX="so" +fi + +# Where to look for an unversioned library, in roughly the order the platform's +# loader considers them. +LIBQUIL_SEARCH_DIRS=(/usr/local/lib /usr/lib) +if [[ "${OS}" == "Darwin" ]] +then + if command -v brew >/dev/null 2>&1 + then + LIBQUIL_BREW_PREFIX="$(brew --prefix)" + LIBQUIL_SEARCH_DIRS+=("${LIBQUIL_BREW_PREFIX}/lib" "${LIBQUIL_BREW_PREFIX}/opt/openblas/lib") + fi +else + LIBQUIL_SEARCH_DIRS+=(/usr/lib64 /lib /lib64) +fi + +library_is_available() { + local soname="lib${1}.${LIBQUIL_LIB_SUFFIX}" + + # The loader's own cache is authoritative where it exists. + if [[ "${OS}" != "Darwin" ]] && command -v ldconfig >/dev/null 2>&1 + then + ldconfig -p | grep -q "[[:space:]]${soname}[[:space:]]" && return 0 + fi + + local dir + for dir in "${LIBQUIL_SEARCH_DIRS[@]}" + do + [[ -e "${dir}/${soname}" ]] && return 0 + done + return 1 +} + +LIBQUIL_MISSING_LIBS=() +for lib in blas lapack +do + library_is_available "${lib}" || LIBQUIL_MISSING_LIBS+=("lib${lib}.${LIBQUIL_LIB_SUFFIX}") +done + +if [[ "${#LIBQUIL_MISSING_LIBS[@]}" -gt 0 ]] +then + err "Missing required libraries: ${LIBQUIL_MISSING_LIBS[*]}" \ + "" \ + "libquil loads these at runtime under exactly these unversioned names, so a" \ + "runtime-only package that provides a versioned name is not sufficient." \ + "" \ + "Re-run with --install-deps to install them with apt or Homebrew, or install" \ + "them yourself: https://github.com/rigetti/libquil#requirements" +fi + +# The files that make up an installed libquil, named once. libquil.core has to +# land beside libsbcl_librarian, so the libraries and the core share a directory: +# the runtime locates its core relative to its own path. +LIBQUIL_HEADERS=(libquil.h sbcl_librarian.h sbcl_librarian_err.h) +LIBQUIL_LIBS=( + "libquil.${LIBQUIL_LIB_SUFFIX}" + "libsbcl_librarian.${LIBQUIL_LIB_SUFFIX}" + # SBCL names its linkable runtime libsbcl.so on every platform, macOS included. + libsbcl.so + libquil.core +) + +# A release unpacks with everything in one directory; a build tree keeps the +# runtime in runtime/. Accept both, so `make install` can hand over the tree it +# just built without staging a copy first. +locate_artifact() { + local dir="${1}" name="${2}" candidate + for candidate in "${dir}/${name}" "${dir}/runtime/${name}" + do + if [[ -f "${candidate}" ]] + then + printf '%s' "${candidate}" + return 0 + fi + done + return 1 +} + +install_artifacts() { + local source="${1}" name path + for name in "${LIBQUIL_HEADERS[@]}" + do + path="$(locate_artifact "${source}" "${name}")" || + err "${name} is missing from ${source}." + cp "${path}" "${LIBQUIL_INCLUDE_PREFIX}" + done + for name in "${LIBQUIL_LIBS[@]}" + do + path="$(locate_artifact "${source}" "${name}")" || + err "${name} is missing from ${source}." + cp "${path}" "${LIBQUIL_LIB_PREFIX}" + done +} + +if [[ -n "${LIBQUIL_FROM}" ]] +then + [[ -d "${LIBQUIL_FROM}" ]] || err "--from ${LIBQUIL_FROM} is not a directory." + LIBQUIL_SOURCE_DIR="$(cd "${LIBQUIL_FROM}" && pwd)" +else + LIBQUIL_RELEASE_URL="${LIBQUIL_URL_PREFIX}/${LIBQUIL_RELEASE_FILE}" + LIBQUIL_TEMP_DIR="$(mktemp -d)" + + trap 'rm -rf "${LIBQUIL_TEMP_DIR}"' EXIT + cd "${LIBQUIL_TEMP_DIR}" + + # -f so an HTTP error is a non-zero exit rather than an error page written to the + # archive: without it a bad version tag saves a "404: Not Found" body as the .zip + # and the failure only surfaces later, as a confusing unzip error. + curl -fL "${LIBQUIL_RELEASE_URL}" -o "${LIBQUIL_RELEASE_FILE}" || + err "Could not download ${LIBQUIL_RELEASE_URL}" \ + "Check that the requested version exists: https://github.com/${LIBQUIL_RELEASE_REPO}/releases" + unzip "${LIBQUIL_RELEASE_FILE}" + LIBQUIL_SOURCE_DIR="${LIBQUIL_TEMP_DIR}/libquil" +fi + +install_artifacts "${LIBQUIL_SOURCE_DIR}" + +if [[ -n "${IS_LINUX-}" ]] +then + # Only meaningful for a system prefix, and only permitted as root. + if [[ "$(id -u)" -eq 0 ]] + then + ldconfig + fi +else + echo "Removing the quarantine attribute from the installed files." + # This disables the "cannot open libquil.dylib from untrusted developer" dialog. + # A better solution for this would be to properly codesign the files, but that + # is a non-trivial amount of work. A no-op on files that were never quarantined, + # such as a local build. + for name in "${LIBQUIL_LIBS[@]}" + do + xattr -r -d com.apple.quarantine "${LIBQUIL_LIB_PREFIX}/${name}" 2>/dev/null || true + done +fi + +# A prefix other than /usr/local is not on any default search path, so tell the +# caller how consumers find it. libquil-sys reads both: LIBQUIL_SRC_PATH for the +# headers, LIBQUIL_LIB_PATH for the libraries, which differ because the headers +# live one directory deeper. +if [[ "${LIBQUIL_PREFIX}" != "/usr/local" ]] +then + cat <