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
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,33 @@ Workspace file references use location specifiers:
- `container:path` - Absolute container reference (rare)
- `absolute:path` - Absolute file path

### IDEWorkspaceChecks

Manage `IDEWorkspaceChecks.plist` files that store workspace check states. Introduced in Xcode 9.3, these files prevent Xcode from recomputing checks each time a workspace is opened.

The primary use is suppressing the macOS 32-bit deprecation warning:

```ts
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();

// Low-level API
import * as workspace from "@bacons/xcode/workspace";

const plist = workspace.parseChecks(plistString);
console.log(plist.IDEDidComputeMac32BitWarning); // true

const output = workspace.buildChecks({ IDEDidComputeMac32BitWarning: true });
```

## XCConfig Support

Parse and manipulate Xcode configuration files (`.xcconfig`). These files define build settings that can be shared across targets and configurations.
Expand Down Expand Up @@ -496,7 +523,7 @@ We support the following types: `Object`, `Array`, `Data`, `String`. Notably, we
- [ ] Skills.
- [ ] Import from other tools.
- [ ] **XCUserData**: (`xcuserdata/<user>.xcuserdatad/`) Per-user schemes, breakpoints, UI state.
- [ ] **IDEWorkspaceChecks**: (`xcshareddata/IDEWorkspaceChecks.plist`) "Trust this project" flag that suppresses Xcode warning.
- [x] **IDEWorkspaceChecks**: (`xcshareddata/IDEWorkspaceChecks.plist`) Workspace check state storage (e.g., 32-bit deprecation warning).

# Docs

Expand Down
176 changes: 176 additions & 0 deletions src/api/IDEWorkspaceChecks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/**
* High-level API for IDEWorkspaceChecks.plist files.
*
* Introduced in Xcode 9.3, these files store the state of workspace checks
* to prevent them from being recomputed each time the workspace is opened.
*
* Currently known keys:
* - IDEDidComputeMac32BitWarning: Tracks whether the 32-bit macOS deprecation
* warning has been computed/shown. Setting to true suppresses the warning.
*/
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
import path from "path";

import { parseChecks, buildChecks } from "../workspace/checks";
import type { IDEWorkspaceChecks as ChecksData } from "../workspace/types";

/** Relative path from workspace to the checks plist */
const CHECKS_PATH = "xcshareddata/IDEWorkspaceChecks.plist";

/**
* High-level class for working with IDEWorkspaceChecks.plist files.
*/
export class IDEWorkspaceChecks {
/** The parsed checks data */
props: ChecksData;

/** Path to the plist file (may be undefined for new instances) */
filePath?: string;

private constructor(props: ChecksData, filePath?: string) {
this.props = props;
this.filePath = filePath;
}

/**
* Open an existing IDEWorkspaceChecks.plist from a workspace.
*
* @param workspacePath Path to the .xcworkspace directory
* @returns The checks instance, or null if the file doesn't exist
*/
static open(workspacePath: string): IDEWorkspaceChecks | null {
const checksPath = path.join(workspacePath, CHECKS_PATH);
if (!existsSync(checksPath)) {
return null;
}

const plistString = readFileSync(checksPath, "utf-8");
const props = parseChecks(plistString);
return new IDEWorkspaceChecks(props, checksPath);
}

/**
* Open an existing IDEWorkspaceChecks.plist or create a new one.
*
* @param workspacePath Path to the .xcworkspace directory
* @returns The checks instance (opened or newly created)
*/
static openOrCreate(workspacePath: string): IDEWorkspaceChecks {
const existing = IDEWorkspaceChecks.open(workspacePath);
if (existing) {
return existing;
}

const checksPath = path.join(workspacePath, CHECKS_PATH);
return new IDEWorkspaceChecks(
{ IDEDidComputeMac32BitWarning: true },
checksPath
);
}

/**
* Create a new IDEWorkspaceChecks instance.
*
* @param options Optional initial props and file path
*/
static create(options?: {
props?: Partial<ChecksData>;
filePath?: string;
}): IDEWorkspaceChecks {
const defaultProps: ChecksData = {
IDEDidComputeMac32BitWarning: true,
};

const props = { ...defaultProps, ...options?.props };
return new IDEWorkspaceChecks(props, options?.filePath);
}

/**
* Save the checks to disk.
*
* @param filePath Optional path to save to. If not provided, uses this.filePath.
*/
save(filePath?: string): void {
const targetPath = filePath ?? this.filePath;
if (!targetPath) {
throw new Error(
"No file path specified. Either provide a path or set this.filePath."
);
}

// Ensure parent directory exists
const dir = path.dirname(targetPath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}

const plistString = buildChecks(this.props);
writeFileSync(targetPath, plistString, "utf-8");
this.filePath = targetPath;
}

/**
* Save the checks to a workspace directory.
*
* @param workspacePath Path to the .xcworkspace directory
*/
saveToWorkspace(workspacePath: string): void {
const checksPath = path.join(workspacePath, CHECKS_PATH);
this.save(checksPath);
}

/**
* Get the plist representation of the checks.
*/
toPlist(): string {
return buildChecks(this.props);
}

/**
* Get whether the Mac 32-bit warning has been computed/dismissed.
*/
get mac32BitWarningComputed(): boolean {
return this.props.IDEDidComputeMac32BitWarning ?? false;
}

/**
* Set whether the Mac 32-bit warning has been computed/dismissed.
*/
set mac32BitWarningComputed(value: boolean) {
this.props.IDEDidComputeMac32BitWarning = value;
}

/**
* Get a check value by key.
*
* @param key The check key
* @returns The boolean value, or undefined if not set
*/
getCheck(key: string): boolean | undefined {
return this.props[key];
}

/**
* Set a check value.
*
* @param key The check key
* @param value The boolean value
*/
setCheck(key: string, value: boolean): void {
this.props[key] = value;
}

/**
* Remove a check.
*
* @param key The check key
* @returns true if the check was removed, false if it didn't exist
*/
removeCheck(key: string): boolean {
if (key in this.props) {
delete this.props[key];
return true;
}
return false;
}
}
51 changes: 51 additions & 0 deletions src/api/XCWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import path from "path";

import * as workspace from "../workspace";
import type { XCWorkspace as WorkspaceData, FileRef, Group } from "../workspace/types";
import { IDEWorkspaceChecks } from "./IDEWorkspaceChecks";
import { XCSharedData } from "./XCSharedData";

/**
Expand Down Expand Up @@ -236,6 +237,56 @@ export class XCWorkspace {
});
}

/**
* Get the IDEWorkspaceChecks for this workspace.
*
* @returns The checks instance, or null if not set
*/
getWorkspaceChecks(): IDEWorkspaceChecks | null {
if (!this.filePath) {
return null;
}
return IDEWorkspaceChecks.open(this.filePath);
}

/**
* Get or create the IDEWorkspaceChecks for this workspace.
*
* @returns The checks instance (opened or newly created)
*/
getOrCreateWorkspaceChecks(): IDEWorkspaceChecks {
if (!this.filePath) {
throw new Error(
"Workspace must be saved before accessing workspace checks."
);
}
return IDEWorkspaceChecks.openOrCreate(this.filePath);
}

/**
* Check if this workspace has IDEWorkspaceChecks configured.
*
* @returns true if the checks plist exists
*/
hasWorkspaceChecks(): boolean {
if (!this.filePath) {
return false;
}
return IDEWorkspaceChecks.open(this.filePath) !== null;
}

/**
* Mark the 32-bit warning as computed.
*
* This suppresses the macOS 32-bit deprecation warning dialog in Xcode
* by setting IDEDidComputeMac32BitWarning to true.
*/
setMac32BitWarningComputed(): void {
const checks = this.getOrCreateWorkspaceChecks();
checks.mac32BitWarningComputed = true;
checks.save();
}

private collectPathsFromGroup(group: Group): string[] {
const paths: string[] = [];

Expand Down
Loading