Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ describe('session-clear-defaults tool', () => {
const current = sessionStore.getAll();
expect(current.scheme).toBeUndefined();
expect(current.deviceId).toBeUndefined();
expect(current.derivedDataPath).toBeUndefined();
// derivedDataPath is computed from projectPath when not explicitly set
expect(current.derivedDataPath).toContain('proj-');
expect(current.projectPath).toBe('/path/to/proj.xcodeproj');
expect(current.simulatorName).toBe('iPhone 17');
expect(current.useLatestOS).toBe(true);
Expand Down
45 changes: 45 additions & 0 deletions src/utils/__tests__/session-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,49 @@ describe('SessionStore', () => {
const stored = sessionStore.getAll();
expect(stored.env).toEqual({ API_KEY: 'secret' });
});

it('computes a workspace-scoped derivedDataPath when workspacePath is set', () => {
sessionStore.setDefaults({ workspacePath: '/Users/dev/clone-1/MyApp.xcworkspace' });

const defaults = sessionStore.getAll();
expect(defaults.derivedDataPath).toMatch(/MyApp-[a-f0-9]{12}$/);
});

it('computes a project-scoped derivedDataPath when projectPath is set', () => {
sessionStore.setDefaults({ projectPath: '/Users/dev/clone-2/MyApp.xcodeproj' });

const defaults = sessionStore.getAll();
expect(defaults.derivedDataPath).toMatch(/MyApp-[a-f0-9]{12}$/);
});

it('does not override an explicitly set derivedDataPath', () => {
sessionStore.setDefaults({
workspacePath: '/Users/dev/clone-1/MyApp.xcworkspace',
derivedDataPath: '/custom/path',
});

expect(sessionStore.getAll().derivedDataPath).toBe('/custom/path');
});

it('produces different hashes for different workspace paths', () => {
sessionStore.setDefaults({ workspacePath: '/clone-1/MyApp.xcworkspace' });
const path1 = sessionStore.getAll().derivedDataPath;

sessionStore.clearAll();
sessionStore.setDefaults({ workspacePath: '/clone-2/MyApp.xcworkspace' });
const path2 = sessionStore.getAll().derivedDataPath;

expect(path1).not.toBe(path2);
});

it('recomputes derivedDataPath when workspacePath changes', () => {
sessionStore.setDefaults({ workspacePath: '/clone-1/MyApp.xcworkspace' });
const path1 = sessionStore.getAll().derivedDataPath;

sessionStore.setDefaults({ workspacePath: '/clone-2/MyApp.xcworkspace' });
const path2 = sessionStore.getAll().derivedDataPath;

expect(path1).not.toBe(path2);
expect(path2).toMatch(/MyApp-[a-f0-9]{12}$/);
});
});
3 changes: 2 additions & 1 deletion src/utils/build-preflight.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'node:path';
import os from 'node:os';
import { resolveEffectiveDerivedDataPath } from './derived-data-path.ts';
import { sessionStore } from './session-store.ts';

export interface ToolPreflightParams {
operation:
Expand Down Expand Up @@ -94,7 +95,7 @@ export function formatToolPreflight(params: ToolPreflightParams): string {
}

lines.push(
` Derived Data: ${displayPath(resolveEffectiveDerivedDataPath(params.derivedDataPath))}`,
` Derived Data: ${displayPath(resolveEffectiveDerivedDataPath(params.derivedDataPath ?? sessionStore.get('derivedDataPath')))}`,
);

if (params.arch) {
Expand Down
23 changes: 21 additions & 2 deletions src/utils/session-store.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import * as crypto from 'node:crypto';
import * as path from 'node:path';
import { DERIVED_DATA_DIR } from './log-paths.ts';
import { log } from './logger.ts';

export type SessionDefaults = {
Expand Down Expand Up @@ -76,7 +79,7 @@ class SessionStore {
}

setDefaultsForProfile(profile: string | null, partial: Partial<SessionDefaults>): void {
const previous = this.getAllForProfile(profile);
const previous = this.getRawForProfile(profile);
const next = { ...previous, ...partial };
this.setDefaultsForResolvedProfile(profile, next);
this.revision += 1;
Expand Down Expand Up @@ -114,7 +117,7 @@ class SessionStore {
return;
}

const next = this.getAllForProfile(profile);
const next = this.getRawForProfile(profile);
for (const k of keys) delete next[k];

this.setDefaultsForResolvedProfile(profile, next);
Expand All @@ -132,6 +135,22 @@ class SessionStore {
}

getAllForProfile(profile: string | null): SessionDefaults {
const result = this.getRawForProfile(profile);

if (!result.derivedDataPath) {
const anchor = result.workspacePath ?? result.projectPath;
if (anchor) {
const resolved = path.resolve(anchor);
Comment thread
sentry[bot] marked this conversation as resolved.
const hash = crypto.createHash('sha256').update(resolved).digest('hex').slice(0, 12);
const name = path.basename(resolved, path.extname(resolved));
result.derivedDataPath = path.join(DERIVED_DATA_DIR, `${name}-${hash}`);
}
}

return result;
}

private getRawForProfile(profile: string | null): SessionDefaults {
const defaults = profile === null ? this.globalDefaults : (this.profiles[profile] ?? {});
return this.cloneDefaults(defaults);
Comment thread
cursor[bot] marked this conversation as resolved.
}
Comment thread
sentry[bot] marked this conversation as resolved.
Expand Down
4 changes: 3 additions & 1 deletion src/utils/xcodebuild-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { createXcodebuildRunState } from './xcodebuild-run-state.ts';
import type { XcodebuildRunState } from './xcodebuild-run-state.ts';
import { displayPath } from './build-preflight.ts';
import { resolveEffectiveDerivedDataPath } from './derived-data-path.ts';
import { sessionStore } from './session-store.ts';
import { formatDeviceId } from './device-name-resolver.ts';
import { createLogCapture, createParserDebugCapture } from './xcodebuild-log-capture.ts';
import { log as appLog } from './logging/index.ts';
Expand Down Expand Up @@ -158,7 +159,8 @@ function buildHeaderParams(

// Always show Derived Data even if not explicitly provided
if (!result.some((r) => r.label === 'Derived Data')) {
result.push({ label: 'Derived Data', value: displayPath(resolveEffectiveDerivedDataPath()) });
const effectivePath = resolveEffectiveDerivedDataPath(sessionStore.get('derivedDataPath'));
Comment thread
sentry[bot] marked this conversation as resolved.
result.push({ label: 'Derived Data', value: displayPath(effectivePath) });
}

return result;
Expand Down
Loading