Skip to main content
The Bundle struct provides utilities for parsing, inspecting, and modifying iOS application bundles (.app directories).

Bundle struct

pub struct Bundle {
    bundle_dir: PathBuf,
    bundle_type: BundleType,
    info_plist_path: PathBuf,
}
Represents an iOS app bundle with its Info.plist and associated resources.

Creating a bundle

pub fn new<P: Into<PathBuf>>(bundle_path: P) -> Result<Self, Error>
Creates a Bundle instance from a path. The path must contain a valid Info.plist file.
use plume_utils::Bundle;
use std::path::PathBuf;

let bundle = Bundle::new("/path/to/MyApp.app")?;
let bundle = Bundle::new(PathBuf::from("/Applications/App.app"))?;

Bundle types

pub enum BundleType {
    App,
    AppExtension,
    Framework,
    Dylib,
    Unknown,
}
Determined from the file extension:
  • .appBundleType::App
  • .appexBundleType::AppExtension
  • .frameworkBundleType::Framework
  • .dylibBundleType::Dylib
should_have_entitlements
fn
pub fn should_have_entitlements(&self) -> bool
Returns true for App and AppExtension types
should_be_signed
fn
pub fn should_be_signed(&self) -> bool
Returns true for all types except Unknown

Accessors

bundle_dir
fn
pub fn bundle_dir(&self) -> &PathBuf
Returns the bundle’s directory path
bundle_type
fn
pub fn bundle_type(&self) -> &BundleType
Returns the bundle type

Discovering nested bundles

collect_nested_bundles
fn
pub fn collect_nested_bundles(&self) -> Result<Vec<Bundle>, Error>
Recursively finds all nested bundles (app extensions, frameworks, dylibs)
collect_bundles_sorted
fn
pub fn collect_bundles_sorted(&self) -> Result<Vec<Bundle>, Error>
Returns all bundles sorted by depth (deepest first). Used for signing in the correct order.
let bundle = Bundle::new("/path/to/App.app")?;
let all_bundles = bundle.collect_bundles_sorted()?;

for b in all_bundles {
    println!("Bundle: {:?} - {:?}", b.bundle_dir(), b.bundle_type());
}

Info.plist methods

These methods read and modify the bundle’s Info.plist file.

Reading values

Implements PlistInfoTrait:
get_name
fn
pub fn get_name(&self) -> Option<String>
Returns CFBundleDisplayName or CFBundleName
get_executable
fn
pub fn get_executable(&self) -> Option<String>
Returns CFBundleExecutable
get_bundle_identifier
fn
pub fn get_bundle_identifier(&self) -> Option<String>
Returns CFBundleIdentifier
get_bundle_name
fn
pub fn get_bundle_name(&self) -> Option<String>
Returns CFBundleName
get_version
fn
pub fn get_version(&self) -> Option<String>
Returns CFBundleShortVersionString
get_build_version
fn
pub fn get_build_version(&self) -> Option<String>
Returns CFBundleVersion

Writing values

set_info_plist_key
fn
pub fn set_info_plist_key<V: Into<Value>>(
    &self,
    key: &str,
    value: V
) -> Result<(), Error>
Sets an arbitrary key in Info.plist
set_name
fn
pub fn set_name(&self, new_name: &str) -> Result<(), Error>
Sets both CFBundleDisplayName and CFBundleName
set_version
fn
pub fn set_version(&self, new_version: &str) -> Result<(), Error>
Sets both CFBundleShortVersionString and CFBundleVersion
set_bundle_identifier
fn
pub fn set_bundle_identifier(&self, new_identifier: &str) -> Result<(), Error>
Sets CFBundleIdentifier
set_matching_identifier
fn
pub fn set_matching_identifier(
    &self,
    old_identifier: &str,
    new_identifier: &str,
) -> Result<(), Error>
Replaces all occurrences of old_identifier with new_identifier in:
  • CFBundleIdentifier
  • WKCompanionAppBundleIdentifier
  • NSExtension → NSExtensionAttributes → WKAppBundleIdentifier

Example: Reading bundle info

use plume_utils::{Bundle, PlistInfoTrait};

let bundle = Bundle::new("/path/to/App.app")?;

if let Some(name) = bundle.get_name() {
    println!("App name: {}", name);
}

if let Some(id) = bundle.get_bundle_identifier() {
    println!("Bundle ID: {}", id);
}

if let Some(version) = bundle.get_version() {
    println!("Version: {}", version);
}

if let Some(executable) = bundle.get_executable() {
    println!("Executable: {}", executable);
}

Example: Modifying bundle

use plume_utils::Bundle;

let bundle = Bundle::new("/path/to/App.app")?;

// Change app name
bundle.set_name("My Custom App")?;

// Change version
bundle.set_version("2.0.0")?;

// Change bundle identifier
bundle.set_bundle_identifier("com.example.newid")?;

// Set custom key
bundle.set_info_plist_key("UIFileSharingEnabled", true)?;
bundle.set_info_plist_key("MinimumOSVersion", "14.0")?;

Example: Updating identifiers across extensions

let bundle = Bundle::new("/path/to/App.app")?;
let old_id = bundle.get_bundle_identifier().unwrap();
let new_id = format!("{}.customteam", old_id);

// Update main app and all nested bundles
let all_bundles = bundle.collect_bundles_sorted()?;
for nested_bundle in all_bundles {
    nested_bundle.set_matching_identifier(&old_id, &new_id)?;
}

Example: Processing all bundles

use plume_utils::{Bundle, BundleType};

let bundle = Bundle::new("/path/to/App.app")?;
let bundles = bundle.collect_bundles_sorted()?;

for b in &bundles {
    match b.bundle_type() {
        BundleType::App => {
            println!("Main app: {:?}", b.bundle_dir());
        }
        BundleType::AppExtension => {
            println!("Extension: {:?}", b.bundle_dir());
        }
        BundleType::Framework => {
            println!("Framework: {:?}", b.bundle_dir());
        }
        BundleType::Dylib => {
            println!("Dylib: {:?}", b.bundle_dir());
        }
        BundleType::Unknown => {}
    }
}

Advanced: Working with nested structures

let bundle = Bundle::new("/path/to/App.app")?;
let bundles = bundle.collect_nested_bundles()?;

// Find watch extension
let watch_ext = bundles.iter().find(|b| {
    b.bundle_dir()
        .file_name()
        .and_then(|n| n.to_str())
        .map(|s| s.contains("Watch"))
        .unwrap_or(false)
});

if let Some(watch) = watch_ext {
    println!("Found watch extension: {:?}", watch.bundle_dir());
}

Build docs developers (and LLMs) love