Skip to content

Commit b26304c

Browse files
jjfeilerclaude
andcommitted
Add automatic platform detection from scheme build settings
When using session-set-defaults to configure a watchOS, tvOS, or visionOS scheme, the simulator build tools (build_sim, build_run_sim, test_sim) would incorrectly use "iOS Simulator" as the destination platform, causing build failures with errors like: "Unable to find a device matching the provided destination specifier: { platform:iOS Simulator, OS:latest, name:Apple Watch Ultra 2 }" This change adds automatic platform detection by querying the scheme's build settings (SDKROOT and SUPPORTED_PLATFORMS) before building. The detected platform is then used to construct the correct destination string. Changes: - Add src/utils/platform-detection.ts with detectPlatformFromScheme() - Integrate platform detection into build_sim, build_run_sim, test_sim - Update tests to account for the additional platform detection call Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ecfa37f commit b26304c

6 files changed

Lines changed: 394 additions & 106 deletions

File tree

src/mcp/tools/simulator/__tests__/build_run_sim.test.ts

Lines changed: 53 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,19 @@ describe('build_run_sim tool', () => {
6363
const mockExecutor: CommandExecutor = async (command) => {
6464
callCount++;
6565
if (callCount === 1) {
66-
// First call: build succeeds
66+
// First call: platform detection succeeds
6767
return createMockCommandResponse({
6868
success: true,
69-
output: 'BUILD SUCCEEDED',
69+
output: 'SDKROOT = iphonesimulator\nSUPPORTED_PLATFORMS = iphonesimulator iphoneos',
7070
});
7171
} else if (callCount === 2) {
72-
// Second call: showBuildSettings fails to get app path
72+
// Second call: build succeeds
73+
return createMockCommandResponse({
74+
success: true,
75+
output: 'BUILD SUCCEEDED',
76+
});
77+
} else if (callCount === 3) {
78+
// Third call: showBuildSettings fails to get app path
7379
return createMockCommandResponse({
7480
success: false,
7581
error: 'Could not get build settings',
@@ -258,9 +264,13 @@ describe('build_run_sim tool', () => {
258264
trackingExecutor,
259265
);
260266

261-
// Should generate the initial build command
262-
expect(callHistory).toHaveLength(1);
263-
expect(callHistory[0].command).toEqual([
267+
// Should generate two commands: platform detection + build
268+
expect(callHistory).toHaveLength(2);
269+
// First call is platform detection
270+
expect(callHistory[0].command[0]).toBe('xcodebuild');
271+
expect(callHistory[0].command).toContain('-showBuildSettings');
272+
// Second call is the actual build
273+
expect(callHistory[1].command).toEqual([
264274
'xcodebuild',
265275
'-workspace',
266276
'/path/to/MyProject.xcworkspace',
@@ -273,7 +283,7 @@ describe('build_run_sim tool', () => {
273283
'platform=iOS Simulator,name=iPhone 16,OS=latest',
274284
'build',
275285
]);
276-
expect(callHistory[0].logPrefix).toBe('iOS Simulator Build');
286+
expect(callHistory[1].logPrefix).toBe('iOS Simulator Build');
277287
});
278288

279289
it('should generate correct build command after finding simulator', async () => {
@@ -285,26 +295,16 @@ describe('build_run_sim tool', () => {
285295
}> = [];
286296

287297
let callCount = 0;
288-
// Create tracking executor that succeeds on first call (list) and fails on second
298+
// Create tracking executor: platform detection, then build (fails)
289299
const trackingExecutor: CommandExecutor = async (command, logPrefix, useShell, opts) => {
290300
callHistory.push({ command, logPrefix, useShell, opts });
291301
callCount++;
292302

293303
if (callCount === 1) {
294-
// First call: simulator list succeeds
304+
// First call: platform detection succeeds
295305
return createMockCommandResponse({
296306
success: true,
297-
output: JSON.stringify({
298-
devices: {
299-
'iOS 16.0': [
300-
{
301-
udid: 'test-uuid-123',
302-
name: 'iPhone 16',
303-
state: 'Booted',
304-
},
305-
],
306-
},
307-
}),
307+
output: 'SDKROOT = iphonesimulator\nSUPPORTED_PLATFORMS = iphonesimulator iphoneos',
308308
error: undefined,
309309
});
310310
} else {
@@ -326,39 +326,29 @@ describe('build_run_sim tool', () => {
326326
trackingExecutor,
327327
);
328328

329-
// Should generate build command and then build settings command
329+
// Should generate platform detection and then build command
330330
expect(callHistory).toHaveLength(2);
331331

332-
// First call: build command
333-
expect(callHistory[0].command).toEqual([
334-
'xcodebuild',
335-
'-workspace',
336-
'/path/to/MyProject.xcworkspace',
337-
'-scheme',
338-
'MyScheme',
339-
'-configuration',
340-
'Debug',
341-
'-skipMacroValidation',
342-
'-destination',
343-
'platform=iOS Simulator,name=iPhone 16,OS=latest',
344-
'build',
345-
]);
346-
expect(callHistory[0].logPrefix).toBe('iOS Simulator Build');
332+
// First call: platform detection
333+
expect(callHistory[0].command[0]).toBe('xcodebuild');
334+
expect(callHistory[0].command).toContain('-showBuildSettings');
335+
expect(callHistory[0].logPrefix).toBe('Platform Detection');
347336

348-
// Second call: build settings command to get app path
337+
// Second call: build command (which fails)
349338
expect(callHistory[1].command).toEqual([
350339
'xcodebuild',
351-
'-showBuildSettings',
352340
'-workspace',
353341
'/path/to/MyProject.xcworkspace',
354342
'-scheme',
355343
'MyScheme',
356344
'-configuration',
357345
'Debug',
346+
'-skipMacroValidation',
358347
'-destination',
359348
'platform=iOS Simulator,name=iPhone 16,OS=latest',
349+
'build',
360350
]);
361-
expect(callHistory[1].logPrefix).toBe('Get App Path');
351+
expect(callHistory[1].logPrefix).toBe('iOS Simulator Build');
362352
});
363353

364354
it('should generate correct build settings command after successful build', async () => {
@@ -370,26 +360,16 @@ describe('build_run_sim tool', () => {
370360
}> = [];
371361

372362
let callCount = 0;
373-
// Create tracking executor that succeeds on first two calls and fails on third
363+
// Create tracking executor: platform detection, build, then build settings for app path
374364
const trackingExecutor: CommandExecutor = async (command, logPrefix, useShell, opts) => {
375365
callHistory.push({ command, logPrefix, useShell, opts });
376366
callCount++;
377367

378368
if (callCount === 1) {
379-
// First call: simulator list succeeds
369+
// First call: platform detection succeeds
380370
return createMockCommandResponse({
381371
success: true,
382-
output: JSON.stringify({
383-
devices: {
384-
'iOS 16.0': [
385-
{
386-
udid: 'test-uuid-123',
387-
name: 'iPhone 16',
388-
state: 'Booted',
389-
},
390-
],
391-
},
392-
}),
372+
output: 'SDKROOT = iphonesimulator\nSUPPORTED_PLATFORMS = iphonesimulator iphoneos',
393373
error: undefined,
394374
});
395375
} else if (callCount === 2) {
@@ -420,11 +400,16 @@ describe('build_run_sim tool', () => {
420400
trackingExecutor,
421401
);
422402

423-
// Should generate build command and build settings command
424-
expect(callHistory).toHaveLength(2);
403+
// Should generate platform detection, build command, and build settings command
404+
expect(callHistory).toHaveLength(3);
405+
406+
// First call: platform detection
407+
expect(callHistory[0].command[0]).toBe('xcodebuild');
408+
expect(callHistory[0].command).toContain('-showBuildSettings');
409+
expect(callHistory[0].logPrefix).toBe('Platform Detection');
425410

426-
// First call: build command
427-
expect(callHistory[0].command).toEqual([
411+
// Second call: build command
412+
expect(callHistory[1].command).toEqual([
428413
'xcodebuild',
429414
'-workspace',
430415
'/path/to/MyProject.xcworkspace',
@@ -437,10 +422,10 @@ describe('build_run_sim tool', () => {
437422
'platform=iOS Simulator,name=iPhone 16',
438423
'build',
439424
]);
440-
expect(callHistory[0].logPrefix).toBe('iOS Simulator Build');
425+
expect(callHistory[1].logPrefix).toBe('iOS Simulator Build');
441426

442-
// Second call: build settings command
443-
expect(callHistory[1].command).toEqual([
427+
// Third call: build settings command for app path
428+
expect(callHistory[2].command).toEqual([
444429
'xcodebuild',
445430
'-showBuildSettings',
446431
'-workspace',
@@ -452,7 +437,7 @@ describe('build_run_sim tool', () => {
452437
'-destination',
453438
'platform=iOS Simulator,name=iPhone 16',
454439
]);
455-
expect(callHistory[1].logPrefix).toBe('Get App Path');
440+
expect(callHistory[2].logPrefix).toBe('Get App Path');
456441
});
457442

458443
it('should handle paths with spaces in command generation', async () => {
@@ -482,9 +467,13 @@ describe('build_run_sim tool', () => {
482467
trackingExecutor,
483468
);
484469

485-
// Should generate build command first
486-
expect(callHistory).toHaveLength(1);
487-
expect(callHistory[0].command).toEqual([
470+
// Should generate two commands: platform detection + build
471+
expect(callHistory).toHaveLength(2);
472+
// First call is platform detection
473+
expect(callHistory[0].command[0]).toBe('xcodebuild');
474+
expect(callHistory[0].command).toContain('-showBuildSettings');
475+
// Second call is the actual build with paths containing spaces
476+
expect(callHistory[1].command).toEqual([
488477
'xcodebuild',
489478
'-workspace',
490479
'/Users/dev/My Project/MyProject.xcworkspace',
@@ -497,7 +486,7 @@ describe('build_run_sim tool', () => {
497486
'platform=iOS Simulator,name=iPhone 16 Pro,OS=latest',
498487
'build',
499488
]);
500-
expect(callHistory[0].logPrefix).toBe('iOS Simulator Build');
489+
expect(callHistory[1].logPrefix).toBe('iOS Simulator Build');
501490
});
502491
});
503492

src/mcp/tools/simulator/__tests__/build_sim.test.ts

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,13 @@ describe('build_sim tool', () => {
217217
trackingExecutor,
218218
);
219219

220-
// Should generate one build command
221-
expect(callHistory).toHaveLength(1);
222-
expect(callHistory[0].command).toEqual([
220+
// Should generate two commands: platform detection + build
221+
expect(callHistory).toHaveLength(2);
222+
// First call is platform detection
223+
expect(callHistory[0].command[0]).toBe('xcodebuild');
224+
expect(callHistory[0].command).toContain('-showBuildSettings');
225+
// Second call is the actual build
226+
expect(callHistory[1].command).toEqual([
223227
'xcodebuild',
224228
'-workspace',
225229
'/path/to/MyProject.xcworkspace',
@@ -232,7 +236,7 @@ describe('build_sim tool', () => {
232236
'platform=iOS Simulator,name=iPhone 16,OS=latest',
233237
'build',
234238
]);
235-
expect(callHistory[0].logPrefix).toBe('iOS Simulator Build');
239+
expect(callHistory[1].logPrefix).toBe('iOS Simulator Build');
236240
});
237241

238242
it('should generate correct build command with minimal parameters (project)', async () => {
@@ -262,9 +266,13 @@ describe('build_sim tool', () => {
262266
trackingExecutor,
263267
);
264268

265-
// Should generate one build command
266-
expect(callHistory).toHaveLength(1);
267-
expect(callHistory[0].command).toEqual([
269+
// Should generate two commands: platform detection + build
270+
expect(callHistory).toHaveLength(2);
271+
// First call is platform detection
272+
expect(callHistory[0].command[0]).toBe('xcodebuild');
273+
expect(callHistory[0].command).toContain('-showBuildSettings');
274+
// Second call is the actual build
275+
expect(callHistory[1].command).toEqual([
268276
'xcodebuild',
269277
'-project',
270278
'/path/to/MyProject.xcodeproj',
@@ -277,7 +285,7 @@ describe('build_sim tool', () => {
277285
'platform=iOS Simulator,name=iPhone 16,OS=latest',
278286
'build',
279287
]);
280-
expect(callHistory[0].logPrefix).toBe('iOS Simulator Build');
288+
expect(callHistory[1].logPrefix).toBe('iOS Simulator Build');
281289
});
282290

283291
it('should generate correct build command with all optional parameters', async () => {
@@ -311,9 +319,13 @@ describe('build_sim tool', () => {
311319
trackingExecutor,
312320
);
313321

314-
// Should generate one build command with all parameters
315-
expect(callHistory).toHaveLength(1);
316-
expect(callHistory[0].command).toEqual([
322+
// Should generate two commands: platform detection + build
323+
expect(callHistory).toHaveLength(2);
324+
// First call is platform detection
325+
expect(callHistory[0].command[0]).toBe('xcodebuild');
326+
expect(callHistory[0].command).toContain('-showBuildSettings');
327+
// Second call is the actual build with all parameters
328+
expect(callHistory[1].command).toEqual([
317329
'xcodebuild',
318330
'-workspace',
319331
'/path/to/MyProject.xcworkspace',
@@ -329,7 +341,7 @@ describe('build_sim tool', () => {
329341
'--verbose',
330342
'build',
331343
]);
332-
expect(callHistory[0].logPrefix).toBe('iOS Simulator Build');
344+
expect(callHistory[1].logPrefix).toBe('iOS Simulator Build');
333345
});
334346

335347
it('should handle paths with spaces in command generation', async () => {
@@ -359,9 +371,13 @@ describe('build_sim tool', () => {
359371
trackingExecutor,
360372
);
361373

362-
// Should generate one build command with paths containing spaces
363-
expect(callHistory).toHaveLength(1);
364-
expect(callHistory[0].command).toEqual([
374+
// Should generate two commands: platform detection + build
375+
expect(callHistory).toHaveLength(2);
376+
// First call is platform detection
377+
expect(callHistory[0].command[0]).toBe('xcodebuild');
378+
expect(callHistory[0].command).toContain('-showBuildSettings');
379+
// Second call is the actual build with paths containing spaces
380+
expect(callHistory[1].command).toEqual([
365381
'xcodebuild',
366382
'-workspace',
367383
'/Users/dev/My Project/MyProject.xcworkspace',
@@ -374,7 +390,7 @@ describe('build_sim tool', () => {
374390
'platform=iOS Simulator,name=iPhone 16 Pro,OS=latest',
375391
'build',
376392
]);
377-
expect(callHistory[0].logPrefix).toBe('iOS Simulator Build');
393+
expect(callHistory[1].logPrefix).toBe('iOS Simulator Build');
378394
});
379395

380396
it('should generate correct build command with useLatestOS set to true', async () => {
@@ -405,9 +421,13 @@ describe('build_sim tool', () => {
405421
trackingExecutor,
406422
);
407423

408-
// Should generate one build command with OS=latest
409-
expect(callHistory).toHaveLength(1);
410-
expect(callHistory[0].command).toEqual([
424+
// Should generate two commands: platform detection + build
425+
expect(callHistory).toHaveLength(2);
426+
// First call is platform detection
427+
expect(callHistory[0].command[0]).toBe('xcodebuild');
428+
expect(callHistory[0].command).toContain('-showBuildSettings');
429+
// Second call is the actual build with OS=latest
430+
expect(callHistory[1].command).toEqual([
411431
'xcodebuild',
412432
'-workspace',
413433
'/path/to/MyProject.xcworkspace',
@@ -420,7 +440,7 @@ describe('build_sim tool', () => {
420440
'platform=iOS Simulator,name=iPhone 16,OS=latest',
421441
'build',
422442
]);
423-
expect(callHistory[0].logPrefix).toBe('iOS Simulator Build');
443+
expect(callHistory[1].logPrefix).toBe('iOS Simulator Build');
424444
});
425445
});
426446

0 commit comments

Comments
 (0)