Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 79 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,84 @@ Workspace file references use location specifiers:
- `container:path` - Absolute container reference (rare)
- `absolute:path` - Absolute file path

## XCConfig Support

Parse and manipulate Xcode configuration files (`.xcconfig`). These files define build settings that can be shared across targets and configurations.

### Low-level API

```ts
import * as xcconfig from "@bacons/xcode/xcconfig";
import fs from "fs";

// Parse an xcconfig string
const config = xcconfig.parse(`
#include "Base.xcconfig"
PRODUCT_NAME = MyApp
OTHER_LDFLAGS[sdk=iphoneos*] = -framework UIKit
`);

// Parse from file (resolves #include directives)
const config = xcconfig.parseFile("/path/to/Project.xcconfig");

// Flatten build settings (merges includes, applies conditions)
const allSettings = xcconfig.flattenBuildSettings(config);

// Filter by platform conditions
const iosSettings = xcconfig.flattenBuildSettings(config, {
sdk: "iphoneos",
arch: "arm64",
config: "Release",
});

// Serialize back to xcconfig format
const output = xcconfig.build(config);
fs.writeFileSync("/path/to/Project.xcconfig", output);
```

### Conditional Settings

XCConfig supports conditional settings based on SDK, architecture, and configuration:

```
// SDK-specific settings
OTHER_LDFLAGS[sdk=iphoneos*] = -framework UIKit
OTHER_LDFLAGS[sdk=macosx*] = -framework AppKit

// Architecture-specific settings
ARCHS[arch=arm64] = arm64
ARCHS[arch=x86_64] = x86_64

// Configuration-specific settings
GCC_OPTIMIZATION_LEVEL[config=Debug] = 0
GCC_OPTIMIZATION_LEVEL[config=Release] = s

// Combined conditions
LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64] = /usr/lib/arm64
```

### Include Directives

```
// Required include - throws if file not found
#include "Base.xcconfig"

// Optional include - silently ignored if file not found
#include? "Optional.xcconfig"
```

### Variable Expansion

XCConfig supports variable references and the `$(inherited)` keyword:

```
// Reference other settings
PRODUCT_BUNDLE_IDENTIFIER = $(BUNDLE_ID_PREFIX).$(PRODUCT_NAME:lower)

// Inherit from included files
OTHER_LDFLAGS = $(inherited) -framework UIKit
```

## Solution

- Uses a hand-optimized single-pass parser that is 11x faster than the legacy `xcode` package (which uses PEG.js).
Expand Down Expand Up @@ -307,7 +385,7 @@ We support the following types: `Object`, `Array`, `Data`, `String`. Notably, we
- [ ] Create robust xcode projects from scratch.
- [ ] Skills.
- [ ] Import from other tools.
- [ ] **XCConfig** Parsing: `.xcconfig` file parsing with `#include` support and build settings flattening.
- [x] **XCConfig** Parsing: `.xcconfig` file parsing with `#include` support and build settings flattening.
- [ ] **XCSharedData**: Shared project data directory (schemes, breakpoints, workspace settings).
- [ ] **XCSchemeManagement**: Scheme ordering, visibility, and management plist. Controls which schemes appear and in what order in Xcode.
- [ ] **XCUserData**: User-specific data (breakpoints, UI state). Useful for tooling that manages user preferences.
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
"./workspace": {
"types": "./build/workspace/index.d.ts",
"default": "./build/workspace/index.js"
},
"./xcconfig": {
"types": "./build/xcconfig/index.d.ts",
"default": "./build/xcconfig/index.js"
}
},
"files": [
Expand Down
58 changes: 58 additions & 0 deletions src/api/XCBuildConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import type { SansIsa } from "./utils/util.types";
import type { XcodeProject } from "./XcodeProject";
import type { PBXFileReference } from "./PBXFileReference";
import { resolveXcodeBuildSetting } from "./utils/resolveBuildSettings";
import * as xcconfig from "../xcconfig";
import type { XCConfig, XCConfigFlattenOptions } from "../xcconfig";

const debug = require("debug")(
"xcode:XCBuildConfiguration"
Expand Down Expand Up @@ -170,6 +172,62 @@ export class XCBuildConfiguration extends AbstractObject<XCBuildConfigurationMod
baseConfigurationReference: String,
};
}

/**
* Get the absolute file path of the base configuration xcconfig file.
* @returns The absolute path or null if no base configuration is set.
*/
getBaseConfigurationFilePath(): string | null {
const fileRef = this.props.baseConfigurationReference;
if (!fileRef) return null;

const filePath = fileRef.props.path;
if (!filePath) return null;

const root = this.getXcodeProject().getProjectRoot();

// Handle different source tree types
const sourceTree = fileRef.props.sourceTree;
if (sourceTree === "<group>" || sourceTree === "SOURCE_ROOT") {
return path.join(root, filePath);
}
if (sourceTree === "<absolute>" || path.isAbsolute(filePath)) {
return filePath;
}

return path.join(root, filePath);
}

/**
* Parse and return the base configuration xcconfig file.
* @returns Parsed XCConfig or null if no base configuration is set or file doesn't exist.
*/
getBaseConfiguration(): XCConfig | null {
const filePath = this.getBaseConfigurationFilePath();
if (!filePath || !fs.existsSync(filePath)) return null;

try {
return xcconfig.parseFile(filePath);
} catch (error) {
debug("Failed to parse base configuration: %s", error);
return null;
}
}

/**
* Get flattened build settings from the base configuration xcconfig file.
* Settings are merged from included files with current file taking precedence.
*
* @param options - Optional filtering by sdk/arch/config
* @returns Flattened key-value map of build settings, or empty object if no base config.
*/
getBaseConfigurationSettings(
options?: XCConfigFlattenOptions
): Record<string, string> {
const config = this.getBaseConfiguration();
if (!config) return {};
return xcconfig.flattenBuildSettings(config, options);
}
}

// https://opensource.apple.com/source/pb_makefiles/pb_makefiles-1005/platform-variables.make.auto.html
Expand Down
3 changes: 3 additions & 0 deletions src/xcconfig/__tests__/fixtures/Base.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Base xcconfig - root settings
BASE_SETTING = base_value
OVERRIDDEN_SETTING = from_base
6 changes: 6 additions & 0 deletions src/xcconfig/__tests__/fixtures/Children.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Children xcconfig - includes Parent
#include "Parent.xcconfig"

CHILD_SETTING = child_value
OVERRIDDEN_SETTING = from_child
INHERITED_SETTING = $(inherited) child_part
6 changes: 6 additions & 0 deletions src/xcconfig/__tests__/fixtures/Parent.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Parent xcconfig - includes Base
#include "Base.xcconfig"

PARENT_SETTING = parent_value
OVERRIDDEN_SETTING = from_parent
INHERITED_SETTING = parent_part
20 changes: 20 additions & 0 deletions src/xcconfig/__tests__/fixtures/conditional.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Conditional settings based on SDK, architecture, and config
PRODUCT_NAME = MyApp
DEBUG_INFORMATION_FORMAT = dwarf-with-dsym

// SDK-specific settings
OTHER_LDFLAGS[sdk=iphoneos*] = -framework UIKit
OTHER_LDFLAGS[sdk=iphonesimulator*] = -framework UIKit
OTHER_LDFLAGS[sdk=macosx*] = -framework AppKit

// Architecture-specific settings
ARCHS[arch=arm64] = arm64
ARCHS[arch=x86_64] = x86_64

// Combined conditions
LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64] = $(inherited) /usr/lib/arm64
LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64] = $(inherited) /usr/lib/x86_64

// Config-specific settings
DEBUG_INFORMATION_FORMAT[config=Debug] = dwarf
DEBUG_INFORMATION_FORMAT[config=Release] = dwarf-with-dsym
5 changes: 5 additions & 0 deletions src/xcconfig/__tests__/fixtures/optional-include.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Optional include test
#include? "NonExistent.xcconfig"
#include "Base.xcconfig"

LOCAL_SETTING = local_value
4 changes: 4 additions & 0 deletions src/xcconfig/__tests__/fixtures/simple.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Simple xcconfig file
PRODUCT_NAME = MyApp
PRODUCT_BUNDLE_IDENTIFIER = com.example.myapp
SWIFT_VERSION = 5.0
Loading