diff --git a/.github/workflows/covector-version-or-publish.yml b/.github/workflows/covector-version-or-publish.yml index 15d7404a5..175c20190 100644 --- a/.github/workflows/covector-version-or-publish.yml +++ b/.github/workflows/covector-version-or-publish.yml @@ -10,6 +10,91 @@ on: - dev jobs: + run-integration-tests: + runs-on: ${{ matrix.platform }} + + strategy: + fail-fast: false + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - name: install webkit2gtk (ubuntu only) + if: matrix.platform == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libayatana-appindicator3-dev librsvg2-dev patchelf + + - name: Get current date + run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + if: matrix.platform == 'macos-latest' || matrix.platform == 'ubuntu-latest' + + - name: Get current date + if: matrix.platform == 'windows-latest' + run: echo "CURRENT_DATE=$(Get-Date -Format "yyyy-MM-dd")" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Cache cargo state + uses: actions/cache@v2 + env: + cache-name: cargo-state + with: + path: | + ~/.cargo/registry + ~/.cargo/git + ~/.cargo/bin + key: ${{ matrix.platform }}-stable-${{ env.cache-name }}-${{ hashFiles('**/Cargo.toml') }}-${{ env.CURRENT_DATE }} + restore-keys: | + ${{ matrix.platform }}-stable-${{ env.cache-name }}-${{ hashFiles('**/Cargo.toml') }}- + ${{ matrix.platform }}-stable-${{ env.cache-name }}- + ${{ matrix.platform }}-stable- + ${{ matrix.platform }}- + + - name: Cache core cargo target + uses: actions/cache@v2 + env: + cache-name: cargo-core + with: + path: target + # Add date to the cache to keep it up to date + key: ${{ matrix.platform }}-stable-${{ env.cache-name }}-${{ hashFiles('core/**/Cargo.toml') }}-${{ env.CURRENT_DATE }} + # Restore from outdated cache for speed + restore-keys: | + ${{ matrix.platform }}-stable-${{ env.cache-name }}-${{ hashFiles('core/**/Cargo.toml') }} + ${{ matrix.platform }}-stable-${{ env.cache-name }}- + ${{ matrix.platform }}-stable- + ${{ matrix.platform }}- + + - name: Cache CLI cargo target + uses: actions/cache@v2 + env: + cache-name: cargo-cli + with: + path: tooling/cli/target + # Add date to the cache to keep it up to date + key: ${{ matrix.platform }}-stable-${{ env.cache-name }}-${{ hashFiles('tooling/cli/Cargo.lock') }}-${{ env.CURRENT_DATE }} + # Restore from outdated cache for speed + restore-keys: | + ${{ matrix.platform }}-stable-${{ env.cache-name }}-${{ hashFiles('tooling/cli/Cargo.lock') }} + ${{ matrix.platform }}-stable-${{ env.cache-name }}- + ${{ matrix.platform }}-stable- + ${{ matrix.platform }}- + + - name: build CLI + uses: actions-rs/cargo@v1 + with: + command: build + args: --manifest-path ./tooling/cli/Cargo.toml + + - name: run integration tests + run: cargo test -- --ignored + version-or-publish: runs-on: ubuntu-latest timeout-minutes: 65 @@ -17,6 +102,8 @@ jobs: change: ${{ steps.covector.outputs.change }} commandRan: ${{ steps.covector.outputs.commandRan }} successfulPublish: ${{ steps.covector.outputs.successfulPublish }} + needs: + - run-integration-tests steps: - uses: actions/checkout@v2 @@ -35,6 +122,7 @@ jobs: run: | git config --global user.name "${{ github.event.pusher.name }}" git config --global user.email "${{ github.event.pusher.email }}" + - name: covector version or publish (publish when no change files present) uses: jbolda/covector/packages/action@covector-v0 id: covector @@ -45,6 +133,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} command: 'version-or-publish' createRelease: true + - name: Create Pull Request With Versions Bumped if: steps.covector.outputs.commandRan == 'version' uses: tauri-apps/create-pull-request@v3.4.1 diff --git a/Cargo.toml b/Cargo.toml index 318929490..cc56de529 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,8 @@ members = [ "core/tauri-codegen", # integration tests - "core/tests/restart" + "core/tests/restart", + "core/tests/app-updater" ] exclude = [ diff --git a/core/tests/app-updater/Cargo.toml b/core/tests/app-updater/Cargo.toml new file mode 100644 index 000000000..dfd512e94 --- /dev/null +++ b/core/tests/app-updater/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "app-updater" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +tauri-build = { path = "../../tauri-build", features = [] } + +[dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tiny_http = "0.11" +tauri = { path = "../../tauri", features = ["updater"] } + +[features] +default = ["custom-protocol"] +custom-protocol = ["tauri/custom-protocol"] diff --git a/core/tests/app-updater/build.rs b/core/tests/app-updater/build.rs new file mode 100644 index 000000000..7361c3f72 --- /dev/null +++ b/core/tests/app-updater/build.rs @@ -0,0 +1,13 @@ +// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri_build::{try_build, Attributes, WindowsAttributes}; + +fn main() { + if let Err(error) = try_build(Attributes::new().windows_attributes( + WindowsAttributes::new().window_icon_path("../../../examples/.icons/icon.ico"), + )) { + panic!("error found during tauri-build: {:#?}", error); + } +} diff --git a/core/tests/app-updater/src/main.rs b/core/tests/app-updater/src/main.rs new file mode 100644 index 000000000..6afbc50f3 --- /dev/null +++ b/core/tests/app-updater/src/main.rs @@ -0,0 +1,33 @@ +// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +fn main() { + tauri::Builder::default() + .setup(|app| { + let handle = app.handle(); + tauri::async_runtime::spawn(async move { + match handle.updater().check().await { + Ok(update) => { + if let Err(e) = update.download_and_install().await { + println!("{}", e); + std::process::exit(1); + } + std::process::exit(0); + } + Err(e) => { + println!("{}", e); + std::process::exit(1); + } + } + }); + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/core/tests/app-updater/tauri.conf.json b/core/tests/app-updater/tauri.conf.json new file mode 100644 index 000000000..b650d8a99 --- /dev/null +++ b/core/tests/app-updater/tauri.conf.json @@ -0,0 +1,32 @@ +{ + "build": { + "distDir": [], + "devPath": [] + }, + "tauri": { + "bundle": { + "active": true, + "targets": "all", + "identifier": "com.tauri.updater", + "icon": [ + "../../../examples/.icons/32x32.png", + "../../../examples/.icons/128x128.png", + "../../../examples/.icons/128x128@2x.png", + "../../../examples/.icons/icon.icns", + "../../../examples/.icons/icon.ico" + ], + "category": "DeveloperTool" + }, + "allowlist": { + "all": false + }, + "updater": { + "active": true, + "dialog": false, + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK", + "endpoints": [ + "http://localhost:3007" + ] + } + } +} diff --git a/core/tests/app-updater/tests/update.rs b/core/tests/app-updater/tests/update.rs new file mode 100644 index 000000000..1941e68a1 --- /dev/null +++ b/core/tests/app-updater/tests/update.rs @@ -0,0 +1,233 @@ +// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![allow(dead_code, unused_imports)] + +use std::{ + collections::HashMap, + fs::File, + path::{Path, PathBuf}, + process::Command, +}; + +use serde::Serialize; + +const UPDATER_PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg=="; + +#[derive(Serialize)] +struct PackageConfig { + version: &'static str, +} + +#[derive(Serialize)] +struct Config { + package: PackageConfig, +} + +#[derive(Serialize)] +struct PlatformUpdate { + signature: String, + url: &'static str, +} + +#[derive(Serialize)] +struct Update { + version: &'static str, + platforms: HashMap, +} + +fn get_cli_bin_path(cli_dir: &Path, debug: bool) -> Option { + let mut cli_bin_path = cli_dir.join(format!( + "target/{}/cargo-tauri", + if debug { "debug" } else { "release" } + )); + if cfg!(windows) { + cli_bin_path.set_extension("exe"); + } + if cli_bin_path.exists() { + Some(cli_bin_path) + } else { + None + } +} + +fn build_app(cli_bin_path: &Path, cwd: &Path, config: &Config, bundle_updater: bool) { + let mut command = Command::new(&cli_bin_path); + command + .arg("build") + .arg("--debug") + .arg("--config") + .arg(serde_json::to_string(config).unwrap()) + .current_dir(&cwd); + + #[cfg(windows)] + command.args(["--bundles", "msi"]); + #[cfg(target_os = "linux")] + command.args(["--bundles", "appimage"]); + #[cfg(target_os = "macos")] + command.args(["--bundles", "app"]); + + if bundle_updater { + command + .env("TAURI_PRIVATE_KEY", UPDATER_PRIVATE_KEY) + .env("TAURI_KEY_PASSWORD", "") + .args(["--bundles", "updater"]); + } + + let status = command + .status() + .expect("failed to run Tauri CLI to bundle app"); + + if !status.code().map(|c| c == 0).unwrap_or(true) { + panic!("failed to bundle app {:?}", status.code()); + } +} + +#[cfg(target_os = "linux")] +fn bundle_path(root_dir: &Path, version: &str) -> PathBuf { + root_dir.join(format!( + "target/debug/bundle/appimage/app-updater_{}_amd64.AppImage", + version + )) +} + +#[cfg(target_os = "macos")] +fn bundle_path(root_dir: &Path, _version: &str) -> PathBuf { + root_dir.join(format!("target/debug/bundle/macos/app-updater.app")) +} + +#[cfg(windows)] +fn bundle_path(root_dir: &Path, version: &str) -> PathBuf { + root_dir.join(format!( + "target/debug/bundle/msi/app-updater_{}_x64_en-US.AppImage", + version + )) +} + +#[cfg(not(windows))] +#[test] +#[ignore] +fn update_app() { + let target = tauri::updater::target().expect("running updater test in an unsupported platform"); + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let root_dir = manifest_dir.join("../../.."); + let cli_dir = root_dir.join("tooling/cli"); + + let cli_bin_path = if let Some(p) = get_cli_bin_path(&cli_dir, false) { + p + } else { + if let Some(p) = get_cli_bin_path(&cli_dir, true) { + p + } else { + let status = Command::new("cargo") + .arg("build") + .current_dir(&cli_dir) + .status() + .expect("failed to run cargo"); + if !status.success() { + panic!("failed to build CLI"); + } + get_cli_bin_path(&cli_dir, true).expect("cargo did not build the Tauri CLI") + } + }; + + let mut config = Config { + package: PackageConfig { version: "1.0.0" }, + }; + + // bundle app update + build_app(&cli_bin_path, &manifest_dir, &config, true); + + let updater_ext = if cfg!(windows) { "zip" } else { "tar.gz" }; + + let out_bundle_path = bundle_path(&root_dir, "1.0.0"); + let signature_path = out_bundle_path.with_extension(format!( + "{}.{}.sig", + out_bundle_path.extension().unwrap().to_str().unwrap(), + updater_ext + )); + let signature = std::fs::read_to_string(&signature_path) + .unwrap_or_else(|_| panic!("failed to read signature file {}", signature_path.display())); + let out_updater_path = out_bundle_path.with_extension(format!( + "{}.{}", + out_bundle_path.extension().unwrap().to_str().unwrap(), + updater_ext + )); + let updater_path = root_dir.join(format!( + "target/debug/{}", + out_updater_path.file_name().unwrap().to_str().unwrap() + )); + std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle"); + + std::thread::spawn(move || { + // start the updater server + let server = tiny_http::Server::http("localhost:3007").expect("failed to start updater server"); + + loop { + if let Ok(request) = server.recv() { + match request.url() { + "/" => { + let mut platforms = HashMap::new(); + + platforms.insert( + target.clone(), + PlatformUpdate { + signature: signature.clone(), + url: "http://localhost:3007/download", + }, + ); + let body = serde_json::to_vec(&Update { + version: "1.0.0", + platforms, + }) + .unwrap(); + let len = body.len(); + let response = tiny_http::Response::new( + tiny_http::StatusCode(200), + Vec::new(), + std::io::Cursor::new(body), + Some(len), + None, + ); + let _ = request.respond(response); + } + "/download" => { + let _ = request.respond(tiny_http::Response::from_file( + File::open(&updater_path).unwrap_or_else(|_| { + panic!("failed to open updater bundle {}", updater_path.display()) + }), + )); + } + _ => (), + } + } + } + }); + + config.package.version = "0.1.0"; + + // bundle initial app version + build_app(&cli_bin_path, &manifest_dir, &config, false); + + let mut binary_cmd = if cfg!(windows) { + Command::new(root_dir.join("target/debug/app-updater.exe")) + } else if cfg!(target_os = "macos") { + Command::new(bundle_path(&root_dir, "0.1.0").join("Contents/MacOS/app-updater")) + } else { + if std::env::var("CI").map(|v| v == "true").unwrap_or_default() { + let mut c = Command::new("xvfb-run"); + c.arg("--auto-servernum") + .arg(bundle_path(&root_dir, "0.1.0")); + c + } else { + Command::new(bundle_path(&root_dir, "0.1.0")) + } + }; + + let status = binary_cmd.status().expect("failed to run app"); + + if !status.success() { + panic!("failed to run app"); + } +}