Skip to content

Commit 28a2f9b

Browse files
authored
fix(cli): ensure Xcode project is up to date with Cargo project name (#14101)
* fix(cli): ensure Xcode project is up to date with Cargo project name closes #13542 * clippy
1 parent ed7c9a4 commit 28a2f9b

File tree

9 files changed

+109
-20
lines changed

9 files changed

+109
-20
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri-cli": patch:bug
3+
"@tauri-apps/cli": patch:bug
4+
---
5+
6+
Fix iOS CLI usage after modifying the package name.

crates/tauri-cli/src/helpers/fs.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// SPDX-License-Identifier: MIT
44

5-
use crate::{error::ErrorExt, Error};
6-
use std::path::Path;
5+
use crate::{
6+
error::{Context, ErrorExt},
7+
Error,
8+
};
9+
use std::path::{Path, PathBuf};
710

811
pub fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> crate::Result<()> {
912
let from = from.as_ref();
@@ -28,3 +31,25 @@ pub fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> crate::Result<
2831
std::fs::copy(from, to).fs_context("failed to copy file", from.to_path_buf())?;
2932
Ok(())
3033
}
34+
35+
/// Find an entry in a directory matching a glob pattern.
36+
/// Currently does not traverse subdirectories.
37+
// currently only used on macOS
38+
#[allow(dead_code)]
39+
pub fn find_in_directory(path: &Path, glob_pattern: &str) -> crate::Result<PathBuf> {
40+
let pattern = glob::Pattern::new(glob_pattern)
41+
.with_context(|| format!("failed to parse glob pattern {glob_pattern}"))?;
42+
for entry in std::fs::read_dir(path)
43+
.with_context(|| format!("failed to read directory {}", path.display()))?
44+
{
45+
let entry = entry.context("failed to read directory entry")?;
46+
if pattern.matches_path(&entry.path()) {
47+
return Ok(entry.path());
48+
}
49+
}
50+
crate::error::bail!(
51+
"No file found in {} matching {}",
52+
path.display(),
53+
glob_pattern
54+
)
55+
}

crates/tauri-cli/src/mobile/android/android_studio_script.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ pub fn command(options: Options) -> Result<()> {
9191
config.app(),
9292
config.project_dir(),
9393
MobileTarget::Android,
94+
std::env::var("CI").is_ok(),
9495
)?;
9596

9697
if !cli_options.config.is_empty() {

crates/tauri-cli/src/mobile/android/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
161161
config.app(),
162162
config.project_dir(),
163163
MobileTarget::Android,
164+
options.ci,
164165
)?;
165166

166167
let mut env = env(options.ci)?;

crates/tauri-cli/src/mobile/android/dev.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
206206
config.app(),
207207
config.project_dir(),
208208
MobileTarget::Android,
209+
false,
209210
)?;
210211
run_dev(
211212
interface,

crates/tauri-cli/src/mobile/ios/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
203203
config.app(),
204204
config.project_dir(),
205205
MobileTarget::Ios,
206+
options.ci,
206207
)?;
207208
inject_resources(&config, tauri_config.lock().unwrap().as_ref().unwrap())?;
208209

crates/tauri-cli/src/mobile/ios/dev.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
212212
config.app(),
213213
config.project_dir(),
214214
MobileTarget::Ios,
215+
false,
215216
)?;
216217
inject_resources(&config, tauri_config.lock().unwrap().as_ref().unwrap())?;
217218

crates/tauri-cli/src/mobile/ios/xcode_script.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ pub fn command(options: Options) -> Result<()> {
138138
config.app(),
139139
config.project_dir(),
140140
MobileTarget::Ios,
141+
std::env::var("CI").is_ok(),
141142
)?;
142143

143144
if !cli_options.config.is_empty() {

crates/tauri-cli/src/mobile/mod.rs

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ fn ensure_init(
497497
app: &App,
498498
project_dir: PathBuf,
499499
target: Target,
500+
noninteractive: bool,
500501
) -> Result<()> {
501502
if !project_dir.exists() {
502503
crate::error::bail!(
@@ -527,32 +528,83 @@ fn ensure_init(
527528
}
528529
#[cfg(target_os = "macos")]
529530
Target::Ios => {
530-
let pbxproj_contents = read_to_string(
531-
project_dir
532-
.join(format!("{}.xcodeproj", app.name()))
533-
.join("project.pbxproj"),
534-
)
535-
.fs_context(
536-
"missing project.pbxproj file in the Xcode project directory",
537-
project_dir
538-
.join(format!("{}.xcodeproj", app.name()))
539-
.join("project.pbxproj"),
540-
)?;
541-
542-
if !(pbxproj_contents.contains(ios::LIB_OUTPUT_FILE_NAME)
543-
|| pbxproj_contents.contains(&format!("lib{}.a", app.lib_name())))
544-
{
545-
project_outdated_reasons
546-
.push("you have modified your [lib.name] or [package.name] in the Cargo.toml file");
531+
let xcodeproj_path = crate::helpers::fs::find_in_directory(&project_dir, "*.xcodeproj")
532+
.with_context(|| format!("failed to locate xcodeproj in {}", project_dir.display()))?;
533+
534+
let xcodeproj_name = xcodeproj_path.file_stem().unwrap().to_str().unwrap();
535+
if xcodeproj_name != app.name() {
536+
let rename_targets = vec![
537+
// first rename the entitlements
538+
(
539+
format!("{xcodeproj_name}_iOS/{xcodeproj_name}_iOS.entitlements"),
540+
format!("{xcodeproj_name}_iOS/{}_iOS.entitlements", app.name()),
541+
),
542+
// then the scheme folder
543+
(
544+
format!("{xcodeproj_name}_iOS"),
545+
format!("{}_iOS", app.name()),
546+
),
547+
(
548+
format!("{xcodeproj_name}.xcodeproj"),
549+
format!("{}.xcodeproj", app.name()),
550+
),
551+
];
552+
let rename_info = rename_targets
553+
.iter()
554+
.map(|(from, to)| format!("- {from} to {to}"))
555+
.collect::<Vec<_>>()
556+
.join("\n");
557+
log::error!(
558+
"you have modified your package name from {current_project_name} to {new_project_name}\nWe need to apply the name change to the Xcode project, renaming:\n{rename_info}",
559+
new_project_name = app.name(),
560+
current_project_name = xcodeproj_name,
561+
);
562+
if noninteractive {
563+
project_outdated_reasons
564+
.push("you have modified your [lib.name] or [package.name] in the Cargo.toml file");
565+
} else {
566+
let confirm = crate::helpers::prompts::confirm(
567+
"Do you want to apply the name change to the Xcode project?",
568+
Some(true),
569+
)
570+
.unwrap_or_default();
571+
if confirm {
572+
for (from, to) in rename_targets {
573+
std::fs::rename(project_dir.join(&from), project_dir.join(&to))
574+
.with_context(|| format!("failed to rename {from} to {to}"))?;
575+
}
576+
577+
// update scheme name in pbxproj
578+
// identifier / product name are synchronized by the dev/build commands
579+
let pbxproj_path =
580+
project_dir.join(format!("{}.xcodeproj/project.pbxproj", app.name()));
581+
let pbxproj_contents = std::fs::read_to_string(&pbxproj_path)
582+
.with_context(|| format!("failed to read {}", pbxproj_path.display()))?;
583+
std::fs::write(
584+
&pbxproj_path,
585+
pbxproj_contents.replace(
586+
&format!("{xcodeproj_name}_iOS"),
587+
&format!("{}_iOS", app.name()),
588+
),
589+
)
590+
.with_context(|| format!("failed to write {}", pbxproj_path.display()))?;
591+
} else {
592+
project_outdated_reasons
593+
.push("you have modified your [lib.name] or [package.name] in the Cargo.toml file");
594+
}
595+
}
547596
}
597+
598+
// note: pbxproj is synchronied by the dev/build commands
548599
}
549600
}
550601

551602
if !project_outdated_reasons.is_empty() {
552603
let reason = project_outdated_reasons.join(" and ");
553604
crate::error::bail!(
554-
"{} project directory is outdated because {reason}. Please run `tauri {} init` and try again.",
605+
"{} project directory is outdated because {reason}. Please delete {}, run `tauri {} init` and try again.",
555606
target.ide_name(),
607+
project_dir.display(),
556608
target.command_name(),
557609
)
558610
}

0 commit comments

Comments
 (0)