diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..beb596d4 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.aarch64-unknown-linux-gnu] +linker = "aarch64-linux-gnu-gcc" + +[target.armv7-unknown-linux-gnueabihf] +linker = "arm-linux-gnueabihf-gcc" diff --git a/.github/Dockerfile b/.github/Dockerfile index c09a7c6f..ef6823a5 100644 --- a/.github/Dockerfile +++ b/.github/Dockerfile @@ -1,11 +1,34 @@ FROM ubuntu:24.04 +ARG RUST_VERSION=1.94.0 + ENV DEBIAN_FRONTEND=noninteractive ENV RUSTUP_HOME=/usr/local/rustup ENV CARGO_HOME=/usr/local/cargo ENV PATH="/usr/local/cargo/bin:${PATH}" -# System dependencies +# Configure multi-arch package sources based on host architecture. +# On amd64: add arm64 + armhf foreign arches via ports.ubuntu.com +# On arm64: add armhf foreign arch only (arm64 packages are native) +RUN set -eux; \ + NATIVE_ARCH=$(dpkg --print-architecture); \ + if [ "$NATIVE_ARCH" = "amd64" ]; then \ + dpkg --add-architecture arm64; \ + dpkg --add-architecture armhf; \ + . /etc/os-release; \ + echo "deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${VERSION_CODENAME} main restricted universe multiverse" > /etc/apt/sources.list.d/arm-ports.list; \ + echo "deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${VERSION_CODENAME}-updates main restricted universe multiverse" >> /etc/apt/sources.list.d/arm-ports.list; \ + echo "deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${VERSION_CODENAME}-security main restricted universe multiverse" >> /etc/apt/sources.list.d/arm-ports.list; \ + if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then \ + sed -i '/^Architectures:/d; /^Types:/a Architectures: amd64' /etc/apt/sources.list.d/ubuntu.sources; \ + elif [ -f /etc/apt/sources.list ]; then \ + sed -i 's/^deb http/deb [arch=amd64] http/' /etc/apt/sources.list; \ + fi; \ + elif [ "$NATIVE_ARCH" = "arm64" ]; then \ + dpkg --add-architecture armhf; \ + fi + +# System dependencies (native) RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ curl \ @@ -20,10 +43,46 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ git \ && rm -rf /var/lib/apt/lists/* -# Rust stable -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ - sh -s -- -y --default-toolchain stable --profile minimal && \ - rustup component add clippy rustfmt +# Cross-compilers (architecture-dependent) +# amd64: cross-compile to both arm64 and armhf +# arm64: cross-compile to armhf only (arm64 is native) +RUN set -eux; \ + NATIVE_ARCH=$(dpkg --print-architecture); \ + apt-get update; \ + if [ "$NATIVE_ARCH" = "amd64" ]; then \ + apt-get install -y --no-install-recommends \ + gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \ + gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf; \ + elif [ "$NATIVE_ARCH" = "arm64" ]; then \ + apt-get install -y --no-install-recommends \ + gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf; \ + fi; \ + rm -rf /var/lib/apt/lists/* + +# Multi-arch dev libraries for cross-compilation targets +RUN set -eux; \ + NATIVE_ARCH=$(dpkg --print-architecture); \ + apt-get update; \ + if [ "$NATIVE_ARCH" = "amd64" ]; then \ + apt-get install -y --no-install-recommends \ + libfuse-dev:arm64 libsqlite3-dev:arm64 libssl-dev:arm64 zlib1g-dev:arm64 libudev-dev:arm64 \ + libfuse-dev:armhf libsqlite3-dev:armhf libssl-dev:armhf zlib1g-dev:armhf libudev-dev:armhf; \ + elif [ "$NATIVE_ARCH" = "arm64" ]; then \ + apt-get install -y --no-install-recommends \ + libfuse-dev:armhf libsqlite3-dev:armhf libssl-dev:armhf zlib1g-dev:armhf libudev-dev:armhf; \ + fi; \ + rm -rf /var/lib/apt/lists/* + +# Rust stable with cross-compilation targets +RUN set -eux; \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "${RUST_VERSION}" --profile minimal; \ + rustup component add clippy rustfmt; \ + NATIVE_ARCH=$(dpkg --print-architecture); \ + if [ "$NATIVE_ARCH" = "amd64" ]; then \ + rustup target add aarch64-unknown-linux-gnu armv7-unknown-linux-gnueabihf; \ + elif [ "$NATIVE_ARCH" = "arm64" ]; then \ + rustup target add armv7-unknown-linux-gnueabihf; \ + fi # Packaging tools -RUN cargo install cargo-deb cargo-generate-rpm +RUN cargo install cargo-deb cargo-generate-rpm \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9be0152..4d71d447 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,6 @@ env: RELEASE_BUILD: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) }} jobs: - # 1. New setup job to create the lowercase repository variable setup: runs-on: ubuntu-latest outputs: @@ -28,7 +27,7 @@ jobs: lint: needs: setup runs-on: ubuntu-latest - container: ghcr.io/${{ needs.setup.outputs.repo_lc }}/ci:1.0 + container: ghcr.io/${{ needs.setup.outputs.repo_lc }}/ci:2.0 steps: - uses: actions/checkout@v4 with: @@ -54,10 +53,31 @@ jobs: run: cargo check build-and-test: - needs: setup # 2. Require the setup job to finish first - runs-on: ubuntu-latest - # 3. Use the output from the setup job - container: ghcr.io/${{ needs.setup.outputs.repo_lc }}/ci:1.0 + needs: setup + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-gnu + runner: ubuntu-latest + use-container: true + artifact-name: linux-amd64 + strip-cmd: strip + - target: aarch64-unknown-linux-gnu + runner: ubuntu-24.04-arm + use-container: true + artifact-name: linux-arm64 + strip-cmd: strip + - target: armv7-unknown-linux-gnueabihf + runner: ubuntu-24.04-arm + use-container: true + artifact-name: linux-armhf + strip-cmd: arm-linux-gnueabihf-strip + cc: arm-linux-gnueabihf-gcc + pkg-config-path: /usr/lib/arm-linux-gnueabihf/pkgconfig + bindgen-target: arm-linux-gnueabihf + runs-on: ${{ matrix.runner }} + container: ${{ matrix.use-container && format('ghcr.io/{0}/ci:2.0', needs.setup.outputs.repo_lc) || '' }} steps: - uses: actions/checkout@v4 with: @@ -70,8 +90,8 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock', 'Cargo.toml') }} - restore-keys: cargo-${{ runner.os }}- + key: cargo-${{ matrix.target }}-${{ hashFiles('Cargo.lock', 'Cargo.toml') }} + restore-keys: cargo-${{ matrix.target }}- - name: Build run: | @@ -79,28 +99,125 @@ jobs: if [ "$RELEASE_BUILD" = "true" ]; then FEATURES="--features crash-reporting" fi - cargo build --release $FEATURES + cargo build --release --target ${{ matrix.target }} $FEATURES env: BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }} + CC: ${{ matrix.cc || '' }} + PKG_CONFIG_PATH: ${{ matrix.pkg-config-path || env.PKG_CONFIG_PATH || '' }} + PKG_CONFIG_ALLOW_CROSS: ${{ matrix.cc && '1' || '' }} + BINDGEN_EXTRA_CLANG_ARGS: ${{ matrix.bindgen-target && format('--target={0} --sysroot=/usr/{0}', matrix.bindgen-target) || '' }} - name: Test - run: cargo test --release + run: cargo test --release --target ${{ matrix.target }} + env: + CC: ${{ matrix.cc || '' }} + PKG_CONFIG_PATH: ${{ matrix.pkg-config-path || env.PKG_CONFIG_PATH || '' }} + PKG_CONFIG_ALLOW_CROSS: ${{ matrix.cc && '1' || '' }} + BINDGEN_EXTRA_CLANG_ARGS: ${{ matrix.bindgen-target && format('--target={0} --sysroot=/usr/{0}', matrix.bindgen-target) || '' }} - name: Upload binary if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) uses: actions/upload-artifact@v4 with: - name: binary - path: target/release/pcloud + name: binary-${{ matrix.artifact-name }} + path: target/${{ matrix.target }}/release/pcloud + retention-days: 5 + + build-macos: + needs: setup + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install native (arm64) dependencies + run: | + brew install macfuse sqlite openssl llvm pkg-config + echo "LIBCLANG_PATH=$(brew --prefix llvm)/lib" >> $GITHUB_ENV + + - name: Install x86_64 dependencies via Rosetta + run: | + arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + arch -x86_64 /usr/local/bin/brew install openssl@3 sqlite + + - name: Add x86_64 target + run: rustup target add x86_64-apple-darwin + + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: cargo-macos-universal-${{ hashFiles('Cargo.lock', 'Cargo.toml') }} + restore-keys: cargo-macos-universal- + + - name: Build aarch64 + run: | + FEATURES="" + if [ "$RELEASE_BUILD" = "true" ]; then + FEATURES="--features crash-reporting" + fi + cargo build --release --target aarch64-apple-darwin $FEATURES + env: + BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }} + PKG_CONFIG_PATH: /opt/homebrew/opt/openssl@3/lib/pkgconfig:/opt/homebrew/opt/sqlite/lib/pkgconfig + + - name: Build x86_64 + run: | + FEATURES="" + if [ "$RELEASE_BUILD" = "true" ]; then + FEATURES="--features crash-reporting" + fi + cargo build --release --target x86_64-apple-darwin $FEATURES + env: + BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }} + PKG_CONFIG_PATH: /usr/local/opt/openssl@3/lib/pkgconfig:/usr/local/opt/sqlite/lib/pkgconfig + + - name: Test (native aarch64 only) + run: cargo test --release --target aarch64-apple-darwin + + - name: Create universal binary + run: | + lipo -create -output pcloud-macos-universal \ + target/aarch64-apple-darwin/release/pcloud \ + target/x86_64-apple-darwin/release/pcloud + lipo -info pcloud-macos-universal + strip pcloud-macos-universal + + - name: Upload universal binary + if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) + uses: actions/upload-artifact@v4 + with: + name: binary-macos-universal + path: pcloud-macos-universal retention-days: 5 package: runs-on: ubuntu-latest - # 4. Include setup in the needs array to access its outputs here - needs: [setup, build-and-test] + needs: [setup, lint, build-and-test] if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) - # 5. Use the output from the setup job - container: ghcr.io/${{ needs.setup.outputs.repo_lc }}/ci:latest + container: ghcr.io/${{ needs.setup.outputs.repo_lc }}/ci:2.0 + strategy: + matrix: + include: + - target: x86_64-unknown-linux-gnu + artifact-name: linux-amd64 + strip-cmd: strip + deb-arch: amd64 + rpm-arch: x86_64 + - target: aarch64-unknown-linux-gnu + artifact-name: linux-arm64 + strip-cmd: aarch64-linux-gnu-strip + deb-arch: arm64 + rpm-arch: aarch64 + - target: armv7-unknown-linux-gnueabihf + artifact-name: linux-armhf + strip-cmd: arm-linux-gnueabihf-strip + deb-arch: armhf + rpm-arch: armv7hl steps: - uses: actions/checkout@v4 with: @@ -108,48 +225,71 @@ jobs: - uses: actions/download-artifact@v4 with: - name: binary - path: target/release/ + name: binary-${{ matrix.artifact-name }} + path: target/${{ matrix.target }}/release/ - name: Ensure binary is executable - run: chmod +x target/release/pcloud + run: chmod +x target/${{ matrix.target }}/release/pcloud - name: Build .deb - run: cargo deb --no-build + run: cargo deb --no-build --target ${{ matrix.target }} - name: Build .rpm - run: cargo generate-rpm + run: cargo generate-rpm --target ${{ matrix.target }} - name: Strip binary - run: strip target/release/pcloud + run: ${{ matrix.strip-cmd }} target/${{ matrix.target }}/release/pcloud - name: Upload packages uses: actions/upload-artifact@v4 with: - name: packages + name: packages-${{ matrix.artifact-name }} retention-days: 30 path: | - target/debian/*.deb - target/generate-rpm/*.rpm - target/release/pcloud + target/${{ matrix.target }}/debian/*.deb + target/${{ matrix.target }}/generate-rpm/*.rpm + target/${{ matrix.target }}/release/pcloud release: runs-on: ubuntu-latest - needs: package + needs: [package, build-macos] if: startsWith(github.ref, 'refs/tags/v') steps: - uses: actions/download-artifact@v4 with: - name: packages + path: artifacts/ + + - name: Prepare release assets + run: | + mkdir -p release + + # Linux binaries and packages + for arch in linux-amd64 linux-arm64 linux-armhf; do + if [ -d "artifacts/packages-${arch}" ]; then + # Rename binary with arch suffix + cp "artifacts/packages-${arch}/release/pcloud" "release/pcloud-${arch}" 2>/dev/null || \ + cp "artifacts/packages-${arch}/"*/release/pcloud "release/pcloud-${arch}" 2>/dev/null || true + + # Copy .deb packages + find "artifacts/packages-${arch}" -name '*.deb' -exec cp {} release/ \; 2>/dev/null || true + + # Copy .rpm packages + find "artifacts/packages-${arch}" -name '*.rpm' -exec cp {} release/ \; 2>/dev/null || true + fi + done + + # macOS universal binary + if [ -d "artifacts/binary-macos-universal" ]; then + cp "artifacts/binary-macos-universal/pcloud-macos-universal" "release/pcloud-macos-universal" 2>/dev/null || true + fi + + ls -la release/ - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: name: ${{ github.ref_name }} generate_release_notes: true - files: | - debian/*.deb - generate-rpm/*.rpm - release/pcloud + files: release/* env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9a3b451f..ef3e53b5 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -11,6 +11,9 @@ on: permissions: packages: write +env: + RUST_VERSION: '1.94.0' + jobs: build-image: runs-on: ubuntu-latest @@ -23,15 +26,23 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # 1. Convert the repository name to lowercase - name: Set lowercase repository name run: | echo "REPO_LC=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV + - uses: docker/setup-qemu-action@v3 + + - uses: docker/setup-buildx-action@v3 + - uses: docker/build-push-action@v6 with: context: .github file: .github/Dockerfile + platforms: linux/amd64,linux/arm64 push: true - # 2. Replaced ${{ github.repository }} with ${{ env.REPO_LC }} - tags: ghcr.io/${{ env.REPO_LC }}/ci:1.0 \ No newline at end of file + build-args: | + RUST_VERSION=${{ env.RUST_VERSION }} + tags: | + ghcr.io/${{ env.REPO_LC }}/ci:2.0 + ghcr.io/${{ env.REPO_LC }}/ci:2.0-rust-${{ env.RUST_VERSION }} + ghcr.io/${{ env.REPO_LC }}/ci:latest diff --git a/build.rs b/build.rs index 996cd36f..366cd4b2 100644 --- a/build.rs +++ b/build.rs @@ -453,16 +453,12 @@ fn link_system_libraries(target_os: &str) { // Link libraries on macOS // Try pkg-config first, fall back to direct linking - // FUSE (osxfuse or macfuse) - if pkg_config::Config::new().probe("osxfuse").is_err() - && pkg_config::Config::new().probe("fuse").is_err() - { - println!("cargo:rustc-link-lib=osxfuse"); - // Common library search paths on macOS - println!("cargo:rustc-link-search=/usr/local/lib"); - println!("cargo:rustc-link-search=/opt/homebrew/lib"); - println!("cargo:rustc-link-search=/Library/Frameworks/macFUSE.framework/Libraries"); - } + // FUSE (macFUSE) — link directly; pkg-config is unreliable + // when cross-compiling (e.g. arm64 pkg-config on x86_64 target). + // macFUSE installs a universal dylib at /usr/local/lib/libfuse.dylib. + println!("cargo:rustc-link-lib=fuse"); + println!("cargo:rustc-link-search=/usr/local/lib"); + println!("cargo:rustc-link-search=/opt/homebrew/lib"); // SQLite3 link_with_pkgconfig_or_fallback("sqlite3", "sqlite3"); @@ -478,8 +474,14 @@ fn link_system_libraries(target_os: &str) { println!("cargo:rustc-link-search=/opt/homebrew/opt/openssl@3/lib"); } + // zlib for pcompression.c (deflate/inflate) + println!("cargo:rustc-link-lib=z"); + // Cocoa framework for macOS println!("cargo:rustc-link-lib=framework=Cocoa"); + + // IOKit framework for pdevice_monitor.c (USB device monitoring) + println!("cargo:rustc-link-lib=framework=IOKit"); } _ => { // Fallback: try to link common libraries diff --git a/pclsync b/pclsync index 7a329d22..cb53868d 160000 --- a/pclsync +++ b/pclsync @@ -1 +1 @@ -Subproject commit 7a329d228e5dfcf7e8d9dfdde5181e07cc0a7ec9 +Subproject commit cb53868d280e421368725a1f192d441fa5360932 diff --git a/src/wrapper/crypto.rs b/src/wrapper/crypto.rs index 68884758..adf8a2c0 100644 --- a/src/wrapper/crypto.rs +++ b/src/wrapper/crypto.rs @@ -288,7 +288,7 @@ impl PCloudClient { /// Unix timestamp of when crypto expires, or 0 if never set up. pub fn crypto_expires(&self) -> i64 { // Safety: psync_crypto_expires is safe to call anytime - unsafe { raw::psync_crypto_expires() } + unsafe { raw::psync_crypto_expires() as i64 } } /// Get the crypto password hint.