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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Fixed `suppressWarnings` being ignored in settled build, build-run, and test output. The flag was honored only while streaming, so warnings still reached the final MCP tool response ([#447](https://github.com/getsentry/XcodeBuildMCP/issues/447)).
- Fixed iOS scaffold orientation and device-family settings, LLDB command isolation and argument escaping, run-destination parsing without an active scheme, concurrent working-directory mutations, blocking physical-device name lookup, and unverified `xcodemake` downloads ([#459](https://github.com/getsentry/XcodeBuildMCP/issues/459)).
- Fixed simulator UI launching and keyboard controls to prefer Xcode 27's Device Hub when available, with Simulator.app as the legacy fallback.
- Fixed incremental `xcodemake` builds when DerivedData uses an absolute path by updating the pinned wrapper and delegating Makefile reuse to it so its argument and freshness checks are always applied ([#466](https://github.com/getsentry/XcodeBuildMCP/issues/466)).

## [2.6.2]

Expand Down
111 changes: 111 additions & 0 deletions src/utils/__tests__/build-utils-xcodemake.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { createMockExecutor } from '../../test-utils/mock-executors.ts';

const { executeXcodemakeCommandMock } = vi.hoisted(() => ({
executeXcodemakeCommandMock: vi.fn(),
}));

vi.mock('../xcodemake.ts', () => ({
isXcodemakeEnabled: () => true,
isXcodemakeAvailable: () => Promise.resolve(true),
executeXcodemakeCommand: executeXcodemakeCommandMock,
}));

import { executeXcodeBuildCommand } from '../build-utils.ts';
import { XcodePlatform } from '../xcode.ts';

describe('build-utils xcodemake lifecycle', () => {
let projectDirectory: string;

beforeEach(() => {
projectDirectory = mkdtempSync(path.join(tmpdir(), 'xcodebuildmcp-xcodemake-'));
writeFileSync(path.join(projectDirectory, 'Makefile'), 'all:\n\t@true\n');
executeXcodemakeCommandMock.mockResolvedValue({ success: true, output: 'BUILD SUCCEEDED' });
});

afterEach(() => {
rmSync(projectDirectory, { recursive: true, force: true });
vi.clearAllMocks();
});

it('delegates existing Makefile validation to xcodemake for external DerivedData', async () => {
const workspacePath = path.join(projectDirectory, 'MyWorkspace.xcworkspace');
const derivedDataPath =
'/Users/developer/Library/Developer/XcodeBuildMCP/DerivedData/MyWorkspace-57a542dedf16';
const executorCall = vi.fn();
const executor = createMockExecutor({ onExecute: executorCall });

const result = await executeXcodeBuildCommand(
{
scheme: 'MyScheme',
configuration: 'Debug',
workspacePath,
derivedDataPath,
},
{
platform: XcodePlatform.iOSSimulator,
simulatorId: 'SIMULATOR-UDID',
logPrefix: 'iOS Simulator Build',
},
false,
'build',
executor,
);

expect(result.isError).toBeFalsy();
expect(executorCall).not.toHaveBeenCalled();
expect(executeXcodemakeCommandMock).toHaveBeenCalledWith(
projectDirectory,
[
'-workspace',
workspacePath,
'-scheme',
'MyScheme',
'-configuration',
'Debug',
'-skipMacroValidation',
'-destination',
'platform=iOS Simulator,id=SIMULATOR-UDID',
'-collect-test-diagnostics',
'never',
'-derivedDataPath',
derivedDataPath,
'build',
],
'iOS Simulator Build',
);
});

it('uses the current working directory when no project or workspace path is provided', async () => {
const derivedDataPath =
'/Users/developer/Library/Developer/XcodeBuildMCP/DerivedData/MyScheme-57a542dedf16';
const executorCall = vi.fn();
const executor = createMockExecutor({ onExecute: executorCall });

const result = await executeXcodeBuildCommand(
{
scheme: 'MyScheme',
derivedDataPath,
},
{
platform: XcodePlatform.iOSSimulator,
simulatorId: 'SIMULATOR-UDID',
logPrefix: 'iOS Simulator Build',
},
false,
'build',
executor,
);

expect(result.isError).toBeFalsy();
expect(executorCall).not.toHaveBeenCalled();
expect(executeXcodemakeCommandMock).toHaveBeenCalledWith(
process.cwd(),
expect.arrayContaining(['-derivedDataPath', derivedDataPath]),
'iOS Simulator Build',
);
});
});
9 changes: 9 additions & 0 deletions src/utils/__tests__/fixtures/xcodemake/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Pinned xcodemake test data

`xcodemake-75f47d4b69c1604cb886ab37d348c2c245d18329` is an exact, unmodified copy of
[`cameroncooke/xcodemake` at commit `75f47d4b69c1604cb886ab37d348c2c245d18329`](https://github.com/cameroncooke/xcodemake/blob/75f47d4b69c1604cb886ab37d348c2c245d18329/xcodemake).

SHA-256: `0934f784661f8b295f51064b8e94659c986ca25affc5062f20d5210ae0e25201`

The wrapper regression test verifies this checksum against the production pin before execution. Do
not edit the fixture independently of `XCODEMAKE_COMMIT` and `XCODEMAKE_SHA256`.
Loading
Loading