diff --git a/.bazelignore b/.bazelignore index 3c3629e64..1c30ea3ed 100644 --- a/.bazelignore +++ b/.bazelignore @@ -1 +1,2 @@ node_modules +.bazel diff --git a/.bazelrc b/.bazelrc index dc09b785b..81430cc64 100644 --- a/.bazelrc +++ b/.bazelrc @@ -8,7 +8,7 @@ build --enable_runfiles build:windows --build_python_zip=false # record version/build hash -build --workspace_status_command='bash ./scripts/status.sh' +build --workspace_status_command='bash ./tools/status.sh' # run clippy when compiling rust in test mode test --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect --output_groups=+clippy_checks @@ -31,7 +31,9 @@ build:windows --worker_quit_after_build # place convenience symlinks inside a single folder for easier exclusion in IDEs build --symlink_prefix=.bazel/ -build --experimental_no_product_name_out_symlink + +# if (auto-created) windows.bazelrc exists, import it +try-import %workspace%/windows.bazelrc # allow extra user customizations in a separate file # (see .user.bazelrc for an example) diff --git a/.bazelversion b/.bazelversion index fae6e3d04..0062ac971 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -4.2.1 +5.0.0 diff --git a/scripts/docker/Dockerfile.amd64 b/.buildkite/linux/docker/Dockerfile.amd64 similarity index 53% rename from scripts/docker/Dockerfile.amd64 rename to .buildkite/linux/docker/Dockerfile.amd64 index 51e57df4e..2e745396d 100644 --- a/scripts/docker/Dockerfile.amd64 +++ b/.buildkite/linux/docker/Dockerfile.amd64 @@ -1,13 +1,17 @@ -FROM debian:10-slim +FROM python:3.9-slim-buster ARG DEBIAN_FRONTEND="noninteractive" -ARG uid=1000 -ARG gid=1000 -RUN apt-get update \ +RUN useradd -d /state -m -u 998 user + +RUN apt-get update && apt install --yes gnupg ca-certificates && \ + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 32A37959C2FA5C3C99EFBC32A79206696452D198 \ + && echo "deb https://apt.buildkite.com/buildkite-agent stable main" > /etc/apt/sources.list.d/buildkite-agent.list \ + && apt-get update \ && apt-get install --yes --no-install-recommends \ autoconf \ bash \ + buildkite-agent \ ca-certificates \ curl \ findutils \ @@ -23,6 +27,7 @@ RUN apt-get update \ libgstreamer-plugins-base1.0 \ libgstreamer1.0-0 \ libnss3 \ + libpulse-mainloop-glib0 \ libpulse-mainloop-glib0 \ libssl-dev \ libxcomposite1 \ @@ -37,23 +42,23 @@ RUN apt-get update \ make \ pkg-config \ portaudio19-dev \ + python3-dev \ rsync \ + zstd \ && rm -rf /var/lib/apt/lists/* -RUN curl -L https://github.com/bazelbuild/bazelisk/releases/download/v1.10.1/bazelisk-linux-amd64 \ + +RUN curl -L https://github.com/bazelbuild/bazelisk/releases/download/v1.7.4/bazelisk-linux-amd64 \ -o /usr/local/bin/bazel \ && chmod +x /usr/local/bin/bazel -RUN mkdir -p /code/bazel-docker/home && \ - echo groupadd -g ${gid} user && \ - useradd -d /code/bazel-docker/home -m -u ${uid} user && \ - chown -R user.user /code - RUN ln -sf /usr/bin/python3 /usr/bin/python -USER user -COPY build-entrypoint /tmp -WORKDIR /code -ENV XDG_CACHE_HOME=/code/bazel-docker/home +RUN mkdir -p /etc/buildkite-agent/hooks && chown -R user /etc/buildkite-agent -ENTRYPOINT ["/bin/bash", "/tmp/build-entrypoint"] +COPY buildkite.cfg /etc/buildkite-agent/buildkite-agent.cfg +COPY environment /etc/buildkite-agent/hooks/environment + +USER user +WORKDIR /code/buildkite +ENTRYPOINT ["/usr/bin/buildkite-agent", "start"] diff --git a/scripts/docker/Dockerfile.arm64 b/.buildkite/linux/docker/Dockerfile.arm64 similarity index 57% rename from scripts/docker/Dockerfile.arm64 rename to .buildkite/linux/docker/Dockerfile.arm64 index 56f6f00da..2067c9a97 100644 --- a/scripts/docker/Dockerfile.arm64 +++ b/.buildkite/linux/docker/Dockerfile.arm64 @@ -1,13 +1,18 @@ FROM debian:11-slim ARG DEBIAN_FRONTEND="noninteractive" -ARG uid=1000 -ARG gid=1000 +ENV PYTHON_SITE_PACKAGES=/usr/lib/python3/dist-packages/ -RUN apt-get update \ +RUN useradd -d /state -m -u 998 user + +RUN apt-get update && apt install --yes gnupg ca-certificates && \ + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 32A37959C2FA5C3C99EFBC32A79206696452D198 \ + && echo "deb https://apt.buildkite.com/buildkite-agent stable main" > /etc/apt/sources.list.d/buildkite-agent.list \ + && apt-get update \ && apt-get install --yes --no-install-recommends \ autoconf \ bash \ + buildkite-agent \ ca-certificates \ curl \ findutils \ @@ -23,6 +28,7 @@ RUN apt-get update \ libgstreamer-plugins-base1.0 \ libgstreamer1.0-0 \ libnss3 \ + libpulse-mainloop-glib0 \ libpulse-mainloop-glib0 \ libssl-dev \ libxcomposite1 \ @@ -37,12 +43,13 @@ RUN apt-get update \ make \ pkg-config \ portaudio19-dev \ + python3-dev \ rsync \ # -- begin only required for arm64/debian11 clang-format \ python-is-python3 \ python3-pyqt5.qtwebengine \ - # -- end only required for arm64/debian11 + # -- end only required for arm64/debian11 && rm -rf /var/lib/apt/lists/* @@ -50,12 +57,13 @@ RUN curl -L https://github.com/bazelbuild/bazelisk/releases/download/v1.10.1/baz -o /usr/local/bin/bazel \ && chmod +x /usr/local/bin/bazel -RUN echo groupadd -g ${gid} user && useradd -d /code/bazel-docker/home -m -u ${uid} user +RUN ln -sf /usr/bin/python3 /usr/bin/python + +RUN mkdir -p /etc/buildkite-agent/hooks && chown -R user /etc/buildkite-agent + +COPY buildkite.cfg /etc/buildkite-agent/buildkite-agent.cfg +COPY environment /etc/buildkite-agent/hooks/environment USER user -COPY build-entrypoint /tmp -WORKDIR /code -ENV XDG_CACHE_HOME=/code/bazel-docker/home -ENV PYTHON_SITE_PACKAGES=/usr/lib/python3/dist-packages/ - -ENTRYPOINT ["/bin/bash", "/tmp/build-entrypoint"] +WORKDIR /code/buildkite +ENTRYPOINT ["/usr/bin/buildkite-agent", "start"] diff --git a/.buildkite/linux/docker/buildkite.cfg b/.buildkite/linux/docker/buildkite.cfg new file mode 100644 index 000000000..3b5345c46 --- /dev/null +++ b/.buildkite/linux/docker/buildkite.cfg @@ -0,0 +1,6 @@ +name="lin-ci" +tags="queue=lin-ci" +build-path="/state/build" +hooks-path="/etc/buildkite-agent/hooks" +no-plugins=true +no-local-hooks=true diff --git a/.buildkite/linux/docker/environment b/.buildkite/linux/docker/environment new file mode 100755 index 000000000..9a43ce00b --- /dev/null +++ b/.buildkite/linux/docker/environment @@ -0,0 +1,7 @@ +#!/bin/bash + +if [[ "${BUILDKITE_COMMAND}" != ".buildkite/linux/entrypoint" && + "${BUILDKITE_COMMAND}" != ".buildkite/linux/release-entrypoint" ]]; then + echo "Command not allowed: ${BUILDKITE_COMMAND}" + exit 1 +fi diff --git a/.buildkite/linux/docker/run.sh b/.buildkite/linux/docker/run.sh new file mode 100755 index 000000000..494c4c70e --- /dev/null +++ b/.buildkite/linux/docker/run.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# use './run.sh serve' to daemonize + +set -e + +if [ "$1" = "serve" ]; then + extra_args="-d --restart always" +else + extra_args="-it" +fi + +if [ $(uname -m) = "aarch64" ]; then + arch=arm64 +else + arch=amd64 +fi + +DOCKER_BUILDKIT=1 docker build -f Dockerfile.${arch} --tag linci . + +if docker container inspect linci > /dev/null 2>&1; then + docker stop linci || true + docker container rm linci +fi + +docker run $extra_args \ + --name linci \ + -v ci-state:/state \ + -e BUILDKITE_AGENT_TOKEN \ + -e BUILDKITE_AGENT_TAGS \ + linci diff --git a/.buildkite/linux/entrypoint b/.buildkite/linux/entrypoint index 9ddba1eb5..bee05ae7e 100755 --- a/.buildkite/linux/entrypoint +++ b/.buildkite/linux/entrypoint @@ -17,7 +17,7 @@ test -e /state/node_modules && mv /state/node_modules . $BAZEL test $BUILDARGS ... //rslib/linkchecker echo "--- Running lints" -python scripts/copyright_headers.py +python tools/copyright_headers.py echo "--- Cleanup" # if tests succeed, back up node_modules folder diff --git a/.buildkite/linux/release-entrypoint b/.buildkite/linux/release-entrypoint new file mode 100755 index 000000000..496a1bd8e --- /dev/null +++ b/.buildkite/linux/release-entrypoint @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +# move existing node_modules into tree +test -e /state/node_modules && mv /state/node_modules . + +if [ $(uname -m) = "aarch64" ]; then + ./tools/build +else + ./tools/bundle +fi + +rm -rf /state/dist +mv .bazel/out/dist /state + +# if tests succeed, back up node_modules folder +mv node_modules /state/ diff --git a/.buildkite/mac/entrypoint b/.buildkite/mac/entrypoint index 4eafed2c8..d0f05c149 100755 --- a/.buildkite/mac/entrypoint +++ b/.buildkite/mac/entrypoint @@ -15,7 +15,7 @@ test -e $STATE/node_modules && mv $STATE/node_modules . $BAZEL test $BUILDARGS ... echo "--- Building wheels" -$BAZEL build dist +$BAZEL build wheels # if tests succeed, back up node_modules folder mv node_modules $STATE/ diff --git a/.gitignore b/.gitignore index b59d2f5af..350f526f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,10 @@ __pycache__ .DS_Store -/bazel-* anki.prof target -user.bazelrc +/user.bazelrc .dmypy.json -rust-project.json node_modules -.idea/ -.bazel +/.idea/ +/.bazel +/windows.bazelrc diff --git a/.vscode/settings.json b/.vscode/settings.json index 993214aef..53e657d93 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,19 +4,21 @@ "**/.git/objects/**": true, "**/.git/subtree-cache/**": true, "**/node_modules/*/**": true, - ".bazel/**": true + ".bazel/**": true, + "dist/**": true }, "python.analysis.extraPaths": ["./pylib"], "python.formatting.provider": "black", "rust-analyzer.cargo.runBuildScripts": true, "rust-analyzer.checkOnSave.allTargets": false, - "rust-analyzer.files.excludeDirs": [".bazel", "node_modules"], + "rust-analyzer.files.excludeDirs": [".bazel", "node_modules", "dist"], "rust-analyzer.procMacro.enable": true, // this formats 'use' blocks in a nicer way, but requires you to run // 'rustup install nightly'. "rust-analyzer.rustfmt.extraArgs": ["+nightly"], "search.exclude": { "**/node_modules": true, - ".bazel/**": true + ".bazel/**": true, + "dist/**": true } } diff --git a/BUILD.bazel b/BUILD.bazel index 79ba87f85..d6dc535ba 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -12,24 +12,23 @@ genrule( srcs = ["//:defs.bzl"], outs = ["buildinfo.txt"], cmd = select({ - "release": "$(location //scripts:buildinfo) $(location //:defs.bzl) bazel-out/stable-status.txt release > $@", - "//conditions:default": "$(location //scripts:buildinfo) $(location //:defs.bzl) bazel-out/stable-status.txt devel > $@", + "release": "$(location //tools:buildinfo) $(location //:defs.bzl) bazel-out/stable-status.txt release > $@", + "//conditions:default": "$(location //tools:buildinfo) $(location //:defs.bzl) bazel-out/stable-status.txt devel > $@", }), stamp = 1, tools = [ - "//scripts:buildinfo", + "//tools:buildinfo", ], visibility = ["//visibility:public"], ) pkg_tar( - name = "dist", + name = "wheels", srcs = [ "//pylib/anki:wheel", "//qt/aqt:wheel", ], mode = "0644", - package_dir = "bazel-dist", tags = ["manual"], ) diff --git a/Cargo.toml b/Cargo.toml index f63c742ec..e8a6b708f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "AGPL-3.0-or-later" [workspace] members = ["rslib", "rslib/i18n", "rslib/i18n_helpers", "rslib/linkchecker", "pylib/rsbridge"] -exclude = ["qt/package"] +exclude = ["qt/bundle"] [lib] # dummy top level for tooling diff --git a/bazel.bat b/bazel.bat deleted file mode 100755 index 3875a5917..000000000 --- a/bazel.bat +++ /dev/null @@ -1,2 +0,0 @@ -@set PATH=c:\msys64\usr\bin;c:\python;%PATH% -\bazel\bazel --output_user_root=\bazel\anki %* diff --git a/cargo/README.md b/cargo/README.md index bec55c40e..1fe6acbf4 100644 --- a/cargo/README.md +++ b/cargo/README.md @@ -1,7 +1,7 @@ This folder integrates Rust crates.io fetching into Bazel. To add or update dependencies, ensure a local Rust environment is available -(eg `source scripts/cargo-env`), then install cargo-raze: +(eg `source tools/cargo-env`), then install cargo-raze: ``` cargo install cargo-raze --version 0.14.1 diff --git a/docs/development.md b/docs/development.md index 161202ea1..96eec4b94 100644 --- a/docs/development.md +++ b/docs/development.md @@ -56,13 +56,13 @@ Run the following command to create Python packages: On Mac/Linux: ``` -./scripts/build +./tools/build ``` On Windows: ``` -.\scripts\build.bat +.\tools\build.bat ``` The generated wheel paths will be printed as the build completes. @@ -71,7 +71,7 @@ Follow the steps [on the beta site](https://betas.ankiweb.net/#via-pypipip), but `pip install --upgrade --pre aqt[qt6]` line with something like: ``` -pyenv/bin/pip install --upgrade bazel-dist/*.whl +pyenv/bin/pip install --upgrade dist/*.whl ``` (On Windows you'll need to list out the filenames manually instead of using a wildcard). @@ -94,36 +94,14 @@ The build process will download about a gigabyte of dependencies, and produce about 6 gigabytes of temporary files. Once you've created the wheels, you can remove the other files to free up space if you wish. -- `bazel clean --expunge` will remove the generated files, freeing up most - of the space. The files are usualy stored in a subdir of ~/.cache/bazel/ -- `rm -rf ~/.cache/bazel*` will remove the cached downloads as well, requiring - them to be redownloaded if you want to build again. +- `bazel clean --expunge` will remove the generated Bazel files, freeing up + most of the space. The files are usualy stored in a subdir of + `~/.cache/bazel` or `\bazel\anki` +- `rm -rf ~/.cache/bazel*` or `\bazel\anki` will remove cached downloads as + well, requiring them to be redownloaded if you want to build again. - `rm -rf ~/.cache/{yarn,pip}` will remove the shared pip and yarn caches that other apps may be using as well. -## Building with Docker - -Linux users can build using the instructions above, or they can optionally [build -via Docker](../scripts/docker/README.md). - -On Linux, the generated Anki wheel will have a filename like: - - anki-2.1.49-cp39-abi3-manylinux_2_31_aarch64.whl - -The 2_31 part means that the wheel requires glibc 2.31 or later. If you have -built the wheel on a machine with an older glibc version, you will get an error -if you try to install the wheel: - - ERROR: No matching distribution found for anki - -To avoid the error, you can rename the .whl file to match your glibc version. - -If you still get the error, another possibility is that you are trying to -install with an old version of Python - 3.9 or later is required. - -On ARM Linux, please see the instructions in the pre-built wheels section about -a system PyQt, and the notes at the bottom of [Linux](./linux.md). - ## Running tests You can run all tests at once. From the top level project folder: @@ -151,7 +129,7 @@ On Mac/Linux, after installing 'fswatch', you can run mypy on each file save automatically with: ``` -./scripts/mypy-watch +./tools/mypy-watch ``` ## Fixing formatting @@ -233,6 +211,12 @@ in the collection2.log file will also be printed on stdout. If ANKI_PROFILE_CODE is set, Python profiling data will be written on exit. +# Binary Bundles + +Anki's official binary packages are created with `tools/bundle`. The script was created specifically +for the official builds, and is provided as-is; we are unfortunately not able to provide assistance with +any issues you may run into when using it. + ## Mixing development and study You may wish to create a separate profile with File>Switch Profile for use diff --git a/docs/docker/Dockerfile b/docs/docker/Dockerfile index cf41a9091..6682f70f6 100644 --- a/docs/docker/Dockerfile +++ b/docs/docker/Dockerfile @@ -17,12 +17,12 @@ RUN curl -fsSL https://github.com/bazelbuild/bazelisk/releases/download/v1.7.4/b WORKDIR /opt/anki COPY . . # Build python wheels. -RUN ./scripts/build +RUN ./tools/build # Install pre-compiled Anki. FROM python:${PYTHON_VERSION}-slim as installer WORKDIR /opt/anki/ -COPY --from=build /opt/anki/bazel-dist/ wheels/ +COPY --from=build /opt/anki/wheels/ wheels/ # Use virtual environment. RUN python -m venv venv \ && ./venv/bin/python -m pip install --no-cache-dir setuptools wheel \ @@ -35,29 +35,29 @@ ENV PATH=/opt/anki/venv/bin:$PATH # Install run-time dependencies. RUN apt-get update \ && apt-get install --yes --no-install-recommends \ - libasound2 \ - libdbus-1-3 \ - libfontconfig1 \ - libfreetype6 \ - libgl1 \ - libglib2.0-0 \ - libnss3 \ - libxcb-icccm4 \ - libxcb-image0 \ - libxcb-keysyms1 \ - libxcb-randr0 \ - libxcb-render-util0 \ - libxcb-shape0 \ - libxcb-xinerama0 \ - libxcb-xkb1 \ - libxcomposite1 \ - libxcursor1 \ - libxi6 \ - libxkbcommon0 \ - libxkbcommon-x11-0 \ - libxrandr2 \ - libxrender1 \ - libxtst6 \ + libasound2 \ + libdbus-1-3 \ + libfontconfig1 \ + libfreetype6 \ + libgl1 \ + libglib2.0-0 \ + libnss3 \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-randr0 \ + libxcb-render-util0 \ + libxcb-shape0 \ + libxcb-xinerama0 \ + libxcb-xkb1 \ + libxcomposite1 \ + libxcursor1 \ + libxi6 \ + libxkbcommon0 \ + libxkbcommon-x11-0 \ + libxrandr2 \ + libxrender1 \ + libxtst6 \ && rm -rf /var/lib/apt/lists/* # Add non-root user. RUN useradd --create-home anki diff --git a/docs/docker/README.md b/docs/docker/README.md index f57232d5c..a9f233bf9 100644 --- a/docs/docker/README.md +++ b/docs/docker/README.md @@ -1,14 +1,17 @@ -# Anki in Docker +# Building and running Anki in Docker -This is an example of how you can build and run Anki from inside Docker. This -approach keeps everything inside Docker images, and sends the GUI to an X11 -display over TCP/IP. This approach keeps things tidy, so may be a good choice -for if you wish to build Anki irregularly and don't want to build it outside of -Docker. +This is an example Dockerfile contributed by an Anki user, which shows how Anki +can be both built and run from within a container. It works by streaming the GUI +over an X11 socket. -It takes longer to build after small changes however, so for development, if you -wish to use Docker, the approach [in the build -scripts](../../scripts/docker/README.md) may be more appropriate. +Building and running Anki within a container has the advantage of fully isolating +the build products and runtime dependencies from the rest of your system, but it is +a somewhat niche approach, with some downsides such as an inability to display natively +on Wayland, and a lack of integration with desktop icons/filetypes. But even if you +do not use this Dockerfile as-is, you may find it useful as a reference. + +Anki's Linux CI is also implemented with Docker, and the Dockerfiles for that may +also be useful for reference - they can be found in `.buildkite/linux/docker`. # Build the Docker image diff --git a/docs/editing.md b/docs/editing.md index 8ff351a6a..6000dac72 100644 --- a/docs/editing.md +++ b/docs/editing.md @@ -34,7 +34,7 @@ run 'rustup install nightly'. Code completion partly depends on files that are generated as part of the regular build process, so for things to work correctly, use './run' or -'scripts/build' prior to using code completion. +'tools/build' prior to using code completion. ## PyCharm/IntelliJ diff --git a/docs/linux.md b/docs/linux.md index 87ac069c5..08dd56d50 100644 --- a/docs/linux.md +++ b/docs/linux.md @@ -6,8 +6,9 @@ These instructions are written for Debian/Ubuntu; adjust for your distribution. Some extra notes have been provided by a forum member: https://forums.ankiweb.net/t/guide-how-to-build-and-run-anki-from-source-with-xubuntu-20-04/12865 -You can see a full list of requirements by looking at the [Dockerfiles](../scripts/docker/README.md) -in the scripts folder. +You can see a full list of buildtime and runtime requirements by looking at the +[Dockerfiles](../.buildkite/linux/docker/Dockerfile.amd64) used to build the +official releases. Glibc is required - if you are on a distro like Alpine that uses musl, you'll need to contribute fixes to the upstream [Rust rules](https://github.com/bazelbuild/rules_rust/issues/390), @@ -101,7 +102,7 @@ to compile, but will mean Anki will run considerably slower. To run Anki in optimized mode, use: ``` -./scripts/runopt +./tools/runopt ``` ## ARM64 support @@ -122,7 +123,7 @@ Note: the trailing slash at the end is required. There are a few things to be aware of: -- You should use ./run and not scripts/run-qt5\*, even if your system libraries are Qt5. +- You should use ./run and not tools/run-qt5\*, even if your system libraries are Qt5. - If your system libraries are Qt5, when creating an aqt wheel, the wheel will not work on Qt6 environments. - Some of the tests only work with PyQt6, and will show failures when run under PyQt5. diff --git a/docs/mac.md b/docs/mac.md index 0dd71e3fe..6bcce0db2 100644 --- a/docs/mac.md +++ b/docs/mac.md @@ -7,14 +7,12 @@ Install the latest XCode from the App Store. Open it at least once so it installs the command line tools. -**Homebrew & Homebrew Deps**: - -Install Homebrew from - -Then install Bazel: +**Bazelisk**: ``` -$ brew install bazelisk +$ curl -L https://github.com/bazelbuild/bazelisk/releases/download/v1.11.0/bazelisk-darwin -o bazel \ + && chmod +x bazel \ + && sudo mv bazel /usr/local/bin ``` **Python**: @@ -61,7 +59,7 @@ to compile, but will mean Anki will run considerably slower. To run Anki in optimized mode, use: ``` -./scripts/runopt +./tools/runopt ``` ## More diff --git a/docs/syncserver.md b/docs/syncserver.md index 23c2afe34..aa23713a6 100644 --- a/docs/syncserver.md +++ b/docs/syncserver.md @@ -26,7 +26,7 @@ Things to be aware of: If you run Anki from git, you can run a sync server with: ``` -./scripts/runopt --syncserver +./tools/runopt --syncserver ``` ## From a packaged build diff --git a/docs/windows.md b/docs/windows.md index 42c804694..f123860b3 100644 --- a/docs/windows.md +++ b/docs/windows.md @@ -74,7 +74,7 @@ to compile, but will mean Anki will run considerably slower. To run Anki in optimized mode, use: ``` -.\scripts\runopt +.\tools\runopt ``` ## More @@ -82,7 +82,7 @@ To run Anki in optimized mode, use: For info on running tests, building wheels and so on, please see [Development](./development.md). -Note that where the instructions on that page say "bazel", please use ".\bazel" -instead. This runs bazel.bat inside the Anki source folder, instead of -calling Bazel directly. This takes care of setting up the path and output folder -correctly, which avoids issues with long path names. +When you run a script like .\run, MSYS and bazel will automatically be added to +the path, and Bazel will be configured to output build products into +\bazel\anki. If you want to directly invoke bazel before having run any of the +.bat files in this repo, please run tools\setup-env first. diff --git a/pyrightconfig.json b/pyrightconfig.json index e3fbcd57b..e58a21fb2 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -1,3 +1,3 @@ { - "exclude": ["**/node_modules", ".bazel"] + "exclude": ["**/node_modules", ".bazel", "dist"] } diff --git a/python/licenses.sh b/python/licenses.sh index 273edf99e..a7985a429 100755 --- a/python/licenses.sh +++ b/python/licenses.sh @@ -14,10 +14,10 @@ python -m venv venv ../bazel.bat --output_base=/c/bazel/anki/base build //pylib/anki:wheel //qt/aqt:wheel # install wheels, bound to constrained versions -venv/scripts/pip install -c requirements.txt ../bazel-bin/pylib/anki/*.whl ../bazel-bin/qt/aqt/*.whl pip-licenses +venv/tools/pip install -c requirements.txt ../bazel-bin/pylib/anki/*.whl ../bazel-bin/qt/aqt/*.whl pip-licenses # dump licenses - ptable is a pip-licenses dep -venv/scripts/pip-licenses --format=json --ignore-packages anki aqt pip-license PTable > licenses.json +venv/tools/pip-licenses --format=json --ignore-packages anki aqt pip-license PTable > licenses.json # clean up rm -rf venv diff --git a/qt/package/.cargo/config b/qt/bundle/.cargo/config similarity index 100% rename from qt/package/.cargo/config rename to qt/bundle/.cargo/config diff --git a/qt/package/Cargo.lock b/qt/bundle/Cargo.lock similarity index 100% rename from qt/package/Cargo.lock rename to qt/bundle/Cargo.lock diff --git a/qt/package/Cargo.toml b/qt/bundle/Cargo.toml similarity index 95% rename from qt/package/Cargo.toml rename to qt/bundle/Cargo.toml index fcc42cbe9..ee591b411 100644 --- a/qt/package/Cargo.toml +++ b/qt/bundle/Cargo.toml @@ -11,6 +11,7 @@ libc-stdhandle = "=0.1.0" [dependencies.pyembed] git = "https://github.com/ankitects/PyOxidizer.git" +# when changing this, pyoxidizer in /repos.bzl needs to be updated as well rev = "eb26dd7cd1290de6503869f3d719eabcec45e139" default-features = false diff --git a/qt/bundle/build.py b/qt/bundle/build.py new file mode 100644 index 000000000..99d253b31 --- /dev/null +++ b/qt/bundle/build.py @@ -0,0 +1,395 @@ +# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + + +from __future__ import annotations + +import glob +import os +import platform +import re +import shutil +import subprocess +import sys +from pathlib import Path + +is_win = sys.platform == "win32" +is_mac = sys.platform == "darwin" + +workspace = Path(sys.argv[1]) +bazel_external = Path(sys.argv[2]) + + +def with_exe_extension(program: str) -> str: + if is_win: + return program + ".exe" + else: + return program + + +output_root = workspace / ".bazel" / "out" / "build" +dist_folder = output_root / ".." / "dist" +venv = output_root / f"venv-{platform.machine()}" +build_folder = output_root / f"build-{platform.machine()}" +cargo_target = output_root / f"target-{platform.machine()}" +artifacts = output_root / "artifacts" +pyo3_config = output_root / "pyo3-build-config-file.txt" +pyoxidizer_folder = bazel_external / "pyoxidizer" +arm64_protobuf_wheel = bazel_external / "protobuf_wheel_mac_arm64" +pyoxidizer_binary = cargo_target / "release" / with_exe_extension("pyoxidizer") + +for path in dist_folder.glob("*.zst"): + path.unlink() + +os.environ["PYOXIDIZER_ARTIFACT_DIR"] = str(artifacts) +os.environ["PYOXIDIZER_CONFIG"] = str(Path(os.getcwd()) / "pyoxidizer.bzl") +os.environ["CARGO_TARGET_DIR"] = str(cargo_target) + +# OS-specific things +pyqt5_folder_name = "pyqt515" +pyqt6_folder_path = bazel_external / "pyqt6" / "PyQt6" +extra_linux_deps = bazel_external / "bundle_extras_linux_amd64" +extra_qt5_linux_plugins = extra_linux_deps / "qt5" +extra_qt6_linux_plugins = extra_linux_deps / "qt6" +is_lin = False +arm64_linux = arm64_mac = False +if is_win: + os.environ["TARGET"] = "x86_64-pc-windows-msvc" +elif sys.platform.startswith("darwin"): + if platform.machine() == "arm64": + arm64_mac = True + pyqt5_folder_name = None + os.environ["TARGET"] = "aarch64-apple-darwin" + os.environ["MACOSX_DEPLOYMENT_TARGET"] = "11.0" + else: + pyqt5_folder_name = "pyqt514" + os.environ["TARGET"] = "x86_64-apple-darwin" + os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.13" +else: + is_lin = True + if platform.machine() == "x86_64": + os.environ["TARGET"] = "x86_64-unknown-linux-gnu" + else: + os.environ["TARGET"] = "aarch64-unknown-linux-gnu" + pyqt5_folder_name = None + pyqt6_folder_path = None + arm64_linux = True + +if is_win: + python_bin_folder = venv / "scripts" + os.environ["PATH"] += rf";{os.getenv('USERPROFILE')}\.cargo\bin" + cargo_features = "build-mode-prebuilt-artifacts" +else: + python_bin_folder = venv / "bin" + # PyOxidizer build depends on a system-installed version of Python, + # as the standalone build does not have its config set up properly, + # leading to "directory not found for option '-L/install/lib'". + # On macOS, after installing a system Python in /usr/local/bin, + # make sure /usr/local/bin/python3 is symlinked to /usr/local/bin/python. + os.environ["PATH"] = ":".join( + ["/usr/local/bin", f"{os.getenv('HOME')}/.cargo/bin", os.getenv("PATH")] + ) + cargo_features = "build-mode-prebuilt-artifacts" + if not is_mac or arm64_mac: + cargo_features += " global-allocator-jemalloc allocator-jemalloc" + +python = python_bin_folder / with_exe_extension("python") +pip = python_bin_folder / with_exe_extension("pip") +artifacts_in_build = ( + build_folder / os.getenv("TARGET") / "release" / "resources" / "extra_files" +) + + +def build_pyoxidizer(): + pyoxidizer_folder_mtime = pyoxidizer_folder.stat().st_mtime + if ( + pyoxidizer_binary.exists() + and pyoxidizer_binary.stat().st_mtime == pyoxidizer_folder_mtime + ): + # avoid recompiling if pyoxidizer folder has not changed + return + subprocess.run( + [ + "cargo", + "build", + "--release", + ], + cwd=pyoxidizer_folder, + check=True, + ) + os.utime(pyoxidizer_binary, (pyoxidizer_folder_mtime, pyoxidizer_folder_mtime)) + + +def install_wheels_into_venv(): + # Pip's handling of hashes is somewhat broken. It spots the hashes in the constraints + # file and forces all files to have a hash. We can manually hash our generated wheels + # and pass them in with hashes, but it still breaks, because the 'protobuf>=3.17' + # specifier in the pylib wheel is not allowed. Nevermind that a specific version is + # included in the constraints file we pass along! To get things working, we're + # forced to strip the hashes out before installing. This should be safe, as the files + # have already been validated as part of the build process. + constraints = output_root / "deps_without_hashes.txt" + with open(workspace / "python" / "requirements.txt") as f: + buf = f.read() + with open(constraints, "w") as f: + extracted = re.findall("^(\S+==\S+) ", buf, flags=re.M) + extracted = [ + line for line in extracted if not arm64_mac or "protobuf" not in line + ] + f.write("\n".join(extracted)) + # pypi protobuf lacks C extension on darwin-arm64, so we have to use a version + # we built ourselves + if arm64_mac: + wheels = glob.glob(str(arm64_protobuf_wheel / "*.whl")) + subprocess.run( + [pip, "install", "--upgrade", "-c", constraints, *wheels], check=True + ) + # install wheels and upgrade any deps + wheels = glob.glob(str(workspace / ".bazel" / "out" / "dist" / "*.whl")) + subprocess.run( + [pip, "install", "--upgrade", "-c", constraints, *wheels], check=True + ) + # always reinstall our wheels + subprocess.run( + [pip, "install", "--force-reinstall", "--no-deps", *wheels], check=True + ) + + +def build_artifacts(): + if os.path.exists(artifacts): + shutil.rmtree(artifacts) + if os.path.exists(artifacts_in_build): + shutil.rmtree(artifacts_in_build) + + subprocess.run( + [ + pyoxidizer_binary, + "--system-rust", + "run-build-script", + "build.rs", + "--var", + "venv", + venv, + "--var", + "build", + build_folder, + ], + check=True, + env=os.environ + | dict( + CARGO_MANIFEST_DIR=".", + OUT_DIR=str(artifacts), + PROFILE="release", + PYO3_PYTHON=str(python), + ), + ) + + existing_config = None + if os.path.exists(pyo3_config): + with open(pyo3_config) as f: + existing_config = f.read() + + with open(artifacts / "pyo3-build-config-file.txt") as f: + new_config = f.read() + + # avoid bumping mtime, which triggers crate recompile + if new_config != existing_config: + with open(pyo3_config, "w") as f: + f.write(new_config) + + +def build_pkg(): + subprocess.run( + [ + "cargo", + "build", + "--release", + "--no-default-features", + "--features", + cargo_features, + ], + check=True, + env=os.environ | dict(PYO3_CONFIG_FILE=str(pyo3_config)), + ) + + +def adj_path_for_windows_rsync(path: Path) -> str: + if not is_win: + return str(path) + + path = path.absolute() + rest = str(path)[2:].replace("\\", "/") + return f"/{path.drive[0]}{rest}" + + +def merge_into_dist(output_folder: Path, pyqt_src_path: Path | None): + if output_folder.exists(): + shutil.rmtree(output_folder) + output_folder.mkdir(parents=True) + # PyQt + if pyqt_src_path and not is_mac: + subprocess.run( + [ + "rsync", + "-a", + "--delete", + "--exclude-from", + "qt.exclude", + adj_path_for_windows_rsync(pyqt_src_path), + adj_path_for_windows_rsync(output_folder / "lib") + "/", + ], + check=True, + ) + if is_lin: + if "PyQt5" in str(pyqt_src_path): + src = extra_qt5_linux_plugins + dest = output_folder / "lib" / "PyQt5" / "Qt5" / "plugins" + else: + src = extra_qt6_linux_plugins + dest = output_folder / "lib" / "PyQt6" / "Qt6" / "plugins" + subprocess.run( + ["rsync", "-a", str(src) + "/", str(dest) + "/"], + check=True, + ) + + # Executable and other resources + resources = [ + adj_path_for_windows_rsync( + cargo_target / "release" / ("anki.exe" if is_win else "anki") + ), + adj_path_for_windows_rsync(artifacts_in_build) + "/", + ] + if is_lin: + resources.append("lin/") + + subprocess.run( + [ + "rsync", + "-a", + "--delete", + "--exclude", + "PyQt6", + "--exclude", + "PyQt5", + *resources, + adj_path_for_windows_rsync(output_folder) + "/", + ], + check=True, + ) + # Ensure all files are world-readable + if not is_win: + subprocess.run(["chmod", "-R", "a+r", output_folder]) + + +def anki_version() -> str: + with open(workspace / "defs.bzl") as fobj: + data = fobj.read() + return re.search('^anki_version = "(.*)"$', data, re.MULTILINE).group(1) + + +def annotated_linux_folder_name(variant: str) -> str: + components = ["anki", anki_version(), "linux", variant] + return "-".join(components) + + +def annotated_mac_dmg_name(variant: str) -> str: + if platform.machine() == "arm64": + arch = "apple" + else: + arch = "intel" + components = ["anki", anki_version(), "mac", arch, variant] + return "-".join(components) + + +def build_bundle(src_path: Path, variant: str) -> None: + if is_lin: + print("--- Build tarball") + build_tarball(src_path, variant) + elif is_mac: + print("--- Build app bundle") + build_app_bundle(src_path, variant) + + +def build_app_bundle(src_path: Path, variant: str) -> None: + if arm64_mac: + variant = "qt6_arm64" + else: + variant = f"{variant}_amd64" + subprocess.run( + ["cargo", "run", variant, src_path, anki_version(), bazel_external], + check=True, + cwd=workspace / "qt" / "bundle" / "mac", + ) + variant_path = src_path.parent / "app" / variant + if os.getenv("NOTARIZE_USER"): + subprocess.run( + ["python", "mac/notarize.py", "upload", variant_path], + check=True, + ) + # note down the dmg name for later + open(variant_path / "dmg_name", "w").write( + annotated_mac_dmg_name(variant[0:3]) + ".dmg" + ) + + +def build_tarball(src_path: Path, variant: str) -> None: + if not is_lin: + return + dest_path = src_path.with_name(annotated_linux_folder_name(variant)) + if dest_path.exists(): + shutil.rmtree(dest_path) + os.rename(src_path, dest_path) + print("compress", dest_path.name, "...") + subprocess.run( + [ + "tar", + "--zstd", + "-cf", + dist_folder / (dest_path.name + ".tar.zst"), + dest_path.name, + ], + check=True, + env=dict(ZSTD_CLEVEL="9"), + cwd=dest_path.parent, + ) + + +def build_windows_installers() -> None: + subprocess.run( + [ + "cargo", + "run", + output_root, + bazel_external, + Path(__file__).parent, + anki_version(), + ], + check=True, + cwd=workspace / "qt" / "bundle" / "win", + ) + + +print("--- Build PyOxidizer") +build_pyoxidizer() +print("--- Install wheels into venv") +install_wheels_into_venv() +print("--- Build PyOxidizer artifacts") +build_artifacts() +print("--- Build Anki binary") +build_pkg() +print("--- Copy binary+resources into folder (Qt6)") +merge_into_dist(output_root / "std", pyqt6_folder_path) +build_bundle(output_root / "std", "qt6") +if pyqt5_folder_name: + print("--- Copy binary+resources into folder (Qt5)") + merge_into_dist(output_root / "alt", bazel_external / pyqt5_folder_name / "PyQt5") + build_bundle(output_root / "alt", "qt5") + +if is_win: + build_windows_installers() + +if is_mac: + print("outputs are in .bazel/out/build/{std,alt}") + print("dmg can be created with mac/finalize.py dmg") +else: + print("outputs are in .bazel/out/dist/") diff --git a/qt/package/build.rs b/qt/bundle/build.rs similarity index 98% rename from qt/package/build.rs rename to qt/bundle/build.rs index 75a6d64c4..000dbd61a 100644 --- a/qt/package/build.rs +++ b/qt/bundle/build.rs @@ -104,6 +104,6 @@ fn main() { // embed manifest and icon if target_family == "windows" { - embed_resource::compile("anki-manifest.rc"); + embed_resource::compile("win/anki-manifest.rc"); } } diff --git a/qt/package/lin/README.md b/qt/bundle/lin/README.md similarity index 100% rename from qt/package/lin/README.md rename to qt/bundle/lin/README.md diff --git a/qt/package/lin/anki.1 b/qt/bundle/lin/anki.1 similarity index 100% rename from qt/package/lin/anki.1 rename to qt/bundle/lin/anki.1 diff --git a/qt/package/lin/anki.desktop b/qt/bundle/lin/anki.desktop similarity index 100% rename from qt/package/lin/anki.desktop rename to qt/bundle/lin/anki.desktop diff --git a/qt/package/lin/anki.png b/qt/bundle/lin/anki.png similarity index 100% rename from qt/package/lin/anki.png rename to qt/bundle/lin/anki.png diff --git a/qt/package/lin/anki.xml b/qt/bundle/lin/anki.xml similarity index 100% rename from qt/package/lin/anki.xml rename to qt/bundle/lin/anki.xml diff --git a/qt/package/lin/anki.xpm b/qt/bundle/lin/anki.xpm similarity index 100% rename from qt/package/lin/anki.xpm rename to qt/bundle/lin/anki.xpm diff --git a/qt/package/lin/install.sh b/qt/bundle/lin/install.sh similarity index 100% rename from qt/package/lin/install.sh rename to qt/bundle/lin/install.sh diff --git a/qt/package/lin/uninstall.sh b/qt/bundle/lin/uninstall.sh similarity index 100% rename from qt/package/lin/uninstall.sh rename to qt/bundle/lin/uninstall.sh diff --git a/qt/bundle/mac/Cargo.lock b/qt/bundle/mac/Cargo.lock new file mode 100644 index 000000000..3d9f6e9c6 --- /dev/null +++ b/qt/bundle/mac/Cargo.lock @@ -0,0 +1,204 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" + +[[package]] +name = "apple-bundles" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48681b45ff6789616b243c0758d6d97639951f937ccc0ea635363505d72cdec3" +dependencies = [ + "anyhow", + "plist", + "tugger-file-manifest", + "walkdir", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0005d08a8f7b65fb8073cb697aa0b12b631ed251ce73d862ce50eeb52ce3b50" + +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + +[[package]] +name = "makeapp" +version = "0.1.0" +dependencies = [ + "anyhow", + "apple-bundles", + "glob", + "lazy_static", + "plist", + "tugger-file-manifest", + "walkdir", +] + +[[package]] +name = "num_threads" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +dependencies = [ + "libc", +] + +[[package]] +name = "plist" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" +dependencies = [ + "base64", + "indexmap", + "line-wrap", + "serde", + "time", + "xml-rs", +] + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" + +[[package]] +name = "time" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +dependencies = [ + "itoa", + "libc", + "num_threads", +] + +[[package]] +name = "tugger-file-manifest" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29e91ac69050080a0a9fd50af05da5baa8562347ca7b8909f8ed3adbc6ef026f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" diff --git a/qt/bundle/mac/Cargo.toml b/qt/bundle/mac/Cargo.toml new file mode 100644 index 000000000..97355bc25 --- /dev/null +++ b/qt/bundle/mac/Cargo.toml @@ -0,0 +1,15 @@ +[package] +edition = "2021" +name = "makeapp" +version = "0.1.0" +authors = ["Ankitects Pty Ltd and contributors"] +license = "AGPL-3.0-or-later" + +[dependencies] +anyhow = "1.0.53" +glob = "0.3.0" +plist = "1.3.1" +walkdir = "2.3.2" +apple-bundles= "0.6.0" +tugger-file-manifest= "0.6.0" +lazy_static = "1.4.0" diff --git a/qt/bundle/mac/dmg/anki-logo-bg.png b/qt/bundle/mac/dmg/anki-logo-bg.png new file mode 100644 index 000000000..84f165653 Binary files /dev/null and b/qt/bundle/mac/dmg/anki-logo-bg.png differ diff --git a/qt/bundle/mac/dmg/build.sh b/qt/bundle/mac/dmg/build.sh new file mode 100755 index 000000000..e90b0a814 --- /dev/null +++ b/qt/bundle/mac/dmg/build.sh @@ -0,0 +1,42 @@ +# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +set -e + +# base folder with Anki.app in it +dist=$1 +dmg_path=$2 +script_folder=$(dirname $0) + +if [ -d "/Volumes/Anki" ] +then + echo "You already have one Anki mounted, unmount it first!" + exit 1 +fi + +echo "bundling..." +ln -s /Applications $dist/Applications +mkdir $dist/.background +cp ${script_folder}/anki-logo-bg.png $dist/.background +cp ${script_folder}/dmg_ds_store $dist/.DS_Store + +# create a writable dmg first, and modify its layout with AppleScript +hdiutil create -attach -ov -format UDRW -fs HFS+ -volname Anki -srcfolder $dist -o /tmp/Anki-rw.dmg +# announce before making the window appear +say "applescript" +open /tmp/Anki-rw.dmg +sleep 2 +open ${script_folder}/set-dmg-settings.app +sleep 2 +hdiutil detach "/Volumes/Anki" +sleep 1 +if [ -d "/Volumes/Anki" ] +then + echo "drive did not detach" + exit 1 +fi + +# convert it to a read-only image +rm -rf $dmg_path +hdiutil convert /tmp/Anki-rw.dmg -ov -format ULFO -o $dmg_path +rm -rf /tmp/Anki-rw.dmg $dist diff --git a/qt/bundle/mac/dmg/dmg_ds_store b/qt/bundle/mac/dmg/dmg_ds_store new file mode 100644 index 000000000..632e1a884 Binary files /dev/null and b/qt/bundle/mac/dmg/dmg_ds_store differ diff --git a/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Info.plist b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Info.plist new file mode 100644 index 000000000..068ebc435 --- /dev/null +++ b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Info.plist @@ -0,0 +1,74 @@ + + + + + CFBundleAllowMixedLocalizations + + CFBundleDevelopmentRegion + English + CFBundleExecutable + applet + CFBundleIconFile + applet + CFBundleIdentifier + com.apple.ScriptEditor.id.set-dmg-settings + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + set-dmg-settings + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + aplt + LSMinimumSystemVersionByArchitecture + + x86_64 + 10.6 + + LSRequiresCarbon + + NSAppleEventsUsageDescription + This script needs to control other applications to run. + NSAppleMusicUsageDescription + This script needs access to your music to run. + NSCalendarsUsageDescription + This script needs access to your calendars to run. + NSCameraUsageDescription + This script needs access to your camera to run. + NSContactsUsageDescription + This script needs access to your contacts to run. + NSHomeKitUsageDescription + This script needs access to your HomeKit Home to run. + NSMicrophoneUsageDescription + This script needs access to your microphone to run. + NSPhotoLibraryUsageDescription + This script needs access to your photos to run. + NSRemindersUsageDescription + This script needs access to your reminders to run. + NSSiriUsageDescription + This script needs access to Siri to run. + NSSystemAdministrationUsageDescription + This script needs access to administer this system to run. + WindowState + + bundleDividerCollapsed + + bundlePositionOfDivider + 0.0 + dividerCollapsed + + eventLogLevel + 2 + name + ScriptWindowState + positionOfDivider + 388 + savedFrame + 1308 314 700 672 0 0 2880 1597 + selectedTab + result + + + diff --git a/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/MacOS/applet b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/MacOS/applet new file mode 100755 index 000000000..42a481607 Binary files /dev/null and b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/MacOS/applet differ diff --git a/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/PkgInfo b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/PkgInfo new file mode 100644 index 000000000..3253614c4 --- /dev/null +++ b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPLaplt \ No newline at end of file diff --git a/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Resources/Scripts/main.scpt b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Resources/Scripts/main.scpt new file mode 100644 index 000000000..7852f0318 Binary files /dev/null and b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Resources/Scripts/main.scpt differ diff --git a/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Resources/applet.icns b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Resources/applet.icns new file mode 100644 index 000000000..0cdd17086 Binary files /dev/null and b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Resources/applet.icns differ diff --git a/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Resources/applet.rsrc b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Resources/applet.rsrc new file mode 100644 index 000000000..868773cbb Binary files /dev/null and b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Resources/applet.rsrc differ diff --git a/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Resources/description.rtfd/TXT.rtf b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Resources/description.rtfd/TXT.rtf new file mode 100644 index 000000000..b882bf154 --- /dev/null +++ b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/Resources/description.rtfd/TXT.rtf @@ -0,0 +1,5 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1671 +{\fonttbl} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +} \ No newline at end of file diff --git a/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/_CodeSignature/CodeResources b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/_CodeSignature/CodeResources new file mode 100644 index 000000000..6891d4d4c --- /dev/null +++ b/qt/bundle/mac/dmg/set-dmg-settings.app/Contents/_CodeSignature/CodeResources @@ -0,0 +1,177 @@ + + + + + files + + Resources/Scripts/main.scpt + + BbcHsL7M8GleNWeDVHOZVEfpSUQ= + + Resources/applet.icns + + sINd6lbiqHD5dL8c6u79cFvVXhw= + + Resources/applet.rsrc + + 7JOq2AjTwoRdSRoaun87Me8EbB4= + + Resources/description.rtfd/TXT.rtf + + HZLGvORC/avx2snxaACit3D0IJY= + + + files2 + + Resources/Scripts/main.scpt + + hash + + BbcHsL7M8GleNWeDVHOZVEfpSUQ= + + hash2 + + T6pvOxUGXyc+qwn+hdv1xPzvnYM+qo9uxLLWUkIFq3Q= + + + Resources/applet.icns + + hash + + sINd6lbiqHD5dL8c6u79cFvVXhw= + + hash2 + + J7weZ6vlnv9r32tS5HFcyuPXl2StdDnfepLxAixlryk= + + + Resources/applet.rsrc + + hash + + 7JOq2AjTwoRdSRoaun87Me8EbB4= + + hash2 + + WvL2TvNeKuY64Sp86Cyvcmiood5xzbJmcAH3R0+gIc8= + + + Resources/description.rtfd/TXT.rtf + + hash + + HZLGvORC/avx2snxaACit3D0IJY= + + hash2 + + XuDTd2OPOPGq65NBuXy6WuqU+bODdg+oDmBFhsZTaVU= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/qt/bundle/mac/dmg/set-dmg-settings.scpt b/qt/bundle/mac/dmg/set-dmg-settings.scpt new file mode 100644 index 000000000..ac8d1c2f5 Binary files /dev/null and b/qt/bundle/mac/dmg/set-dmg-settings.scpt differ diff --git a/qt/bundle/mac/entitlements.python.xml b/qt/bundle/mac/entitlements.python.xml new file mode 100644 index 000000000..adefed5e4 --- /dev/null +++ b/qt/bundle/mac/entitlements.python.xml @@ -0,0 +1,14 @@ + + + + + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.device.audio-input + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + + diff --git a/qt/bundle/mac/finalize.py b/qt/bundle/mac/finalize.py new file mode 100644 index 000000000..d438f1011 --- /dev/null +++ b/qt/bundle/mac/finalize.py @@ -0,0 +1,60 @@ +# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +# These steps are outsite bundle/build.py, so that multiple builds can be done +# in sequence without blocking on Apple's notarization, and so that the final +# dmg build can be done in bulk at the end. + +import os +import subprocess +import sys +from pathlib import Path + +output_root = Path(__file__).parent / "../../../.bazel/out" +dist_folder = output_root / "dist" +apps = output_root / "build" / "app" +variants = ["qt6_arm64", "qt6_amd64", "qt5_amd64"] + + +def staple_apps() -> None: + for variant in variants: + variant_base = apps / variant + if variant_base.exists(): + if os.getenv("NOTARIZE_USER"): + subprocess.run( + [ + "python", + Path(__file__).with_name("notarize.py"), + "staple", + variant_base, + ], + check=True, + ) + else: + print("skip missing", variant_base) + + +def build_dmgs() -> None: + for variant in variants: + variant_base = apps / variant + if variant_base.exists(): + dmg_name_path = variant_base / "dmg_name" + dmg_name = open(dmg_name_path).read() + dmg_name_path.unlink() + subprocess.run( + [ + "bash", + Path(__file__).with_name("dmg") / "build.sh", + variant_base, + dist_folder / dmg_name, + ], + check=True, + ) + else: + print("skip missing", variant_base) + + +if sys.argv[1] == "staple": + staple_apps() +elif sys.argv[1] == "dmg": + build_dmgs() diff --git a/qt/bundle/mac/icon/Assets.car b/qt/bundle/mac/icon/Assets.car new file mode 100644 index 000000000..d3803495b Binary files /dev/null and b/qt/bundle/mac/icon/Assets.car differ diff --git a/qt/bundle/mac/icon/Assets.xcassets/AppIcon.appiconset/Contents.json b/qt/bundle/mac/icon/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d0600b28d --- /dev/null +++ b/qt/bundle/mac/icon/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,59 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "round-1024-512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/qt/bundle/mac/icon/Assets.xcassets/AppIcon.appiconset/round-1024-512.png b/qt/bundle/mac/icon/Assets.xcassets/AppIcon.appiconset/round-1024-512.png new file mode 100644 index 000000000..3ab3ed40a Binary files /dev/null and b/qt/bundle/mac/icon/Assets.xcassets/AppIcon.appiconset/round-1024-512.png differ diff --git a/qt/bundle/mac/icon/Assets.xcassets/Contents.json b/qt/bundle/mac/icon/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/qt/bundle/mac/icon/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/qt/bundle/mac/icon/build.sh b/qt/bundle/mac/icon/build.sh new file mode 100755 index 000000000..bba481554 --- /dev/null +++ b/qt/bundle/mac/icon/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e + +xcrun actool --app-icon AppIcon $(pwd)/Assets.xcassets --compile . --platform macosx --minimum-deployment-target 13.0 --target-device mac --output-partial-info-plist /dev/null diff --git a/qt/bundle/mac/notarize.py b/qt/bundle/mac/notarize.py new file mode 100644 index 000000000..acc6d8aae --- /dev/null +++ b/qt/bundle/mac/notarize.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +import os +import re +import subprocess +import sys +import time +from pathlib import Path + +USERNAME = os.getenv("NOTARIZE_USER") +PASSWORD = os.getenv("NOTARIZE_PASSWORD") +BUNDLE_ID = "net.ankiweb.dtop" + + +def upload(base_dir: Path, uuid_path: Path) -> None: + print("--- Prepare notarization zip") + + app_dir = base_dir / "Anki.app" + zip_path = app_dir.with_suffix(".zip") + + subprocess.run(["ditto", "-c", "-k", "--keepParent", app_dir, zip_path]) + + print("--- Upload for notarization") + + try: + output = subprocess.check_output( + [ + "xcrun", + "altool", + "--notarize-app", + "--primary-bundle-id", + BUNDLE_ID, + "--username", + USERNAME, + "--password", + PASSWORD, + "--file", + zip_path, + ], + stderr=subprocess.STDOUT, + encoding="utf8", + ) + except subprocess.CalledProcessError as e: + print("error uploading:", e.output) + sys.exit(1) + + uuid = None + for line in output.splitlines(): + m = re.search(r"RequestUUID = (.*)", line) + if m: + uuid = m.group(1) + + if not uuid: + print("no uuid found - upload output:") + print(output) + sys.exit(1) + + open(uuid_path, "w").write(uuid) + zip_path.unlink() + + +def _extract_status(output): + for line in output.splitlines(): + m = re.search(r"Status: (.*)", line) + if m: + return m.group(1) + + return None + + +def wait_for_success(uuid, wait=True): + while True: + print("checking status...", end="", flush=True) + + try: + output = subprocess.check_output( + [ + "xcrun", + "altool", + "--notarization-info", + uuid, + "--username", + USERNAME, + "--password", + PASSWORD, + ], + stderr=subprocess.STDOUT, + encoding="utf8", + ) + except subprocess.CalledProcessError as e: + print("error checking status:") + print(e.output) + sys.exit(1) + + status = _extract_status(output) + if status is None: + print("could not extract status:") + print(output) + sys.exit(1) + + if status == "invalid": + print("notarization failed:") + print(output) + sys.exit(1) + + if status == "success": + print("success!") + print(output) + return + + print(status) + if not wait: + return + time.sleep(30) + + +def staple(app_path): + try: + subprocess.check_call( + [ + "xcrun", + "stapler", + "staple", + app_path, + ] + ) + except subprocess.CalledProcessError as e: + print("error stapling:") + print(e.output) + sys.exit(1) + + +cmd = sys.argv[1] +base_dir = Path(sys.argv[2]) +uuid_path = base_dir / "uuid" + +if cmd == "upload": + upload(base_dir, uuid_path) +elif cmd == "status": + uuid = open(uuid_path).read() + wait_for_success(uuid, False) +elif cmd == "staple": + uuid = open(uuid_path).read() + wait_for_success(uuid) + staple(base_dir / "Anki.app") + uuid_path.unlink() diff --git a/qt/bundle/mac/src/Info.plist b/qt/bundle/mac/src/Info.plist new file mode 100644 index 000000000..b960057cd --- /dev/null +++ b/qt/bundle/mac/src/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDisplayName + Anki + CFBundleShortVersionString + 2.1.46 + LSMinimumSystemVersion + 10.13.4 + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + colpkg + apkg + ankiaddon + + CFBundleTypeIconName + AppIcon + CFBundleTypeName + Anki File + CFBundleTypeRole + Editor + + + CFBundleExecutable + MacOS/anki + CFBundleIconName + AppIcon + CFBundleIdentifier + net.ankiweb.dtop + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Anki + CFBundlePackageType + APPL + NSHighResolutionCapable + + NSMicrophoneUsageDescription + The microphone will only be used when you tap the record button. + NSRequiresAquaSystemAppearance + + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/qt/bundle/mac/src/main.rs b/qt/bundle/mac/src/main.rs new file mode 100644 index 000000000..dd77271bf --- /dev/null +++ b/qt/bundle/mac/src/main.rs @@ -0,0 +1,223 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +//! Munge the output of PyOxidizer into a macOS app bundle, and combine it +//! with our other runtime dependencies. + +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; + +use anyhow::{bail, Context, Result}; +use apple_bundles::MacOsApplicationBundleBuilder; +use plist::Value; +use tugger_file_manifest::FileEntry; +use walkdir::WalkDir; + +const CODESIGN_ARGS: &[&str] = &["-vvvv", "-o", "runtime", "-s", "Developer ID Application:"]; + +#[derive(Clone, Copy, Debug)] +enum Variant { + StandardX86, + StandardArm, + AlternateX86, +} + +impl FromStr for Variant { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "qt6_arm64" => Variant::StandardArm, + "qt6_amd64" => Variant::StandardX86, + "qt5_amd64" => Variant::AlternateX86, + other => bail!("unexpected variant: {other}"), + }) + } +} + +impl Variant { + fn output_base(&self) -> &str { + match self { + Variant::StandardX86 => "qt6_amd64", + Variant::StandardArm => "qt6_arm64", + Variant::AlternateX86 => "qt5_amd64", + } + } + + fn macos_min(&self) -> &str { + match self { + Variant::StandardX86 => "10.14.4", + Variant::StandardArm => "11", + Variant::AlternateX86 => "10.13.4", + } + } + + fn qt_repo(&self) -> &str { + match self { + Variant::StandardX86 => "pyqt6.2_mac_bundle_amd64", + Variant::StandardArm => "pyqt6.2_mac_bundle_arm64", + Variant::AlternateX86 => "pyqt5.14_mac_bundle_amd64", + } + } + + fn audio_repo(&self) -> &str { + match self { + Variant::StandardX86 | Variant::AlternateX86 => "audio_mac_amd64", + Variant::StandardArm => "audio_mac_arm64", + } + } +} + +fn main() -> anyhow::Result<()> { + let args: Vec<_> = std::env::args().collect(); + let variant: Variant = args.get(1).context("variant")?.parse()?; + let bundle_folder = PathBuf::from(args.get(2).context("bundle folder")?); + let anki_version = args.get(3).context("anki version")?; + let bazel_external = PathBuf::from(args.get(4).context("bazel external folder")?); + + let plist = get_plist(anki_version); + make_app(variant, &bundle_folder, plist, &bazel_external) +} + +fn make_app( + variant: Variant, + input_folder: &Path, + mut plist: plist::Dictionary, + bazel_external: &Path, +) -> Result<()> { + let output_folder = input_folder + .with_file_name("app") + .join(variant.output_base()) + .join("Anki.app"); + if output_folder.exists() { + std::fs::remove_dir_all(&output_folder)?; + } + std::fs::create_dir_all(&output_folder)?; + + let mut builder = MacOsApplicationBundleBuilder::new("Anki")?; + plist.insert( + "LSMinimumSystemVersion".into(), + Value::from(variant.macos_min()), + ); + builder.set_info_plist_from_dictionary(plist)?; + builder.add_file_resources("Assets.car", &include_bytes!("../icon/Assets.car")[..])?; + + for entry in WalkDir::new(&input_folder) + .into_iter() + .map(Result::unwrap) + .filter(|e| !e.file_type().is_dir()) + { + let path = entry.path(); + let entry = FileEntry::try_from(path)?; + let relative_path = path.strip_prefix(&input_folder)?; + let path_str = relative_path.to_str().unwrap(); + if path_str.contains("libankihelper") { + builder.add_file_macos("libankihelper.dylib", entry)?; + } else if path_str.contains("aqt/data") { + builder.add_file_resources(relative_path.strip_prefix("lib").unwrap(), entry)?; + } else { + if path_str.contains("__pycache__") { + continue; + } + builder.add_file_macos(relative_path, entry)?; + } + } + + let dry_run = false; + if dry_run { + for file in builder.files().iter_files() { + println!("{}", file.path_string()); + } + } else { + builder.files().materialize_files(&output_folder)?; + fix_rpath(output_folder.join("Contents/MacOS/anki"))?; + codesign_python_libs(&output_folder)?; + copy_in_audio(&output_folder, variant, bazel_external)?; + copy_in_qt(&output_folder, variant, bazel_external)?; + codesign_app(&output_folder)?; + } + + Ok(()) +} + +/// Copy everything at the provided path into the Contents/ folder of our app. +/// Excludes standard Bazel repo files. +fn extend_app_contents(source: &Path, bundle_dir: &Path) -> Result<()> { + let status = std::process::Command::new("rsync") + .arg("-a") + .args(["--exclude", "BUILD.bazel", "--exclude", "WORKSPACE"]) + .arg(format!("{}/", source.to_string_lossy())) + .arg(bundle_dir.join("Contents/")) + .status()?; + if !status.success() { + bail!("error syncing {source:?}"); + } + Ok(()) +} + +fn copy_in_audio(bundle_dir: &Path, variant: Variant, bazel_external: &Path) -> Result<()> { + println!("Copying in audio..."); + extend_app_contents(&bazel_external.join(variant.audio_repo()), bundle_dir) +} + +fn copy_in_qt(bundle_dir: &Path, variant: Variant, bazel_external: &Path) -> Result<()> { + println!("Copying in Qt..."); + extend_app_contents(&bazel_external.join(variant.qt_repo()), bundle_dir) +} + +fn codesign_file(path: &Path, extra_args: &[&str]) -> Result<()> { + if option_env!("ANKI_CODESIGN").is_some() { + let status = std::process::Command::new("codesign") + .args(CODESIGN_ARGS) + .args(extra_args) + .arg(path.to_str().unwrap()) + .status()?; + if !status.success() { + bail!("codesign failed"); + } + } + + Ok(()) +} + +fn codesign_python_libs(bundle_dir: &PathBuf) -> Result<()> { + for entry in glob::glob( + bundle_dir + .join("Contents/MacOS/lib/**/*.so") + .to_str() + .unwrap(), + )? { + let entry = entry?; + codesign_file(&entry, &[])?; + } + codesign_file(&bundle_dir.join("Contents/MacOS/libankihelper.dylib"), &[]) +} + +fn codesign_app(bundle_dir: &PathBuf) -> Result<()> { + codesign_file(bundle_dir, &["--entitlements", "entitlements.python.xml"]) +} + +fn fix_rpath(exe_path: PathBuf) -> Result<()> { + let status = std::process::Command::new("install_name_tool") + .arg("-add_rpath") + .arg("@executable_path/../Frameworks") + .arg(exe_path.to_str().unwrap()) + .status()?; + assert!(status.success()); + Ok(()) +} + +fn get_plist(anki_version: &str) -> plist::Dictionary { + let reader = std::io::Cursor::new(include_bytes!("Info.plist")); + let mut plist = plist::Value::from_reader(reader) + .unwrap() + .into_dictionary() + .unwrap(); + plist.insert( + "CFBundleShortVersionString".into(), + Value::from(anki_version), + ); + plist +} diff --git a/qt/package/pyoxidizer.bzl b/qt/bundle/pyoxidizer.bzl similarity index 96% rename from qt/package/pyoxidizer.bzl rename to qt/bundle/pyoxidizer.bzl index c70052272..be78a1ad8 100644 --- a/qt/package/pyoxidizer.bzl +++ b/qt/bundle/pyoxidizer.bzl @@ -1,4 +1,4 @@ -set_build_path("../../bazel-pkg/build") +set_build_path(VARS.get("build")) excluded_source_prefixes = [ "ctypes.test", @@ -131,6 +131,9 @@ def make_exe(): # detected libs do not need this, but we add extra afterwards python_config.module_search_paths = ["$ORIGIN/lib"] python_config.optimization_level = 2 + if BUILD_TARGET_TRIPLE == "x86_64-apple-darwin": + # jemalloc currently fails to build when run under Rosetta + python_config.allocator_backend = "default" python_config.run_command = "import aqt; aqt.run()" diff --git a/qt/package/qt.exclude b/qt/bundle/qt.exclude similarity index 100% rename from qt/package/qt.exclude rename to qt/bundle/qt.exclude diff --git a/qt/package/rustfmt.toml b/qt/bundle/rustfmt.toml similarity index 100% rename from qt/package/rustfmt.toml rename to qt/bundle/rustfmt.toml diff --git a/qt/package/src/anki.rs b/qt/bundle/src/anki.rs similarity index 100% rename from qt/package/src/anki.rs rename to qt/bundle/src/anki.rs diff --git a/qt/package/src/main.rs b/qt/bundle/src/main.rs similarity index 100% rename from qt/package/src/main.rs rename to qt/bundle/src/main.rs diff --git a/qt/bundle/win/Cargo.lock b/qt/bundle/win/Cargo.lock new file mode 100644 index 000000000..138492420 --- /dev/null +++ b/qt/bundle/win/Cargo.lock @@ -0,0 +1,1497 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anyhow" +version = "1.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "bzip2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "des" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac41dd49fb554432020d52c875fc290e110113f864c6b1b525cd62c7e7747a5d" +dependencies = [ + "byteorder", + "cipher", + "opaque-debug", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "duct" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc6a0a59ed0888e0041cf708e66357b7ae1a82f1c67247e1f93b5e0818f7d8d" +dependencies = [ + "libc", + "once_cell", + "os_pipe", + "shared_child", +] + +[[package]] +name = "encoding_rs" +version = "0.8.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "find-winsdk" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8cbf17b871570c1f8612b763bac3e86290602bcf5dc3c5ce657e0e1e9071d9e" +dependencies = [ + "serde", + "serde_derive", + "winreg 0.5.1", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "h2" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest", +] + +[[package]] +name = "http" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.1", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 0.4.8", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "makeinstall" +version = "0.1.0" +dependencies = [ + "anyhow", + "glob", + "slog", + "slog-term", + "tugger-windows-codesign", + "walkdir", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "os_pipe" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "p12" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10105b08ad3c4cd872ddf396860544f9dbc1800fed7d552f10aa5b585ac79e1d" +dependencies = [ + "block-modes", + "des", + "getrandom", + "hmac", + "lazy_static", + "rc2", + "sha-1", + "yasna", +] + +[[package]] +name = "pem" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9a3b09a20e374558580a4914d3b7d89bd61b954a5a5e1dcbea98753addb1947" +dependencies = [ + "base64", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rc2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f197c283075d1345c20d5ad172526a7837882cdc998b1fcd2b2f3cfff1cb94" +dependencies = [ + "cipher", + "opaque-debug", +] + +[[package]] +name = "rcgen" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5911d1403f4143c9d56a702069d593e8d0f3fab880a85e103604d0893ea31ba7" +dependencies = [ + "chrono", + "pem", + "ring", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg 0.7.0", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7" + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +dependencies = [ + "itoa 1.0.1", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.1", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "shared_child" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6be9f7d5565b1483af3e72975e2dee33879b3b86bd48c0929fccf6585d79e65a" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" + +[[package]] +name = "slog-term" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95c1e7e5aab61ced6006149ea772770b84a0d16ce0f7885def313e4829946d76" +dependencies = [ + "atty", + "chrono", + "slog", + "term", + "thread_local", +] + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "winapi", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-util" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tugger-common" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a7438a6e6ed337853dfeb03e3eb463c25d60141186b65363a1dfa3ed8fa6bab" +dependencies = [ + "anyhow", + "fs2", + "glob", + "hex", + "once_cell", + "reqwest", + "sha2", + "slog", + "tempfile", + "url", + "zip", +] + +[[package]] +name = "tugger-windows" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862f31d91b88b479a2671584a9772f81ec3d21b8314adc4fdf17619b12f8750c" +dependencies = [ + "anyhow", + "duct", + "find-winsdk", + "glob", + "once_cell", + "semver", + "tugger-common", + "winapi", +] + +[[package]] +name = "tugger-windows-codesign" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fe75b7e13b3ba9d95001be2f5a0b5bd1246d38492f5658fd5e90c1006eed00" +dependencies = [ + "anyhow", + "chrono", + "duct", + "p12", + "rcgen", + "slog", + "tugger-common", + "tugger-windows", + "yasna", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" + +[[package]] +name = "web-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" +dependencies = [ + "webpki", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a" +dependencies = [ + "serde", + "winapi", +] + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] + +[[package]] +name = "yasna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75" +dependencies = [ + "chrono", +] + +[[package]] +name = "zip" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +dependencies = [ + "byteorder", + "bzip2", + "crc32fast", + "flate2", + "thiserror", + "time", +] diff --git a/qt/bundle/win/Cargo.toml b/qt/bundle/win/Cargo.toml new file mode 100644 index 000000000..757a3ad29 --- /dev/null +++ b/qt/bundle/win/Cargo.toml @@ -0,0 +1,14 @@ +[package] +edition = "2021" +name = "makeinstall" +version = "0.1.0" +authors = ["Ankitects Pty Ltd and contributors"] +license = "AGPL-3.0-or-later" + +[dependencies] +anyhow = "1.0.53" +glob = "0.3.0" +slog = "2.7.0" +slog-term = "2.8.0" +tugger-windows-codesign = "0.6.0" +walkdir = "2.3.2" diff --git a/qt/package/win/anki-console.bat b/qt/bundle/win/anki-console.bat similarity index 100% rename from qt/package/win/anki-console.bat rename to qt/bundle/win/anki-console.bat diff --git a/qt/package/anki-icon.ico b/qt/bundle/win/anki-icon.ico similarity index 100% rename from qt/package/anki-icon.ico rename to qt/bundle/win/anki-icon.ico diff --git a/qt/package/anki-manifest.rc b/qt/bundle/win/anki-manifest.rc similarity index 100% rename from qt/package/anki-manifest.rc rename to qt/bundle/win/anki-manifest.rc diff --git a/qt/package/anki.exe.manifest b/qt/bundle/win/anki.exe.manifest similarity index 100% rename from qt/package/anki.exe.manifest rename to qt/bundle/win/anki.exe.manifest diff --git a/qt/bundle/win/anki.template.nsi b/qt/bundle/win/anki.template.nsi new file mode 100644 index 000000000..db414b943 --- /dev/null +++ b/qt/bundle/win/anki.template.nsi @@ -0,0 +1,210 @@ +;; This installer was written many years ago, and it is probably worth investigating modern +;; installer alternatives. + +!include "fileassoc.nsh" +!include WinVer.nsh +!include x64.nsh +; must be installed into NSIS install location +; can be found on https://github.com/ankitects/anki-bundle-extras/releases/tag/anki-2022-02-09 +!include nsProcess.nsh + +;-------------------------------- + +!pragma warning disable 6020 ; don't complain about missing installer in second invocation + +; The name of the installer +Name "Anki" + +Unicode true + +; The file to write +OutFile "anki-setup.exe" + +; The default installation directory +InstallDir "$PROGRAMFILES64\Anki" + +; Remember the install location +InstallDirRegKey HKLM "Software\Anki" "Install_Dir64" + +AllowSkipFiles off + +!ifdef NO_COMPRESS +SetCompress off +!else +SetCompressor /solid lzma +!endif + +Function .onInit + ${IfNot} ${AtLeastWin10} + MessageBox MB_OK "Windows 10 or later required." + Quit + ${EndIf} + + ${IfNot} ${RunningX64} + MessageBox MB_OK "64bit Windows is required." + Quit + ${EndIf} + + ${nsProcess::FindProcess} "anki.exe" $R0 + StrCmp $R0 0 0 notRunning + MessageBox MB_OK|MB_ICONEXCLAMATION "Anki.exe is already running. Please close it, then run the installer again." /SD IDOK + Abort + notRunning: +FunctionEnd + +!ifdef WRITE_UNINSTALLER +!uninstfinalize 'copy "%1" "std\uninstall.exe"' +!endif + +;-------------------------------- + +; Pages + +Page directory +Page instfiles + + +;; manifest removal script shared by installer and uninstaller +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +!define UninstLog "anki.install-manifest" +Var UninstLog + +!macro removeManifestFiles un +Function ${un}removeManifestFiles + IfFileExists "$INSTDIR\${UninstLog}" proceed + DetailPrint "No previous install manifest found, skipping cleanup." + return + +;; this code was based on an example found on the net, which I can no longer find +proceed: + Push $R0 + Push $R1 + Push $R2 + SetFileAttributes "$INSTDIR\${UninstLog}" NORMAL + FileOpen $UninstLog "$INSTDIR\${UninstLog}" r + StrCpy $R1 -1 + + GetLineCount: + ClearErrors + FileRead $UninstLog $R0 + IntOp $R1 $R1 + 1 + StrCpy $R0 $R0 -2 + Push $R0 + IfErrors 0 GetLineCount + + Pop $R0 + + LoopRead: + StrCmp $R1 0 LoopDone + Pop $R0 + ;; manifest is relative to instdir + StrCpy $R0 "$INSTDIR\$R0" + + IfFileExists "$R0\*.*" 0 +3 + RMDir $R0 #is dir + Goto processed + IfFileExists $R0 0 +3 + Delete $R0 #is file + Goto processed + +processed: + + IntOp $R1 $R1 - 1 + Goto LoopRead + LoopDone: + FileClose $UninstLog + Delete "$INSTDIR\${UninstLog}" + RMDir "$INSTDIR" + Pop $R2 + Pop $R1 + Pop $R0 +FunctionEnd +!macroend + +!insertmacro removeManifestFiles "" +!insertmacro removeManifestFiles "un." + +;-------------------------------- + +; The stuff to install +Section "" + + SetShellVarContext all + + Call removeManifestFiles + + ; Set output path to the installation directory. + SetOutPath $INSTDIR + CreateShortCut "$DESKTOP\Anki.lnk" "$INSTDIR\anki.exe" "" + CreateShortCut "$SMPROGRAMS\Anki.lnk" "$INSTDIR\anki.exe" "" + + ; Add files to installer + !ifndef WRITE_UNINSTALLER + File /r @@SRC@@\*.* + !endif + + !insertmacro APP_ASSOCIATE "apkg" "anki.apkg" \ + "Anki deck package" "$INSTDIR\anki.exe,0" \ + "Open with Anki" "$INSTDIR\anki.exe $\"%L$\"" + + !insertmacro APP_ASSOCIATE "colpkg" "anki.colpkg" \ + "Anki collection package" "$INSTDIR\anki.exe,0" \ + "Open with Anki" "$INSTDIR\anki.exe $\"%L$\"" + + !insertmacro APP_ASSOCIATE "ankiaddon" "anki.ankiaddon" \ + "Anki add-on" "$INSTDIR\anki.exe,0" \ + "Open with Anki" "$INSTDIR\anki.exe $\"%L$\"" + + !insertmacro UPDATEFILEASSOC + + ; Write the installation path into the registry + WriteRegStr HKLM Software\Anki "Install_Dir64" "$INSTDIR" + + ; Write the uninstall keys for Windows + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "DisplayName" "Anki" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "DisplayVersion" "@@VERSION@@" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "NoModify" 1 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "NoRepair" 1 + + !ifdef WRITE_UNINSTALLER + WriteUninstaller "uninstall.exe" + !endif + +SectionEnd ; end the section + +;-------------------------------- + +; Uninstaller + +function un.onInit + MessageBox MB_OKCANCEL "This will remove Anki's program files, but will not delete your card data. If you wish to delete your card data as well, you can do so via File>Switch Profile inside Anki first. Are you sure you wish to uninstall Anki?" IDOK next + Quit + next: +functionEnd + +Section "Uninstall" + + SetShellVarContext all + + Call un.removeManifestFiles + + ; Remove other shortcuts + Delete "$DESKTOP\Anki.lnk" + Delete "$SMPROGRAMS\Anki.lnk" + + ; associations + !insertmacro APP_UNASSOCIATE "apkg" "anki.apkg" + !insertmacro APP_UNASSOCIATE "colpkg" "anki.colpkg" + !insertmacro APP_UNASSOCIATE "ankiaddon" "anki.ankiaddon" + !insertmacro UPDATEFILEASSOC + + ; try to remove top level folder if empty + RMDir "$INSTDIR" + + ; Remove registry keys + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" + DeleteRegKey HKLM Software\Anki + +SectionEnd diff --git a/qt/bundle/win/fileassoc.nsh b/qt/bundle/win/fileassoc.nsh new file mode 100644 index 000000000..f76c28e35 --- /dev/null +++ b/qt/bundle/win/fileassoc.nsh @@ -0,0 +1,120 @@ +; fileassoc.nsh +; https://nsis.sourceforge.io/File_Association +; File association helper macros +; Written by Saivert +; +; Features automatic backup system and UPDATEFILEASSOC macro for +; shell change notification. +; +; |> How to use <| +; To associate a file with an application so you can double-click it in explorer, use +; the APP_ASSOCIATE macro like this: +; +; Example: +; !insertmacro APP_ASSOCIATE "txt" "myapp.textfile" "$INSTDIR\myapp.exe,0" \ +; "Open with myapp" "$INSTDIR\myapp.exe $\"%1$\"" +; +; Never insert the APP_ASSOCIATE macro multiple times, it is only ment +; to associate an application with a single file and using the +; the "open" verb as default. To add more verbs (actions) to a file +; use the APP_ASSOCIATE_ADDVERB macro. +; +; Example: +; !insertmacro APP_ASSOCIATE_ADDVERB "myapp.textfile" "edit" "Edit with myapp" \ +; "$INSTDIR\myapp.exe /edit $\"%1$\"" +; +; To have access to more options when registering the file association use the +; APP_ASSOCIATE_EX macro. Here you can specify the verb and what verb is to be the +; standard action (default verb). +; +; And finally: To remove the association from the registry use the APP_UNASSOCIATE +; macro. Here is another example just to wrap it up: +; !insertmacro APP_UNASSOCIATE "txt" "myapp.textfile" +; +; |> Note <| +; When defining your file class string always use the short form of your application title +; then a period (dot) and the type of file. This keeps the file class sort of unique. +; Examples: +; Winamp.Playlist +; NSIS.Script +; Photoshop.JPEGFile +; +; |> Tech info <| +; The registry key layout for a file association is: +; HKEY_CLASSES_ROOT +; = <"description"> +; shell +; = <"menu-item text"> +; command = <"command string"> +; + +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 HKCR ".${EXT}" "" + WriteRegStr HKCR ".${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr HKCR ".${EXT}" "" "${FILECLASS}" + + WriteRegStr HKCR "${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr HKCR "${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr HKCR "${FILECLASS}\shell" "" "open" + WriteRegStr HKCR "${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr HKCR "${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_ASSOCIATE_EX EXT FILECLASS DESCRIPTION ICON VERB DEFAULTVERB SHELLNEW COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 HKCR ".${EXT}" "" + WriteRegStr HKCR ".${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr HKCR ".${EXT}" "" "${FILECLASS}" + StrCmp "${SHELLNEW}" "0" +2 + WriteRegStr HKCR ".${EXT}\ShellNew" "NullFile" "" + + WriteRegStr HKCR "${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr HKCR "${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr HKCR "${FILECLASS}\shell" "" `${DEFAULTVERB}` + WriteRegStr HKCR "${FILECLASS}\shell\${VERB}" "" `${COMMANDTEXT}` + WriteRegStr HKCR "${FILECLASS}\shell\${VERB}\command" "" `${COMMAND}` +!macroend + +!macro APP_ASSOCIATE_ADDVERB FILECLASS VERB COMMANDTEXT COMMAND + WriteRegStr HKCR "${FILECLASS}\shell\${VERB}" "" `${COMMANDTEXT}` + WriteRegStr HKCR "${FILECLASS}\shell\${VERB}\command" "" `${COMMAND}` +!macroend + +!macro APP_ASSOCIATE_REMOVEVERB FILECLASS VERB + DeleteRegKey HKCR `${FILECLASS}\shell\${VERB}` +!macroend + + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 HKCR ".${EXT}" `${FILECLASS}_backup` + WriteRegStr HKCR ".${EXT}" "" "$R0" + + DeleteRegKey HKCR `${FILECLASS}` +!macroend + +!macro APP_ASSOCIATE_GETFILECLASS OUTPUT EXT + ReadRegStr ${OUTPUT} HKCR ".${EXT}" "" +!macroend + + +; !defines for use with SHChangeNotify +!ifdef SHCNE_ASSOCCHANGED +!undef SHCNE_ASSOCCHANGED +!endif +!define SHCNE_ASSOCCHANGED 0x08000000 +!ifdef SHCNF_FLUSH +!undef SHCNF_FLUSH +!endif +!define SHCNF_FLUSH 0x1000 + +!macro UPDATEFILEASSOC +; Using the system.dll plugin to call the SHChangeNotify Win32 API function so we +; can update the shell. + System::Call "shell32::SHChangeNotify(i,i,i,i) (${SHCNE_ASSOCCHANGED}, ${SHCNF_FLUSH}, 0, 0)" +!macroend + +;EOF diff --git a/qt/bundle/win/rustfmt.toml b/qt/bundle/win/rustfmt.toml new file mode 100644 index 000000000..3c812a2b9 --- /dev/null +++ b/qt/bundle/win/rustfmt.toml @@ -0,0 +1,4 @@ +# this is not supported on stable Rust, and is ignored by the Bazel rules; it is only +# useful for manual invocation with 'cargo +nightly fmt' +imports_granularity = "Crate" +group_imports = "StdExternalCrate" diff --git a/qt/bundle/win/src/main.rs b/qt/bundle/win/src/main.rs new file mode 100644 index 000000000..0a866be56 --- /dev/null +++ b/qt/bundle/win/src/main.rs @@ -0,0 +1,222 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use std::{ + fs, + io::prelude::*, + path::{Component, Path, PathBuf, Prefix}, + process::Command, +}; + +use anyhow::{bail, Context, Result}; +use slog::*; +use tugger_windows_codesign::{CodeSigningCertificate, SigntoolSign, SystemStore, TimestampServer}; +use walkdir::WalkDir; + +fn main() -> anyhow::Result<()> { + let plain = slog_term::PlainSyncDecorator::new(std::io::stdout()); + let logger = Logger::root(slog_term::FullFormat::new(plain).build().fuse(), o!()); + + let args: Vec<_> = std::env::args().collect(); + let build_folder = PathBuf::from(args.get(1).context("build folder")?); + let bazel_external = PathBuf::from(args.get(2).context("bazel external")?); + // bundle/build.py folder + let build_py_folder = PathBuf::from(args.get(3).context("build_py_folder")?); + let version = args.get(4).context("version")?; + + let std_folder = build_folder.join("std"); + let alt_folder = build_folder.join("alt"); + let folders = &[&std_folder, &alt_folder]; + + for folder in folders { + fs::copy( + build_py_folder.join("win").join("anki-console.bat"), + folder.join("anki-console.bat"), + ) + .context("anki-console")?; + } + + println!("--- Copy in audio"); + copy_in_audio(&std_folder, &bazel_external)?; + copy_in_audio(&alt_folder, &bazel_external)?; + + println!("--- Build uninstaller"); + build_installer(&std_folder, &build_folder, version, true).context("uninstaller")?; + + // sign the anki.exe and uninstaller.exe in std, then copy into alt + println!("--- Sign binaries"); + codesign( + &logger, + &[ + &std_folder.join("anki.exe"), + &std_folder.join("uninstall.exe"), + ], + )?; + for fname in &["anki.exe", "uninstall.exe"] { + fs::copy(std_folder.join(fname), alt_folder.join(fname)) + .with_context(|| format!("copy {fname}"))?; + } + + println!("--- Build manifest"); + for folder in folders { + build_manifest(folder).context("manifest")?; + } + + let mut installer_paths = vec![]; + for (folder, variant) in folders.iter().zip(&["qt6", "qt5"]) { + println!( + "--- Build installer for {}", + folder.file_name().unwrap().to_str().unwrap() + ); + build_installer(folder, &build_folder, version, false)?; + let installer_filename = format!("anki-{version}-windows-{variant}.exe"); + let installer_path = build_folder + .join("..") + .join("dist") + .join(installer_filename); + + fs::rename(build_folder.join("anki-setup.exe"), &installer_path) + .context("rename installer")?; + installer_paths.push(installer_path); + } + + println!("--- Sign installers"); + codesign(&logger, &installer_paths)?; + + Ok(()) +} + +fn build_installer( + variant_folder: &Path, + build_folder: &Path, + version: &str, + uninstaller: bool, +) -> Result<()> { + let rendered_nsi = include_str!("../anki.template.nsi") + .replace("@@SRC@@", variant_folder.to_str().unwrap()) + .replace("@@VERSION@@", version); + let rendered_nsi_path = build_folder.join("anki.nsi"); + fs::write(&rendered_nsi_path, rendered_nsi).context("anki.nsi")?; + fs::write( + build_folder.join("fileassoc.nsh"), + include_str!("../fileassoc.nsh"), + )?; + let mut cmd = Command::new("c:/program files (x86)/nsis/makensis.exe"); + cmd.arg("-V3"); + if uninstaller { + cmd.arg("-DWRITE_UNINSTALLER"); + }; + if option_env!("NO_COMPRESS").is_some() { + cmd.arg("-DNO_COMPRESS"); + } + cmd.arg(rendered_nsi_path); + let status = cmd.status()?; + if !status.success() { + bail!("makensis failed"); + } + Ok(()) +} + +/// Copy everything at the provided path into the bundle dir. +/// Excludes standard Bazel repo files. +fn extend_app_contents(source: &Path, bundle_dir: &Path) -> Result<()> { + let status = Command::new("rsync") + .arg("-a") + .args(["--exclude", "BUILD.bazel", "--exclude", "WORKSPACE"]) + .arg(format!("{}/", path_for_rsync(source, true)?)) + .arg(format!("{}/", path_for_rsync(bundle_dir, true)?)) + .status()?; + if !status.success() { + bail!("error syncing {source:?}"); + } + Ok(()) +} + +/// Munge path into a format rsync expects on Windows. +fn path_for_rsync(path: &Path, trailing_slash: bool) -> Result { + let mut components = path.components(); + let mut drive = None; + if let Some(Component::Prefix(prefix)) = components.next() { + if let Prefix::Disk(letter) = prefix.kind() { + drive = Some(char::from(letter)); + } + }; + let drive = drive.context("missing drive letter")?; + let remaining_path: PathBuf = components.collect(); + Ok(format!( + "/{}{}{}", + drive, + remaining_path + .to_str() + .context("remaining_path")? + .replace("\\", "/"), + if trailing_slash { "/" } else { "" } + )) +} + +fn copy_in_audio(bundle_dir: &Path, bazel_external: &Path) -> Result<()> { + extend_app_contents(&bazel_external.join("audio_win_amd64"), bundle_dir) +} + +fn codesign(logger: &Logger, paths: &[impl AsRef]) -> Result<()> { + if option_env!("ANKI_CODESIGN").is_none() { + return Ok(()); + } + let cert = CodeSigningCertificate::Sha1Thumbprint( + SystemStore::My, + "60abdb9cb52b7dc13550e8838486a00e693770d9".into(), + ); + let mut sign = SigntoolSign::new(cert); + sign.file_digest_algorithm("sha256") + .timestamp_server(TimestampServer::Rfc3161( + "http://time.certum.pl".into(), + "sha256".into(), + )) + .verbose(); + paths.iter().for_each(|path| { + sign.sign_file(path); + }); + sign.run(logger) +} + +// FIXME: check uninstall.exe required or not +fn build_manifest(base_path: &Path) -> Result<()> { + let mut buf = vec![]; + for entry in WalkDir::new(base_path) + .min_depth(1) + .sort_by_file_name() + .into_iter() + { + let entry = entry?; + let path = entry.path(); + let relative_path = path.strip_prefix(base_path)?; + write!( + &mut buf, + "{}\r\n", + relative_path.to_str().context("relative_path utf8")? + )?; + } + fs::write(base_path.join("anki.install-manifest"), buf)?; + Ok(()) +} + +#[cfg(test)] +mod test { + #[allow(unused_imports)] + use super::*; + + #[test] + #[cfg(windows)] + fn test_path_for_rsync() -> Result<()> { + assert_eq!( + path_for_rsync(Path::new("c:\\foo\\bar"), false)?, + "/C/foo/bar" + ); + assert_eq!( + path_for_rsync(Path::new("c:\\foo\\bar"), true)?, + "/C/foo/bar/" + ); + + Ok(()) + } +} diff --git a/qt/package/build.bat b/qt/package/build.bat deleted file mode 100755 index 78c3ca4c2..000000000 --- a/qt/package/build.bat +++ /dev/null @@ -1,21 +0,0 @@ -:: ensure wheels are built and set up Rust env -pushd ..\.. -call scripts\build || exit /b -call scripts\cargo-env -set ROOT=%CD% -popd - -:: ensure venv exists -set OUTPUT_ROOT=%ROOT%/bazel-pkg -set VENV=%OUTPUT_ROOT%/venv -if not exist %VENV% ( - mkdir %OUTPUT_ROOT% - pushd %ROOT% - call scripts\python -m venv %VENV% || exit /b - popd -) - -:: run the rest of the build in Python -FOR /F "tokens=*" %%g IN ('call ..\..\bazel.bat info output_base --ui_event_filters=-INFO') do (SET BAZEL_EXTERNAL=%%g/external) -call ..\..\bazel.bat query @pyqt515//:* -%VENV%\scripts\python build.py %ROOT% %BAZEL_EXTERNAL% || exit /b diff --git a/qt/package/build.py b/qt/package/build.py deleted file mode 100644 index aafd6f246..000000000 --- a/qt/package/build.py +++ /dev/null @@ -1,264 +0,0 @@ -# Copyright: Ankitects Pty Ltd and contributors -# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - - -from __future__ import annotations - -import glob -import os -import platform -import re -import shutil -import subprocess -import sys -from pathlib import Path - -is_win = sys.platform == "win32" - -workspace = Path(sys.argv[1]) -output_root = workspace / "bazel-pkg" -dist_folder = output_root / "dist" -venv = output_root / "venv" -cargo_target = output_root / "target" -bazel_external = Path(sys.argv[2]) -artifacts = output_root / "artifacts" -pyo3_config = output_root / "pyo3-build-config-file.txt" - -if is_win: - python_bin_folder = venv / "scripts" - os.environ["PATH"] += fr";{os.getenv('USERPROFILE')}\.cargo\bin" - cargo_features = "build-mode-prebuilt-artifacts" -else: - python_bin_folder = venv / "bin" - os.environ["PATH"] += f":{os.getenv('HOME')}/.cargo/bin" - cargo_features = ( - "build-mode-prebuilt-artifacts global-allocator-jemalloc allocator-jemalloc" - ) - -os.environ["PYOXIDIZER_ARTIFACT_DIR"] = str(artifacts) -os.environ["PYOXIDIZER_CONFIG"] = str(Path(os.getcwd()) / "pyoxidizer.bzl") -os.environ["CARGO_TARGET_DIR"] = str(cargo_target) - -# OS-specific things -pyqt5_folder_name = "pyqt515" -pyqt6_folder_path = bazel_external / "pyqt6" / "PyQt6" -is_lin = False -arm64_linux = False -if is_win: - os.environ["TARGET"] = "x86_64-pc-windows-msvc" -elif sys.platform.startswith("darwin"): - if platform.machine() == "arm64": - pyqt5_folder_name = None - os.environ["TARGET"] = "aarch64-apple-darwin" - os.environ["MACOSX_DEPLOYMENT_TARGET"] = "11.0" - else: - pyqt5_folder_name = "pyqt514" - os.environ["TARGET"] = "x86_64-apple-darwin" - os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.13" -else: - is_lin = True - if platform.machine() == "x86_64": - os.environ["TARGET"] = "x86_64-unknown-linux-gnu" - else: - os.environ["TARGET"] = "aarch64-unknown-linux-gnu" - pyqt5_folder_name = None - pyqt6_folder_path = None - arm64_linux = True - - -python = python_bin_folder / "python" -pip = python_bin_folder / "pip" -artifacts_in_build = ( - output_root - / "build" - / os.getenv("TARGET") - / "release" - / "resources" - / "extra_files" -) - - -def build_pyoxidizer(): - subprocess.run( - [ - "cargo", - "install", - "--git", - "https://github.com/ankitects/PyOxidizer.git", - "--rev", - # when updating, make sure Cargo.toml updated too - "eb26dd7cd1290de6503869f3d719eabcec45e139", - "pyoxidizer", - ], - check=True, - ) - - -def install_wheels_into_venv(): - # Pip's handling of hashes is somewhat broken. It spots the hashes in the constraints - # file and forces all files to have a hash. We can manually hash our generated wheels - # and pass them in with hashes, but it still breaks, because the 'protobuf>=3.17' - # specifier in the pylib wheel is not allowed. Nevermind that a specific version is - # included in the constraints file we pass along! To get things working, we're - # forced to strip the hashes out before installing. This should be safe, as the files - # have already been validated as part of the build process. - constraints = output_root / "deps_without_hashes.txt" - with open(workspace / "python" / "requirements.txt") as f: - buf = f.read() - with open(constraints, "w") as f: - extracted = re.findall("^(\S+==\S+) ", buf, flags=re.M) - f.write("\n".join(extracted)) - - # install wheels and upgrade any deps - wheels = glob.glob(str(workspace / "bazel-dist" / "*.whl")) - subprocess.run( - [pip, "install", "--upgrade", "-c", constraints, *wheels], check=True - ) - # always reinstall our wheels - subprocess.run( - [pip, "install", "--force-reinstall", "--no-deps", *wheels], check=True - ) - # pypi protobuf lacks C extension on darwin-arm; use a locally built version - protobuf = Path.home() / "protobuf-3.19.1-cp39-cp39-macosx_11_0_arm64.whl" - if protobuf.exists(): - subprocess.run( - [pip, "install", "--force-reinstall", "--no-deps", protobuf], check=True - ) - if arm64_linux: - # orjson doesn't get packaged correctly; remove it and we'll - # copy a copy in later - subprocess.run([pip, "uninstall", "-y", "orjson"], check=True) - - -def build_artifacts(): - if os.path.exists(artifacts): - shutil.rmtree(artifacts) - if os.path.exists(artifacts_in_build): - shutil.rmtree(artifacts_in_build) - - subprocess.run( - [ - "pyoxidizer", - "--system-rust", - "run-build-script", - "build.rs", - "--var", - "venv", - venv, - ], - check=True, - env=os.environ - | dict( - CARGO_MANIFEST_DIR=".", - OUT_DIR=str(artifacts), - PROFILE="release", - PYO3_PYTHON=str(python), - ), - ) - - existing_config = None - if os.path.exists(pyo3_config): - with open(pyo3_config) as f: - existing_config = f.read() - - with open(artifacts / "pyo3-build-config-file.txt") as f: - new_config = f.read() - - # avoid bumping mtime, which triggers crate recompile - if new_config != existing_config: - with open(pyo3_config, "w") as f: - f.write(new_config) - - -def build_pkg(): - subprocess.run( - [ - "cargo", - "build", - "--release", - "--no-default-features", - "--features", - cargo_features, - ], - check=True, - env=os.environ | dict(PYO3_CONFIG_FILE=str(pyo3_config)), - ) - - -def adj_path_for_windows_rsync(path: Path) -> str: - if not is_win: - return str(path) - - path = path.absolute() - rest = str(path)[2:].replace("\\", "/") - return f"/{path.drive[0]}{rest}" - - -def merge_into_dist(output_folder: Path, pyqt_src_path: Path | None): - if not output_folder.exists(): - output_folder.mkdir(parents=True) - # PyQt - if pyqt_src_path: - subprocess.run( - [ - "rsync", - "-a", - "--delete", - "--exclude-from", - "qt.exclude", - adj_path_for_windows_rsync(pyqt_src_path), - adj_path_for_windows_rsync(output_folder / "lib") + "/", - ], - check=True, - ) - # Executable and other resources - resources = [ - adj_path_for_windows_rsync( - cargo_target / "release" / ("anki.exe" if is_win else "anki") - ), - adj_path_for_windows_rsync(artifacts_in_build) + "/", - ] - if is_win: - resources.append(adj_path_for_windows_rsync(Path("win")) + "/") - elif is_lin: - resources.append("lin/") - - subprocess.run( - [ - "rsync", - "-a", - "--delete", - "--exclude", - "PyQt6", - "--exclude", - "PyQt5", - *resources, - adj_path_for_windows_rsync(output_folder) + "/", - ], - check=True, - ) - # Linux ARM workarounds - if arm64_linux: - # copy orjson ends up broken; copy from venv - subprocess.run( - [ - "rsync", - "-a", - "--delete", - os.path.expanduser("~/orjson"), - output_folder / "lib/", - ], - check=True, - ) - # Ensure all files are world-readable - if not is_win: - subprocess.run(["chmod", "-R", "a+r", output_folder]) - - -build_pyoxidizer() -install_wheels_into_venv() -build_artifacts() -build_pkg() -merge_into_dist(dist_folder / "std", pyqt6_folder_path) -if pyqt5_folder_name: - merge_into_dist(dist_folder / "alt", bazel_external / pyqt5_folder_name / "PyQt5") diff --git a/qt/package/build.sh b/qt/package/build.sh deleted file mode 100755 index bd0d64ed6..000000000 --- a/qt/package/build.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -set -e - -cd $(dirname $0) -ROOT=$(pwd)/../.. -OUTPUT_ROOT=$ROOT/bazel-pkg -VENV=$OUTPUT_ROOT/venv -BAZEL_EXTERNAL=$(bazel info output_base --ui_event_filters=-INFO)/external - -# ensure the wheels are built -(cd $ROOT && ./scripts/build) - -# ensure venv exists -test -d $VENV || ( - mkdir -p $OUTPUT_ROOT - (cd $ROOT && ./scripts/python -m venv $VENV) -) - -# run the rest of the build in Python -. $ROOT/scripts/cargo-env -if [[ "$OSTYPE" == "darwin"* ]]; then - if [ $(uname -m) != "arm64" ]; then - bazel query @pyqt514//:* > /dev/null - fi -else - bazel query @pyqt515//:* > /dev/null -fi -$VENV/bin/python build.py $ROOT $BAZEL_EXTERNAL diff --git a/qt/package/buildmanifest.py b/qt/package/buildmanifest.py deleted file mode 100644 index 3057879c9..000000000 --- a/qt/package/buildmanifest.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright: Ankitects Pty Ltd and contributors -# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - -import os -import sys -from pathlib import Path - - -def build_manifest(top: Path) -> None: - manifest = [] - for root, dirnames, fnames in os.walk(top, topdown=True): - relroot = root[len(str(top)) + 1 :] - # if not top level, add folder - if relroot: - manifest.append(relroot) - # then the files - for fname in fnames: - path = os.path.join(relroot, fname) - manifest.append(path) - - with open(top / "anki.install-manifest", "w") as file: - file.write("\n".join(manifest) + "\n") - - -if __name__ == "__main__": - build_manifest(Path(sys.argv[1])) diff --git a/qt/tests/run_format.py b/qt/tests/run_format.py index a5039ad50..092e3d1b0 100644 --- a/qt/tests/run_format.py +++ b/qt/tests/run_format.py @@ -33,7 +33,7 @@ if __name__ == "__main__": "aqt", "tests", "tools", - "package", + "bundle", ] + args, check=False, @@ -51,7 +51,7 @@ if __name__ == "__main__": "aqt", "tests", "tools", - "package", + "bundle", ] + args, check=False, diff --git a/qt/tools/extract_sass_colors.py b/qt/tools/extract_sass_colors.py index c13a1d7d9..219fd6a26 100644 --- a/qt/tools/extract_sass_colors.py +++ b/qt/tools/extract_sass_colors.py @@ -22,6 +22,7 @@ for line in open(input_scss): and not ":root" in line and "Copyright" not in line and "License" not in line + and "color-scheme" not in line ): print("failed to match", line) continue diff --git a/repos.bzl b/repos.bzl index 8cb8619e9..85db1ae59 100644 --- a/repos.bzl +++ b/repos.bzl @@ -151,3 +151,98 @@ exports_files(["l10n.toml"]) ], sha256 = qtftl_i18n_zip_csum, ) + + # binary bundle + ################ + + maybe( + http_archive, + name = "pyoxidizer", + sha256 = "9f7951473d88c7989dc80199146f82020226a3b2425474fd33b6bcbd8fdd1b1c", + urls = [ + # when changing this, the commit hash needs to be updated in qt/bundle/Cargo.toml + "https://github.com/ankitects/PyOxidizer/archive/refs/tags/anki-2021-12-08.tar.gz", + ], + strip_prefix = "PyOxidizer-anki-2021-12-08", + build_file_content = " ", + ) + + maybe( + http_archive, + name = "bundle_extras_linux_amd64", + build_file_content = " ", + urls = [ + "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-02-09/linux-amd64.tar.gz", + ], + sha256 = "cbfb41fb750ae19b381f8137bd307e1167fdc68420052977f6e1887537a131b0", + ) + + maybe( + http_archive, + name = "audio_win_amd64", + build_file_content = " ", + urls = [ + "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-02-09/audio-win-amd64.tar.gz", + ], + sha256 = "0815a601baba05e03bc36b568cdc2332b1cf4aa17125fc33c69de125f8dd687f", + ) + + maybe( + http_archive, + name = "protobuf_wheel_mac_arm64", + build_file_content = " ", + urls = [ + "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-02-09/protobuf-wheel-mac-arm64.tar", + ], + sha256 = "401d1cd6d949af463b3945f0d5dc887185b27fa5478cb6847bf94f680ea797b4", + ) + + maybe( + http_archive, + name = "audio_mac_amd64", + build_file_content = " ", + urls = [ + "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-02-09/audio-mac-amd64.tar.gz", + ], + sha256 = "d9310cbd6bed09d6d36deb8b7611bffbd161628512b1bf8d7becfdf78b5cd1dd", + ) + + maybe( + http_archive, + name = "audio_mac_arm64", + build_file_content = " ", + urls = [ + "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-02-09/audio-mac-arm64.tar.gz", + ], + sha256 = "c30a772132a16fa79d9a1e60f5dce2f91fe8077e2709a8f39ef499d49f6a4b0e", + ) + + maybe( + http_archive, + name = "pyqt6.2_mac_bundle_amd64", + build_file_content = " ", + urls = [ + "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-02-09/pyqt6.2-mac-amd64.tar.gz", + ], + sha256 = "c7bf899eee33fcb3b5848f5d3e5fc390012efc05c2308e4349b7bbd5939c85f0", + ) + + maybe( + http_archive, + name = "pyqt6.2_mac_bundle_arm64", + build_file_content = " ", + urls = [ + "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-02-09/pyqt6.2-mac-arm64.tar.gz", + ], + sha256 = "7a4b7d5bd65c83fd16cf7e56929183ef0d1d7bb67f9deea8f2482d7378e0ea02", + ) + + maybe( + http_archive, + name = "pyqt5.14_mac_bundle_amd64", + build_file_content = " ", + urls = [ + "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-02-09/pyqt5.14-mac-amd64.tar.gz", + ], + sha256 = "474951bed79ddb9570ee4c5a6079041772551ea77e77171d9e33d6f5e7877ec1", + ) diff --git a/run.bat b/run.bat index d5d9a78b0..910998fb3 100755 --- a/run.bat +++ b/run.bat @@ -1,2 +1,7 @@ +@echo off +pushd "%~dp0" +call tools\setup-env.bat + set PYTHONWARNINGS=default -call .\bazel.bat run %BUILDARGS% //qt:runanki -k -- %* +bazel run %BUILDARGS% //qt:runanki -k -- %* || exit /b 1 +popd diff --git a/scripts/build b/scripts/build deleted file mode 100755 index cb6081935..000000000 --- a/scripts/build +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -e - -test -e WORKSPACE || ( - echo "Run from project root" - exit 1 -) - -rm -rf bazel-dist -bazel build --config opt dist -tar xvf .bazel/bin/dist.tar diff --git a/scripts/build.bat b/scripts/build.bat deleted file mode 100755 index ca43defa9..000000000 --- a/scripts/build.bat +++ /dev/null @@ -1,12 +0,0 @@ -@echo off - -if not exist WORKSPACE ( - echo Run from project root - exit /b 1 -) - -rd /s /q bazel-dist - -set BUILDARGS=-k -c opt dist --color=yes -call .\bazel build %BUILDARGS% || exit /b 1 -tar xvf ..\bazel\anki\bin\dist.tar || exit /b 1 diff --git a/scripts/cargo-env.bat b/scripts/cargo-env.bat deleted file mode 100644 index 0ca775e93..000000000 --- a/scripts/cargo-env.bat +++ /dev/null @@ -1,4 +0,0 @@ -rem Run this from the repo root folder - -FOR /F "tokens=*" %%g IN ('call bazel.bat info output_base --ui_event_filters=-INFO') do (SET BAZEL_EXTERNAL=%%g/external) -set PATH=%BAZEL_EXTERNAL%\rust_windows_x86_64\bin;%PATH% diff --git a/scripts/docker/README.md b/scripts/docker/README.md deleted file mode 100644 index 83d02658b..000000000 --- a/scripts/docker/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Building in Docker - -This folder contains a script for building Anki inside a Docker container. -It works by creating an image with the required dependencies, and then runs the -build with the source folder mounted into the image. This will cause files to be -written into `bazel-\*` and `node_modules` in the source folder as the build proceeds. -The advantage of doing it this way is that most of the efficiency of building -outside Docker is retained - you can make minor changes and run the build again, -and only the changed parts need to be rebuilt. - -If you're looking for a fully isolated build, [this other -approach](../../docs/docker/README.md) in the docs folder may suit you better. As -it also includes runtime dependencies, it may be a useful reference for libraries -you'll need to install before running Anki. - -# Usage - -Ensure Docker is installed on your machine, and your user has permission to connect -to Docker. Then run the following command from the root folder of this source repo: - -``` -$ scripts/docker/build.sh amd64 -``` - -The resulting wheels will be written into bazel-dist. See -[Development](../docs/development.md) for information on how to install them. - -If you're on an ARM Linux machine, replace amd64 with arm64. diff --git a/scripts/docker/build-entrypoint b/scripts/docker/build-entrypoint deleted file mode 100644 index 6a97fbc20..000000000 --- a/scripts/docker/build-entrypoint +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -e - -rm -rf bazel-dist -bazel build -c opt dist --symlink_prefix=bazel-docker/links/ \ - --experimental_no_product_name_out_symlink -tar xvf bazel-docker/links/bin/dist.tar -if [ "$PACKAGE" != "" ]; then - (cd qt/package && ./build.sh) -fi -bazel shutdown diff --git a/scripts/docker/build.sh b/scripts/docker/build.sh deleted file mode 100755 index 2c3968b55..000000000 --- a/scripts/docker/build.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -set -e - -test -e WORKSPACE || ( - echo "Run from project root" - exit 1 -) - -arch=$1 - -if [ "$arch" != "amd64" -a "$arch" != "arm64" ]; then - echo "usage: build [amd64|arm64]" - exit 1 -fi - -rm -rf bazel-dist - -export DOCKER_BUILDKIT=1 - -docker build --tag ankibuild --file scripts/docker/Dockerfile.$arch \ - --build-arg uid=$(id -u) --build-arg gid=$(id -g) \ - scripts/docker -docker run --rm -it -e PACKAGE=$PACKAGE \ - --mount type=bind,source="$(pwd)",target=/code \ - ankibuild diff --git a/scripts/python.bat b/scripts/python.bat deleted file mode 100755 index 44d6a5d3c..000000000 --- a/scripts/python.bat +++ /dev/null @@ -1 +0,0 @@ -call bazel run python --ui_event_filters=-INFO -- %* diff --git a/scripts/run-qt5.14 b/scripts/run-qt5.14 deleted file mode 100755 index 6eaba20e9..000000000 --- a/scripts/run-qt5.14 +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# note: 5.14 is not available on Windows for Python 3.9 - -set -e - -export PYTHONWARNINGS=default -bazel run $BUILDARGS //qt:runanki_qt514 -- $* diff --git a/scripts/run-qt5.15 b/scripts/run-qt5.15 deleted file mode 100755 index e04622eeb..000000000 --- a/scripts/run-qt5.15 +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -set -e - -export PYTHONWARNINGS=default -bazel run $BUILDARGS //qt:runanki_qt515 -- $* diff --git a/scripts/run-qt5.15.bat b/scripts/run-qt5.15.bat deleted file mode 100755 index 252685f03..000000000 --- a/scripts/run-qt5.15.bat +++ /dev/null @@ -1,4 +0,0 @@ -REM run this from the scripts folder, not from root - -set PYTHONWARNINGS=default -call ..\bazel.bat run %BUILDARGS% //qt:runanki_qt515 -k -- %* diff --git a/scripts/runopt.bat b/scripts/runopt.bat deleted file mode 100755 index 9027d87d7..000000000 --- a/scripts/runopt.bat +++ /dev/null @@ -1,2 +0,0 @@ -set BUILDARGS=-c opt -call .\run.bat %* diff --git a/scripts/BUILD.bazel b/tools/BUILD.bazel similarity index 100% rename from scripts/BUILD.bazel rename to tools/BUILD.bazel diff --git a/tools/bazel b/tools/bazel new file mode 100755 index 000000000..4e99266e6 --- /dev/null +++ b/tools/bazel @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +# When building under Rosetta, use a separate output root, so that repo rules don't +# need to be run again when switching between x86_64 and arm64 builds. +extra_args="" +if [[ $OSTYPE == 'darwin'* ]]; then + if [ $(uname -m) = x86_64 -a "$(sysctl -in sysctl.proc_translated)" = 1 ]; then + extra_args="--output_base=$HOME/.cache/anki-rosetta" + fi +fi + +# Bazelisk will place the tools folder at the front of the path. This breaks +# genrule() invocations like //:buildinfo, as they call 'bazel run python', which +# fails as BAZEL_REAL is not passed to the child process. Work around it by removing +# the tools folder from the path. +export PATH=$(echo "$PATH" | sed 's@^.*/tools:@@') + +exec $BAZEL_REAL $extra_args "$@" diff --git a/tools/build b/tools/build new file mode 100755 index 000000000..250c4f4c3 --- /dev/null +++ b/tools/build @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + +test -e WORKSPACE || ( + echo "Run from project root" + exit 1 +) + +cwd=$(pwd) +build=.bazel/out/dist + +# we remove wheels between runs, so old builds +# don't break 'pip install dist/*.whl' +rm -rf ${build}/*.whl + +bazel build --config opt wheels +mkdir -p $build +cd $build && tar xf ${cwd}/.bazel/bin/wheels.tar + +echo "wheels are in .bazel/out/dist" diff --git a/tools/build.bat b/tools/build.bat new file mode 100755 index 000000000..d9f5c0c2c --- /dev/null +++ b/tools/build.bat @@ -0,0 +1,18 @@ +@echo off +pushd "%~dp0"\.. +call tools\setup-env.bat + +echo --- Build wheels + +set cwd=%CD% +set dist=.bazel\out\dist + +bazel build -c opt wheels --color=yes || exit /b 1 +if exist %dist% ( + rd /s /q %dist% || exit /b 1 +) +md %dist% || exit /b 1 +cd %dist% +tar xvf %cwd%\.bazel\bin\wheels.tar || exit /b 1 +echo wheels are in %dist% +popd diff --git a/scripts/buildinfo.py b/tools/buildinfo.py similarity index 100% rename from scripts/buildinfo.py rename to tools/buildinfo.py diff --git a/tools/bundle b/tools/bundle new file mode 100755 index 000000000..7843a6654 --- /dev/null +++ b/tools/bundle @@ -0,0 +1,39 @@ +#!/bin/bash + +set -e + +cd $(dirname $0) +ROOT=$(pwd)/.. +OUTPUT_ROOT=$ROOT/.bazel/out/build +VENV=$OUTPUT_ROOT/venv-$(uname -m) +BAZEL_EXTERNAL=$(bazel info output_base --ui_event_filters=-INFO)/external + +echo "--- Building wheels for $(uname -m)" +(cd $ROOT && ./tools/build) + +echo "--- Setup venv" +test -d $VENV || ( + mkdir -p $OUTPUT_ROOT + (cd $ROOT && ./tools/python -m venv $VENV) +) + +echo "--- Fetch extra deps" +bazel query @pyoxidizer//:* +if [[ "$OSTYPE" == "darwin"* ]]; then + if [ $(uname -m) != "arm64" ]; then + bazel query @audio_mac_amd64//:* > /dev/null + bazel query @pyqt6.2_mac_bundle_amd64//:* > /dev/null + bazel query @pyqt5.14_mac_bundle_amd64//:* > /dev/null + else + bazel query @audio_mac_arm64//:* > /dev/null + bazel query @pyqt6.2_mac_bundle_arm64//:* > /dev/null + bazel query @protobuf_wheel_mac_arm64//:* > /dev/null + + fi +else + bazel query @pyqt515//:* > /dev/null + bazel query @bundle_extras_linux_amd64//:* > /dev/null +fi + +. $ROOT/tools/cargo-env +(cd $ROOT/qt/bundle && $VENV/bin/python build.py $ROOT $BAZEL_EXTERNAL) diff --git a/tools/bundle.bat b/tools/bundle.bat new file mode 100755 index 000000000..c1dbe0765 --- /dev/null +++ b/tools/bundle.bat @@ -0,0 +1,33 @@ +@echo off +pushd "%~dp0"\.. +call tools\setup-env.bat + +:: ensure wheels are built and set up Rust env +call tools\build || exit /b 1 +call tools\cargo-env +set ROOT=%CD% + +:: ensure venv exists +echo --- Setup venv +set OUTPUT_ROOT=%ROOT%\.bazel\out\build +set VENV=%OUTPUT_ROOT%\venv-AMD64 +if not exist %VENV% ( + mkdir %OUTPUT_ROOT% + call tools\python -m venv %VENV% || exit /b +) + +:: pyoxidizer requires python.org for build +set PATH=\python39;%PATH% + +:: run the rest of the build in Python +echo --- Fetching extra deps +FOR /F "tokens=*" %%g IN ('bazel info output_base --ui_event_filters=-INFO') do (SET BAZEL_EXTERNAL=%%g/external) +bazel query @pyqt515//:* > nul +bazel query @pyoxidizer//:* > nul +bazel query @audio_win_amd64//:* > nul + +echo --- Build bundle +pushd qt\bundle +%VENV%\scripts\python build.py %ROOT% %BAZEL_EXTERNAL% || exit /b +popd +popd \ No newline at end of file diff --git a/scripts/cargo-env b/tools/cargo-env similarity index 85% rename from scripts/cargo-env rename to tools/cargo-env index b987d169a..e242bbd1e 100755 --- a/scripts/cargo-env +++ b/tools/cargo-env @@ -4,19 +4,19 @@ # quick access to cargo on a machine that does not have Rust installed # separately, or want to run a quick check. Eg: -# $ . scripts/cargo-env +# $ . tools/cargo-env # $ (cd rslib && cargo check) BAZEL_EXTERNAL=$(bazel info output_base --ui_event_filters=-INFO)/external if [[ "$OSTYPE" == "darwin"* ]]; then - if [ "$(arch)" == "i386" ]; then + if [ "$(uname -m)" == "x86_64" ]; then export PATH="$BAZEL_EXTERNAL/rust_darwin_x86_64/bin:$PATH" else export PATH="$BAZEL_EXTERNAL/rust_darwin_aarch64/bin:$PATH" fi else - if [ "$(arch)" == "aarch64" ]; then + if [ "$(uname -m)" == "aarch64" ]; then export PATH="$BAZEL_EXTERNAL/rust_linux_aarch64/bin:$PATH" else export PATH="$BAZEL_EXTERNAL/rust_linux_x86_64/bin:$PATH" diff --git a/tools/cargo-env.bat b/tools/cargo-env.bat new file mode 100644 index 000000000..474793e1e --- /dev/null +++ b/tools/cargo-env.bat @@ -0,0 +1,7 @@ +@echo off +pushd "%~dp0"\.. +call tools\setup-env.bat + +FOR /F "tokens=*" %%g IN ('bazel info output_base --ui_event_filters=-INFO') do (SET BAZEL_EXTERNAL=%%g/external) +set PATH=%BAZEL_EXTERNAL%\rust_windows_x86_64\bin;%PATH% +popd diff --git a/scripts/copyright_headers.py b/tools/copyright_headers.py similarity index 96% rename from scripts/copyright_headers.py rename to tools/copyright_headers.py index 5cb1dfc06..44aea6aae 100644 --- a/scripts/copyright_headers.py +++ b/tools/copyright_headers.py @@ -14,8 +14,8 @@ nonstandard_header = { "python/pyqt/install.py", "qt/aqt/mpv.py", "qt/aqt/winpaths.py", - "qt/package/build.rs", - "qt/package/src/main.rs", + "qt/bundle/build.rs", + "qt/bundle/src/main.rs", } ignored_folders = [ diff --git a/scripts/mypy b/tools/mypy similarity index 100% rename from scripts/mypy rename to tools/mypy diff --git a/scripts/mypy-watch b/tools/mypy-watch similarity index 100% rename from scripts/mypy-watch rename to tools/mypy-watch diff --git a/scripts/node-env b/tools/node-env similarity index 96% rename from scripts/node-env rename to tools/node-env index 9f72b4472..37d8b00cb 100755 --- a/scripts/node-env +++ b/tools/node-env @@ -4,7 +4,7 @@ # quick access on a machine that does not have Node installed separately. # Eg: -# $ . scripts/node-env +# $ . tools/node-env # $ (cd ts && yarn) BAZEL_EXTERNAL=$(bazel info output_base --ui_event_filters=-INFO)/external diff --git a/scripts/python b/tools/python similarity index 100% rename from scripts/python rename to tools/python diff --git a/tools/python.bat b/tools/python.bat new file mode 100755 index 000000000..b4c237f09 --- /dev/null +++ b/tools/python.bat @@ -0,0 +1,6 @@ +@echo off +pushd "%~dp0"\.. +call tools\setup-env.bat + +bazel run python --ui_event_filters=-INFO -- %* +popd diff --git a/tools/run-qt5.14 b/tools/run-qt5.14 new file mode 100755 index 000000000..d039be07a --- /dev/null +++ b/tools/run-qt5.14 @@ -0,0 +1,17 @@ +#!/bin/bash + +# note: 5.14 is not available on Windows for Python 3.9 + +set -e + +# automatically switch to an x86_64 build on macOS, as this +# Qt version does not work on arm64 +prefix="" +if [[ $OSTYPE == 'darwin'* ]]; then + if [ $(uname -m) = arm64 ]; then + prefix="arch -arch x86_64" + fi +fi + +export PYTHONWARNINGS=default +$prefix bazel run $BUILDARGS //qt:runanki_qt514 -- $* diff --git a/tools/run-qt5.15 b/tools/run-qt5.15 new file mode 100755 index 000000000..c22c47b68 --- /dev/null +++ b/tools/run-qt5.15 @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e + +# automatically switch to an x86_64 build on macOS, as this +# Qt version does not work on arm64 +prefix="" +if [[ $OSTYPE == 'darwin'* ]]; then + if [ $(uname -m) = arm64 ]; then + prefix="arch -arch x86_64" + fi +fi + +export PYTHONWARNINGS=default +$prefix bazel run $BUILDARGS //qt:runanki_qt515 -- $* diff --git a/tools/run-qt5.15.bat b/tools/run-qt5.15.bat new file mode 100755 index 000000000..641e6ab93 --- /dev/null +++ b/tools/run-qt5.15.bat @@ -0,0 +1,7 @@ +@echo off +pushd "%~dp0"\.. +call tools\setup-env.bat + +set PYTHONWARNINGS=default +bazel run %BUILDARGS% //qt:runanki_qt515 -k -- %* +popd \ No newline at end of file diff --git a/scripts/runopt b/tools/runopt similarity index 100% rename from scripts/runopt rename to tools/runopt diff --git a/tools/runopt.bat b/tools/runopt.bat new file mode 100755 index 000000000..47b503ac2 --- /dev/null +++ b/tools/runopt.bat @@ -0,0 +1,7 @@ +@echo off +pushd "%~dp0"\.. +call tools\setup-env.bat + +set BUILDARGS=-c opt +call .\run.bat %* +popd \ No newline at end of file diff --git a/tools/setup-env.bat b/tools/setup-env.bat new file mode 100644 index 000000000..6e6435eab --- /dev/null +++ b/tools/setup-env.bat @@ -0,0 +1,16 @@ +@echo off +pushd "%~dp0"\.. + +REM add msys/bazel to path if they're not already on it +where /q bazel || ( + set PATH=c:\msys64\usr\bin;c:\bazel;%PATH% +) + +if not exist windows.bazelrc ( + rem By default, Bazel will place build files in c:\users\\_bazel_, and this + rem can lead to build failures when the path names grow too long. So on Windows, the + rem default storage location is \bazel\anki instead. + echo startup --output_user_root=\\bazel\\anki > windows.bazelrc +) + +popd diff --git a/scripts/status.sh b/tools/status.sh similarity index 100% rename from scripts/status.sh rename to tools/status.sh diff --git a/scripts/svelte-check b/tools/svelte-check similarity index 85% rename from scripts/svelte-check rename to tools/svelte-check index 0f9732cd7..287230191 100755 --- a/scripts/svelte-check +++ b/tools/svelte-check @@ -1,6 +1,6 @@ #!/bin/bash -. scripts/node-env +. tools/node-env SASS_PATH=ts/sass:$(pwd)/.bazel/bin/ts/sass \ node_modules/.bin/svelte-check \ --workspace ts diff --git a/scripts/ts-run b/tools/ts-run similarity index 100% rename from scripts/ts-run rename to tools/ts-run diff --git a/scripts/ts-watch b/tools/ts-watch similarity index 100% rename from scripts/ts-watch rename to tools/ts-watch