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
This commit is contained in:
Lucas Fernandes Nogueira 2025-10-06 14:06:04 -03:00 committed by GitHub
parent ed7c9a4100
commit 28a2f9bc55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 108 additions and 19 deletions

View File

@ -0,0 +1,6 @@
---
"tauri-cli": patch:bug
"@tauri-apps/cli": patch:bug
---
Fix iOS CLI usage after modifying the package name.

View File

@ -2,8 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{error::ErrorExt, Error};
use std::path::Path;
use crate::{
error::{Context, ErrorExt},
Error,
};
use std::path::{Path, PathBuf};
pub fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> crate::Result<()> {
let from = from.as_ref();
@ -28,3 +31,25 @@ pub fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> crate::Result<
std::fs::copy(from, to).fs_context("failed to copy file", from.to_path_buf())?;
Ok(())
}
/// Find an entry in a directory matching a glob pattern.
/// Currently does not traverse subdirectories.
// currently only used on macOS
#[allow(dead_code)]
pub fn find_in_directory(path: &Path, glob_pattern: &str) -> crate::Result<PathBuf> {
let pattern = glob::Pattern::new(glob_pattern)
.with_context(|| format!("failed to parse glob pattern {glob_pattern}"))?;
for entry in std::fs::read_dir(path)
.with_context(|| format!("failed to read directory {}", path.display()))?
{
let entry = entry.context("failed to read directory entry")?;
if pattern.matches_path(&entry.path()) {
return Ok(entry.path());
}
}
crate::error::bail!(
"No file found in {} matching {}",
path.display(),
glob_pattern
)
}

View File

@ -91,6 +91,7 @@ pub fn command(options: Options) -> Result<()> {
config.app(),
config.project_dir(),
MobileTarget::Android,
std::env::var("CI").is_ok(),
)?;
if !cli_options.config.is_empty() {

View File

@ -161,6 +161,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
config.app(),
config.project_dir(),
MobileTarget::Android,
options.ci,
)?;
let mut env = env(options.ci)?;

View File

@ -206,6 +206,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
config.app(),
config.project_dir(),
MobileTarget::Android,
false,
)?;
run_dev(
interface,

View File

@ -203,6 +203,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
config.app(),
config.project_dir(),
MobileTarget::Ios,
options.ci,
)?;
inject_resources(&config, tauri_config.lock().unwrap().as_ref().unwrap())?;

View File

@ -212,6 +212,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
config.app(),
config.project_dir(),
MobileTarget::Ios,
false,
)?;
inject_resources(&config, tauri_config.lock().unwrap().as_ref().unwrap())?;

View File

@ -138,6 +138,7 @@ pub fn command(options: Options) -> Result<()> {
config.app(),
config.project_dir(),
MobileTarget::Ios,
std::env::var("CI").is_ok(),
)?;
if !cli_options.config.is_empty() {

View File

@ -497,6 +497,7 @@ fn ensure_init(
app: &App,
project_dir: PathBuf,
target: Target,
noninteractive: bool,
) -> Result<()> {
if !project_dir.exists() {
crate::error::bail!(
@ -527,32 +528,83 @@ fn ensure_init(
}
#[cfg(target_os = "macos")]
Target::Ios => {
let pbxproj_contents = read_to_string(
project_dir
.join(format!("{}.xcodeproj", app.name()))
.join("project.pbxproj"),
)
.fs_context(
"missing project.pbxproj file in the Xcode project directory",
project_dir
.join(format!("{}.xcodeproj", app.name()))
.join("project.pbxproj"),
)?;
let xcodeproj_path = crate::helpers::fs::find_in_directory(&project_dir, "*.xcodeproj")
.with_context(|| format!("failed to locate xcodeproj in {}", project_dir.display()))?;
if !(pbxproj_contents.contains(ios::LIB_OUTPUT_FILE_NAME)
|| pbxproj_contents.contains(&format!("lib{}.a", app.lib_name())))
{
project_outdated_reasons
.push("you have modified your [lib.name] or [package.name] in the Cargo.toml file");
let xcodeproj_name = xcodeproj_path.file_stem().unwrap().to_str().unwrap();
if xcodeproj_name != app.name() {
let rename_targets = vec![
// first rename the entitlements
(
format!("{xcodeproj_name}_iOS/{xcodeproj_name}_iOS.entitlements"),
format!("{xcodeproj_name}_iOS/{}_iOS.entitlements", app.name()),
),
// then the scheme folder
(
format!("{xcodeproj_name}_iOS"),
format!("{}_iOS", app.name()),
),
(
format!("{xcodeproj_name}.xcodeproj"),
format!("{}.xcodeproj", app.name()),
),
];
let rename_info = rename_targets
.iter()
.map(|(from, to)| format!("- {from} to {to}"))
.collect::<Vec<_>>()
.join("\n");
log::error!(
"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}",
new_project_name = app.name(),
current_project_name = xcodeproj_name,
);
if noninteractive {
project_outdated_reasons
.push("you have modified your [lib.name] or [package.name] in the Cargo.toml file");
} else {
let confirm = crate::helpers::prompts::confirm(
"Do you want to apply the name change to the Xcode project?",
Some(true),
)
.unwrap_or_default();
if confirm {
for (from, to) in rename_targets {
std::fs::rename(project_dir.join(&from), project_dir.join(&to))
.with_context(|| format!("failed to rename {from} to {to}"))?;
}
// update scheme name in pbxproj
// identifier / product name are synchronized by the dev/build commands
let pbxproj_path =
project_dir.join(format!("{}.xcodeproj/project.pbxproj", app.name()));
let pbxproj_contents = std::fs::read_to_string(&pbxproj_path)
.with_context(|| format!("failed to read {}", pbxproj_path.display()))?;
std::fs::write(
&pbxproj_path,
pbxproj_contents.replace(
&format!("{xcodeproj_name}_iOS"),
&format!("{}_iOS", app.name()),
),
)
.with_context(|| format!("failed to write {}", pbxproj_path.display()))?;
} else {
project_outdated_reasons
.push("you have modified your [lib.name] or [package.name] in the Cargo.toml file");
}
}
}
// note: pbxproj is synchronied by the dev/build commands
}
}
if !project_outdated_reasons.is_empty() {
let reason = project_outdated_reasons.join(" and ");
crate::error::bail!(
"{} project directory is outdated because {reason}. Please run `tauri {} init` and try again.",
"{} project directory is outdated because {reason}. Please delete {}, run `tauri {} init` and try again.",
target.ide_name(),
project_dir.display(),
target.command_name(),
)
}