mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-09-11 16:08:21 +03:00
parent
a7777ff485
commit
964d81ff01
@ -8,7 +8,8 @@
|
||||
"pref": "Performance Improvements",
|
||||
"changes": "What's Changed",
|
||||
"sec": "Security fixes",
|
||||
"deps": "Dependencies"
|
||||
"deps": "Dependencies",
|
||||
"breaking": "Breaking Changes"
|
||||
},
|
||||
"defaultChangeTag": "changes",
|
||||
"pkgManagers": {
|
||||
|
5
.changes/notarytool.md
Normal file
5
.changes/notarytool.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-bundler": minor:breaking
|
||||
---
|
||||
|
||||
The macOS notarization now uses `notarytool` as `altool` will be discontinued on November 2023. When authenticating with an API key, the key `.p8` file path must be provided in the `APPLE_API_KEY_PATH` environment variable.
|
@ -25,7 +25,7 @@
|
||||
use super::{
|
||||
super::common,
|
||||
icon::create_icns_file,
|
||||
sign::{notarize, notarize_auth_args, sign},
|
||||
sign::{notarize, notarize_auth, sign},
|
||||
};
|
||||
use crate::Settings;
|
||||
|
||||
@ -87,9 +87,9 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
// sign application
|
||||
sign(app_bundle_path.clone(), identity, settings, true)?;
|
||||
// notarization is required for distribution
|
||||
match notarize_auth_args() {
|
||||
Ok(args) => {
|
||||
notarize(app_bundle_path.clone(), args, settings)?;
|
||||
match notarize_auth() {
|
||||
Ok(auth) => {
|
||||
notarize(app_bundle_path.clone(), auth, settings)?;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("skipping app notarization, {}", e.to_string());
|
||||
|
@ -3,13 +3,19 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::{fs::File, io::prelude::*, path::PathBuf, process::Command};
|
||||
use std::{
|
||||
env::{var, var_os},
|
||||
ffi::OsString,
|
||||
fs::File,
|
||||
io::prelude::*,
|
||||
path::PathBuf,
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use crate::{bundle::common::CommandExt, Settings};
|
||||
use anyhow::Context;
|
||||
use log::info;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
|
||||
const KEYCHAIN_ID: &str = "tauri-build.keychain";
|
||||
const KEYCHAIN_PWD: &str = "tauri-build";
|
||||
@ -147,8 +153,8 @@ pub fn sign(
|
||||
info!(action = "Signing"; "{} with identity \"{}\"", path_to_sign.display(), identity);
|
||||
|
||||
let setup_keychain = if let (Some(certificate_encoded), Some(certificate_password)) = (
|
||||
std::env::var_os("APPLE_CERTIFICATE"),
|
||||
std::env::var_os("APPLE_CERTIFICATE_PASSWORD"),
|
||||
var_os("APPLE_CERTIFICATE"),
|
||||
var_os("APPLE_CERTIFICATE_PASSWORD"),
|
||||
) {
|
||||
// setup keychain allow you to import your certificate
|
||||
// for CI build
|
||||
@ -212,13 +218,18 @@ fn try_sign(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct NotarytoolSubmitOutput {
|
||||
id: String,
|
||||
status: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
pub fn notarize(
|
||||
app_bundle_path: PathBuf,
|
||||
auth_args: Vec<String>,
|
||||
auth: NotarizeAuth,
|
||||
settings: &Settings,
|
||||
) -> crate::Result<()> {
|
||||
let identifier = settings.bundle_identifier();
|
||||
|
||||
let bundle_stem = app_bundle_path
|
||||
.file_stem()
|
||||
.expect("failed to get bundle filename");
|
||||
@ -252,55 +263,47 @@ pub fn notarize(
|
||||
sign(zip_path.clone(), identity, settings, false)?;
|
||||
};
|
||||
|
||||
let mut notarize_args = vec![
|
||||
"altool",
|
||||
"--notarize-app",
|
||||
"-f",
|
||||
let notarize_args = vec![
|
||||
"notarytool",
|
||||
"submit",
|
||||
zip_path
|
||||
.to_str()
|
||||
.expect("failed to convert zip_path to string"),
|
||||
"--primary-bundle-id",
|
||||
identifier,
|
||||
"--wait",
|
||||
"--output-format",
|
||||
"json",
|
||||
];
|
||||
|
||||
if let Some(provider_short_name) = &settings.macos().provider_short_name {
|
||||
notarize_args.push("--asc-provider");
|
||||
notarize_args.push(provider_short_name);
|
||||
}
|
||||
|
||||
info!(action = "Notarizing"; "{}", app_bundle_path.display());
|
||||
|
||||
let output = Command::new("xcrun")
|
||||
.args(notarize_args)
|
||||
.args(auth_args.clone())
|
||||
.notarytool_args(&auth)
|
||||
.output_ok()
|
||||
.context("failed to upload app to Apple's notarization servers.")?;
|
||||
|
||||
// combine both stdout and stderr to support macOS below 10.15
|
||||
let mut notarize_response = std::str::from_utf8(&output.stdout)?.to_string();
|
||||
notarize_response.push('\n');
|
||||
notarize_response.push_str(std::str::from_utf8(&output.stderr)?);
|
||||
notarize_response.push('\n');
|
||||
if let Some(uuid) = Regex::new(r"\nRequestUUID = (.+?)\n")?
|
||||
.captures_iter(¬arize_response)
|
||||
.next()
|
||||
{
|
||||
info!("notarization started; waiting for Apple response...");
|
||||
|
||||
let uuid = uuid[1].to_string();
|
||||
get_notarization_status(uuid, auth_args)?;
|
||||
staple_app(app_bundle_path.clone())?;
|
||||
} else {
|
||||
return Err(
|
||||
anyhow::anyhow!(
|
||||
"failed to parse RequestUUID from upload output. {}",
|
||||
notarize_response
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("failed to notarize app").into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
if let Ok(submit_output) = serde_json::from_str::<NotarytoolSubmitOutput>(&output_str) {
|
||||
let log_message = format!(
|
||||
"Finished with status {} for id {} ({})",
|
||||
submit_output.status, submit_output.id, submit_output.message
|
||||
);
|
||||
if submit_output.status == "Accepted" {
|
||||
log::info!(action = "Notarizing"; "{}", log_message);
|
||||
staple_app(app_bundle_path)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("{log_message}").into())
|
||||
}
|
||||
} else {
|
||||
return Err(
|
||||
anyhow::anyhow!("failed to parse notarytool output as JSON: `{output_str}`").into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn staple_app(mut app_bundle_path: PathBuf) -> crate::Result<()> {
|
||||
@ -322,82 +325,66 @@ fn staple_app(mut app_bundle_path: PathBuf) -> crate::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_notarization_status(uuid: String, auth_args: Vec<String>) -> crate::Result<()> {
|
||||
std::thread::sleep(std::time::Duration::from_secs(10));
|
||||
let result = Command::new("xcrun")
|
||||
.args(vec!["altool", "--notarization-info", &uuid])
|
||||
.args(auth_args.clone())
|
||||
.output_ok();
|
||||
pub enum NotarizeAuth {
|
||||
AppleId {
|
||||
apple_id: String,
|
||||
password: String,
|
||||
},
|
||||
ApiKey {
|
||||
key: String,
|
||||
key_path: PathBuf,
|
||||
issuer: String,
|
||||
},
|
||||
}
|
||||
|
||||
if let Ok(output) = result {
|
||||
// combine both stdout and stderr to support macOS below 10.15
|
||||
let mut notarize_status = std::str::from_utf8(&output.stdout)?.to_string();
|
||||
notarize_status.push('\n');
|
||||
notarize_status.push_str(std::str::from_utf8(&output.stderr)?);
|
||||
notarize_status.push('\n');
|
||||
if let Some(status) = Regex::new(r"\n *Status: (.+?)\n")?
|
||||
.captures_iter(¬arize_status)
|
||||
.next()
|
||||
{
|
||||
let status = status[1].to_string();
|
||||
if status == "in progress" {
|
||||
get_notarization_status(uuid, auth_args)
|
||||
} else if status == "invalid" {
|
||||
Err(
|
||||
anyhow::anyhow!(format!(
|
||||
"Apple failed to notarize your app. {}",
|
||||
notarize_status
|
||||
))
|
||||
.into(),
|
||||
)
|
||||
} else if status != "success" {
|
||||
Err(
|
||||
anyhow::anyhow!(format!(
|
||||
"Unknown notarize status {}. {}",
|
||||
status, notarize_status
|
||||
))
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
get_notarization_status(uuid, auth_args)
|
||||
pub trait NotarytoolCmdExt {
|
||||
fn notarytool_args(&mut self, auth: &NotarizeAuth) -> &mut Self;
|
||||
}
|
||||
|
||||
impl NotarytoolCmdExt for Command {
|
||||
fn notarytool_args(&mut self, auth: &NotarizeAuth) -> &mut Self {
|
||||
match auth {
|
||||
NotarizeAuth::AppleId { apple_id, password } => self
|
||||
.arg("--apple-id")
|
||||
.arg(apple_id)
|
||||
.arg("--password")
|
||||
.arg(password),
|
||||
NotarizeAuth::ApiKey {
|
||||
key,
|
||||
key_path,
|
||||
issuer,
|
||||
} => self
|
||||
.arg("--key-id")
|
||||
.arg(key)
|
||||
.arg("--key")
|
||||
.arg(key_path)
|
||||
.arg("--issuer")
|
||||
.arg(issuer),
|
||||
}
|
||||
} else {
|
||||
get_notarization_status(uuid, auth_args)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notarize_auth_args() -> crate::Result<Vec<String>> {
|
||||
match (
|
||||
std::env::var_os("APPLE_ID"),
|
||||
std::env::var_os("APPLE_PASSWORD"),
|
||||
) {
|
||||
pub fn notarize_auth() -> crate::Result<NotarizeAuth> {
|
||||
match (var_os("APPLE_ID"), var_os("APPLE_PASSWORD")) {
|
||||
(Some(apple_id), Some(apple_password)) => {
|
||||
let apple_id = apple_id
|
||||
.to_str()
|
||||
.expect("failed to convert APPLE_ID to string")
|
||||
.to_string();
|
||||
let apple_password = apple_password
|
||||
let password = apple_password
|
||||
.to_str()
|
||||
.expect("failed to convert APPLE_PASSWORD to string")
|
||||
.to_string();
|
||||
Ok(vec![
|
||||
"-u".to_string(),
|
||||
apple_id,
|
||||
"-p".to_string(),
|
||||
apple_password,
|
||||
])
|
||||
Ok(NotarizeAuth::AppleId { apple_id, password })
|
||||
}
|
||||
_ => {
|
||||
match (std::env::var_os("APPLE_API_KEY"), std::env::var_os("APPLE_API_ISSUER")) {
|
||||
(Some(api_key), Some(api_issuer)) => {
|
||||
let api_key = api_key.to_str().expect("failed to convert APPLE_API_KEY to string").to_string();
|
||||
let api_issuer = api_issuer.to_str().expect("failed to convert APPLE_API_ISSUER to string").to_string();
|
||||
Ok(vec!["--apiKey".to_string(), api_key, "--apiIssuer".to_string(), api_issuer])
|
||||
match (var_os("APPLE_API_KEY"), var_os("APPLE_API_ISSUER"), var("APPLE_API_KEY_PATH")) {
|
||||
(Some(api_key), Some(api_issuer), Ok(key_path)) => {
|
||||
let key = api_key.to_str().expect("failed to convert APPLE_API_KEY to string").to_string();
|
||||
let issuer = api_issuer.to_str().expect("failed to convert APPLE_API_ISSUER to string").to_string();
|
||||
Ok(NotarizeAuth::ApiKey { key, key_path: key_path.into(), issuer })
|
||||
},
|
||||
_ => Err(anyhow::anyhow!("no APPLE_ID & APPLE_PASSWORD or APPLE_API_KEY & APPLE_API_ISSUER environment variables found").into())
|
||||
_ => Err(anyhow::anyhow!("no APPLE_ID & APPLE_PASSWORD or APPLE_API_KEY & APPLE_API_ISSUER & APPLE_API_KEY_PATH environment variables found").into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ These environment variables are inputs to the CLI which may have an equivalent C
|
||||
- This option will search the following directories in sequence for a private key file with the name of 'AuthKey_<api_key>.p8': './private_keys', '~/private_keys', '~/.private_keys', and '~/.appstoreconnect/private_keys'. Additionally, you can set environment variable $API_PRIVATE_KEYS_DIR or a user default API_PRIVATE_KEYS_DIR to specify the directory where your AuthKey file is located.
|
||||
- See [creating API keys](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api) for more information.
|
||||
- `APPLE_API_ISSUER` — Issuer ID. Required if `APPLE_API_KEY` is specified.
|
||||
- `APPLE_API_KEY_PATH` - path to the API key `.p8` file.
|
||||
- `APPLE_SIGNING_IDENTITY` — The identity used to code sign. Overwrites `tauri.conf.json > tauri > bundle > macOS > signingIdentity`.
|
||||
- `APPLE_PROVIDER_SHORT_NAME` — If your Apple ID is connected to multiple teams, you have to specify the provider short name of the team you want to use to notarize your app. Overwrites `tauri.conf.json > tauri > bundle > macOS > providerShortName`.
|
||||
- `CI` — If set, the CLI will run in CI mode and won't require any user interaction.
|
||||
|
Loading…
Reference in New Issue
Block a user