@bacons/xcode

repository·main·Indexed 42 days ago

https://github.com/evanbacon/xcode

A high-performance library for reading and manipulating Xcode project files (.pbxproj), schemes (.xcscheme), and workspaces (.xcworkspace). It provides tools for parsing XCConfig files, managing Swift Package Manager dependencies, and handling shared and user data, including breakpoints and workspace settings. Version 1.0.0-alpha.33.

Tokens
22.3K
Snippets
78
Records
92
Agent score
52%

What's inside @bacons/xcode

  1. Manage Xcode workspace file references and locations

    main

    Workspace file references use specific location specifiers:

    • group:path: Relative path from workspace root (most common)
    • self:: Self-reference (embedded workspace inside .xcodeproj)
    • container:path: Absolute container reference (rare)
    • absolute:path: Absolute file path
  2. Understand XCConfig conditional settings and includes

    main

    XCConfig files support conditional settings and include directives:

    Conditional Settings

    • SDK-specific: OTHER_LDFLAGS[sdk=iphoneos*] = -framework UIKit
    • Architecture-specific: ARCHS[arch=arm64] = arm64
    • Configuration-specific: GCC_OPTIMIZATION_LEVEL[config=Debug] = 0
    • Combined: LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64] = /usr/lib/arm64

    Include Directives

    • #include "File.xcconfig": Required include (throws if missing).
    • #include? "File.xcconfig": Optional include (silently ignored if missing).

    Variable Expansion

    • Use $(VARIABLE_NAME) for references.
    • Use $(inherited) to inherit from included files.
    • Use modifiers like :lower (e.g., $(PRODUCT_NAME:lower)).
  3. Understand file path resolution in pbxproj

    main

    In an Xcode project, file paths are resolved using the sourceTree attribute. The path attribute is relative to the directory specified by sourceTree. Supported sourceTree values include:

    • BUILT_PRODUCTS_DIR: Relative to the built products directory.
    • DEVELOPER_DIR: Relative to the developer directory.
    • SOURCE_ROOT: Relative to the project root.
    • SDKROOT: Relative to the SDK directory.
    • <group>: Relative to the containing PBXGroup's path.
    • <absolute>: The path is an absolute path.
  4. Add Swift Package Manager dependencies

    main

    You can add both remote and local Swift Package Manager (SPM) dependencies to an Xcode project. When using target.addSwiftPackageProduct(), the library automatically handles creating the XCSwiftPackageProductDependency, creating the PBXBuildFile, and adding it to the target's Frameworks build phase.

    import { XcodeProject } from "@bacons/xcode";
    import fs from "fs";
    
    const project = XcodeProject.open("/path/to/project.pbxproj");
    const rootProject = project.rootObject;
    
    // Add a remote Swift package to the project
    const packageRef = rootProject.addRemoteSwiftPackage({
      repositoryURL: "https://github.com/apple/swift-collections",
      requirement: {
        kind: "upToNextMajorVersion",
        minimumVersion: "1.0.0",
      },
    });
    
    // Add the package product to a target
    const target = rootProject.getMainAppTarget("ios");
    const productDep = target.addSwiftPackageProduct({
      productName: "Collections",
      package: packageRef,
    });
    
    // Save the project
    fs.writeFileSync("/path/to/project.pbxproj", build(project.toJSON()));
  5. Suppress macOS 32-bit deprecation warnings in workspaces

    main

    You can manage IDEWorkspaceChecks.plist files to prevent Xcode from recomputing checks. This is commonly used to suppress the macOS 32-bit deprecation warning.

    import { XCWorkspace, IDEWorkspaceChecks } from "@bacons/xcode";
    
    // Suppress the 32-bit deprecation warning via XCWorkspace
    const workspace = XCWorkspace.open("/path/to/MyApp.xcworkspace");
    workspace.setMac32BitWarningComputed();
    
    // Or work with IDEWorkspaceChecks directly
    const checks = IDEWorkspaceChecks.openOrCreate("/path/to/MyApp.xcworkspace");
    checks.mac32BitWarningComputed = true;
    checks.save();
    import { XCWorkspace, IDEWorkspaceChecks } from "@bacons/xcode";
    
    // Suppress the 32-bit deprecation warning
    const workspace = XCWorkspace.open("/path/to/MyApp.xcworkspace");
    workspace.setMac32BitWarningComputed();
    
    // Or work with IDEWorkspaceChecks directly
    const checks = IDEWorkspaceChecks.openOrCreate("/path/to/MyApp.xcworkspace");
    checks.mac32BitWarningComputed = true;
    checks.save();
  6. Manage Xcode workspaces with the High-level API

    main

    The XCWorkspace class provides a high-level interface for managing .xcworkspace files, including adding/removing projects and managing groups.

    import { XCWorkspace } from "@bacons/xcode";
    
    // Open an existing workspace
    const workspace = XCWorkspace.open("/path/to/MyApp.xcworkspace");
    
    // Get all project paths
    const projects = workspace.getProjectPaths();
    console.log(projects); // ["group:App.xcodeproj", "group:Pods/Pods.xcodeproj"]
    
    // Check if workspace contains a project
    if (!workspace.hasProject("NewProject.xcodeproj")) {
      workspace.addProject("NewProject.xcodeproj");
    }
    
    // Remove a project
    workspace.removeProject("OldProject.xcodeproj");
    
    // Work with groups
    const group = workspace.addGroup("Libraries", "group:Libraries");
    group.fileRefs?.push({ location: "group:Libraries/MyLib.xcodeproj" });
    
    // Save changes
    workspace.save();
    
    // Create a new workspace
    const newWorkspace = XCWorkspace.create("MyWorkspace");
    newWorkspace.addProject("App.xcodeproj");
    newWorkspace.addProject("Pods/Pods.xcodeproj");
    newWorkspace.save("/path/to/MyWorkspace.xcworkspace");
  7. Manage Xcode schemes with the High-level API

    main

    The high-level API allows you to load projects, retrieve existing schemes, create new schemes, and save them back to the project.

    import { XcodeProject, XCScheme } from "@bacons/xcode";
    
    // Load project and get schemes
    const project = XcodeProject.open("/path/to/project.pbxproj");
    const schemes = project.getSchemes();
    const appScheme = project.getScheme("App");
    
    // Create a new scheme
    const newScheme = XCScheme.create("MyNewScheme");
    newScheme.addBuildTarget({
      buildableIdentifier: "primary",
      blueprintIdentifier: "ABC123", // Target UUID
      buildableName: "App.app",
      blueprintName: "App",
      referencedContainer: "container:Project.xcodeproj",
    });
    newScheme.setLaunchEnvironmentVariable("API_URL", "https://api.example.com");
    newScheme.addLaunchArgument("-verbose");
    
    // Save to disk
    project.saveScheme(newScheme);
    
    // Create scheme for an existing target
    const mainTarget = project.rootObject.props.targets[0];
    const targetScheme = project.createSchemeForTarget(mainTarget);
    project.saveScheme(targetScheme);
  8. Understand the XCScheme structure

    main

    An XCScheme object represents an Xcode .xcscheme file, which is an XML document defining how targets are built, run, tested, profiled, analyzed, and archived. The root object contains configuration for various actions:

    • buildAction: Configuration for building targets.
    • testAction: Configuration for running tests.
    • launchAction: Configuration for running/launching the app.
    • profileAction: Configuration for Instruments profiling.
    • analyzeAction: Configuration for static analysis.
    • archiveAction: Configuration for archiving the app.

    Each action can include preActions and postActions (of type ExecutionAction) to run shell scripts at specific lifecycle points.

    export interface XCScheme {
      version?: string;
      lastUpgradeVersion?: string;
      buildAction?: BuildAction;
      testAction?: TestAction;
      launchAction?: LaunchAction;
      profileAction?: ProfileAction;
      analyzeAction?: AnalyzeAction;
      archiveAction?: ArchiveAction;
    }
  9. Manage Xcode shared data with XCSharedData

    main

    The XCSharedData class provides a high-level API for accessing and modifying Xcode shared data directories (xcshareddata). This includes managing schemes, scheme management settings, breakpoints, and workspace settings stored within .xcodeproj or .xcworkspace bundles.

    To use XCSharedData, you can either open an existing directory or create a new instance. Once initialized, you can read and modify various Xcode configurations and then save them back to disk.

    import { XCSharedData } from '@bacons/xcode';
    
    // Open an existing shared data directory
    const sharedData = XCSharedData.open('/path/to/project.xcodeproj/xcshareddata');
    
    // Or create a new instance to build data from scratch
    const newSharedData = XCSharedData.create();
    newSharedData.filePath = '/path/to/new/xcshareddata';
  10. Manage Xcode user data with XCUserData

    main

    The XCUserData class provides a high-level API for accessing and modifying per-developer Xcode data stored in .xcuserdatad directories. This includes user-specific schemes, scheme management settings, and breakpoints. Unlike shared data (xcshareddata), this data is typically unique to a developer and is not intended to be checked into version control.

    Key capabilities include:

    • Scheme Management: Accessing and saving .xcscheme files and xcschememanagement.plist.
    • Breakpoints: Reading and writing Breakpoints_v2.xcbkptlist files.
    • Discovery: Finding all user data directories within an xcuserdata folder.
    import { XCUserData } from '@bacons/xcode';
    
    // Open an existing user data directory
    const userData = XCUserData.open('path/to/Project.xcodeproj/xcuserdata/username.xcuserdatad');
    
    // Or discover all users in a project's xcuserdata folder
    const users = XCUserData.discoverUsers('path/to/Project.xcodeproj/xcuserdata');
  11. Create build files and file references in a project

    main

    Use PBXBuildFile.create and PBXFileReference.create to inject new files into an existing XcodeProject instance. The objects are automatically injected into the project's objects dictionary.

    import { PBXBuildFile, PBXFileReference } from "@bacons/xcode";
    import path from "path";
    
    // Assuming `project` is an instance of XcodeProject
    const file = PBXBuildFile.create(project, {
      fileRef: PBXFileReference.create(project, {
        path: "MyFile.swift",
        sourceTree: "<group>",
      }),
    });
    import { PBXBuildFile, PBXFileReference } from "@bacons/xcode";
    import path from "path";
    
    // Get `project` from XcodeProject.
    
    const file = PBXBuildFile.create(project, {
      fileRef: PBXFileReference.create(project, {
        path: "MyFile.swift",
        sourceTree: "<group>",
      }),
    });