diff --git a/.cargo/README.md b/.cargo/README.md new file mode 100644 index 000000000..23569eafd --- /dev/null +++ b/.cargo/README.md @@ -0,0 +1,3 @@ +## Attention + +Make sure to execute the `setup-system` script after any modification to `cargo.toml`. diff --git a/.editorconfig b/.editorconfig index ae1c20816..6c6e1ddc2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -71,3 +71,14 @@ indent_style = space # http://yaml.org/spec/1.2/2009-07-21/spec.html#id2576668 [*.{yaml,yml}] indent_style = space + +# Shell +# https://google.github.io/styleguide/shell.xml#Indentation +[*.{bash,sh,zsh}] +indent_style = space + +# PowerShell +# https://poshcode.gitbook.io/powershell-practice-and-style/style-guide/code-layout-and-formatting +[*.{ps1,psd1,psm1}] +indent_size = 4 +indent_style = space diff --git a/.github/actions/cache-rust-deps/action.yaml b/.github/actions/cache-rust-deps/action.yaml deleted file mode 100644 index bb1a5b1ec..000000000 --- a/.github/actions/cache-rust-deps/action.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: Cache Rust Dependencies -description: Caches Rust dependencies -inputs: - save-cache: - description: Whether to save the Rust cache - required: false - default: 'false' -runs: - using: 'composite' - steps: - - name: Cache Rust Dependencies - uses: Swatinem/rust-cache@v2 - with: - shared-key: rust-deps - save-if: ${{ inputs.save-cache }} diff --git a/.github/actions/generate-prisma-client/action.yaml b/.github/actions/generate-prisma-client/action.yaml deleted file mode 100644 index bf1995d1a..000000000 --- a/.github/actions/generate-prisma-client/action.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: Generate Prisma client -description: Generates the Prisma client, using a cached value if one is available -runs: - using: 'composite' - steps: - - name: Cache Prisma codegen - id: cache-prisma - uses: actions/cache@v3 - with: - path: ./core/src/prisma*.rs - key: prisma-0-${{ runner.os }}-${{ hashFiles('./core/prisma/*', './crates/sync-generator/*', './Cargo.toml') }} - - - name: Generate Prisma client - working-directory: core - if: steps.cache-prisma.outputs.cache-hit != 'true' - shell: bash - run: cargo run -p prisma-cli --bin prisma -- generate diff --git a/.github/actions/publish-artifacts/action.yaml b/.github/actions/publish-artifacts/action.yaml index 0b4c652cf..3a921ae57 100644 --- a/.github/actions/publish-artifacts/action.yaml +++ b/.github/actions/publish-artifacts/action.yaml @@ -1,6 +1,8 @@ name: Publish artifacts description: Publishes artifacts after CI process inputs: + target: + description: target triples for built artifact profile: description: "'debug' or 'release'" runs: @@ -12,57 +14,44 @@ runs: export GITHUB_SHA_SHORT=$(git rev-parse --short "$GITHUB_SHA") echo "GITHUB_SHA_SHORT=$GITHUB_SHA_SHORT" >> $GITHUB_ENV - - name: Set AppImage file permissions - if: matrix.platform == 'ubuntu-latest' - shell: bash - run: | - chmod +x target/${{ inputs.profile }}/bundle/appimage/spacedrive*.AppImage - - - name: Publish artifacts (AppImage) - if: matrix.platform == 'ubuntu-latest' + - name: Publish artifacts (Linux - AppImage) + if: ${{ matrix.settings.host == 'ubuntu-20.04' }} uses: actions/upload-artifact@v3 with: - name: Spacedrive-AppImage-${{ env.GITHUB_SHA_SHORT }} - path: target/${{ inputs.profile }}/bundle/appimage/*.AppImage + name: Spacedrive-AppImage-${{ inputs.target }}-${{ env.GITHUB_SHA_SHORT }} + path: target/${{ inputs.target }}/${{ inputs.profile }}/bundle/appimage/*.AppImage + if-no-files-found: error - - name: Publish artifacts (deb) - if: matrix.platform == 'ubuntu-latest' + - name: Publish artifacts (Debian - deb) + if: ${{ matrix.settings.host == 'ubuntu-20.04' }} uses: actions/upload-artifact@v3 with: - name: Spacedrive-deb-${{ env.GITHUB_SHA_SHORT }} - path: target/${{ inputs.profile }}/bundle/deb/*.deb + name: Spacedrive-deb-${{ inputs.target }}-${{ env.GITHUB_SHA_SHORT }} + path: target/${{ inputs.target }}/${{ inputs.profile }}/bundle/deb/*.deb + if-no-files-found: error - # - name: Publish artifacts (server) - # if: matrix.platform == 'ubuntu-latest' - # uses: actions/upload-artifact@v3 - # with: - # name: Spacedrive-server-${{ env.GITHUB_SHA_SHORT }} - # path: ./target/${{ inputs.profile }}/server - - - name: Publish artifacts (Windows) - if: matrix.platform == 'windows-latest' + - name: Publish artifacts (Windows - msi) + if: ${{ matrix.settings.host == 'windows-latest' }} uses: actions/upload-artifact@v3 with: - name: Spacedrive-Windows-${{ env.GITHUB_SHA_SHORT }} - path: target\${{ inputs.profile }}\bundle\msi\*.msi + name: Spacedrive-Windows-msi-${{ inputs.target }}-${{ env.GITHUB_SHA_SHORT }} + path: target/${{ inputs.target }}/${{ inputs.profile }}/bundle/msi/*.msi + if-no-files-found: error - - name: Set app file permissions - if: matrix.platform == 'macos-latest' - shell: bash - run: | - chmod +x target/${{ inputs.profile }}/bundle/macos/*.app/Contents/MacOS/Spacedrive - - - name: Publish artifacts (macOS) - if: matrix.platform == 'macos-latest' + - name: Publish artifacts (macOS - dmg) + if: ${{ matrix.settings.host == 'macos-latest' }} uses: actions/upload-artifact@v3 with: - name: Spacedrive-macOS-${{ env.GITHUB_SHA_SHORT }} - path: target/${{ inputs.profile }}/bundle/macos/*.app + name: Spacedrive-macOS-dmg-${{ inputs.target }}-${{ env.GITHUB_SHA_SHORT }} + path: target/${{ inputs.target }}/${{ inputs.profile }}/bundle/dmg/*.dmg + if-no-files-found: error - name: Publish updater binaries uses: actions/upload-artifact@v3 with: - name: Spacedrive-Updaters-${{ env.GITHUB_SHA_SHORT }} + name: Spacedrive-Updaters-${{ inputs.target }}-${{ env.GITHUB_SHA_SHORT }} path: | - target/${{ inputs.profile }}/bundle/*/*.tar.gz* - target/${{ inputs.profile }}/bundle/*/*.zip* + target/${{ inputs.target }}/${{ inputs.profile }}/bundle/**/*.tar.gz* + target/${{ inputs.target }}/${{ inputs.profile }}/bundle/**/*.zip* + !target/**/deb/**/*.tar.gz + if-no-files-found: error diff --git a/.github/actions/publish-server-image/action.yaml b/.github/actions/publish-server-image/action.yaml index 8727e1c34..4bf0f9252 100644 --- a/.github/actions/publish-server-image/action.yaml +++ b/.github/actions/publish-server-image/action.yaml @@ -34,14 +34,14 @@ runs: docker push $IMAGE_NAME:$IMAGE_TAG - name: Tag & push image as latest staging image - if: github.event_name != 'release' + if: ${{ github.event_name != 'release' }} shell: bash run: | docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:staging docker push $IMAGE_NAME:staging - name: Tag & push image as latest production image - if: github.event_name == 'release' + if: ${{ github.event_name == 'release' }} shell: bash run: | docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:production diff --git a/.github/actions/setup-pnpm/action.yml b/.github/actions/setup-pnpm/action.yml new file mode 100644 index 000000000..a83e01a39 --- /dev/null +++ b/.github/actions/setup-pnpm/action.yml @@ -0,0 +1,25 @@ +name: Setup Node.js, pnpm and dependencies +description: Setup Node.js, pnpm and dependencies +inputs: + token: + description: Github token + required: false + default: '' +runs: + using: 'composite' + steps: + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8.x.x + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + token: ${{ inputs.token }} + check-latest: true + node-version-file: '.nvmrc' + + - name: Install pnpm deps + shell: ${{ runner.os == 'Windows' && 'powershell' || 'bash' }} + run: pnpm i --frozen-lockfile diff --git a/.github/actions/setup-rust/action.yaml b/.github/actions/setup-rust/action.yaml new file mode 100644 index 000000000..1ffc7c784 --- /dev/null +++ b/.github/actions/setup-rust/action.yaml @@ -0,0 +1,51 @@ +name: Setup Rust and Prisma +description: Setup Rust and Prisma +inputs: + targets: + description: Comma-separated list of target triples to install for this toolchain + required: false + save-cache: + description: Whether to save the Rust cache + required: false + default: 'false' +runs: + using: 'composite' + steps: + - name: Install Rust + id: toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ inputs.targets }} + toolchain: stable + components: clippy, rustfmt + + - name: Cache Rust Dependencies + uses: Swatinem/rust-cache@v2 + with: + key: ${{ inputs.targets }} + save-if: ${{ inputs.save-cache }} + prefix-key: "v0" + shared-key: rust-deps + # Windows is having disk related problems with target caching + cache-targets: ${{ runner.os != 'Windows' }} + + - name: Restore cached Prisma codegen + id: cache-prisma-restore + uses: actions/cache/restore@v3 + with: + key: prisma-0-${{ runner.os }}-${{ hashFiles('./core/prisma/*', './crates/sync-generator/*', './Cargo.toml') }} + path: ./core/src/prisma*.rs + + - name: Generate Prisma client + working-directory: core + if: ${{ steps.cache-prisma-restore.outputs.cache-hit != 'true' }} + shell: bash + run: cargo run -p prisma-cli --bin prisma -- generate + + - name: Save Prisma codegen + id: cache-prisma-save + if: ${{ inputs.save-cache == 'true' }} + uses: actions/cache/save@v3 + with: + key: ${{ steps.cache-prisma-restore.outputs.cache-primary-key }} + path: ./core/src/prisma*.rs diff --git a/.github/actions/setup-system/action.yml b/.github/actions/setup-system/action.yml new file mode 100644 index 000000000..75a3825f0 --- /dev/null +++ b/.github/actions/setup-system/action.yml @@ -0,0 +1,64 @@ +name: Setup System and Rust +description: Setup System and Rust +inputs: + token: + description: Github token + required: false + default: '' + targets: + description: Comma-separated list of target triples to install for this toolchain + required: false + setup-arg: + description: Argument for the system setup script + required: false + default: '' + save-cache: + description: Whether to save the System cache + required: false + default: 'false' +runs: + using: 'composite' + steps: + - name: Restore cached LLVM and Clang + if: ${{ runner.os == 'Windows' }} + id: cache-llvm-restore + uses: actions/cache/restore@v3 + with: + key: llvm-15 + path: C:/Program Files/LLVM + + - name: Install LLVM and Clang + if: ${{ runner.os == 'Windows' }} + uses: KyleMayes/install-llvm-action@v1 + with: + cached: ${{ steps.cache-llvm-restore.outputs.cache-hit }} + version: '15' + + - name: Save LLVM and Clang + if: ${{ runner.os == 'Windows' && inputs.save-cache == 'true' }} + id: cache-llvm-save + uses: actions/cache/save@v3 + with: + key: ${{ steps.cache-llvm-restore.outputs.cache-primary-key }} + path: C:/Program Files/LLVM + + - name: Setup Rust and Dependencies + uses: ./.github/actions/setup-rust + with: + targets: ${{ inputs.targets }} + save-cache: ${{ inputs.save-cache }} + + - name: Run 'setup-system.sh' script + shell: bash + if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }} + run: ./.github/scripts/setup-system.sh ${{ inputs.setup-arg }} + env: + TARGET: ${{ inputs.targets }} + GITHUB_TOKEN: ${{ inputs.token }} + + - name: Run 'setup-system.ps1' script + shell: powershell + if: ${{ runner.os == 'Windows' }} + run: ./.github/scripts/setup-system.ps1 + env: + GITHUB_TOKEN: ${{ inputs.token }} diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml deleted file mode 100644 index f4aa9e2b1..000000000 --- a/.github/actions/setup/action.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Setup -description: Sets up runner, Rust and pnpm -inputs: - token: - description: Github token - required: false - default: '' - save-cache: - description: Whether to save the Rust cache - required: false - default: 'false' -runs: - using: 'composite' - steps: - - name: Install Rust - id: toolchain - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - components: clippy, rustfmt - - - name: Install pnpm - uses: pnpm/action-setup@v2 - with: - version: 8.x.x - run_install: false - - - name: Cache LLVM and Clang - if: runner.os == 'Windows' - id: cache-llvm - uses: actions/cache@v3 - with: - path: C:/Program Files/LLVM - key: llvm-15 - - - name: Install LLVM and Clang - if: runner.os == 'Windows' - uses: KyleMayes/install-llvm-action@v1 - with: - version: '15' - cached: ${{ steps.cache-llvm.outputs.cache-hit }} - - - name: Cache Rust deps - uses: ./.github/actions/cache-rust-deps - with: - save-cache: ${{ inputs.save-cache }} - - - name: Run 'setup-system.sh' script - shell: bash - if: runner.os == 'Linux' || runner.os == 'macOS' - run: ./.github/scripts/setup-system.sh - - - name: Run 'setup-system.ps1' script - shell: powershell - if: runner.os == 'Windows' - run: ./.github/scripts/setup-system.ps1 - env: - GITHUB_TOKEN: ${{ inputs.token }} - - - name: Generate Prisma client - uses: ./.github/actions/generate-prisma-client diff --git a/.github/scripts/ffmpeg-macos/Dockerfile b/.github/scripts/ffmpeg-macos/Dockerfile new file mode 100644 index 000000000..68dd5c344 --- /dev/null +++ b/.github/scripts/ffmpeg-macos/Dockerfile @@ -0,0 +1,65 @@ +ARG FAKE_DEPS="python311 perl5.34 gdk-pixbuf2 xorg-libsm xorg-libX11" \ + FFMPEG_DEPS="aom bzip2 fontconfig freetype fribidi lame libgsm libheif libogg libopus libpng \ + libtheora libvidstab libvorbis libvpx-devel lzo2 openjpeg rav1e soxr svt-av1 twolame webp x264 \ + x265 XviD xz zimg zlib" \ + FFMPEG_VERSION=6.0 + +FROM vvasconcellos/osxcross:12.3-50e86eb-1 as base + +# Download ffmpeg +ARG FFMPEG_VERSION +ADD "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.xz" ./ +RUN tar -xf ffmpeg-${FFMPEG_VERSION}.tar.xz && rm ffmpeg-${FFMPEG_VERSION}.tar.xz + +WORKDIR /srv/ffmpeg-${FFMPEG_VERSION} + +COPY ./ffmpeg-build-macos.sh ./ + +# --- +FROM base as x86_64 + +# Fake Install macOS dependencies not required to build ffmpeg +ARG FAKE_DEPS +# hadolint ignore=SC2086 +RUN osxcross-macports fake-install $FAKE_DEPS + +# Install macOS dependencies required to build ffmpeg +ARG FFMPEG_DEPS +# hadolint ignore=SC2086 +RUN --mount=type=cache,id=macports-x86_64,target=/opt/osxcross/macports/cache \ + osxcross-macports install $FFMPEG_DEPS + +# Build ffmpeg +RUN ./ffmpeg-build-macos.sh x86_64 "$MACOSX_SDK" + +# --- +FROM base as aarch64 + +# https://ffmpeg.org/pipermail/ffmpeg-user/2016-January/030202.html +ADD https://raw.githubusercontent.com/yuvi/gas-preprocessor/master/gas-preprocessor.pl /usr/local/bin/ +RUN chmod +x /usr/local/bin/gas-preprocessor.pl + +# Update min macOS version for arm64 +# libbrotli macports precompiled binaries are only available for macOS 11.2+ +ENV OSX_VERSION_MIN="11.2" \ + MACOSX_DEPLOYMENT_TARGET="11.2" + +# Fake Install macOS dependencies not required to build ffmpeg +ARG FAKE_DEPS +# hadolint ignore=SC2086 +RUN osxcross-macports fake-install --arm64 $FAKE_DEPS + +# Install macOS dependencies required to build ffmpeg +ARG FFMPEG_DEPS +# hadolint ignore=SC2086 +RUN --mount=type=cache,id=macports-arm64,target=/opt/osxcross/macports/cache \ + osxcross-macports install --arm64 $FFMPEG_DEPS + +# Build ffmpeg +RUN ./ffmpeg-build-macos.sh aarch64 "$MACOSX_SDK" + +# --- +FROM scratch + +COPY --from=x86_64 /FFMpeg.framework /ffmpeg/x86_64/FFMpeg.framework +COPY --from=aarch64 /FFMpeg.framework /ffmpeg/aarch64/FFMpeg.framework diff --git a/.github/scripts/ffmpeg-macos/README.md b/.github/scripts/ffmpeg-macos/README.md new file mode 100644 index 000000000..f79cec999 --- /dev/null +++ b/.github/scripts/ffmpeg-macos/README.md @@ -0,0 +1,25 @@ +# FFMpeg.framework + +## Build instructions + +To build `FFMpeg.framework` a `docker` or `podman` installation is required. +It is recomended to enable [`BuildKit`](https://docs.docker.com/build/buildkit/#getting-started) in docker. + +Just run the following inside this directory: + +```sh +$> docker build -o . . +``` + +or + +```sh +$> podman build -o . . +``` + +After some time (it takes aroung 15min in Github CI) a directory named `ffmpeg` will show up with both a `x86_64` and `arm64` directory inside, +both will have a `FFMpeg.framework` for their respective architecture. + +### How does the build process work? + +The `FFMpeg.framework` is built inside an Alpine Linux container that contains a copy of [`osxcross`](https://github.com/tpoechtrager/osxcross), which is a cross toolchain that enables building native macOS binaries on Linux. Most of the build process is similar to how you would do it in macOS. The main advantage of using `osxcross` is that it handles the configuration for both `x86` and `arm64` and all the required compiling tools without the need for Xcode and with a more direct and easier managment of macOS SDKs. Any required macOS dependencies are handled by a MacPorts-compatible package manager. diff --git a/.github/scripts/ffmpeg-macos/ffmpeg-build-macos.sh b/.github/scripts/ffmpeg-macos/ffmpeg-build-macos.sh new file mode 100755 index 000000000..d418549dc --- /dev/null +++ b/.github/scripts/ffmpeg-macos/ffmpeg-build-macos.sh @@ -0,0 +1,345 @@ +#!/usr/bin/env bash + +# This script builds ffmpeg for macOS using osxcross. +# This script is heavly influenced by: +# https://github.com/FFmpeg/FFmpeg/blob/ea3d24bbe3c58b171e55fe2151fc7ffaca3ab3d2/configure +# https://github.com/GerardSoleCa/macports-ports/blob/6f646dfaeb58ccb4a8b877df1ae4eecc4650fac7/multimedia/ffmpeg-upstream/Portfile +# https://github.com/arthenica/ffmpeg-kit/blob/47f85fa9ea3f8c34f3c817b87d8667b61b87d0bc/scripts/apple/ffmpeg.sh +# https://github.com/zimbatm/ffmpeg-static/blob/3206c0d74cd129c2ddfc3e928dcd3ea317d54857/build.sh + +set -euox pipefail + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +if [ -z "$MACOSX_DEPLOYMENT_TARGET" ]; then + echo "You must set MACOSX_DEPLOYMENT_TARGET first." >&2 + exit 1 +fi + +ARCH="$1" +MACOS_VERSION="$2" + +set -- # Clear command line arguments +if [ "$ARCH" = "x86_64" ]; then + TARGET_CPU="x86_64" + TARGET_ARCH="x86_64" + set -- --enable-x86asm +elif [ "$ARCH" = "aarch64" ]; then + TARGET_CPU="armv8" + TARGET_ARCH="aarch64" + set -- --enable-neon --enable-asm +else + echo "Unsupported architecture: $ARCH" >&2 + exit 1 +fi + +# Get darwin version and build compiler triple +DARWIN_VERSION="$(basename "$(realpath "$(command -v "oa64-clang")")" | awk -F- '{print $3}')" +TRIPLE="${ARCH}-apple-${DARWIN_VERSION}" + +# Check macOS clang exists +if ! CC="$(command -v "${TRIPLE}-clang" 2>/dev/null)"; then + echo "${TRIPLE}-clang not found" >&2 + exit 1 +fi +export CC + +# Get osxcross root directory +_osxcross_root="$(dirname "$(dirname "$CC")")" + +# Check macports root exists +_macports_root="${_osxcross_root}/macports/pkgs/opt/local" +if ! [ -d "$_macports_root" ]; then + echo "macports root not found: $_macports_root" >&2 + exit 1 +fi + +# Check SDK exists +_sdk="${_osxcross_root}/SDK/MacOSX${MACOS_VERSION}.sdk" +if ! [ -d "$_sdk" ]; then + echo "Invalid MacOS version: $MACOS_VERSION" >&2 + exit 1 +fi + +# Gather all SDK libs +_skd_libs="$( + while IFS= read -r -d '' _lib; do + _lib="${_lib#"${_sdk}/usr/lib/"}" + _lib="${_lib%.*}" + printf '%s.dylib\n' "$_lib" + done < <(find "${_sdk}/usr/lib" \( -name '*.tbd' -o -name '*.dylib' \) -print0) \ + | sort -u +)" + +# Change cwd to the script directory (which should be ffmpeg source root) +CDPATH='' cd -- "$(dirname -- "$0")" + +# Save FFmpeg version +FFMPEG_VERSION="$(xargs printf '%s' "/${_framework}/Versions/Current/Resources/Info.plist" + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + FFMpeg + CFBundleGetInfoString + FFMpeg ${FFMPEG_VERSION} + CFBundleIdentifier + com.spacedrive.ffmpeg + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + FFMpeg + CFBundlePackageType + FMWK + CFBundleShortVersionString + ${FFMPEG_VERSION} + CFBundleSignature + ???? + CFBundleVersion + ${FFMPEG_VERSION} + + +EOF + +# Process FFMpeg libraries to be compatible with the Framework structure +cd "$TARGET_DIR/lib" + +# Move all symlinks of ffmpeg libraries to Framework +while IFS= read -r -d '' _lib; do + # Copy symlinks to the output directory + cp -Ppv "$_lib" "/${_framework}/Libraries/${_lib#./}" + rm "$_lib" +done < <(find . -type l -print0) + +# Populate queue with ffmpeg libraries +set -- # Clear command line arguments +while IFS= read -r -d '' _lib; do + set -- "$@" "${_lib#./}" +done < <(find . -name '*.dylib' -print0) + +# Copy all symlinks of libheif libraries to Framework +while IFS= read -r -d '' _lib; do + # Copy symlinks to the output directory + cp -Ppv "$_lib" "/${_framework}/Libraries/${_lib#"${_macports_root}/lib/"}" +done < <(find "${_macports_root}/lib" -type l \( -name 'libheif.*' -a -name '*.dylib' \) -print0) + +# Copy libheif to cwd and add it to queue +while IFS= read -r -d '' _lib; do + _lib_rel="${_lib#"${_macports_root}/lib/"}" + cp -Lpv "$_lib" "./${_lib_rel}" + set -- "$@" "${_lib_rel}" +done < <(find "${_macports_root}/lib" -type f \( -name 'libheif.*' -a -name '*.dylib' \) -print0) + +while [ $# -gt 0 ]; do + # Loop through each of the library's dependencies + for _dep in $("${TRIPLE}-otool" -L "$1" | tail -n+2 | awk '{print $1}'); do + case "$_dep" in + # FFMpeg inter dependency + "${TARGET_DIR}/lib/"*) + _linker_path="@loader_path/${_dep#"${TARGET_DIR}/lib/"}" + ;; + # Macports dependency (/opt/local/lib means it was installed by Macports) + /opt/local/lib/*) + _dep_rel="${_dep#/opt/local/lib/}" + # Check if the macports dependency is already included in the macOS SDK + if [ -n "$(comm -12 <(printf "%s" "$_dep_rel") <(printf "%s" "$_skd_libs"))" ]; then + # Relink libs already included in macOS SDK + _linker_path="/usr/lib/${_dep_rel}" + else + _linker_path="@loader_path/${_dep_rel}" + if ! [ -e "${_macports_root}/lib/${_dep_rel}" ]; then + echo "Missing macports dependency: ${_dep_rel}" + exit 1 + elif ! { [ -f "$_dep_rel" ] || [ -e "/${_framework}/Libraries/${_dep_rel}" ]; }; then + # Copy dependency to the current directory if this is the first time we see it + cp -Lpv "${_macports_root}/lib/${_dep_rel}" "./${_dep_rel}" + # Add it to the queue to have it's own dependencies processed + set -- "$@" "$_dep_rel" + fi + fi + ;; + *) # Ignore system libraries + continue + ;; + esac + + # Change the dependency linker path to make it compatible with an .app bundle + "${TRIPLE}-install_name_tool" -change "$_dep" "$_linker_path" "$1" + done + + # Update the library's own id + "${TRIPLE}-install_name_tool" -id "@executable_path/../Frameworks/${_framework}/Libraries/${1}" "$1" + + # Copy the library to framework + cp -Lpv "$1" "/${_framework}/Libraries/${1}" + + # Remove library from queue + shift +done + +# Copy all libheif headers to framework +cp -av "${_macports_root}/include/libheif" "/${_framework}/Headers/" + +# Copy all FFMPEG headers to framework +cp -av "${TARGET_DIR}/include/"* "/${_framework}/Headers/" diff --git a/.github/scripts/osxcross/180.diff b/.github/scripts/osxcross/180.diff new file mode 100644 index 000000000..40db8cc20 --- /dev/null +++ b/.github/scripts/osxcross/180.diff @@ -0,0 +1,51 @@ +diff --git a/tools/osxcross-macports b/tools/osxcross-macports +index f008a8d..725fac1 100755 +--- a/tools/osxcross-macports ++++ b/tools/osxcross-macports +@@ -306,20 +306,34 @@ getPkgUrl() + verboseMsg " $p" + done + +- local pkg=$(echo "$pkgs" | \ +- grep "$pkgname-$pkgversion" | grep $OSXVERSION | grep $ARCH | \ +- uniq | tail -n1) +- if [ -z "$pkg" ]; then ++ local allpkgs="$pkgs" ++ if [ $ARCH != "i386-x86_64" ]; then ++ pkgs=$(echo "$pkgs" | grep -v universal) ++ fi ++ ++ while [ -z "$pkg" ] && [ -n "$pkgs" ]; do + pkg=$(echo "$pkgs" | \ +- grep "$pkgname-$pkgversion" | grep $OSXVERSION | grep "noarch" | \ ++ grep "$pkgname-$pkgversion" | grep $OSXVERSION | grep $ARCH | \ + uniq | tail -n1) +- fi +- if [ -z "$pkg" ]; then +- pkg=$(echo "$pkgs" | grep $OSXVERSION | grep $ARCH | uniq | tail -n1) +- fi +- if [ -z "$pkg" ]; then +- pkg=$(echo "$pkgs" | grep $OSXVERSION | grep "noarch" | uniq | tail -n1) +- fi ++ if [ -z "$pkg" ]; then ++ pkg=$(echo "$pkgs" | \ ++ grep "$pkgname-$pkgversion" | grep $OSXVERSION | grep "noarch" | \ ++ uniq | tail -n1) ++ fi ++ if [ -z "$pkg" ]; then ++ pkg=$(echo "$pkgs" | grep $OSXVERSION | grep $ARCH | uniq | tail -n1) ++ fi ++ if [ -z "$pkg" ]; then ++ pkg=$(echo "$pkgs" | grep $OSXVERSION | grep "noarch" | uniq | tail -n1) ++ fi ++ ++ if [ $ARCH != "i386-x86_64" ]; then ++ pkgs="$allpkgs" ++ allpkgs="" ++ else ++ pkgs="" ++ fi ++ done + + verboseMsg " selected: $pkg" + diff --git a/.github/scripts/osxcross/181.diff b/.github/scripts/osxcross/181.diff new file mode 100644 index 000000000..95205ba12 --- /dev/null +++ b/.github/scripts/osxcross/181.diff @@ -0,0 +1,41 @@ +diff --git a/tools/osxcross-macports b/tools/osxcross-macports +index f008a8d..23dd68d 100755 +--- a/tools/osxcross-macports ++++ b/tools/osxcross-macports +@@ -181,7 +181,7 @@ selectMirror() + } + + function download() +-{ ++( + local uri=$1 + local filename + +@@ -191,6 +191,18 @@ function download() + filename=$(basename $1) + fi + ++ # Remove file if any error occurs during download ++ trap 'rm -f "$filename"' ERR ++ ++ # Return if file already exists in cache ++ case "$filename" in ++ "$CACHE"*) ++ if [ -e "$filename" ]; then ++ return ++ fi ++ ;; ++ esac ++ + if command -v curl &>/dev/null; then + ## cURL ## + local curl_opts="-L -C - " +@@ -220,7 +232,7 @@ function download() + echo "Required dependency 'curl or wget' not installed" 1>&2 + exit 1 + fi +-} ++) + + getFileStdout() + { diff --git a/.github/scripts/osxcross/314.diff b/.github/scripts/osxcross/314.diff new file mode 100644 index 000000000..b5d5bcdb2 --- /dev/null +++ b/.github/scripts/osxcross/314.diff @@ -0,0 +1,15 @@ +diff --git a/wrapper/target.cpp b/wrapper/target.cpp +index 82bf65c40..a81ce97b7 100644 +--- a/wrapper/target.cpp ++++ b/wrapper/target.cpp +@@ -741,6 +741,10 @@ bool Target::setup() { + (stdlib == StdLib::libstdcxx && usegcclibs)) { + fargs.push_back("-nostdinc++"); + fargs.push_back("-Qunused-arguments"); ++ ++ if ((SDKOSNum >= OSVersion(11, 1)) && (stdlib == StdLib::libcxx)) { ++ fargs.push_back("-lc++"); ++ } + } + + if (stdlib == StdLib::libstdcxx && usegcclibs && targetarch.size() < 2 && diff --git a/.github/scripts/osxcross/372.diff b/.github/scripts/osxcross/372.diff new file mode 100644 index 000000000..5cdc5ad5a --- /dev/null +++ b/.github/scripts/osxcross/372.diff @@ -0,0 +1,11 @@ +diff --git a/build.sh b/build.sh +index eeab219ac..14b9137b5 100755 +--- a/build.sh ++++ b/build.sh +@@ -50,6 +50,7 @@ case $SDK_VERSION in + 12.3*) TARGET=darwin21.4; X86_64H_SUPPORTED=1; I386_SUPPORTED=0; ARM_SUPPORTED=1; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9; ;; + 12.4*) TARGET=darwin21.5; X86_64H_SUPPORTED=1; I386_SUPPORTED=0; ARM_SUPPORTED=1; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9; ;; + 13.0*) TARGET=darwin22; X86_64H_SUPPORTED=1; I386_SUPPORTED=0; ARM_SUPPORTED=1; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9; ;; ++ 13.1*) TARGET=darwin22.2; X86_64H_SUPPORTED=1; I386_SUPPORTED=0; ARM_SUPPORTED=1; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9; ;; + *) echo "Unsupported SDK"; exit 1 ;; + esac diff --git a/.github/scripts/osxcross/379.diff b/.github/scripts/osxcross/379.diff new file mode 100644 index 000000000..4a3a9e2a9 --- /dev/null +++ b/.github/scripts/osxcross/379.diff @@ -0,0 +1,15 @@ +diff --git a/tools/osxcross-macports b/tools/osxcross-macports +index f008a8da7..3e87735b8 100755 +--- a/tools/osxcross-macports ++++ b/tools/osxcross-macports +@@ -81,8 +81,9 @@ case $MACOSX_DEPLOYMENT_TARGET in + 10.14* ) OSXVERSION="darwin_18" ;; + 10.15* ) OSXVERSION="darwin_19" ;; + 10.16* ) OSXVERSION="darwin_20" ;; +- 11.* ) OSXVERSION="darwin_21" ;; ++ 11.* ) OSXVERSION="darwin_20" ;; + 12.* ) OSXVERSION="darwin_21" ;; ++ 13.* ) OSXVERSION="darwin_22" ;; + * ) unsupportedDepTarget ;; + esac + diff --git a/.github/scripts/osxcross/Dockerfile b/.github/scripts/osxcross/Dockerfile new file mode 100644 index 000000000..625058429 --- /dev/null +++ b/.github/scripts/osxcross/Dockerfile @@ -0,0 +1,67 @@ +ARG MACOS_VERSION=12.3 \ + MACOS_MIN_VERSION=10.15 \ + # aarch64 requires a higher min macOS version to build ffmpeg + ARM64_MACOS_MIN_VERSION=11.0 + +FROM alpine:3.17 as base + +WORKDIR /srv + +# Host dependencies, required to build osxcross, gcc for macOS and ffmpeg. ~1GiB +# hadolint ignore=DL3018 +RUN --mount=type=cache,target=/var/cache/apk ln -vs /var/cache/apk /etc/apk/cache && apk add --update \ + bash bsd-compat-headers build-base bzip2-dev clang15 cmake curl git gmp-dev libc++-dev libc-dev libuuid libxml2-dev \ + llvm15-dev llvm15-static mpc1-dev mpfr-dev musl-fts-dev openssl openssl-dev perl python3 xz yasm zlib-dev + +# Download osxcross, use a specific commit to avoid breaking changes and allow docker to cache it +ADD https://github.com/tpoechtrager/osxcross/archive/50e86eb.zip /srv/osxcross.zip +RUN unzip osxcross.zip && mv osxcross-* osxcross && rm osxcross.zip + +WORKDIR /srv/osxcross/tarballs + +# Download MacOS SDK +ARG MACOS_VERSION +ENV MACOSX_SDK="$MACOS_VERSION" +ADD "https://github.com/joseluisq/macosx-sdks/releases/download/${MACOS_VERSION}/MacOSX${MACOS_VERSION}.sdk.tar.xz" ./ + +# Setupt osxcross environment variables +ARG MACOS_MIN_VERSION ARM64_MACOS_MIN_VERSION +ENV PATH="$PATH:/opt/osxcross/bin" \ + UNATTENDED=yes \ + OSXCROSS_MP_INC=1 \ + OSX_VERSION_MIN="$MACOS_MIN_VERSION" \ + MACOSX_DEPLOYMENT_TARGET="$MACOS_MIN_VERSION" \ + MACOSX_ARM64_DEPLOYMENT_TARGET="$ARM64_MACOS_MIN_VERSION" + +WORKDIR /srv/osxcross + +# Some important patches from unmerged PRs +# PR 180 code needed to be updated to work with the latest osxcross +# 181 is not related to the 181 PR. It's just custom code that needed to be patched after 180 and before 379 +COPY 180.diff 181.diff 314.diff 372.diff 379.diff ./ +RUN set -eux; for patch in *.diff; do patch -p1 < "$patch"; done + +# Build osxcross +RUN set -eux; export TARGET_DIR=/opt/osxcross \ + && \ + ./build.sh \ + && \ + ./build_compiler_rt.sh \ + && \ + # Ugly workaround for linker not finding the macOS SDK's Framework directory + ln -fs "${TARGET_DIR}/SDK/MacOSX${MACOS_VERSION}.sdk/System" '/System' \ + && \ + ./cleanup.sh + +WORKDIR /srv + +# Setup macports +RUN osxcross-macports --help + +LABEL org.opencontainers.image.title="alpine-osxcross" \ + # Version is macOS SDK version + osxcross commit hash + org.opencontainers.image.version="12.3-50e86eb" \ + org.opencontainers.image.authors="VĂ­tor Vasconcellos , Spacedrive " \ + org.opencontainers.image.revision="1" \ + org.opencontainers.image.licenses="GPL-2.0" \ + org.opencontainers.image.description="macOS cross toolchain configured inside Alpine Linux" diff --git a/.github/scripts/osxcross/README.md b/.github/scripts/osxcross/README.md new file mode 100644 index 000000000..2b5f49b5b --- /dev/null +++ b/.github/scripts/osxcross/README.md @@ -0,0 +1,5 @@ +# OSXCross container + +This container is currently available at https://hub.docker.com/r/vvasconcellos/osxcross. + +It is based on alpine 3.17, with the most commom build depencies installed, and a built version of [`osxcross`](https://github.com/tpoechtrager/osxcross) plus the macOS SDK 12.3 (Monterey) targetting a minimum compatibility of macOS 10.15 (Catalina) for x86_64 and macOS 11.0 (BigSur) for arm64. diff --git a/.github/scripts/setup-system.ps1 b/.github/scripts/setup-system.ps1 index a3736237f..634a3af83 100644 --- a/.github/scripts/setup-system.ps1 +++ b/.github/scripts/setup-system.ps1 @@ -3,102 +3,93 @@ $ErrorActionPreference = if ($env:CI) { 'Stop' } else { 'Inquire' } Set-StrictMode -Version Latest function Reset-Path { - $env:Path = [System.Environment]::ExpandEnvironmentVariables( - [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User') - ) -} - -function Set-EnvVar($variable, $value = $null) { - if ($null -ne $value) { - [System.Environment]::SetEnvironmentVariable($variable, $value, 'User') - if ($env:CI -and ($variable -ne 'Path')) { - # If running in CI, we need to use GITHUB_ENV instead of the normal env variables - Add-Content $env:GITHUB_ENV "$variable=$value`n" - } - } - # The following is needed to make the environment variable available to the current PowerShell process - if ($variable -eq 'Path') { - Reset-Path - } else { - [System.Environment]::SetEnvironmentVariable( - $variable, [System.Environment]::GetEnvironmentVariable($variable, 'User'), 'Process' - ) - } + $env:Path = [System.Environment]::ExpandEnvironmentVariables( + [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + + [IO.Path]::PathSeparator + + [System.Environment]::GetEnvironmentVariable('Path', 'User') + ) } # Verify if environment is Windows 64-bit and if the user is an administrator if ((-not [string]::IsNullOrEmpty($env:PROCESSOR_ARCHITEW6432)) -or ( - "$env:PROCESSOR_ARCHITECTURE" -eq 'ARM64' - ) -or ( - -not [System.Environment]::Is64BitOperatingSystem - # Powershell >= 6 is cross-platform, check if running on Windows - ) -or (($PSVersionTable.PSVersion.Major -ge 6) -and (-not $IsWindows)) + "$env:PROCESSOR_ARCHITECTURE" -eq 'ARM64' + ) -or ( + -not [System.Environment]::Is64BitOperatingSystem + # Powershell >= 6 is cross-platform, check if running on Windows + ) -or (($PSVersionTable.PSVersion.Major -ge 6) -and (-not $IsWindows)) ) { - $ErrorActionPreference = 'Continue' - Write-Host # There is no oficial ffmpeg binaries for Windows 32 or ARM - if (Test-Path "$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -PathType Leaf) { - throw 'You are using PowerShell (32-bit), please re-run in PowerShell (64-bit)' - } else { - throw 'This script is only supported on Windows 64-bit' - } - Exit 1 + $ErrorActionPreference = 'Continue' + Write-Host # There is no oficial ffmpeg binaries for Windows 32 or ARM + if (Test-Path "$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -PathType Leaf) { + throw 'You are using PowerShell (32-bit), please re-run in PowerShell (64-bit)' + } else { + throw 'This script is only supported on Windows 64-bit' + } + Exit 1 } elseif ( - -not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + -not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) ) { - # Start a new PowerShell process with administrator privileges and set the working directory to the directory where the script is located - Start-Process -Wait -FilePath 'PowerShell.exe' -Verb RunAs -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$($MyInvocation.MyCommand.Definition)`"" -WorkingDirectory "$PSScriptRoot" - # NOTICE: Any modified environment variables should be reloaded here, so the user doesn't have to restart the shell after running the script - Reset-Path - Set-EnvVar('PROTOC') - Set-EnvVar('FFMPEG_DIR') - Exit + # Start a new PowerShell process with administrator privileges and set the working directory to the directory where the script is located + $proc = Start-Process -PassThru -Wait -FilePath 'PowerShell.exe' -Verb RunAs -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$($MyInvocation.MyCommand.Definition)`"" -WorkingDirectory "$PSScriptRoot" + # Reset path so the user doesn't have to restart the shell to use the tools installed by this script + Reset-Path + Exit $proc.ExitCode } function Exit-WithError($err, $help = $null) { - if ($null -ne $help) { - Write-Host - Write-Host $help -ForegroundColor DarkRed - } - throw $err - Exit 1 + if ($null -ne $help) { + Write-Host + Write-Host $help -ForegroundColor DarkRed + } + throw $err + Exit 1 } function Add-DirectoryToPath($directory) { - if ($env:Path.Split(';') -notcontains $directory) { - Set-EnvVar 'Path' "$($env:Path);$directory" - if ($env:CI) { - # If running in CI, we need to use GITHUB_PATH instead of the normal PATH env variables - Add-Content $env:GITHUB_PATH "$directory`n" - } - } - Reset-Path + Reset-Path + if ($env:Path.Split([IO.Path]::PathSeparator) -notcontains $directory) { + [System.Environment]::SetEnvironmentVariable( + 'Path', + [System.Environment]::GetEnvironmentVariable('Path', 'User') + [IO.Path]::PathSeparator + $directory, + 'User' + ) + + if ($env:CI) { + # If running in CI, we need to use GITHUB_PATH instead of the normal PATH env variables + Add-Content $env:GITHUB_PATH "$directory`n" + } + } + Reset-Path } function Invoke-RestMethodGithub { - [CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] - [string]$Uri, - [string]$Method = 'GET', - [hashtable]$Headers = @{}, - [string]$UserAgent = 'PowerShell' - ) + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string]$Uri, + [string]$Method = 'GET', + [hashtable]$Headers = @{}, + [string]$UserAgent = 'PowerShell' + ) - if (![string]::IsNullOrEmpty($env:GITHUB_TOKEN)) { - $headers.Add("Authorization", "Bearer $($env:GITHUB_TOKEN)") - } + $headers.Add('Accept', 'application/vnd.github+json') + $headers.Add('X-GitHub-Api-Version', '2022-11-28') - $params = @{ - Uri = $Uri - Method = $Method - Headers = $Headers - UserAgent = $UserAgent - } + if (![string]::IsNullOrEmpty($env:GITHUB_TOKEN)) { + $headers.Add('Authorization', "Bearer $($env:GITHUB_TOKEN)") + } - Invoke-RestMethod @params + $params = @{ + Uri = $Uri + Method = $Method + Headers = $Headers + UserAgent = $UserAgent + } + + Invoke-RestMethod @params } -# Reset PATH to ensure the script doesn't read any stale entries +# Reset PATH to ensure the script doesn't have stale Path entries Reset-Path # Get temp folder @@ -106,16 +97,19 @@ $temp = [System.IO.Path]::GetTempPath() # Get project dir (get grandparent dir from script location: \.github\scripts) $projectRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$packageJson = Get-Content -Raw -Path "$projectRoot\package.json" | ConvertFrom-Json -# Pnpm -$pnpm_major = '8' +# Valid winget exit status +$wingetValidExit = 0, -1978335189, -1978335153, -1978335135 # Currently LLVM >= 16 is not supported due to incompatibilities with ffmpeg-sys-next # See https://github.com/spacedriveapp/spacedrive/issues/677 -$llvm_major = '15' +$llvmVersion = [Version]'15.0.7' # Change CWD to project root Set-Location $projectRoot +Remove-Item -Force -ErrorAction SilentlyContinue -Path "$projectRoot\.cargo\config" +Remove-Item -Force -ErrorAction SilentlyContinue -Path "$projectRoot\target\Frameworks" -Recurse Write-Host 'Spacedrive Development Environment Setup' -ForegroundColor Magenta Write-Host @" @@ -125,259 +119,262 @@ To set up your machine for Spacedrive development, this script will do the follo 2) Install Edge Webview 2 3) Install Rust and Cargo 4) Install Rust tools -5) Install Node.js, npm and pnpm $pnpm_major -6) Install LLVM $llvm_major (compiler for ffmpeg-rust) -7) Download protbuf compiler and set the PROTOC environment variable -8) Download ffmpeg and set the FFMPEG_DIR environment variable +5) Install Node.js, npm and pnpm +6) Install LLVM $llvmVersion (compiler for ffmpeg-rust) +7) Download the protbuf compiler +8) Download a compatible ffmpeg build "@ -# Check connectivity to GitHub -$ProgressPreference = 'SilentlyContinue' -if (-not ((Test-NetConnection -ComputerName 'github.com' -Port 80).TcpTestSucceeded)) { - throw "Can't connect to github, maybe internet is down?" -} -$ProgressPreference = 'Continue' - -# Install C++ build tools and Rust -# GitHub Actions already has all of this installed +# Install System dependencies (GitHub Actions already has all of those installed) if (-not $env:CI) { - if (-not (Get-Command winget -ea 0)) { - Exit-WithError 'winget not available' @' + if (-not (Get-Command winget -ea 0)) { + Exit-WithError 'winget not available' @' Follow the instructions here to install winget: https://learn.microsoft.com/windows/package-manager/winget/ '@ - } + } - Write-Host - Write-Host 'Installing Visual Studio Build Tools...' -ForegroundColor Yellow - Write-Host 'This will take some time as it involves downloading several gigabytes of data....' -ForegroundColor Cyan - # Force install because BuildTools is itself an installer for multiple packages, so let itself decide if it need to install anythin or not - winget install --exact --no-upgrade --accept-source-agreements --force --disable-interactivity --id Microsoft.VisualStudio.2022.BuildTools --override '--wait --quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended' + # Check connectivity to GitHub + $ProgressPreference = 'SilentlyContinue' + if (-not ((Test-NetConnection -ComputerName 'github.com' -Port 80).TcpTestSucceeded)) { + Exit-WithError "Can't connect to github, check your internet connection and run this script again" + } + $ProgressPreference = 'Continue' - Write-Host - Write-Host 'Installing Edge Webview 2...' -ForegroundColor Yellow - try { - # This is normally already available, but on some early Windows 10 versions it isn't - winget install --exact --no-upgrade --accept-source-agreements --disable-interactivity --id Microsoft.EdgeWebView2Runtime - } catch {} + Write-Host + Read-Host 'Press Enter to continue' - Write-Host - Write-Host 'Installing Rust and Cargo...' -ForegroundColor Yellow - try { - winget install --exact --no-upgrade --accept-source-agreements --disable-interactivity --id Rustlang.Rustup - Reset-Path # Reset Path to ensure that cargo is available to the command bellow - } catch {} + Write-Host + Write-Host 'Installing Visual Studio Build Tools...' -ForegroundColor Yellow + Write-Host 'This will take some time as it involves downloading several gigabytes of data....' -ForegroundColor Cyan + # Force install because BuildTools is itself a package manager, so let it decide if something needs to be installed or not + winget install -e --accept-source-agreements --force --disable-interactivity --id Microsoft.VisualStudio.2022.BuildTools ` + --override '--wait --quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended' + if (-not ($wingetValidExit -contains $LASTEXITCODE)) { + Exit-WithError 'Failed to install Visual Studio Build Tools' + } else { + $LASTEXITCODE = 0 + } - Write-Host - Write-Host 'Installing Rust tools' -ForegroundColor Yellow - cargo install cargo-watch + Write-Host + Write-Host 'Installing Edge Webview 2...' -ForegroundColor Yellow + # This is normally already available, but on some early Windows 10 versions it isn't + winget install -e --accept-source-agreements --disable-interactivity --id Microsoft.EdgeWebView2Runtime + if (-not ($wingetValidExit -contains $LASTEXITCODE)) { + Exit-WithError 'Failed to install Edge Webview 2' + } else { + $LASTEXITCODE = 0 + } + + Write-Host + Write-Host 'Installing Rust and Cargo...' -ForegroundColor Yellow + winget install -e --accept-source-agreements --disable-interactivity --id Rustlang.Rustup + if (-not ($wingetValidExit -contains $LASTEXITCODE)) { + Exit-WithError 'Failed to install Rust and Cargo' + } else { + $LASTEXITCODE = 0 + } + + Write-Host + Write-Host 'Installing NodeJS...' -ForegroundColor Yellow + # Check if Node.JS is already installed and if it's compatible with the project + $currentNode = Get-Command node -ea 0 + $currentNodeVersion = if (-not $currentNode) { $null } elseif ($currentNode.Version) { $currentNode.Version } elseif ((node --version) -match '(?sm)(\d+(\.\d+)*)') { [Version]$matches[1] } else { $null } + $enginesNodeVersion = if ($packageJson.engines.node -match '(?sm)(\d+(\.\d+)*)') { [Version]$matches[1] } else { $null } + if ($currentNodeVersion -and $enginesNodeVersion -and $currentNodeVersion.CompareTo($enginesNodeVersion) -lt 0) { + Exit-WithError "Current Node.JS version: $currentNodeVersion (required: $enginesNodeVersion)" ` + 'Uninstall the current version of Node.JS and run this script again' + } + # Install Node.JS + winget install -e --accept-source-agreements --disable-interactivity --id OpenJS.NodeJS + if (-not ($wingetValidExit -contains $LASTEXITCODE)) { + Exit-WithError 'Failed to install NodeJS' + } else { + $LASTEXITCODE = 0 + } + # Add NodeJS to the PATH + Add-DirectoryToPath "$env:SystemDrive\Program Files\nodejs" + + Write-Host + Write-Host 'Checking for LLVM...' -ForegroundColor Yellow + # Check if LLVM is already installed and if it's compatible with the project + $currentLLVMVersion = if ("$(winget list -e --disable-interactivity --id LLVM.LLVM)" -match '(?sm)LLVM.LLVM\s+(\d+(\.\d+)*)') { [Version]$matches[1] } else { $null } + if ($currentLLVMVersion -and $currentLLVMVersion.Major -gt $llvmVersion.Major) { + Exit-WithError "Current LLVM version: $currentLLVMVersion (required: $llvmVersion)" ` + 'Uninstall the current version of LLVM and run this script again' + } + # Install LLVM + winget install -e --accept-source-agreements --disable-interactivity --id LLVM.LLVM --version "$llvmVersion" + if (-not ($wingetValidExit -contains $LASTEXITCODE)) { + Exit-WithError 'Failed to install NodeJS' + } else { + $LASTEXITCODE = 0 + } + # Add LLVM to the PATH + Add-DirectoryToPath "$env:SystemDrive\Program Files\LLVM\bin" + + # Reset Path to ensure that executable installed above are available to rest of the script + Reset-Path + + Write-Host + Write-Host 'Installing Rust MSVC Toolchain...' -ForegroundColor Yellow + rustup toolchain install stable-msvc + if ($LASTEXITCODE -ne 0) { + Exit-WithError 'Failed to install Rust MSVC Toolchain' + } + + Write-Host + Write-Host 'Installing Rust tools...' -ForegroundColor Yellow + cargo install cargo-watch + if ($LASTEXITCODE -ne 0) { + Exit-WithError 'Failed to install Rust tools' + } + + Write-Host + Write-Host 'Installing for pnpm...' -ForegroundColor Yellow + # Check if pnpm is already installed and if it's compatible with the project + $currentPnpmVersion = if (-not (Get-Command pnpm -ea 0)) { $null } elseif ((pnpm --version) -match '(?sm)(\d+(\.\d+)*)') { [Version]$matches[1] } else { $null } + $enginesPnpmVersion = if ($packageJson.engines.pnpm -match '(?sm)(\d+(\.\d+)*)') { [Version]$matches[1] } else { $null } + + if (-not $currentPnpmVersion) { + # Remove possible remaining envvars from old pnpm installation + [System.Environment]::SetEnvironmentVariable('PNPM_HOME', $null, [System.EnvironmentVariableTarget]::Machine) + [System.Environment]::SetEnvironmentVariable('PNPM_HOME', $null, [System.EnvironmentVariableTarget]::User) + + # Install pnpm + npm install -g "pnpm@latest-$($enginesPnpmVersion.Major)" + if ($LASTEXITCODE -ne 0) { + Exit-WithError 'Failed to install pnpm' + } + + # Add NPM global modules to the PATH + if (Test-Path "$env:APPDATA\npm" -PathType Container) { + Add-DirectoryToPath "$env:APPDATA\npm" + } + } elseif ($currentPnpmVersion -and $enginesPnpmVersion -and $currentPnpmVersion.CompareTo($enginesPnpmVersion) -lt 0) { + Exit-WithError "Current pnpm version: $currentPnpmVersion (required: $enginesPnpmVersion)" ` + 'Uninstall the current version of pnpm and run this script again' + } } +# Create target folder, continue if already exists +New-Item -Force -ErrorAction SilentlyContinue -ItemType Directory -Path "$projectRoot\target\Frameworks" | Out-Null + +# -- + Write-Host -Write-Host 'Checking for pnpm...' -ForegroundColor Yellow -if ($env:CI) { - # The CI has pnpm installed already - Write-Host 'Running with CI, skipping pnpm install.' -ForegroundColor Green -}if ((Get-Command pnpm -ea 0) -and (pnpm --version | Select-Object -First 1) -match "^$pnpm_major\." ) { - Write-Host "pnpm $pnpm_major is installed." -ForegroundColor Green -} else { - # Check for pnpm installed with standalone installer - if (($null -ne $env:PNPM_HOME) -and (Test-Path "$env:PNPM_HOME/pnpm.exe" -PathType Leaf)) { - Exit-WithError 'You have a incompatible version of pnpm installed, please remove it and run this script again' @' -Follow the instructions here to uninstall pnpm: -https://pnpm.io/uninstall -'@ - } else { - # Remove possible remaining envvars from old pnpm installation - [System.Environment]::SetEnvironmentVariable('PNPM_HOME', $null, [System.EnvironmentVariableTarget]::Machine) - [System.Environment]::SetEnvironmentVariable('PNPM_HOME', $null, [System.EnvironmentVariableTarget]::User) - } +Write-Host 'Retrieving protobuf version...' -ForegroundColor Yellow - if (-not $env:CI) { - Write-Host 'Installing NodeJS...' -ForegroundColor Yellow - try { - winget install --exact --no-upgrade --accept-source-agreements --disable-interactivity --id OpenJS.NodeJS - # Add NodeJS to the PATH - Add-DirectoryToPath "$env:SystemDrive\Program Files\nodejs" - } catch {} - } +$filename = $null +$downloadUri = $null +$releasesUri = 'https://api.github.com/repos/protocolbuffers/protobuf/releases' +$filenamePattern = '*-win64.zip' - Write-Host 'Installing pnpm...' - npm install -g "pnpm@latest-$pnpm_major" - # Add NPM global modules to the PATH - if (Test-Path "$env:APPDATA\npm" -PathType Container) { - Add-DirectoryToPath "$env:APPDATA\npm" - } -} - -Write-Host -Write-Host 'Checking for LLVM...' -ForegroundColor Yellow -if ($env:CI) { - # The CI has LLVM installed already - Write-Host 'Running with CI, skipping LLVM install.' -ForegroundColor Green -} elseif ( - (Get-Command clang -ea 0) -and ( - (clang --version | Select-String -Pattern 'version\s+(\d+)' | ForEach-Object { $_.Matches.Groups[1].Value }) -eq "$llvm_major" - ) -) { - Write-Host "LLVM $llvm_major is installed." -ForegroundColor Green -} else { - $downloadUri = $null - $releasesUri = 'https://api.github.com/repos/llvm/llvm-project/releases' - $llvmVersion = "LLVM $llvm_major*" - $filenamePattern = '*-win64.exe' - - Write-Host "Downloading LLVM $llvm_major installer..." -ForegroundColor Yellow - $releases = Invoke-RestMethodGithub -Uri $releasesUri - $downloadUri = $releases | ForEach-Object { - if ($_.name -like $llvmVersion) { - $_.assets | Where-Object { $_.name -like $filenamePattern } | Select-Object -ExpandProperty 'browser_download_url' - } - } | Select-Object -First 1 - - if ($null -eq $downloadUri) { - Exit-WithError "Couldn't find a LLVM installer for version: $llvm_major" - } - - $oldUninstaller = "$env:SystemDrive\Program Files\LLVM\Uninstall.exe" - if (Test-Path $oldUninstaller -PathType Leaf) { - Exit-WithError 'You have a incompatible version of LLVM installed' 'Uninstall the current version of LLVM and run this script again' - } - - Start-BitsTransfer -TransferType Download -Source $downloadUri -Destination "$temp\llvm.exe" - - Write-Host "Installing LLVM $llvm_major" -ForegroundColor Yellow - Write-Host 'This may take a while and will have no visual feedback, please wait...' -ForegroundColor Cyan - Start-Process -FilePath "$temp\llvm.exe" -Verb RunAs -ArgumentList '/S' -Wait -ErrorAction Stop - - Add-DirectoryToPath "$env:SystemDrive\Program Files\LLVM\bin" - - Remove-Item "$temp\llvm.exe" -} - -Write-Host -Write-Host 'Checking for protobuf compiler...' -ForegroundColor Yellow -$protocVersion = $null -if (($null -ne $env:PROTOC) -and (Test-Path $env:PROTOC -PathType Leaf)) { - $protocVersion = &"$env:PROTOC" --version 2>&1 | Out-String -} - -if ($protocVersion) { - Write-Host 'protobuf compiler is installed.' -ForegroundColor Green -} else { - $filename = $null - $downloadUri = $null - $releasesUri = 'https://api.github.com/repos/protocolbuffers/protobuf/releases' - $filenamePattern = '*-win64.zip' - - Write-Host 'Downloading protobuf compiler...' -ForegroundColor Yellow - $releases = Invoke-RestMethodGithub -Uri $releasesUri - for ($i = 0; $i -lt $releases.Count; $i++) { - $release = $releases[$i] - foreach ($asset in $release.assets) { - if ($asset.name -like $filenamePattern) { +$releases = Invoke-RestMethodGithub -Uri $releasesUri +for ($i = 0; $i -lt $releases.Count; $i++) { + $release = $releases[$i] + foreach ($asset in $release.assets) { + if ($asset.name -like $filenamePattern) { $filename = $asset.name $downloadUri = $asset.browser_download_url $i = $releases.Count break - } - } - } - - if (-not ($filename -and $downloadUri)) { - Exit-WithError "Couldn't find a protobuf compiler installer" - } - - $foldername = "$env:LOCALAPPDATA\$([System.IO.Path]::GetFileNameWithoutExtension($fileName))" - New-Item -Path $foldername -ItemType Directory -ErrorAction SilentlyContinue - - Write-Host 'Dowloading protobuf zip...' -ForegroundColor Yellow - Start-BitsTransfer -TransferType Download -Source $downloadUri -Destination "$temp\protobuf.zip" - - Write-Host 'Expanding protobuf zip...' -ForegroundColor Yellow - Expand-Archive "$temp\protobuf.zip" $foldername -ErrorAction SilentlyContinue - Remove-Item "$temp\protobuf.zip" - - Write-Host 'Setting PROTOC environment variable...' -ForegroundColor Yellow - Set-EnvVar 'PROTOC' "$foldername\bin\protoc.exe" - Add-DirectoryToPath "$foldername\bin" + } + } } -Write-Host -Write-Host 'Update cargo packages...' -ForegroundColor Yellow -# Run first time to ensure packages are up to date -cargo metadata --format-version 1 > $null +if (-not ($filename -and $downloadUri)) { + Exit-WithError "Couldn't find a protobuf compiler installer" +} + +Write-Host 'Dowloading protobuf zip...' -ForegroundColor Yellow +Start-BitsTransfer -TransferType Download -Source $downloadUri -Destination "$temp\protobuf.zip" + +Write-Host 'Expanding protobuf zip...' -ForegroundColor Yellow +Expand-Archive "$temp\protobuf.zip" "$projectRoot\target\Frameworks" -Force +Remove-Item -Force -ErrorAction SilentlyContinue -Path "$temp\protobuf.zip" + +# -- Write-Host -Write-Host 'Checking for ffmpeg build...' -ForegroundColor Yellow +Write-Host 'Retrieving ffmpeg version...' -ForegroundColor Yellow + +# Run first to update packages +cargo metadata --format-version 1 | Out-Null + # Get ffmpeg-sys-next version $ffmpegVersion = (cargo metadata --format-version 1 | ConvertFrom-Json).packages.dependencies | Where-Object { - $_.name -like 'ffmpeg-sys-next' + $_.name -like 'ffmpeg-sys-next' } | Select-Object -ExpandProperty 'req' | ForEach-Object { - $_ -replace '[~^<>=!*]+', '' + $_ -replace '[~^<>=!*]+', '' } | Sort-Object -Unique | Select-Object -Last 1 -if (($null -ne $env:FFMPEG_DIR) -and ( - $ffmpegVersion.StartsWith( - (($env:FFMPEG_DIR.split('\') | Where-Object { $_ -like 'ffmpeg-*' }) -replace 'ffmpeg-(\d+(\.\d+)*).*', '$1'), - [System.StringComparison]::InvariantCulture - ) - ) -) { - Write-Host 'ffmpeg is installed.' -ForegroundColor Green -} else { - $filename = $null - $downloadUri = $null - $releasesUri = 'https://api.github.com/repos/GyanD/codexffmpeg/releases' - $filenamePattern = '*-full_build-shared.zip' - - # Downloads a build of ffmpeg from GitHub compatible with the declared ffmpeg-sys-next version - $releases = Invoke-RestMethodGithub -Uri $releasesUri - $version = $ffmpegVersion - while (-not ($filename -and $downloadUri) -and $version) { - for ($i = 0; $i -lt $releases.Count; $i++) { - $release = $releases[$i] - if ($release.tag_name -eq $version) { - foreach ($asset in $release.assets) { - if ($asset.name -like $filenamePattern) { - $filename = $asset.name - $downloadUri = $asset.browser_download_url - $i = $releases.Count - break - } - } - } - } - $version = $version -replace '\.?\d+$' - } - - if (-not ($filename -and $downloadUri)) { - Exit-WithError "Couldn't find a ffmpeg installer for version: $ffmpegVersion" - } - - $foldername = "$env:LOCALAPPDATA\$([System.IO.Path]::GetFileNameWithoutExtension($fileName))" - - Write-Host 'Dowloading ffmpeg zip...' -ForegroundColor Yellow - Start-BitsTransfer -TransferType Download -Source $downloadUri -Destination "$temp\ffmpeg.zip" - - Write-Host 'Expanding ffmpeg zip...' -ForegroundColor Yellow - # FFmpeg zip contains a subdirectory with the same name as the zip file - Expand-Archive "$temp\ffmpeg.zip" $env:LOCALAPPDATA -ErrorAction SilentlyContinue - Remove-Item "$temp\ffmpeg.zip" - - Write-Host 'Setting FFMPEG_DIR environment variable...' -ForegroundColor Yellow - Set-EnvVar 'FFMPEG_DIR' "$foldername" - Add-DirectoryToPath "$foldername\bin" +if ($LASTEXITCODE -ne 0) { + Exit-WithError 'Failed to get ffmpeg-sys-next version' } -Write-Host -Write-Host 'Copying Required .dll files...' -ForegroundColor Yellow -# Create target\debug folder, continue if already exists -New-Item -Path $projectRoot\target\debug -ItemType Directory -ErrorAction SilentlyContinue -# Copies all .dll required for rust-ffmpeg to target\debug folder -Get-ChildItem "$env:FFMPEG_DIR\bin" -Recurse -Filter *.dll | Copy-Item -Destination "$projectRoot\target\debug" +$filename = $null +$downloadUri = $null +$releasesUri = 'https://api.github.com/repos/GyanD/codexffmpeg/releases' +$filenamePattern = '*-full_build-shared.zip' -Write-Host -Write-Host 'Your machine has been setup for Spacedrive development!' -ForegroundColor Green -Write-Host 'You will need to re-run this script if there are rust dependencies changes or you use `pnpm clean` or `cargo clean`!' -ForegroundColor Red -if (-not $env:CI) { Read-Host 'Press Enter to continue' } +# Downloads a build of ffmpeg from GitHub compatible with the declared ffmpeg-sys-next version +$releases = Invoke-RestMethodGithub -Uri $releasesUri +$version = $ffmpegVersion +while (-not ($filename -and $downloadUri) -and $version) { + for ($i = 0; $i -lt $releases.Count; $i++) { + $release = $releases[$i] + if ($release.tag_name -eq $version) { + foreach ($asset in $release.assets) { + if ($asset.name -like $filenamePattern) { + $filename = $asset.name + $downloadUri = $asset.browser_download_url + $i = $releases.Count + break + } + } + } + } + $version = $version -replace '\.?\d+$' +} + +if (-not ($filename -and $downloadUri)) { + Exit-WithError "Couldn't find a ffmpeg installer for version: $ffmpegVersion" +} + +Write-Host 'Dowloading ffmpeg zip...' -ForegroundColor Yellow +Start-BitsTransfer -TransferType Download -Source $downloadUri -Destination "$temp\ffmpeg.zip" + +Write-Host 'Expanding ffmpeg zip...' -ForegroundColor Yellow +# FFmpeg zip contains a subdirectory with the same name as the zip file +Expand-Archive -Force -Path "$temp\ffmpeg.zip" -DestinationPath "$temp" +Remove-Item -Force -ErrorAction SilentlyContinue -Path "$temp\ffmpeg.zip" + +$ffmpegDir = "$temp\$([System.IO.Path]::GetFileNameWithoutExtension($fileName))" +$proc = Start-Process -PassThru -Wait -FilePath 'Robocopy.exe' -Verb RunAs -ArgumentList ` + "`"$ffmpegDir`" `"$projectRoot\target\Frameworks`" /E /NS /NC /NFL /NDL /NP /NJH /NJS" +# https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy#exit-return-codes +if ($proc.ExitCode -ge 8) { + Exit-WithError 'Failed to copy ffmpeg files' +} +Remove-Item -Force -ErrorAction SilentlyContinue -Recurse -Path "$ffmpegDir" + +@( + '[env]', + "PROTOC = `"$("$projectRoot\target\Frameworks\bin\protoc" -replace '\\', '\\')`"", + "FFMPEG_DIR = `"$("$projectRoot\target\Frameworks" -replace '\\', '\\')`"", + '', + (Get-Content "$projectRoot\.cargo\config.toml" -Encoding utf8) +) | Out-File -Force -Encoding utf8 -FilePath "$projectRoot\.cargo\config" + +if (-not $env:CI) { + Write-Host + Write-Host 'Your machine has been setup for Spacedrive development!' -ForegroundColor Green + Write-Host 'You will need to re-run this script if there are rust dependencies changes or you use `pnpm clean` or `cargo clean`!' -ForegroundColor Red + Write-Host + Read-Host 'Press Enter to continue' +} + +if ($LASTEXITCODE -ne 0) { + Exit-WithError "Something went wrong, exit code: $LASTEXITCODE" +} diff --git a/.github/scripts/setup-system.sh b/.github/scripts/setup-system.sh index 51c2759cd..d8157e049 100755 --- a/.github/scripts/setup-system.sh +++ b/.github/scripts/setup-system.sh @@ -2,13 +2,55 @@ set -euo pipefail -function log_err() { - echo "$@" >&2 +if [ "${CI:-}" = "true" ]; then + set -x +fi + +SYSNAME="$(uname)" +FFMPEG_VERSION='6.0' + +err() { + for _line in "$@"; do + echo "$@" >&2 + done + exit 1 } -function script_failure() { - log_err "An error occurred $(if [ -n "${1:-}" ]; then echo "on line $1"; else echo "(unknown)"; fi)." - log_err "Setup failed." +has() { + if [ "$#" -ne 1 ]; then + err "Usage: has " + fi + + command -v "$1" >/dev/null 2>&1 +} + +_gh_url="https://api.github.com/repos" +_sd_gh_path='spacedriveapp/spacedrive' +gh_curl() { + if [ "$#" -ne 1 ]; then + err "Usage: gh_curl " + fi + + url="$1" + + # Required headers for GitHub API + set -- -LSs -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" + + # Add authorization header if GITHUB_TOKEN is set, to avoid being rate limited + if [ -n "${GITHUB_TOKEN:-}" ]; then + set -- "$@" -H "Authorization: Bearer $GITHUB_TOKEN" + fi + + curl "$@" "$url" +} + +script_failure() { + if [ -n "${1:-}" ]; then + _line="on line $1" + else + _line="(unknown)" + fi + err "An error occurred $_line." "Setup failed." } trap 'script_failure ${LINENO:-}' ERR @@ -16,181 +58,333 @@ trap 'script_failure ${LINENO:-}' ERR echo "Setting up this system for Spacedrive development." echo -if ! command -v cargo >/dev/null; then - log_err "Rust was not found. Ensure the 'rustc' and 'cargo' binaries are in your \$PATH." - exit 1 +# Change CWD to the directory of this script +CDPATH='' cd -- "$(dirname -- "$0")" +_script_path="$(pwd -P)" +_cargo_config="${_script_path}/../../.cargo" + +rm -rf "$_cargo_config/config" + +if ! has cargo; then + err 'Rust was not found.' \ + "Ensure the 'rustc' and 'cargo' binaries are in your \$PATH." \ + 'https://rustup.rs' fi -if [ "${SPACEDRIVE_SKIP_PNPM_CHECK:-'false'}" != "true" ]; then - echo "Checking for pnpm..." +if [ "${CI:-}" != "true" ] && [ "${spacedrive_skip_pnpm_check:-}" != "true" ]; then + echo "checking for pnpm..." - if ! command -v pnpm >/dev/null; then - log_err "pnpm was not found. Ensure the 'pnpm' command is in your \$PATH." - log_err 'You MUST use pnpm for this project; yarn and npm are not allowed.' - exit 1 - else - echo "Found pnpm!" - fi + if ! has pnpm; then + err 'pnpm was not found.' \ + "Ensure the 'pnpm' command is in your \$PATH." \ + 'You must use pnpm for this project; yarn and npm are not allowed.' \ + 'https://pnpm.io/installation' + else + echo "found pnpm!" + fi else - echo "Skipping pnpm check." + echo "Skipping pnpm check." fi if [ "${CI:-}" != "true" ]; then - echo "Installing Rust tools" - cargo install cargo-watch + echo "Installing Rust tools" + cargo install cargo-watch fi echo -if [ "${1:-}" == "mobile" ]; then - echo "Setting up for mobile development." +if [ "${1:-}" = "mobile" ]; then + echo "Setting up for mobile development." - # iOS targets - if [[ $OSTYPE == "darwin"* ]]; then - echo "Checking for Xcode..." - if ! /usr/bin/xcodebuild -version >/dev/null; then - log_err "Xcode was not detected." - log_err "Please ensure Xcode is installed and try again." - exit 1 - fi + # iOS targets + if [ "$SYSNAME" = "Darwin" ]; then + echo "Checking for Xcode..." + if ! /usr/bin/xcodebuild -version >/dev/null; then + err "Xcode was not detected." \ + "Please ensure Xcode is installed and try again." + fi - echo "Installing iOS targets for Rust..." + echo "Installing iOS targets for Rust..." - rustup target add aarch64-apple-ios - rustup target add aarch64-apple-ios-sim - rustup target add x86_64-apple-ios # for CI - fi + rustup target add aarch64-apple-ios + rustup target add aarch64-apple-ios-sim + rustup target add x86_64-apple-ios # for CI + fi - # Android requires python - if ! command -v python3 >/dev/null; then - log_err "python3 command could not be found. This is required for Android mobile development." - log_err "Ensure python3 is available in your \$PATH and try again." - exit 1 - fi + # Android requires python + if ! command -v python3 >/dev/null; then + err 'python3 was not found.' \ + 'This is required for Android mobile development.' \ + "Ensure 'python3' is available in your \$PATH and try again." + fi - # Android targets - echo "Setting up Android targets for Rust..." + # Android targets + echo "Setting up Android targets for Rust..." - rustup target add armv7-linux-androideabi # for arm - rustup target add aarch64-linux-android # for arm64 - rustup target add i686-linux-android # for x86 - rustup target add x86_64-linux-android # for x86_64 - rustup target add x86_64-unknown-linux-gnu # for linux-x86-64 - rustup target add aarch64-apple-darwin # for darwin arm64 (if you have an M1 Mac) - rustup target add x86_64-apple-darwin # for darwin x86_64 (if you have an Intel Mac) - rustup target add x86_64-pc-windows-gnu # for win32-x86-64-gnu - rustup target add x86_64-pc-windows-msvc # for win32-x86-64-msvc + rustup target add armv7-linux-androideabi # for arm + rustup target add aarch64-linux-android # for arm64 + rustup target add i686-linux-android # for x86 + rustup target add x86_64-linux-android # for x86_64 + rustup target add x86_64-unknown-linux-gnu # for linux-x86-64 + rustup target add aarch64-apple-darwin # for darwin arm64 (if you have an M1 Mac) + rustup target add x86_64-apple-darwin # for darwin x86_64 (if you have an Intel Mac) + rustup target add x86_64-pc-windows-gnu # for win32-x86-64-gnu + rustup target add x86_64-pc-windows-msvc # for win32-x86-64-msvc - echo "Done setting up mobile targets." - echo + echo "Done setting up mobile targets." + echo fi -if [[ $OSTYPE == "linux-gnu"* ]]; then - if command -v apt-get >/dev/null; then - echo "Detected apt!" - echo "Installing dependencies with apt..." +if [ "$SYSNAME" = "Linux" ]; then + if has apt-get; then + echo "Detected apt!" + echo "Installing dependencies with apt..." - # Tauri dependencies - DEBIAN_TAURI_DEPS="libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev" + # Tauri dependencies + DEBIAN_TAURI_DEPS="libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev patchelf" - # FFmpeg dependencies - DEBIAN_FFMPEG_DEPS="libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev ffmpeg" + # FFmpeg dependencies + DEBIAN_FFMPEG_DEPS="libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev ffmpeg" - # Webkit2gtk requires gstreamer plugins for video playback to work - DEBIAN_VIDEO_DEPS="gstreamer1.0-libav gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly" + # Webkit2gtk requires gstreamer plugins for video playback to work + DEBIAN_VIDEO_DEPS="gstreamer1.0-libav gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly" - # Bindgen dependencies - it's used by a dependency of Spacedrive - DEBIAN_BINDGEN_DEPS="pkg-config clang" + # Bindgen dependencies - it's used by a dependency of Spacedrive + DEBIAN_BINDGEN_DEPS="pkg-config clang" - # Protobuf compiler - DEBIAN_LIBP2P_DEPS="protobuf-compiler" + # Protobuf compiler + DEBIAN_LIBP2P_DEPS="protobuf-compiler" - sudo apt-get -y update - sudo apt-get -y install ${SPACEDRIVE_CUSTOM_APT_FLAGS:-} $DEBIAN_TAURI_DEPS $DEBIAN_FFMPEG_DEPS $DEBIAN_BINDGEN_DEPS $DEBIAN_LIBP2P_DEPS $DEBIAN_VIDEO_DEPS - elif command -v pacman >/dev/null; then - echo "Detected pacman!" - echo "Installing dependencies with pacman..." + sudo apt-get -y update + sudo apt-get -y install ${SPACEDRIVE_CUSTOM_APT_FLAGS:-} $DEBIAN_TAURI_DEPS $DEBIAN_FFMPEG_DEPS $DEBIAN_BINDGEN_DEPS $DEBIAN_LIBP2P_DEPS $DEBIAN_VIDEO_DEPS + elif has pacman; then + echo "Detected pacman!" + echo "Installing dependencies with pacman..." - # Tauri deps https://tauri.studio/guides/getting-started/setup/linux#1-system-dependencies - ARCH_TAURI_DEPS="webkit2gtk base-devel curl wget openssl appmenu-gtk-module gtk3 libappindicator-gtk3 librsvg libvips" + # Tauri deps https://tauri.studio/guides/getting-started/setup/linux#1-system-dependencies + ARCH_TAURI_DEPS="webkit2gtk base-devel curl wget openssl appmenu-gtk-module gtk3 libappindicator-gtk3 librsvg libvips patchelf" - # Webkit2gtk requires gstreamer plugins for video playback to work - ARCH_VIDEO_DEPS="gst-libav gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly" + # Webkit2gtk requires gstreamer plugins for video playback to work + ARCH_VIDEO_DEPS="gst-libav gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly" - # FFmpeg dependencies - ARCH_FFMPEG_DEPS="ffmpeg" + # FFmpeg dependencies + ARCH_FFMPEG_DEPS="ffmpeg" - # Bindgen dependencies - it's used by a dependency of Spacedrive - ARCH_BINDGEN_DEPS="clang" + # Bindgen dependencies - it's used by a dependency of Spacedrive + ARCH_BINDGEN_DEPS="clang" - # Protobuf compiler - https://github.com/archlinux/svntogit-packages/blob/packages/protobuf/trunk/PKGBUILD provides `libprotoc` - ARCH_LIBP2P_DEPS="protobuf" + # Protobuf compiler - https://github.com/archlinux/svntogit-packages/blob/packages/protobuf/trunk/PKGBUILD provides `libprotoc` + ARCH_LIBP2P_DEPS="protobuf" - sudo pacman -Sy - sudo pacman -S --needed $ARCH_TAURI_DEPS $ARCH_FFMPEG_DEPS $ARCH_BINDGEN_DEPS $ARCH_LIBP2P_DEPS $ARCH_VIDEO_DEPS - elif command -v dnf >/dev/null; then - echo "Detected dnf!" - echo "Installing dependencies with dnf..." + sudo pacman -Sy --needed $ARCH_TAURI_DEPS $ARCH_FFMPEG_DEPS $ARCH_BINDGEN_DEPS $ARCH_LIBP2P_DEPS $ARCH_VIDEO_DEPS + elif has dnf; then + echo "Detected dnf!" + echo "Installing dependencies with dnf..." - # `webkit2gtk4.0-devel` also provides `webkit2gtk3-devel`, it's just under a different package in fedora versions >= 37. - # https://koji.fedoraproject.org/koji/packageinfo?tagOrder=-blocked&packageID=26162#taglist - # https://packages.fedoraproject.org/pkgs/webkitgtk/webkit2gtk4.0-devel/fedora-38.html#provides - FEDORA_37_TAURI_WEBKIT="webkit2gtk4.0-devel" - FEDORA_36_TAURI_WEBKIT="webkit2gtk3-devel" + # `webkit2gtk4.0-devel` also provides `webkit2gtk3-devel`, it's just under a different package in fedora versions >= 37. + # https://koji.fedoraproject.org/koji/packageinfo?tagOrder=-blocked&packageID=26162#taglist + # https://packages.fedoraproject.org/pkgs/webkitgtk/webkit2gtk4.0-devel/fedora-38.html#provides + FEDORA_37_TAURI_WEBKIT="webkit2gtk4.0-devel" + FEDORA_36_TAURI_WEBKIT="webkit2gtk3-devel" - # Tauri dependencies - # openssl is manually declared here as i don't think openssl and openssl-devel are actually dependant on eachother - # openssl also has a habit of being missing from some of my fresh Fedora installs - i've had to install it at least twice - FEDORA_TAURI_DEPS="openssl-devel curl wget libappindicator-gtk3 librsvg2-devel" + # Tauri dependencies + # openssl is manually declared here as i don't think openssl and openssl-devel are actually dependant on eachother + # openssl also has a habit of being missing from some of my fresh Fedora installs - i've had to install it at least twice + FEDORA_TAURI_DEPS="openssl-devel curl wget libappindicator-gtk3 librsvg2-devel patchelf" - # required for building the openssl-sys crate - FEDORA_OPENSSL_SYS_DEPS="perl-FindBin perl-File-Compare perl-IPC-Cmd perl-File-Copy" + # required for building the openssl-sys crate + FEDORA_OPENSSL_SYS_DEPS="perl-FindBin perl-File-Compare perl-IPC-Cmd perl-File-Copy" - # FFmpeg dependencies - FEDORA_FFMPEG_DEPS="ffmpeg ffmpeg-devel" + # FFmpeg dependencies + FEDORA_FFMPEG_DEPS="ffmpeg ffmpeg-devel" - # Webkit2gtk requires gstreamer plugins for video playback to work - FEDORA_VIDEO_DEPS="gstreamer1-plugin-libav gstreamer1-plugins-base gstreamer1-plugins-good gstreamer1-plugins-good-extras gstreamer1-plugins-bad-free gstreamer1-plugins-bad-free-extras gstreamer1-plugins-ugly-free" + # Webkit2gtk requires gstreamer plugins for video playback to work + FEDORA_VIDEO_DEPS="gstreamer1-plugin-libav gstreamer1-plugins-base gstreamer1-plugins-good gstreamer1-plugins-good-extras gstreamer1-plugins-bad-free gstreamer1-plugins-bad-free-extras gstreamer1-plugins-ugly-free" - # Bindgen dependencies - it's used by a dependency of Spacedrive - FEDORA_BINDGEN_DEPS="clang" + # Bindgen dependencies - it's used by a dependency of Spacedrive + FEDORA_BINDGEN_DEPS="clang" - # Protobuf compiler - FEDORA_LIBP2P_DEPS="protobuf-compiler" + # Protobuf compiler + FEDORA_LIBP2P_DEPS="protobuf-compiler" - sudo dnf update + if ! sudo dnf install $FEDORA_37_TAURI_WEBKIT && ! sudo dnf install $FEDORA_36_TAURI_WEBKIT; then + err 'We were unable to install the webkit2gtk4.0-devel/webkit2gtk3-devel package.' \ + 'Please open an issue if you feel that this is incorrect.' \ + 'https://github.com/spacedriveapp/spacedrive/issues' + fi - if ! sudo dnf install $FEDORA_37_TAURI_WEBKIT && ! sudo dnf install $FEDORA_36_TAURI_WEBKIT; then - log_err "We were unable to install the webkit2gtk4.0-devel/webkit2gtk3-devel package. Please open an issue if you feel that this is incorrect. https://github.com/spacedriveapp/spacedrive/issues" - exit 1 - fi + if ! sudo dnf install $FEDORA_FFMPEG_DEPS; then + err 'We were unable to install the FFmpeg and FFmpeg-devel packages.' \ + 'This is likely because the RPM Fusion free repository is not enabled.' \ + 'https://docs.fedoraproject.org/en-US/quick-docs/setup_rpmfusion' + fi - if ! sudo dnf install $FEDORA_FFMPEG_DEPS; then - log_err "We were unable to install the FFmpeg and FFmpeg-devel packages. This is likely because the RPM Fusion free repository is not enabled. https://docs.fedoraproject.org/en-US/quick-docs/setup_rpmfusion/" - exit 1 - fi + sudo dnf install $FEDORA_TAURI_DEPS $FEDORA_BINDGEN_DEPS $FEDORA_LIBP2P_DEPS $FEDORA_VIDEO_DEPS + sudo dnf group install "C Development Tools and Libraries" + else + err "Your Linux distro '$(lsb_release -s -d)' is not supported by this script." \ + 'We would welcome a PR or some help adding your OS to this script:' \ + 'https://github.com/spacedriveapp/spacedrive/issues' + fi +elif [ "$SYSNAME" = "Darwin" ]; then + # Location for installing script dependencies + _deps_dir="${_script_path}/deps" + mkdir -p "$_deps_dir" + PATH="$PATH:${_deps_dir}" + export PATH - sudo dnf install $FEDORA_TAURI_DEPS $FEDORA_BINDGEN_DEPS $FEDORA_LIBP2P_DEPS $FEDORA_VIDEO_DEPS - sudo dnf group install "C Development Tools and Libraries" - else - log_err "Your Linux distro '$(lsb_release -s -d)' is not supported by this script. We would welcome a PR or some help adding your OS to this script. https://github.com/spacedriveapp/spacedrive/issues" - exit 1 - fi -elif [[ $OSTYPE == "darwin"* ]]; then - if ! command -v brew >/dev/null; then - log_err "Homebrew was not found. Please install it using the instructions at https://brew.sh and try again." - exit 1 - fi + _arch="$(uname -m)" - echo "Installing Homebrew dependencies..." + if ! has jq; then + echo "Download jq build..." - BREW_FFMPEG_DEPS="ffmpeg" - BREW_LIBP2P_DEPS="protobuf" + # Determine the machine's architecture + case "$_arch" in + x86_64) + _jq_url='https://packages.macports.org/jq/jq-1.6_4.darwin_19.x86_64.tbz2' + _oniguruma6_url='https://packages.macports.org/oniguruma6/oniguruma6-6.9.8_0.darwin_19.x86_64.tbz2' + ;; + arm64) + _jq_url='https://packages.macports.org/jq/jq-1.6_4.darwin_20.arm64.tbz2' + _oniguruma6_url='https://packages.macports.org/oniguruma6/oniguruma6-6.9.8_0.darwin_20.arm64.tbz2' + ;; + *) + err "Unsupported architecture: $_arch" + ;; + esac - brew install -q $BREW_FFMPEG_DEPS $BREW_LIBP2P_DEPS + # Download the latest jq binary and deps from macports + curl -LSs "$_jq_url" | tar -xjOf - ./opt/local/bin/jq >"${_deps_dir}/jq" + curl -LSs "$_oniguruma6_url" | tar -xjOf - ./opt/local/lib/libonig.5.dylib >"${_deps_dir}/libonig.5.dylib" + + # Make the binaries executable + chmod +x "$_deps_dir"/* + + # Make jq look for deps in the same directory + install_name_tool -change '/opt/local/lib/libonig.5.dylib' '@executable_path/libonig.5.dylib' "${_deps_dir}/jq" + fi + + # Create frameworks directory to put Spacedrive dependencies + _frameworks_dir="${_script_path}/../../target/Frameworks" + rm -rf "$_frameworks_dir" + mkdir -p "${_frameworks_dir}/"{bin,lib,include} + _frameworks_dir="$(CDPATH='' cd -- "$_frameworks_dir" && pwd -P)" + + exec 3>&1 # Copy stdout to fd 3. + echo "Download ffmpeg build..." + _page=1 + while [ $_page -gt 0 ]; do + # TODO: Filter only actions triggered by the main branch + _success=$(gh_curl "${_gh_url}/${_sd_gh_path}/actions/workflows/ffmpeg.yml/runs?page=${_page}&per_page=100&status=success" \ + | jq -r '. as $raw | .workflow_runs | if length == 0 then error("Error: \($raw)") else .[] | .artifacts_url end' \ + | while IFS= read -r _artifacts_url; do + if _artifact_path="$( + gh_curl "$_artifacts_url" \ + | jq --arg version "$FFMPEG_VERSION" --arg arch "$( + if [ "${TARGET:-}" = 'aarch64-apple-darwin' ]; then + echo 'arm64' + else + echo "$_arch" + fi + )" -r \ + '. as $raw | .artifacts | if length == 0 then error("Error: \($raw)") else .[] | select(.name == "ffmpeg-\($version)-\($arch)") | "suites/\(.workflow_run.id)/artifacts/\(.id)" end' + )"; then + # nightly.link is a workaround for the lack of a public GitHub API to download artifacts from a workflow run + # https://github.com/actions/upload-artifact/issues/51 + # TODO: Use Github's private API when running on CI + if curl -LSs "https://nightly.link/${_sd_gh_path}/${_artifact_path}" | tar -xOf- | XZ_OPT='-T0' tar -xJf- -C "$_frameworks_dir"; then + printf 'yes' + exit + fi + + echo "Failed to ffmpeg artifiact release, trying again in 1sec..." >&3 + sleep 1 + fi + done) + + if [ "${_success:-}" = 'yes' ]; then + break + fi + + _page=$((_page + 1)) + + echo "ffmpeg artifact not found, trying again in 1sec..." + sleep 1 + done + + # Symlink the FFMpeg.framework libs to the lib directory + for _lib in "${_frameworks_dir}/FFMpeg.framework/Libraries/"*; do + _lib="${_lib#"${_frameworks_dir}/FFMpeg.framework/Libraries/"}" + ln -s "../FFMpeg.framework/Libraries/${_lib}" "${_frameworks_dir}/lib/${_lib}" + done + + # Symlink the FFMpeg.framework headers to the include directory + for _header in "${_frameworks_dir}/FFMpeg.framework/Headers/"*; do + _header="${_header#"${_frameworks_dir}/FFMpeg.framework/Headers/"}" + ln -s "../FFMpeg.framework/Headers/${_header}" "${_frameworks_dir}/include/${_header}" + done + + # Workaround while https://github.com/tauri-apps/tauri/pull/3934 is not merged + echo "Download patched tauri cli.js build..." + case "$_arch" in + x86_64) + _artifact_url="https://nightly.link/${_sd_gh_path}/actions/artifacts/687573480.zip" + ;; + arm64) + _artifact_url="https://nightly.link/${_sd_gh_path}/actions/artifacts/687573479.zip" + ;; + *) + err "Unsupported architecture: $_arch" + ;; + esac + curl -LSs "$_artifact_url" | tar -xf- -C "${_frameworks_dir}/bin" + + echo "Download protobuf build" + _page=1 + while [ $_page -gt 0 ]; do + _success=$(gh_curl "${_gh_url}/protocolbuffers/protobuf/releases?page=${_page}&per_page=100" \ + | jq --arg arch "$( + if [ "$_arch" = 'arm64' ]; then + echo 'aarch_64' + else + echo 'x86_64' + fi + )" -r \ + '. as $raw | if length == 0 then error("Error: \($raw)") else .[] | select(.prerelease | not) | .assets[] | select(.name | endswith("osx-\($arch).zip")) | .browser_download_url end' \ + | while IFS= read -r _asset_url; do + if curl -LSs "${_asset_url}" | tar -xf - -C "$_frameworks_dir"; then + printf 'yes' + exit + fi + + echo "Failed to download protobuf release, trying again in 1sec..." >&3 + sleep 1 + done) + + if [ "${_success:-}" = 'yes' ]; then + break + fi + + _page=$((_page + 1)) + + echo "protobuf release not found, trying again in 1sec..." + sleep 1 + done + + # Ensure all binaries are executable + chmod +x "$_frameworks_dir"/bin/* + + cat <"${_cargo_config}/config" +[env] +PROTOC = "${_frameworks_dir}/bin/protoc" +FFMPEG_DIR = "${_frameworks_dir}" + +$(cat "${_cargo_config}/config.toml") +EOF else - log_err "Your OS ($OSTYPE) is not supported by this script. We would welcome a PR or some help adding your OS to this script. https://github.com/spacedriveapp/spacedrive/issues" - exit 1 + err "Your OS ($SYSNAME) is not supported by this script." \ + 'We would welcome a PR or some help adding your OS to this script.' \ + 'https://github.com/spacedriveapp/spacedrive/issues' fi echo "Your machine has been successfully set up for Spacedrive development." diff --git a/.github/workflows/cache-factory.yaml b/.github/workflows/cache-factory.yaml index 54d61e3dc..40c19ac47 100644 --- a/.github/workflows/cache-factory.yaml +++ b/.github/workflows/cache-factory.yaml @@ -5,26 +5,58 @@ name: Cache Factory on: push: + paths: + - 'Cargo.lock' + - '.github/scripts/setup-system.sh' + - '.github/scripts/setup-system.ps1' + - '.github/workflows/cache-factory.yaml' + - '.github/actions/**/*.yml' + - '.github/actions/**/*.yaml' + - '**/build.rs' + - 'core/prisma/**' branches: - main +# Cancel previous runs of the same workflow on the same branch. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: make_cache: - name: 'Make Cache' - runs-on: ${{ matrix.platform }} strategy: fail-fast: true matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] + settings: + - host: macos-latest + target: x86_64-apple-darwin + - host: macos-latest + target: aarch64-apple-darwin + - host: windows-latest + target: x86_64-pc-windows-msvc + # - host: windows-latest + # target: aarch64-pc-windows-msvc + - host: ubuntu-20.04 + target: x86_64-unknown-linux-gnu + # - host: ubuntu-20.04 + # target: x86_64-unknown-linux-musl + # - host: ubuntu-20.04 + # target: aarch64-unknown-linux-gnu + # - host: ubuntu-20.04 + # target: aarch64-unknown-linux-musl + # - host: ubuntu-20.04 + # target: armv7-unknown-linux-gnueabihf + name: 'Make Cache' + runs-on: ${{ matrix.settings.host }} steps: - - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v3 - - uses: ./.github/actions/setup + - name: Setup System and Rust + uses: ./.github/actions/setup-system with: + token: ${{ secrets.GITHUB_TOKEN }} + targets: ${{ matrix.settings.target }} save-cache: 'true' - name: Compile (debug) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29c2bbdfd..1412672c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,11 +4,33 @@ on: push: branches: - main - pull_request: paths-ignore: + - '**.md' - 'docs/**' + - 'LICENSE' - '.cspell/**' - '.vscode/**' + - '.github/CODEOWNERS' + - '.github/FUNDING.yml' + - '.github/ISSUE_TEMPLATE/**' + - '.github/scripts/osxcross/**' + - '.github/scripts/ffmpeg-macos/**' + - '.gitattributes' + - 'cspell.config.yaml' + pull_request: + paths-ignore: + - '**.md' + - 'docs/**' + - 'LICENSE' + - '.cspell/**' + - '.vscode/**' + - '.github/CODEOWNERS' + - '.github/FUNDING.yml' + - '.github/ISSUE_TEMPLATE/**' + - '.github/scripts/osxcross/**' + - '.github/scripts/ffmpeg-macos/**' + - '.gitattributes' + - 'cspell.config.yaml' workflow_dispatch: env: @@ -22,70 +44,43 @@ concurrency: jobs: typescript: name: TypeScript - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Install pnpm - uses: pnpm/action-setup@v2.2.2 + - name: Setup Node.js, pnpm and dependencies + uses: ./.github/actions/setup-pnpm with: - version: 8.x.x - - - name: Install Node.js - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: 'pnpm' - - - name: Install pnpm dependencies - run: pnpm i --frozen-lockfile + token: ${{ secrets.GITHUB_TOKEN }} - name: Perform typechecks run: pnpm typecheck eslint: name: ESLint - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Install pnpm - uses: pnpm/action-setup@v2.2.2 + - name: Setup Node.js, pnpm and dependencies + uses: ./.github/actions/setup-pnpm with: - version: 8.x.x - - - name: Install Node.js - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: 'pnpm' - - - name: Install pnpm dependencies - run: pnpm i --frozen-lockfile + token: ${{ secrets.GITHUB_TOKEN }} - name: Perform linting run: pnpm lint rustfmt: name: Rust Formatting - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: rustfmt - - - name: Cache Rust deps - uses: ./.github/actions/cache-rust-deps - - - name: Generate Prisma client - uses: ./.github/actions/generate-prisma-client + - name: Setup Rust and Prisma + uses: ./.github/actions/setup-rust - name: Run rustfmt run: cargo fmt --all -- --check @@ -95,34 +90,34 @@ jobs: runs-on: ${{ matrix.platform }} strategy: matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] + platform: [ubuntu-20.04, macos-latest, windows-latest] steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - - name: Setup - uses: ./.github/actions/setup + - name: Setup System and Rust + uses: ./.github/actions/setup-system with: token: ${{ secrets.GITHUB_TOKEN }} - name: Run Clippy uses: actions-rs/clippy-check@v1 with: - token: ${{ secrets.GITHUB_TOKEN }} args: --workspace --all-features + token: ${{ secrets.GITHUB_TOKEN }} # test: # name: Test (${{ matrix.platform }}) # runs-on: ${{ matrix.platform }} # strategy: # matrix: - # platform: [ubuntu-latest, macos-latest, windows-latest] + # platform: [ubuntu-20.04, macos-latest, windows-latest] # steps: # - name: Checkout repository # uses: actions/checkout@v3 - + # # - name: Setup # uses: ./.github/actions/setup - + # # - name: Test # run: cargo test --workspace --all-features diff --git a/.github/workflows/ffmpeg.yml b/.github/workflows/ffmpeg.yml new file mode 100644 index 000000000..b4beea06f --- /dev/null +++ b/.github/workflows/ffmpeg.yml @@ -0,0 +1,66 @@ +name: Build ffmpeg + +on: + push: + paths: + - '.github/workflows/ffmpeg.yml' + - '.github/scripts/ffmpeg-macos/**' + branches: + - main + pull_request: + paths: + - '.github/workflows/ffmpeg.yml' + - '.github/scripts/ffmpeg-macos/**' + workflow_dispatch: + +# Cancel previous runs of the same workflow on the same branch. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + FFMPEG_VERSION: '6.0' + +jobs: + build-ffmpeg-macos: + name: Build ffmpeg for macos + runs-on: ubuntu-20.04 + defaults: + run: + shell: bash + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + install: true + platforms: linux/amd64 + driver-opts: | + image=moby/buildkit:master + network=host + + - name: Build ffmpeg + run: | + set -euxo pipefail + cd .github/scripts/ffmpeg-macos + docker build --build-arg FFMPEG_VERSION=$FFMPEG_VERSION -o . . + export XZ_OPT='-T0 -9' + tar -cJf "ffmpeg-${FFMPEG_VERSION}-x86_64.tar.xz" -C ffmpeg/x86_64 . + tar -cJf "ffmpeg-${FFMPEG_VERSION}-arm64.tar.xz" -C ffmpeg/aarch64 . + rm -rf ffmpeg + + - name: Publish ffmpeg x86_64 + uses: actions/upload-artifact@v3 + with: + name: ffmpeg-${{ env.FFMPEG_VERSION }}-x86_64 + path: .github/scripts/ffmpeg-macos/ffmpeg-${{ env.FFMPEG_VERSION }}-x86_64.tar.xz + if-no-files-found: error + + - name: Publish ffmpeg arm64 + uses: actions/upload-artifact@v3 + with: + name: ffmpeg-${{ env.FFMPEG_VERSION }}-arm64 + path: .github/scripts/ffmpeg-macos/ffmpeg-${{ env.FFMPEG_VERSION }}-arm64.tar.xz + if-no-files-found: error diff --git a/.github/workflows/mobile-ci.yml b/.github/workflows/mobile-ci.yml index 7f83f2851..5cfe299ee 100644 --- a/.github/workflows/mobile-ci.yml +++ b/.github/workflows/mobile-ci.yml @@ -32,64 +32,43 @@ jobs: # steps: # - name: Checkout repository # uses: actions/checkout@v3 - + # # - name: Setup Java JDK # uses: actions/setup-java@v3.10.0 # with: # java-version: '18' # distribution: 'temurin' - - # - name: Install pnpm - # uses: pnpm/action-setup@v2.2.2 + # + # - name: Setup Node.js, pnpm and dependencies + # uses: ./.github/actions/setup-pnpm # with: - # version: 8.x.x - - # - name: Install Node.js - # uses: actions/setup-node@v3 + # token: ${{ secrets.GITHUB_TOKEN }} + # + # - name: Setup System and Rust + # uses: ./.github/actions/setup-system # with: - # node-version: 18 - # cache: 'pnpm' - - # - name: Install Rust stable - # uses: actions-rs/toolchain@v1 - # with: - # toolchain: stable - # profile: minimal - - # - name: Cache Rust deps - # uses: Swatinem/rust-cache@v2 - # with: - # save-if: ${{ inputs.save-cache }} == "true" - - # - name: Run 'setup-system.sh' script - # shell: bash - # run: ./.github/scripts/setup-system.sh mobile - - # - name: Generate Prisma client - # uses: ./.github/actions/generate-prisma-client - - # - name: Install pnpm dependencies - # run: pnpm i --frozen-lockfile - + # token: ${{ secrets.GITHUB_TOKEN }} + # setup-arg: mobile + # # - name: Setup Android SDK Tools # uses: android-actions/setup-android@v2.0.2 - + # # - name: Cache NDK # uses: actions/cache@v3 # with: # path: ${{ env.ANDROID_HOME }}/ndk/23.1.7779620 # key: ndk-23.1.7779620 - + # # - name: Install NDK # run: echo "y" | sudo ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --install "ndk;23.1.7779620" - + # # - name: Cache Gradle # uses: gradle/gradle-build-action@v2 - + # # - name: Build Android # working-directory: ./apps/mobile/android # run: chmod +x ./gradlew && ./gradlew assembleRelease -PreactNativeArchitectures=x86_64 --no-daemon - + # # - name: Cache AVD # uses: actions/cache@v3 # id: avd-cache @@ -98,9 +77,9 @@ jobs: # ~/.android/avd/* # ~/.android/adb* # key: avd-30 - + # # - name: Generate AVD Snapshot - # if: steps.avd-cache.outputs.cache-hit != 'true' + # if: ${{ steps.avd-cache.outputs.cache-hit != 'true' }} # uses: ReactiveCircus/android-emulator-runner@v2.28.0 # with: # arch: x86_64 @@ -113,13 +92,13 @@ jobs: # emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none # disable-animations: false # script: echo "Generated AVD snapshot." - + # # - name: Install Maestro # run: | # # workaround for https://github.com/mobile-dev-inc/maestro/issues/877 # export MAESTRO_VERSION=1.21.3; curl -Ls "https://get.maestro.mobile.dev" | bash # echo "$HOME/.maestro/bin" >> $GITHUB_PATH - + # # - name: Run Tests # uses: ReactiveCircus/android-emulator-runner@v2.28.0 # with: @@ -145,41 +124,20 @@ jobs: uses: actions/checkout@v3 - name: Install Xcode - uses: maxim-lobanov/setup-xcode@v1.5.1 + uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: latest-stable - - name: Install pnpm - uses: pnpm/action-setup@v2.2.2 + - name: Setup Node.js, pnpm and dependencies + uses: ./.github/actions/setup-pnpm with: - version: 8.x.x + token: ${{ secrets.GITHUB_TOKEN }} - - name: Install Node.js - uses: actions/setup-node@v3 + - name: Setup System and Rust + uses: ./.github/actions/setup-system with: - node-version: 18 - cache: 'pnpm' - - - name: Install Rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - - - name: Cache Rust deps - uses: Swatinem/rust-cache@v2 - with: - save-if: ${{ inputs.save-cache }} == "true" - - - name: Run 'setup-system.sh' script - shell: bash - run: ./.github/scripts/setup-system.sh mobile - - - name: Generate Prisma client - uses: ./.github/actions/generate-prisma-client - - - name: Install pnpm dependencies - run: pnpm i --frozen-lockfile + token: ${{ secrets.GITHUB_TOKEN }} + setup-arg: mobile - name: Cache Pods uses: actions/cache@v3 @@ -203,7 +161,7 @@ jobs: run: | curl -Ls "https://get.maestro.mobile.dev" | bash brew tap facebook/fb - brew install facebook/fb/idb-companion + brew install facebook/fb/idb-companion echo "$HOME/.maestro/bin" >> $GITHUB_PATH - name: Run Simulator diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 10c739d0a..d187f8838 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,39 +1,64 @@ name: Release on: - push: - branches: - - main workflow_dispatch: # NOTE: For Linux builds, we can only build with Ubuntu. It should be the oldest base system we intend to support. See PR-759 & https://tauri.app/v1/guides/building/linux for reference. jobs: desktop-main: - name: Desktop - Main (${{ matrix.platform }}) - runs-on: ${{ matrix.platform }} strategy: + fail-fast: true matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] + settings: + - host: macos-latest + target: x86_64-apple-darwin + bundles: dmg + - host: macos-latest + target: aarch64-apple-darwin + bundles: dmg + - host: windows-latest + target: x86_64-pc-windows-msvc + bundles: msi + # - host: windows-latest + # target: aarch64-pc-windows-msvc + - host: ubuntu-20.04 + target: x86_64-unknown-linux-gnu + bundles: deb,appimage + # - host: ubuntu-20.04 + # target: x86_64-unknown-linux-musl + # - host: ubuntu-20.04 + # target: aarch64-unknown-linux-gnu + # - host: ubuntu-20.04 + # target: aarch64-unknown-linux-musl + # - host: ubuntu-20.04 + # target: armv7-unknown-linux-gnueabihf + name: Desktop - Main ${{ matrix.settings.target }} + runs-on: ${{ matrix.settings.host }} steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - - name: Setup - uses: ./.github/actions/setup + - name: Setup Node.js, pnpm and dependencies + uses: ./.github/actions/setup-pnpm + with: + token: ${{ secrets.GITHUB_TOKEN }} - - name: Install pnpm dependencies - run: pnpm i --frozen-lockfile + - name: Setup System and Rust + uses: ./.github/actions/setup-system + with: + token: ${{ secrets.GITHUB_TOKEN }} + targets: ${{ matrix.settings.target }} - name: Build - uses: tauri-apps/tauri-action@dev + run: | + pnpm desktop build --ci -v --target ${{ matrix.settings.target }} --bundles ${{ matrix.settings.bundles }},updater env: TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} - with: - projectPath: apps/desktop - name: Publish Artifacts uses: ./.github/actions/publish-artifacts with: + target: ${{ matrix.settings.target }} profile: release diff --git a/.github/workflows/tauri-patched-cli-js.yml b/.github/workflows/tauri-patched-cli-js.yml new file mode 100644 index 000000000..c82504686 --- /dev/null +++ b/.github/workflows/tauri-patched-cli-js.yml @@ -0,0 +1,69 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: publish cli.js +env: + DEBUG: napi:* + APP_NAME: cli + MACOSX_DEPLOYMENT_TARGET: '10.13' +on: + workflow_dispatch: + +defaults: + run: + working-directory: tooling/cli/node/ + +jobs: + build: + strategy: + fail-fast: true + matrix: + settings: + - host: macos-latest + target: x86_64-apple-darwin + architecture: x64 + build: | + yarn build:release --features rustls + strip -x *.node + + - host: macos-latest + target: aarch64-apple-darwin + build: | + yarn build:release --features rustls --target=aarch64-apple-darwin + strip -x *.node + + name: stable - ${{ matrix.settings.target }} - node@16 + runs-on: ${{ matrix.settings.host }} + steps: + - uses: actions/checkout@v3 + with: + repository: 'dceddia/tauri' + ref: 'dd32f97335a6105b134e70dad02d269e49a75b56' + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + check-latest: true + architecture: ${{ matrix.settings.architecture }} + + - name: Install + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.settings.target }} + toolchain: stable + + - name: Install dependencies + run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 + + - name: Build + run: ${{ matrix.settings.build }} + shell: bash + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: bindings-${{ matrix.settings.target }} + path: tooling/cli/node/${{ env.APP_NAME }}.*.node + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 1a939ad63..f50f04fbb 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ packages/*/data apps/*/data apps/*/stats.html apps/releases/.vscode +apps/desktop/src-tauri/tauri.conf.patch.json docs/public/*.st docs/public/*.toml dev.db @@ -73,3 +74,6 @@ dev.db-journal .swiftpm /core/migration_test sd_init.json + +.cargo/config +.github/scripts/deps diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5119e4e06..c0671e7e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,6 @@ This project uses [Cargo](https://doc.rust-lang.org/cargo/getting-started/instal - This will install FFmpeg and any other required dependencies for Spacedrive to build. - For Windows users run using PowerShell: `.\.github\scripts\setup-system.ps1` - This will install pnpm, LLVM, FFmpeg and any other required dependencies for Spacedrive to build. - - Ensure you run it like documented above as it expects it is executed from the root of the repository. - `pnpm i` - `pnpm prep` - Runs all necessary codegen & builds required dependencies. @@ -68,6 +67,8 @@ If you are having issues ensure you are using the following versions of Rust and - Rust version: **1.68.2** - Node version: **18** +After clearing out your build artifacts with either `pnpm clean`, `git clean` or `cargo clean`, it is required to re-run the `setup-system` script. + Be sure to read the [guidelines](https://spacedrive.com/docs/developers/prerequisites/guidelines) to make sure your code is a similar style to ours. ##### Mobile app diff --git a/Cargo.lock b/Cargo.lock index a6886754c..a086979a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7750,8 +7750,7 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "swift-rs" version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e51d6f2b5fff4808614f429f8a7655ac8bcfe218185413f3a60c508482c2d6" +source = "git+https://github.com/Brendonovich/swift-rs?rev=973c22215734d1d5b97c496601d658371e537ece#973c22215734d1d5b97c496601d658371e537ece" dependencies = [ "base64 0.21.0", "serde", diff --git a/Cargo.toml b/Cargo.toml index 9db34a6d4..e51ce4842 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,3 +47,5 @@ mdns-sd = { git = "https://github.com/oscartbeaumont/mdns-sd", rev = "45515a98e9 rspc = { git = "https://github.com/oscartbeaumont/rspc", rev = "799eec5df7533edf331f41d3f1be03de07e038d7" } httpz = { git = "https://github.com/oscartbeaumont/httpz", rev = "a5185f2ed2fdefeb2f582dce38a692a1bf76d1d6" } + +swift-rs = { git = "https://github.com/Brendonovich/swift-rs", rev = "973c22215734d1d5b97c496601d658371e537ece" } diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 46b1d1aad..80070a9a4 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -6,10 +6,9 @@ "private": true, "scripts": { "vite": "vite", - "dev": "tauri dev", + "dev": "node ./src-tauri/scripts/tauri.js dev", "dev-vite": "vite --clearScreen=false --mode development", - "tauri": "tauri", - "build": "node ./src-tauri/build.js", + "build": "node ./src-tauri/scripts/tauri.js build", "dmg": "open ../../target/release/bundle/dmg/", "typecheck": "tsc -b", "lint": "eslint src --cache" @@ -21,21 +20,23 @@ "@sd/interface": "workspace:*", "@sd/ui": "workspace:*", "@tanstack/react-query": "^4.24.4", - "@tauri-apps/api": "1.2.0", + "@tauri-apps/api": "1.3.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "6.9.0", "vite-plugin-html": "^3.2.0" }, "devDependencies": { + "@iarna/toml": "^2.2.5", "@sd/config": "workspace:*", - "@tauri-apps/cli": "1.2.3", + "@tauri-apps/cli": "1.3.1", "@types/babel-core": "^6.25.7", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", "@vitejs/plugin-react": "^2.1.0", "react-devtools": "^4.27.2", "sass": "^1.55.0", + "semver": "^7.5.0", "typescript": "^4.8.4", "vite": "^4.0.4", "vite-plugin-svgr": "^2.2.1", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 2ca243e8d..286191b32 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -9,7 +9,7 @@ default-run = "spacedrive" edition = "2021" [dependencies] -tauri = { version = "1.3.0", features = ["api-all", "linux-protocol-headers", "macos-private-api", "updater"] } +tauri = { version = "1.3.0", features = ["api-all", "linux-protocol-headers", "macos-private-api"] } rspc = { workspace = true, features = ["tauri"] } httpz = { workspace = true, features = ["axum", "tauri"] } # TODO: The `axum` feature should be only enabled on Linux but this currently can't be done: https://github.com/rust-lang/cargo/issues/1197 sd-core = { path = "../../../core", features = ["ffmpeg", "location-watcher"] } @@ -36,4 +36,5 @@ tauri-build = { version = "1.3.0", features = [] } [features] default = ["custom-protocol"] +updater = ["tauri/updater"] custom-protocol = ["tauri/custom-protocol"] diff --git a/apps/desktop/src-tauri/build.js b/apps/desktop/src-tauri/build.js deleted file mode 100644 index eb769e6ea..000000000 --- a/apps/desktop/src-tauri/build.js +++ /dev/null @@ -1,13 +0,0 @@ -const path = require('path'); -const { spawn } = require('child_process'); - -process.env.BACKGROUND_FILE = path.join(__dirname, './dmg-background.png'); -process.env.BACKGROUND_FILE_NAME = path.basename(process.env.BACKGROUND_FILE); -process.env.BACKGROUND_CLAUSE = `set background picture of opts to file ".background:${process.env.BACKGROUND_FILE_NAME}"`; - -const child = spawn('pnpm', ['tauri', 'build']); -child.stdout.on('data', (data) => console.log(data.toString())); -child.stderr.on('data', (data) => console.error(data.toString())); -child.on('exit', (code) => { - if (code !== 0) console.log(`Child exited with code ${code}`); -}); diff --git a/apps/desktop/src-tauri/scripts/const.js b/apps/desktop/src-tauri/scripts/const.js new file mode 100644 index 000000000..6427b3f9e --- /dev/null +++ b/apps/desktop/src-tauri/scripts/const.js @@ -0,0 +1,9 @@ +const path = require('node:path'); + +const platform = /^(msys|cygwin)$/.test(process.env.OSTYPE ?? '') ? 'win32' : process.platform; + +module.exports = { + platform, + workspace: path.resolve(__dirname, '../../../../'), + setupScript: `.github/scripts/${platform === 'win32' ? 'setup-system.ps1' : 'setup-system.sh'}` +}; diff --git a/apps/desktop/src-tauri/scripts/env.js b/apps/desktop/src-tauri/scripts/env.js new file mode 100644 index 000000000..a85e48c33 --- /dev/null +++ b/apps/desktop/src-tauri/scripts/env.js @@ -0,0 +1,72 @@ +const fs = require('node:fs'); +const path = require('node:path'); + +const toml = require('@iarna/toml'); + +const { platform, workspace, setupScript } = require('./const.js'); + +const cargoConfig = path.resolve(workspace, '.cargo/config'); +const cargoConfigTempl = path.resolve(workspace, '.cargo/config.toml'); + +module.exports.setupFFMpegDlls = function setupDlls(FFMPEG_DIR, dev = false) { + const ffmpegBinDir = path.join(FFMPEG_DIR, 'bin'); + const ffmpegDlls = fs.readdirSync(ffmpegBinDir).filter((file) => file.endsWith('.dll')); + + let targetDir = path.join(workspace, 'apps/desktop/src-tauri'); + if (dev) { + targetDir = path.join(workspace, 'target/debug'); + // Ensure the target/debug directory exists + fs.mkdirSync(targetDir, { recursive: true }); + } + + // Copy all DLLs from the $FFMPEG_DIR/bin to targetDir + for (const dll of ffmpegDlls) + fs.copyFileSync(path.join(ffmpegBinDir, dll), path.join(targetDir, dll)); + + return ffmpegDlls; +}; + +module.exports.setupPlatformEnv = function setupEnv() { + const env = {}; + + if (platform === 'darwin' || platform === 'win32') { + env.PROTOC = path.join( + workspace, + 'target/Frameworks/bin', + platform === 'win32' ? 'protoc.exe' : 'protoc' + ); + env.FFMPEG_DIR = path.join(workspace, 'target/Frameworks'); + + // Check if env.PROTOC is not empty and that the value is a valid path pointing to an existing file + if (!(env.PROTOC && fs.existsSync(env.PROTOC) && fs.statSync(env.PROTOC).isFile())) { + console.error(`The path to protoc is invalid: ${env.PROTOC}`); + console.error(`Did you ran the setup script: ${setupScript}?`); + process.exit(1); + } + + // Check if env.FFMPEG_DIR is not empty and that the value is a valid path pointing to an existing directory + if ( + !( + env.FFMPEG_DIR && + fs.existsSync(env.FFMPEG_DIR) && + fs.statSync(env.FFMPEG_DIR).isDirectory() + ) + ) { + console.error(`The path to ffmpeg is invalid: ${env.FFMPEG_DIR}`); + console.error(`Did you ran the setup script: ${setupScript}?`); + process.exit(1); + } + + // Update cargo config with the new env variables + const cargoConf = toml.parse(fs.readFileSync(cargoConfigTempl, { encoding: 'binary' })); + cargoConf.env = { + ...(cargoConf.env ?? {}), + ...(env ?? {}), + PROTOC: env.PROTOC, + FFMPEG_DIR: env.FFMPEG_DIR + }; + fs.writeFileSync(cargoConfig, toml.stringify(cargoConf)); + } + + return env; +}; diff --git a/apps/desktop/src-tauri/scripts/spawn.js b/apps/desktop/src-tauri/scripts/spawn.js new file mode 100644 index 000000000..b426b8625 --- /dev/null +++ b/apps/desktop/src-tauri/scripts/spawn.js @@ -0,0 +1,30 @@ +const { spawn } = require('node:child_process'); + +module.exports.spawn = (command, args) => { + if (typeof command !== 'string' || command.length === 0) + throw new Error('Command must be a string and not empty'); + + if (args == null) args = []; + else if (!Array.isArray(args) || args.some((arg) => typeof arg !== 'string')) + throw new Error('Args must be an array of strings'); + + return new Promise((resolve, reject) => { + const child = spawn(command, args, { shell: true, stdio: 'inherit' }); + process.on('SIGTERM', () => child.kill('SIGTERM')); + process.on('SIGINT', () => child.kill('SIGINT')); + process.on('SIGBREAK', () => child.kill('SIGBREAK')); + process.on('SIGHUP', () => child.kill('SIGHUP')); + child.on('error', (error) => { + console.error(error); + reject(1); + }); + child.on('exit', (code, signal) => { + if (code === null) code = signal === 'SIGINT' ? 0 : 1; + if (code === 0) { + resolve(); + } else { + reject(code); + } + }); + }); +}; diff --git a/apps/desktop/src-tauri/scripts/tauri.js b/apps/desktop/src-tauri/scripts/tauri.js new file mode 100644 index 000000000..095da91d0 --- /dev/null +++ b/apps/desktop/src-tauri/scripts/tauri.js @@ -0,0 +1,151 @@ +const fs = require('node:fs'); +const path = require('node:path'); +const semver = require('semver'); + +const { spawn } = require('./spawn.js'); +const { platform, workspace, setupScript } = require('./const.js'); +const { setupFFMpegDlls, setupPlatformEnv } = require('./env.js'); + +const toRemove = []; +const [_, __, ...args] = process.argv; + +if (args.length === 0) args.push('build'); + +const tauriConf = JSON.parse( + fs.readFileSync(path.resolve(__dirname, '..', 'tauri.conf.json'), 'utf-8') +); + +switch (args[0]) { + case 'dev': { + const env = setupPlatformEnv(); + if (platform === 'win32') setupFFMpegDlls(env.FFMPEG_DIR, true); + break; + } + case 'build': { + if (args.findIndex((e) => e === '-c' || e === '--config') !== -1) { + throw new Error('Custom tauri build config is not supported.'); + } + + const targets = args + .filter((_, index, args) => { + if (index === 0) return false; + const previous = args[index - 1]; + return previous === '-t' || previous === '--target'; + }) + .flatMap((target) => target.split(',')); + + const env = setupPlatformEnv(); + + const tauriPatch = { + build: { features: [] }, + tauri: { bundle: { macOS: {} }, updater: {} } + }; + + if (process.env.TAURI_PRIVATE_KEY) { + console.log('Tauri Private Key detected, enabling updater'); + tauriPatch.build.features.push('updater'); + tauriPatch.tauri.updater.active = true; + } + + switch (platform) { + case 'darwin': { + // Workaround while https://github.com/tauri-apps/tauri/pull/3934 is not merged + const cliNode = + process.arch === 'arm64' ? 'cli.darwin-arm64.node' : 'cli.darwin-x64.node'; + const tauriCliPatch = path.join(workspace, 'target/Frameworks/bin/', cliNode); + if (!fs.existsSync(tauriCliPatch)) { + throw new Error( + `Tauri cli patch not found at ${path.relative( + workspace, + tauriCliPatch + )}. Did you run the setup script: ${setupScript}?` + ); + } + const tauriBin = path.join( + workspace, + 'node_modules/@tauri-apps', + cliNode.replace(/\.[^.]+$/, '').replace(/\./g, '-'), + cliNode + ); + if (!fs.existsSync(tauriBin)) { + throw new Error('tauri bin not found at ${tauriBin}. Did you run `pnpm i`?'); + } + console.log( + `WORKAROUND tauri-apps/tauri#3933: Replace ${path.relative( + workspace, + tauriBin + )} -> ${path.relative(workspace, tauriCliPatch)}` + ); + fs.copyFileSync(tauriCliPatch, tauriBin); + + // ARM64 support was added in macOS 11, but we need at least 11.2 due to our ffmpeg build + let macOSMinimumVersion = tauriConf?.tauri?.bundle?.macOS?.minimumSystemVersion; + let macOSArm64MinimumVersion = '11.2'; + if ( + (targets.includes('aarch64-apple-darwin') || + (targets.length === 0 && process.arch === 'arm64')) && + (macOSMinimumVersion == null || + semver.lt( + semver.coerce(macOSMinimumVersion), + semver.coerce(macOSArm64MinimumVersion) + )) + ) { + macOSMinimumVersion = macOSArm64MinimumVersion; + console.log( + `aarch64-apple-darwin target detected, setting minimum system version to ${macOSMinimumVersion}` + ); + } + + if (macOSMinimumVersion) { + process.env.MACOSX_DEPLOYMENT_TARGET = macOSMinimumVersion; + tauriPatch.tauri.bundle.macOS.minimumSystemVersion = macOSMinimumVersion; + } + + // Point tauri to our ffmpeg framework + tauriPatch.tauri.bundle.macOS.frameworks = [ + path.join(workspace, 'target/Frameworks/FFMpeg.framework') + ]; + + // Configure DMG background + process.env.BACKGROUND_FILE = path.resolve(__dirname, '..', 'dmg-background.png'); + process.env.BACKGROUND_FILE_NAME = path.basename(process.env.BACKGROUND_FILE); + process.env.BACKGROUND_CLAUSE = `set background picture of opts to file ".background:${process.env.BACKGROUND_FILE_NAME}"`; + + break; + } + case 'win32': + // Point tauri to the ffmpeg DLLs + tauriPatch.tauri.bundle.resources = setupFFMpegDlls(env.FFMPEG_DIR); + toRemove.push( + ...tauriPatch.tauri.bundle.resources.map((file) => + path.join(workspace, 'apps/desktop/src-tauri', file) + ) + ); + break; + } + + const tauriPatchConf = path.resolve(__dirname, '..', 'tauri.conf.patch.json'); + fs.writeFileSync(tauriPatchConf, JSON.stringify(tauriPatch, null, 2)); + + toRemove.push(tauriPatchConf); + args.splice(1, 0, '-c', tauriPatchConf); + } +} + +let code = 0; +spawn('pnpm', ['tauri', ...args]) + .catch((exitCode) => { + code = exitCode; + console.error(`tauri ${args[0]} failed with exit code ${exitCode}`); + console.error( + `If you got an error related to FFMpeg or Protoc/Protobuf you may need to run ${setupScript}` + ); + }) + .finally(() => { + for (const file of toRemove) + try { + fs.unlinkSync(file); + } catch (e) {} + + process.exit(code); + }); diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs index c1a606177..559608035 100644 --- a/apps/desktop/src-tauri/src/main.rs +++ b/apps/desktop/src-tauri/src/main.rs @@ -83,6 +83,7 @@ async fn main() -> tauri::Result<()> { let app = app .setup(|app| { + #[cfg(feature = "updater")] tauri::updater::builder(app.handle()).should_install(|_current, _latest| true); let app = app.handle(); diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index b8df5e515..e62f6bed2 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -47,7 +47,7 @@ } }, "updater": { - "active": true, + "active": false, "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEZBMURCMkU5NEU3NDAyOEMKUldTTUFuUk82YklkK296dlkxUGkrTXhCT3ZMNFFVOWROcXNaS0RqWU1kMUdRV2tDdFdIS0Y3YUsK", "endpoints": [ "https://releases-6oxwxxryr-spacedrive.vercel.app/{{target}}/{{arch}}/{{current_version}}" diff --git a/apps/mobile/ios/build-rust.sh b/apps/mobile/ios/build-rust.sh index 35e593020..8558cb4bf 100755 --- a/apps/mobile/ios/build-rust.sh +++ b/apps/mobile/ios/build-rust.sh @@ -1,23 +1,24 @@ -#! /bin/zsh +#!/usr/bin/env zsh set -e -TARGET_DIRECTORY=../../../target +__dirname="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)" +TARGET_DIRECTORY="$(CDPATH='' cd -- "${__dirname}/../../../target" && pwd)" -CARGO_FLAGS= if [[ $CONFIGURATION != "Debug" ]]; then CARGO_FLAGS=--release + export CARGO_FLAGS fi +export PROTOC="${TARGET_DIRECTORY}/Frameworks/bin/protoc" + # TODO: Also do this for non-Apple Silicon Macs if [[ $SPACEDRIVE_CI == "1" ]]; then - # Required for CI export PATH="$HOME/.cargo/bin:$PATH" - export PROTOC=/usr/local/bin/protoc cargo build -p sd-mobile-ios --target x86_64-apple-ios - + if [[ $PLATFORM_NAME = "iphonesimulator" ]] then lipo -create -output $TARGET_DIRECTORY/libsd_mobile_ios-iossim.a $TARGET_DIRECTORY/x86_64-apple-ios/debug/libsd_mobile_ios.a @@ -27,9 +28,6 @@ if [[ $SPACEDRIVE_CI == "1" ]]; then exit 0 fi -# Required for M1 Mac builds (?) -export PROTOC=/opt/homebrew/bin/protoc - if [[ $PLATFORM_NAME = "iphonesimulator" ]] then cargo build -p sd-mobile-ios --target aarch64-apple-ios-sim diff --git a/crates/ffmpeg/src/utils.rs b/crates/ffmpeg/src/utils.rs index 2165151a2..61c227326 100644 --- a/crates/ffmpeg/src/utils.rs +++ b/crates/ffmpeg/src/utils.rs @@ -4,26 +4,19 @@ use std::path::Path; pub(crate) fn from_path(path: impl AsRef) -> Result { let path = path.as_ref(); + let path_str = path.as_os_str(); + #[cfg(unix)] { use std::os::unix::ffi::OsStrExt; - CString::new(path.as_os_str().as_bytes()) + CString::new(path_str.as_bytes()) .map_err(|_| ThumbnailerError::PathConversion(path.to_path_buf())) } - - #[cfg(windows)] + #[cfg(not(unix))] { - use std::os::windows::ffi::OsStrExt; - CString::from_vec_with_nul( - path.as_os_str() - .encode_wide() - .chain(Some(0)) - .flat_map(|b| { - let b = b.to_ne_bytes(); - b.first().copied().into_iter().chain(b.get(1).copied()) - }) - .collect::>(), - ) - .map_err(|_| ThumbnailerError::PathConversion(path.to_path_buf())) + path_str + .to_str() + .and_then(|str| CString::new(str.as_bytes()).ok()) + .ok_or(ThumbnailerError::PathConversion(path.to_path_buf())) } } diff --git a/crates/macos/build.rs b/crates/macos/build.rs index 2eac43ce1..28ed439fb 100644 --- a/crates/macos/build.rs +++ b/crates/macos/build.rs @@ -1,6 +1,14 @@ +#[cfg(target_os = "macos")] +use std::env; + fn main() { #[cfg(target_os = "macos")] - swift_rs::SwiftLinker::new("10.15") - .with_package("sd-macos", "./") - .link() + { + let deployment_target = + env::var("MACOSX_DEPLOYMENT_TARGET").unwrap_or_else(|_| String::from("10.15")); + + swift_rs::SwiftLinker::new(deployment_target.as_str()) + .with_package("sd-macos", "./") + .link() + } } diff --git a/package.json b/package.json index 3e93e61d5..eda88a0b7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "scripts": { - "prep": "pnpm gen:prisma && cargo test -p sd-core api::tests::test_and_export_rspc_bindings -- --exact", + "prep": "pnpm gen:prisma && pnpm codegen", "build": "turbo run build", "landing-web": "turbo run dev --parallel --filter=@sd/landing --filter=@sd/web", "gen:migrations": "cd core && cargo prisma migrate dev", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8dd1872b9..ceca2dd9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,7 +51,7 @@ importers: version: 0.0.0-main-799eec5d '@rspc/tauri': specifier: '=0.0.0-main-799eec5d' - version: 0.0.0-main-799eec5d(@tauri-apps/api@1.2.0) + version: 0.0.0-main-799eec5d(@tauri-apps/api@1.3.0) '@sd/client': specifier: workspace:* version: link:../../packages/client @@ -65,8 +65,8 @@ importers: specifier: ^4.24.4 version: 4.29.5(react-dom@18.2.0)(react-native@0.71.3)(react@18.2.0) '@tauri-apps/api': - specifier: 1.2.0 - version: 1.2.0 + specifier: 1.3.0 + version: 1.3.0 react: specifier: ^18.2.0 version: 18.2.0 @@ -80,12 +80,15 @@ importers: specifier: ^3.2.0 version: 3.2.0(vite@4.3.5) devDependencies: + '@iarna/toml': + specifier: ^2.2.5 + version: 2.2.5 '@sd/config': specifier: workspace:* version: link:../../packages/config '@tauri-apps/cli': - specifier: 1.2.3 - version: 1.2.3 + specifier: 1.3.1 + version: 1.3.1 '@types/babel-core': specifier: ^6.25.7 version: 6.25.7 @@ -104,6 +107,9 @@ importers: sass: specifier: ^1.55.0 version: 1.62.1 + semver: + specifier: ^7.5.0 + version: 7.5.0 typescript: specifier: ^4.8.4 version: 4.9.5 @@ -3392,6 +3398,10 @@ packages: /@humanwhocodes/object-schema@1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + /@iarna/toml@2.2.5: + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + dev: true + /@icons-pack/react-simple-icons@5.11.0(react@18.2.0): resolution: {integrity: sha512-hFJ4Q4JcYe/wQUab+gIug1BONjBUelaUVFYsZ1FKv5CEX/fP56qGet4CPmx18BqDhxrOehidYNlLyGppXL9/7w==} peerDependencies: @@ -5287,13 +5297,13 @@ packages: react: 18.2.0 dev: false - /@rspc/tauri@0.0.0-main-799eec5d(@tauri-apps/api@1.2.0): + /@rspc/tauri@0.0.0-main-799eec5d(@tauri-apps/api@1.3.0): resolution: {integrity: sha512-1EQiAfDqDe0iV/CoSadw58EasA/finfoXx0h4odZx+mUVlkV6CUd9QC03EtTrXPoA6MrjA+oWKbBP/2wj/CrHg==} peerDependencies: '@tauri-apps/api': ^1.2.0 dependencies: '@rspc/client': 0.0.0-main-799eec5d - '@tauri-apps/api': 1.2.0 + '@tauri-apps/api': 1.3.0 dev: false /@rushstack/eslint-patch@1.2.0: @@ -6854,13 +6864,13 @@ packages: engines: {node: '>=12'} dev: false - /@tauri-apps/api@1.2.0: - resolution: {integrity: sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==} + /@tauri-apps/api@1.3.0: + resolution: {integrity: sha512-AH+3FonkKZNtfRtGrObY38PrzEj4d+1emCbwNGu0V2ENbXjlLHMZQlUh+Bhu/CRmjaIwZMGJ3yFvWaZZgTHoog==} engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} dev: false - /@tauri-apps/cli-darwin-arm64@1.2.3: - resolution: {integrity: sha512-phJN3fN8FtZZwqXg08bcxfq1+X1JSDglLvRxOxB7VWPq+O5SuB8uLyssjJsu+PIhyZZnIhTGdjhzLSFhSXfLsw==} + /@tauri-apps/cli-darwin-arm64@1.3.1: + resolution: {integrity: sha512-QlepYVPgOgspcwA/u4kGG4ZUijlXfdRtno00zEy+LxinN/IRXtk+6ErVtsmoLi1ZC9WbuMwzAcsRvqsD+RtNAg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -6868,8 +6878,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-darwin-x64@1.2.3: - resolution: {integrity: sha512-jFZ/y6z8z6v4yliIbXKBXA7BJgtZVMsITmEXSuD6s5+eCOpDhQxbRkr6CA+FFfr+/r96rWSDSgDenDQuSvPAKw==} + /@tauri-apps/cli-darwin-x64@1.3.1: + resolution: {integrity: sha512-fKcAUPVFO3jfDKXCSDGY0MhZFF/wDtx3rgFnogWYu4knk38o9RaqRkvMvqJhLYPuWaEM5h6/z1dRrr9KKCbrVg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -6877,8 +6887,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-arm-gnueabihf@1.2.3: - resolution: {integrity: sha512-C7h5vqAwXzY0kRGSU00Fj8PudiDWFCiQqqUNI1N+fhCILrzWZB9TPBwdx33ZfXKt/U4+emdIoo/N34v3TiAOmQ==} + /@tauri-apps/cli-linux-arm-gnueabihf@1.3.1: + resolution: {integrity: sha512-+4H0dv8ltJHYu/Ma1h9ixUPUWka9EjaYa8nJfiMsdCI4LJLNE6cPveE7RmhZ59v9GW1XB108/k083JUC/OtGvA==} engines: {node: '>= 10'} cpu: [arm] os: [linux] @@ -6886,8 +6896,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-arm64-gnu@1.2.3: - resolution: {integrity: sha512-buf1c8sdkuUzVDkGPQpyUdAIIdn5r0UgXU6+H5fGPq/Xzt5K69JzXaeo6fHsZEZghbV0hOK+taKV4J0m30UUMQ==} + /@tauri-apps/cli-linux-arm64-gnu@1.3.1: + resolution: {integrity: sha512-Pj3odVO1JAxLjYmoXKxcrpj/tPxcA8UP8N06finhNtBtBaxAjrjjxKjO4968KB0BUH7AASIss9EL4Tr0FGnDuw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -6895,8 +6905,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-arm64-musl@1.2.3: - resolution: {integrity: sha512-x88wPS9W5xAyk392vc4uNHcKBBvCp0wf4H9JFMF9OBwB7vfd59LbQCFcPSu8f0BI7bPrOsyHqspWHuFL8ojQEA==} + /@tauri-apps/cli-linux-arm64-musl@1.3.1: + resolution: {integrity: sha512-tA0JdDLPFaj42UDIVcF2t8V0tSha40rppcmAR/MfQpTCxih6399iMjwihz9kZE1n4b5O4KTq9GliYo50a8zYlQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -6904,8 +6914,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-x64-gnu@1.2.3: - resolution: {integrity: sha512-ZMz1jxEVe0B4/7NJnlPHmwmSIuwiD6ViXKs8F+OWWz2Y4jn5TGxWKFg7DLx5OwQTRvEIZxxT7lXHi5CuTNAxKg==} + /@tauri-apps/cli-linux-x64-gnu@1.3.1: + resolution: {integrity: sha512-FDU+Mnvk6NLkqQimcNojdKpMN4Y3W51+SQl+NqG9AFCWprCcSg62yRb84751ujZuf2MGT8HQOfmd0i77F4Q3tQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -6913,8 +6923,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-x64-musl@1.2.3: - resolution: {integrity: sha512-B/az59EjJhdbZDzawEVox0LQu2ZHCZlk8rJf85AMIktIUoAZPFbwyiUv7/zjzA/sY6Nb58OSJgaPL2/IBy7E0A==} + /@tauri-apps/cli-linux-x64-musl@1.3.1: + resolution: {integrity: sha512-MpO3akXFmK8lZYEbyQRDfhdxz1JkTBhonVuz5rRqxwA7gnGWHa1aF1+/2zsy7ahjB2tQ9x8DDFDMdVE20o9HrA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -6922,8 +6932,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-win32-ia32-msvc@1.2.3: - resolution: {integrity: sha512-ypdO1OdC5ugNJAKO2m3sb1nsd+0TSvMS9Tr5qN/ZSMvtSduaNwrcZ3D7G/iOIanrqu/Nl8t3LYlgPZGBKlw7Ng==} + /@tauri-apps/cli-win32-ia32-msvc@1.3.1: + resolution: {integrity: sha512-9Boeo3K5sOrSBAZBuYyGkpV2RfnGQz3ZhGJt4hE6P+HxRd62lS6+qDKAiw1GmkZ0l1drc2INWrNeT50gwOKwIQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -6931,8 +6941,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-win32-x64-msvc@1.2.3: - resolution: {integrity: sha512-CsbHQ+XhnV/2csOBBDVfH16cdK00gNyNYUW68isedmqcn8j+s0e9cQ1xXIqi+Hue3awp8g3ImYN5KPepf3UExw==} + /@tauri-apps/cli-win32-x64-msvc@1.3.1: + resolution: {integrity: sha512-wMrTo91hUu5CdpbElrOmcZEoJR4aooTG+fbtcc87SMyPGQy1Ux62b+ZdwLvL1sVTxnIm//7v6QLRIWGiUjCPwA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -6940,20 +6950,20 @@ packages: dev: true optional: true - /@tauri-apps/cli@1.2.3: - resolution: {integrity: sha512-erxtXuPhMEGJPBtnhPILD4AjuT81GZsraqpFvXAmEJZ2p8P6t7MVBifCL8LznRknznM3jn90D3M8RNBP3wcXTw==} + /@tauri-apps/cli@1.3.1: + resolution: {integrity: sha512-o4I0JujdITsVRm3/0spfJX7FcKYrYV1DXJqzlWIn6IY25/RltjU6qbC1TPgVww3RsRX63jyVUTcWpj5wwFl+EQ==} engines: {node: '>= 10'} hasBin: true optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 1.2.3 - '@tauri-apps/cli-darwin-x64': 1.2.3 - '@tauri-apps/cli-linux-arm-gnueabihf': 1.2.3 - '@tauri-apps/cli-linux-arm64-gnu': 1.2.3 - '@tauri-apps/cli-linux-arm64-musl': 1.2.3 - '@tauri-apps/cli-linux-x64-gnu': 1.2.3 - '@tauri-apps/cli-linux-x64-musl': 1.2.3 - '@tauri-apps/cli-win32-ia32-msvc': 1.2.3 - '@tauri-apps/cli-win32-x64-msvc': 1.2.3 + '@tauri-apps/cli-darwin-arm64': 1.3.1 + '@tauri-apps/cli-darwin-x64': 1.3.1 + '@tauri-apps/cli-linux-arm-gnueabihf': 1.3.1 + '@tauri-apps/cli-linux-arm64-gnu': 1.3.1 + '@tauri-apps/cli-linux-arm64-musl': 1.3.1 + '@tauri-apps/cli-linux-x64-gnu': 1.3.1 + '@tauri-apps/cli-linux-x64-musl': 1.3.1 + '@tauri-apps/cli-win32-ia32-msvc': 1.3.1 + '@tauri-apps/cli-win32-x64-msvc': 1.3.1 dev: true /@testing-library/dom@8.20.0: