From 4afa9f0ab0536a53424c2f20890cd30e614360b9 Mon Sep 17 00:00:00 2001 From: Daniel Szoke Date: Fri, 27 Sep 2024 12:57:27 +0200 Subject: [PATCH] feat(xcode): All uploads in foreground Perform all uploads from Xcode in the foreground. The new behavior is equivalent to the old `--force-foreground` behavior. Since uploads are now always executed in the foreground, (soft) deprecate the `--force-foreground` option by hiding it in the command help text. The `--force-foreground` option no longer has any effect, but passing it will continue to be possible at least until the next major release to keep the API backwards compatible. Background functionality is completely removed from Sentry-CLI with this change. Closes #2166 Fixes #2148 --- src/commands/debug_files/upload.rs | 70 ++-- src/commands/react_native/xcode.rs | 371 +++++++++--------- src/config.rs | 11 - src/utils/xcode.rs | 199 +--------- .../debug_files-upload-help.trycmd | 6 - .../_cases/upload_dif/upload_dif-help.trycmd | 6 - .../upload_dsym/upload_dsym-help.trycmd | 6 - 7 files changed, 209 insertions(+), 460 deletions(-) diff --git a/src/commands/debug_files/upload.rs b/src/commands/debug_files/upload.rs index 1d62cf463e..70d325d4d1 100644 --- a/src/commands/debug_files/upload.rs +++ b/src/commands/debug_files/upload.rs @@ -16,7 +16,7 @@ use crate::utils::args::ArgExt; use crate::utils::dif::{DifType, ObjectDifFeatures}; use crate::utils::dif_upload::{DifFormat, DifUpload}; use crate::utils::system::QuietExit; -use crate::utils::xcode::{InfoPlist, MayDetach}; +use crate::utils::xcode::InfoPlist; static DERIVED_DATA_FOLDER: &str = "Library/Developer/Xcode/DerivedData"; @@ -152,15 +152,12 @@ pub fn make_command(command: Command) -> Command { ) .arg( Arg::new("force_foreground") + .hide(true) .long("force-foreground") .action(ArgAction::SetTrue) .help( - "Wait for the process to finish.{n}\ - By default, the upload process will detach and continue in the \ - background when triggered from Xcode. When an error happens, \ - a dialog is shown. If this parameter is passed Xcode will wait \ - for the process to finish before the build finishes and output \ - will be shown in the Xcode build output.", + "DEPRECATED: Foreground uploads are now the default behavior.{n}\ + This flag has no effect and will be removed in a future version.", ), ) .arg( @@ -306,45 +303,38 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { return Ok(()); } - MayDetach::wrap("Debug symbol upload", |handle| { - // Optionally detach if run from Xcode - if !matches.get_flag("force_foreground") { - handle.may_detach()?; - } - - // Execute the upload - let (uploaded, has_processing_errors) = upload.upload()?; - - // Did we miss explicitly requested symbols? - if matches.get_flag("require_all") { - let required_ids: BTreeSet = matches - .get_many::("ids") - .unwrap_or_default() - .cloned() - .collect(); + // Execute the upload + let (uploaded, has_processing_errors) = upload.upload()?; - let found_ids = uploaded.into_iter().map(|dif| dif.id()).collect(); - let missing_ids: Vec<_> = required_ids.difference(&found_ids).collect(); + // Did we miss explicitly requested symbols? + if matches.get_flag("require_all") { + let required_ids: BTreeSet = matches + .get_many::("ids") + .unwrap_or_default() + .cloned() + .collect(); - if !missing_ids.is_empty() { - eprintln!(); - eprintln!("{}", style("Error: Some symbols could not be found!").red()); - eprintln!("The following symbols are still missing:"); - for id in missing_ids { - println!(" {id}"); - } + let found_ids = uploaded.into_iter().map(|dif| dif.id()).collect(); + let missing_ids: Vec<_> = required_ids.difference(&found_ids).collect(); - return Err(QuietExit(1).into()); + if !missing_ids.is_empty() { + eprintln!(); + eprintln!("{}", style("Error: Some symbols could not be found!").red()); + eprintln!("The following symbols are still missing:"); + for id in missing_ids { + println!(" {id}"); } - } - // report a non 0 status code if the server encountered issues. - if has_processing_errors { - eprintln!(); - eprintln!("{}", style("Error: some symbols did not process correctly")); return Err(QuietExit(1).into()); } + } + + // report a non 0 status code if the server encountered issues. + if has_processing_errors { + eprintln!(); + eprintln!("{}", style("Error: some symbols did not process correctly")); + return Err(QuietExit(1).into()); + } - Ok(()) - }) + Ok(()) } diff --git a/src/commands/react_native/xcode.rs b/src/commands/react_native/xcode.rs index 118ea1f06f..8545abffff 100644 --- a/src/commands/react_native/xcode.rs +++ b/src/commands/react_native/xcode.rs @@ -22,7 +22,7 @@ use crate::utils::file_upload::UploadContext; use crate::utils::fs::TempFile; use crate::utils::sourcemaps::SourceMapProcessor; use crate::utils::system::propagate_exit_status; -use crate::utils::xcode::{InfoPlist, MayDetach}; +use crate::utils::xcode::InfoPlist; #[derive(Serialize, Deserialize, Default, Debug)] struct SourceMapReport { @@ -207,197 +207,197 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { let hermesc = find_hermesc(); info!("Using hermesc interpreter '{}'", &hermesc); - MayDetach::wrap("React native symbol handling", |md| { - let bundle_path; - let sourcemap_path; - let bundle_url; - let sourcemap_url; - let bundle_file; - let sourcemap_file; - - // If we have a fetch URL we need to fetch them from there now. In that - // case we do indeed fetch it right from the running packager and then - // store it in temporary files for later consumption. - if let Some(url) = fetch_url { - if !matches.get_flag("force_foreground") { - md.may_detach()?; - } - let api = Api::current(); - let url = url.trim_end_matches('/'); - bundle_file = TempFile::create()?; - bundle_path = bundle_file.path().to_path_buf(); - bundle_url = "~/index.ios.bundle".to_string(); - sourcemap_file = TempFile::create()?; - sourcemap_path = sourcemap_file.path().to_path_buf(); - sourcemap_url = "~/index.ios.map".to_string(); - - // wait up to 10 seconds for the server to be up. - if !api.wait_until_available(url, Duration::seconds(10))? { - bail!("Error: react-native packager did not respond in time"); - } - - api.download( - &format!("{url}/index.ios.bundle?platform=ios&dev=true"), - &mut bundle_file.open()?, - )?; - api.download( - &format!("{url}/index.ios.map?platform=ios&dev=true"), - &mut sourcemap_file.open()?, - )?; - - // This is the case where we need to hook into the release process to - // collect sourcemaps when they are generated. - // - // this invokes via an indirection of sentry-cli our wrap_call() below. - // What is happening behind the scenes is that we switch out NODE_BINARY - // for ourselves which is what the react-native build script normally - // invokes. Because we export __SENTRY_RN_WRAP_XCODE_CALL=1, the main - // sentry-cli script will invoke our wrap_call() function below. - // - // That will then attempt to figure out that a react-native bundle is - // happening to the build script, parse out the arguments, add additional - // arguments if needed and then report the parsed arguments to a temporary - // JSON file we load back below. - // - // We do the same for Hermes Compiler to retrieve the bundle file and - // the same for the combine source maps for the final Hermes source map. - // - // With that we we then have all the information we need to invoke the - // upload process. - } else { - let mut command = process::Command::new(&script); - command - .env("NODE_BINARY", env::current_exe()?.to_str().unwrap()) - .env("SENTRY_RN_REAL_NODE_BINARY", &node) - .env( - "SENTRY_RN_SOURCEMAP_REPORT", - report_file.path().to_str().unwrap(), - ) - .env("__SENTRY_RN_WRAP_XCODE_CALL", "1"); - - if is_hermes_enabled(&hermesc) { - command - .env("HERMES_CLI_PATH", env::current_exe()?.to_str().unwrap()) - .env("SENTRY_RN_REAL_HERMES_CLI_PATH", &hermesc); - } - - let rv = command.spawn()?.wait()?; - propagate_exit_status(rv); - - if !matches.get_flag("force_foreground") { - md.may_detach()?; - } - let mut f = fs::File::open(report_file.path())?; - let report: SourceMapReport = serde_json::from_reader(&mut f).unwrap_or_else(|_| { - let format_err = format!( - "File {} doesn't contain a valid JSON data.", - report_file.path().display() - ); - panic!("{}", format_err); - }); - let (Some(packager_bundle_path), Some(packager_sourcemap_path)) = - (report.packager_bundle_path, report.packager_sourcemap_path) - else { - println!("Warning: build produced no packager sourcemaps."); - return Ok(()); - }; - - // If Hermes emitted source map we have to use it - if let (Some(hermes_bundle_path), Some(hermes_sourcemap_path)) = - (report.hermes_bundle_path, report.hermes_sourcemap_path) - { - bundle_path = hermes_bundle_path.clone(); - sourcemap_path = hermes_sourcemap_path.clone(); - println!("Using Hermes bundle and combined source map."); - - // If Hermes emitted only bundle or Hermes was disabled use packager bundle and source map - } else { - bundle_path = packager_bundle_path; - sourcemap_path = packager_sourcemap_path; - println!("Using React Native Packager bundle and source map."); - } - bundle_url = format!("~/{}", bundle_path.file_name().unwrap().to_string_lossy()); - sourcemap_url = format!( - "~/{}", - sourcemap_path.file_name().unwrap().to_string_lossy() - ); + let bundle_path; + let sourcemap_path; + let bundle_url; + let sourcemap_url; + let bundle_file; + let sourcemap_file; + + // If we have a fetch URL we need to fetch them from there now. In that + // case we do indeed fetch it right from the running packager and then + // store it in temporary files for later consumption. + if let Some(url) = fetch_url { + let api = Api::current(); + let url = url.trim_end_matches('/'); + bundle_file = TempFile::create()?; + bundle_path = bundle_file.path().to_path_buf(); + bundle_url = "~/index.ios.bundle".to_string(); + sourcemap_file = TempFile::create()?; + sourcemap_path = sourcemap_file.path().to_path_buf(); + sourcemap_url = "~/index.ios.map".to_string(); + + // wait up to 10 seconds for the server to be up. + if !api.wait_until_available(url, Duration::seconds(10))? { + bail!("Error: react-native packager did not respond in time"); } - // now that we have all the data, we can now process and upload the - // sourcemaps. - println!("Processing react-native sourcemaps for Sentry upload."); - info!(" bundle path: {}", bundle_path.display()); - info!(" sourcemap path: {}", sourcemap_path.display()); - - let mut processor = SourceMapProcessor::new(); - processor.add(&bundle_url, ReleaseFileSearch::collect_file(bundle_path)?)?; - processor.add( - &sourcemap_url, - ReleaseFileSearch::collect_file(sourcemap_path)?, + api.download( + &format!("{url}/index.ios.bundle?platform=ios&dev=true"), + &mut bundle_file.open()?, + )?; + api.download( + &format!("{url}/index.ios.map?platform=ios&dev=true"), + &mut sourcemap_file.open()?, )?; - processor.rewrite(&[base.parent().unwrap().to_str().unwrap()])?; - processor.add_sourcemap_references()?; - processor.add_debug_id_references()?; - let api = Api::current(); - let chunk_upload_options = api.authenticated()?.get_chunk_upload_options(&org)?; + // This is the case where we need to hook into the release process to + // collect sourcemaps when they are generated. + // + // this invokes via an indirection of sentry-cli our wrap_call() below. + // What is happening behind the scenes is that we switch out NODE_BINARY + // for ourselves which is what the react-native build script normally + // invokes. Because we export __SENTRY_RN_WRAP_XCODE_CALL=1, the main + // sentry-cli script will invoke our wrap_call() function below. + // + // That will then attempt to figure out that a react-native bundle is + // happening to the build script, parse out the arguments, add additional + // arguments if needed and then report the parsed arguments to a temporary + // JSON file we load back below. + // + // We do the same for Hermes Compiler to retrieve the bundle file and + // the same for the combine source maps for the final Hermes source map. + // + // With that we we then have all the information we need to invoke the + // upload process. + } else { + let mut command = process::Command::new(&script); + command + .env("NODE_BINARY", env::current_exe()?.to_str().unwrap()) + .env("SENTRY_RN_REAL_NODE_BINARY", &node) + .env( + "SENTRY_RN_SOURCEMAP_REPORT", + report_file.path().to_str().unwrap(), + ) + .env("__SENTRY_RN_WRAP_XCODE_CALL", "1"); + + if is_hermes_enabled(&hermesc) { + command + .env("HERMES_CLI_PATH", env::current_exe()?.to_str().unwrap()) + .env("SENTRY_RN_REAL_HERMES_CLI_PATH", &hermesc); + } - let dist_from_env = env::var("SENTRY_DIST"); - let release_from_env = env::var("SENTRY_RELEASE"); + let rv = command.spawn()?.wait()?; + propagate_exit_status(rv); - let wait_for_secs = matches.get_one::("wait_for").copied(); - let wait = matches.get_flag("wait") || wait_for_secs.is_some(); - let max_wait = wait_for_secs.map_or(DEFAULT_MAX_WAIT, std::time::Duration::from_secs); + let mut f = fs::File::open(report_file.path())?; + let report: SourceMapReport = serde_json::from_reader(&mut f).unwrap_or_else(|_| { + let format_err = format!( + "File {} doesn't contain a valid JSON data.", + report_file.path().display() + ); + panic!("{}", format_err); + }); + let (Some(packager_bundle_path), Some(packager_sourcemap_path)) = + (report.packager_bundle_path, report.packager_sourcemap_path) + else { + println!("Warning: build produced no packager sourcemaps."); + return Ok(()); + }; - if dist_from_env.is_err() - && release_from_env.is_err() - && matches.get_flag("no_auto_release") + // If Hermes emitted source map we have to use it + if let (Some(hermes_bundle_path), Some(hermes_sourcemap_path)) = + (report.hermes_bundle_path, report.hermes_sourcemap_path) { - processor.upload(&UploadContext { - org: &org, - project: Some(&project), - release: None, - dist: None, - note: None, - wait, - max_wait, - dedupe: false, - chunk_upload_options: chunk_upload_options.as_ref(), - })?; + bundle_path = hermes_bundle_path.clone(); + sourcemap_path = hermes_sourcemap_path.clone(); + println!("Using Hermes bundle and combined source map."); + + // If Hermes emitted only bundle or Hermes was disabled use packager bundle and source map } else { - let (dist, release_name) = match (&dist_from_env, &release_from_env) { - (Err(_), Err(_)) => { - // Neither environment variable is present, attempt to parse Info.plist - info!("Parsing Info.plist"); - match InfoPlist::discover_from_env() { - Ok(Some(plist)) => { - // Successfully discovered and parsed Info.plist - let dist_string = plist.build().to_string(); - let release_string = format!( - "{}@{}+{}", - plist.bundle_id(), - plist.version(), - dist_string - ); - info!("Parse result from Info.plist: {:?}", &plist); - (Some(dist_string), Some(release_string)) - } - _ => { - bail!("Info.plist was not found or an parsing error occurred"); - } + bundle_path = packager_bundle_path; + sourcemap_path = packager_sourcemap_path; + println!("Using React Native Packager bundle and source map."); + } + bundle_url = format!("~/{}", bundle_path.file_name().unwrap().to_string_lossy()); + sourcemap_url = format!( + "~/{}", + sourcemap_path.file_name().unwrap().to_string_lossy() + ); + } + + // now that we have all the data, we can now process and upload the + // sourcemaps. + println!("Processing react-native sourcemaps for Sentry upload."); + info!(" bundle path: {}", bundle_path.display()); + info!(" sourcemap path: {}", sourcemap_path.display()); + + let mut processor = SourceMapProcessor::new(); + processor.add(&bundle_url, ReleaseFileSearch::collect_file(bundle_path)?)?; + processor.add( + &sourcemap_url, + ReleaseFileSearch::collect_file(sourcemap_path)?, + )?; + processor.rewrite(&[base.parent().unwrap().to_str().unwrap()])?; + processor.add_sourcemap_references()?; + processor.add_debug_id_references()?; + + let api = Api::current(); + let chunk_upload_options = api.authenticated()?.get_chunk_upload_options(&org)?; + + let dist_from_env = env::var("SENTRY_DIST"); + let release_from_env = env::var("SENTRY_RELEASE"); + + let wait_for_secs = matches.get_one::("wait_for").copied(); + let wait = matches.get_flag("wait") || wait_for_secs.is_some(); + let max_wait = wait_for_secs.map_or(DEFAULT_MAX_WAIT, std::time::Duration::from_secs); + + if dist_from_env.is_err() && release_from_env.is_err() && matches.get_flag("no_auto_release") { + processor.upload(&UploadContext { + org: &org, + project: Some(&project), + release: None, + dist: None, + note: None, + wait, + max_wait, + dedupe: false, + chunk_upload_options: chunk_upload_options.as_ref(), + })?; + } else { + let (dist, release_name) = match (&dist_from_env, &release_from_env) { + (Err(_), Err(_)) => { + // Neither environment variable is present, attempt to parse Info.plist + info!("Parsing Info.plist"); + match InfoPlist::discover_from_env() { + Ok(Some(plist)) => { + // Successfully discovered and parsed Info.plist + let dist_string = plist.build().to_string(); + let release_string = + format!("{}@{}+{}", plist.bundle_id(), plist.version(), dist_string); + info!("Parse result from Info.plist: {:?}", &plist); + (Some(dist_string), Some(release_string)) + } + _ => { + bail!("Info.plist was not found or an parsing error occurred"); } } - // At least one environment variable is present, use the values from the environment - _ => (dist_from_env.ok(), release_from_env.ok()), - }; + } + // At least one environment variable is present, use the values from the environment + _ => (dist_from_env.ok(), release_from_env.ok()), + }; - match matches.get_many::("dist") { - None => { + match matches.get_many::("dist") { + None => { + processor.upload(&UploadContext { + org: &org, + project: Some(&project), + release: release_name.as_deref(), + dist: dist.as_deref(), + note: None, + wait, + max_wait, + dedupe: false, + chunk_upload_options: chunk_upload_options.as_ref(), + })?; + } + Some(dists) => { + for dist in dists { processor.upload(&UploadContext { org: &org, project: Some(&project), release: release_name.as_deref(), - dist: dist.as_deref(), + dist: Some(dist), note: None, wait, max_wait, @@ -405,26 +405,11 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { chunk_upload_options: chunk_upload_options.as_ref(), })?; } - Some(dists) => { - for dist in dists { - processor.upload(&UploadContext { - org: &org, - project: Some(&project), - release: release_name.as_deref(), - dist: Some(dist), - note: None, - wait, - max_wait, - dedupe: false, - chunk_upload_options: chunk_upload_options.as_ref(), - })?; - } - } } } + } - Ok(()) - }) + Ok(()) } pub fn wrap_call() -> Result<()> { diff --git a/src/config.rs b/src/config.rs index 54bf8a5625..1a8be2c11c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -439,17 +439,6 @@ impl Config { ) } - /// Returns true if notifications should be displayed. - /// We only use this function in the macOS binary. - #[cfg(target_os = "macos")] - pub fn show_notifications(&self) -> Result { - Ok(self - .ini - .get_from(Some("ui"), "show_notifications") - .map(|x| x == "true") - .unwrap_or(true)) - } - /// Returns the maximum DIF upload size pub fn get_max_dif_archive_size(&self) -> u64 { let key = "max_upload_size"; diff --git a/src/utils/xcode.rs b/src/utils/xcode.rs index 802a191572..33d303ac56 100644 --- a/src/utils/xcode.rs +++ b/src/utils/xcode.rs @@ -15,15 +15,9 @@ use regex::Regex; use serde::Deserialize; #[cfg(target_os = "macos")] -use { - libc::getpid, - mac_process_info, osascript, - unix_daemonize::{daemonize_redirect, ChdirMode}, -}; +use {libc::getpid, mac_process_info}; use crate::utils::fs::SeekRead; -#[cfg(target_os = "macos")] -use crate::utils::fs::TempFile; use crate::utils::system::expand_vars; #[derive(Deserialize, Debug)] @@ -375,121 +369,6 @@ impl InfoPlist { } } -/// Helper struct that allows the current execution to detach from -/// the xcode console and continue in the background. This becomes -/// a dummy shim for non xcode runs or platforms. -pub struct MayDetach<'a> { - #[cfg(target_os = "macos")] // only used in macOS binary - output_file: Option, - #[allow(dead_code)] - task_name: &'a str, -} - -impl<'a> MayDetach<'a> { - fn new(task_name: &'a str) -> MayDetach<'a> { - #[cfg(target_os = "macos")] - { - MayDetach { - output_file: None, - task_name, - } - } - - #[cfg(not(target_os = "macos"))] - MayDetach { task_name } - } - - /// Returns true if we are deteached from xcode. - #[cfg(target_os = "macos")] - pub fn is_detached(&self) -> bool { - self.output_file.is_some() - } - - /// If we are launched from xcode this detaches us from the xcode console - /// and continues execution in the background. From this moment on output - /// is captured and the user is notified with notifications. - #[cfg(target_os = "macos")] - pub fn may_detach(&mut self) -> Result { - if !launched_from_xcode() { - return Ok(false); - } - - let output_file = TempFile::create()?; - - println!("Continuing in background."); - println!("Output is redirected to {}", output_file.path().display()); - show_notification("Sentry", &format!("{} starting", self.task_name))?; - - daemonize_redirect( - Some(output_file.path()), - Some(output_file.path()), - ChdirMode::NoChdir, - ) - .unwrap(); - self.output_file = Some(output_file); - Ok(true) - } - - /// For non mac platforms this just never detaches. - #[cfg(not(target_os = "macos"))] - pub fn may_detach(&mut self) -> Result { - Ok(false) - } - - /// Wraps the execution of a code block. Does not detach until someone - /// calls into `may_detach`. - #[cfg(target_os = "macos")] - pub fn wrap) -> Result>( - task_name: &'a str, - f: F, - ) -> Result { - use std::time::Duration; - - let mut md = MayDetach::new(task_name); - match f(&mut md) { - Ok(x) => { - md.show_done()?; - Ok(x) - } - Err(err) => { - if let Some(ref output_file) = md.output_file { - crate::utils::system::print_error(&err); - if md.show_critical_info()? { - open::that(output_file.path())?; - std::thread::sleep(Duration::from_millis(5000)); - } - } - Err(err) - } - } - } - - /// Dummy wrap call that never detaches for non mac platforms. - #[cfg(not(target_os = "macos"))] - pub fn wrap Result>(task_name: &'a str, f: F) -> Result { - f(&mut MayDetach::new(task_name)) - } - - #[cfg(target_os = "macos")] - fn show_critical_info(&self) -> Result { - show_critical_info( - &format!("{} failed", self.task_name), - "The Sentry build step failed while running in the background. \ - You can ignore this error or view details to attempt to resolve \ - it. Ignoring it might cause your crashes not to be handled \ - properly.", - ) - } - - #[cfg(target_os = "macos")] - fn show_done(&self) -> Result<()> { - if self.is_detached() { - show_notification("Sentry", &format!("{} finished", self.task_name))?; - } - Ok(()) - } -} - /// Returns true if we were invoked from xcode #[cfg(target_os = "macos")] pub fn launched_from_xcode() -> bool { @@ -513,82 +392,6 @@ pub fn launched_from_xcode() -> bool { false } -/// Shows a dialog in xcode and blocks. The dialog will have a title and a -/// message as well as the buttons "Show details" and "Ignore". Returns -/// `true` if the `show details` button has been pressed. -#[cfg(target_os = "macos")] -pub fn show_critical_info(title: &str, message: &str) -> Result { - use serde::Serialize; - - lazy_static! { - static ref SCRIPT: osascript::JavaScript = osascript::JavaScript::new( - " - var App = Application('XCode'); - App.includeStandardAdditions = true; - return App.displayAlert($params.title, { - message: $params.message, - as: \"critical\", - buttons: [\"Show details\", \"Ignore\"] - }); - " - ); - } - - #[derive(Serialize)] - struct AlertParams<'a> { - title: &'a str, - message: &'a str, - } - - #[derive(Debug, Deserialize)] - struct AlertResult { - #[serde(rename = "buttonReturned")] - button: String, - } - - let rv: AlertResult = SCRIPT - .execute_with_params(AlertParams { title, message }) - .context("Failed to display Xcode dialog")?; - - Ok(&rv.button != "Ignore") -} - -/// Shows a notification in xcode -#[cfg(target_os = "macos")] -pub fn show_notification(title: &str, message: &str) -> Result<()> { - use crate::config::Config; - use serde::Serialize; - - lazy_static! { - static ref SCRIPT: osascript::JavaScript = osascript::JavaScript::new( - " - var App = Application.currentApplication(); - App.includeStandardAdditions = true; - App.displayNotification($params.message, { - withTitle: $params.title - }); - " - ); - } - - let config = Config::current(); - if !config.show_notifications()? { - return Ok(()); - } - - #[derive(Serialize)] - struct NotificationParams<'a> { - title: &'a str, - message: &'a str, - } - - SCRIPT - .execute_with_params::<_, ()>(NotificationParams { title, message }) - .context("Failed to display Xcode notification")?; - - Ok(()) -} - #[test] fn test_expansion() { let mut vars = HashMap::new(); diff --git a/tests/integration/_cases/debug_files/debug_files-upload-help.trycmd b/tests/integration/_cases/debug_files/debug_files-upload-help.trycmd index b7f7fe405e..03cde26869 100644 --- a/tests/integration/_cases/debug_files/debug_files-upload-help.trycmd +++ b/tests/integration/_cases/debug_files/debug_files-upload-help.trycmd @@ -52,12 +52,6 @@ Options: This runs all steps for the processing but does not trigger the upload. This is useful if you just want to verify the setup or skip the upload in tests. - --force-foreground Wait for the process to finish. - By default, the upload process will detach and continue in the - background when triggered from Xcode. When an error happens, a - dialog is shown. If this parameter is passed Xcode will wait for - the process to finish before the build finishes and output will be - shown in the Xcode build output. --include-sources Include sources from the local file system and upload them as source bundles. --wait Wait for the server to fully process uploaded files. Errors can diff --git a/tests/integration/_cases/upload_dif/upload_dif-help.trycmd b/tests/integration/_cases/upload_dif/upload_dif-help.trycmd index 2775f2ae28..be378c8e03 100644 --- a/tests/integration/_cases/upload_dif/upload_dif-help.trycmd +++ b/tests/integration/_cases/upload_dif/upload_dif-help.trycmd @@ -52,12 +52,6 @@ Options: This runs all steps for the processing but does not trigger the upload. This is useful if you just want to verify the setup or skip the upload in tests. - --force-foreground Wait for the process to finish. - By default, the upload process will detach and continue in the - background when triggered from Xcode. When an error happens, a - dialog is shown. If this parameter is passed Xcode will wait for - the process to finish before the build finishes and output will be - shown in the Xcode build output. --include-sources Include sources from the local file system and upload them as source bundles. --wait Wait for the server to fully process uploaded files. Errors can diff --git a/tests/integration/_cases/upload_dsym/upload_dsym-help.trycmd b/tests/integration/_cases/upload_dsym/upload_dsym-help.trycmd index fc07d840c5..53045a3197 100644 --- a/tests/integration/_cases/upload_dsym/upload_dsym-help.trycmd +++ b/tests/integration/_cases/upload_dsym/upload_dsym-help.trycmd @@ -52,12 +52,6 @@ Options: This runs all steps for the processing but does not trigger the upload. This is useful if you just want to verify the setup or skip the upload in tests. - --force-foreground Wait for the process to finish. - By default, the upload process will detach and continue in the - background when triggered from Xcode. When an error happens, a - dialog is shown. If this parameter is passed Xcode will wait for - the process to finish before the build finishes and output will be - shown in the Xcode build output. --include-sources Include sources from the local file system and upload them as source bundles. --wait Wait for the server to fully process uploaded files. Errors can