Skip to content

Commit

Permalink
feat(cli): add support for running on macOS devices and visionOS simu…
Browse files Browse the repository at this point in the history
…lators (expo#28430)

# Why

- Why not.
- Better alignment with Xcode 15.

<img width="306" alt="Screenshot 2024-04-24 at 12 13 54 PM"
src="https://github.com/expo/expo/assets/9664363/f7c5b1f2-84e3-44fb-81d1-e8d892cbd034">


<!--
Please describe the motivation for this PR, and link to relevant GitHub
issues, forums posts, or feature requests.
-->

# How

- Use showdestinations to find the macos device ID and validate that the
destination target is supported. This has a high coldboot time but it
nets out fine because we'll invoke xcodebuild later in the process.
- This adds a high degree of uncertainty since the functionality is new
and I only have a small subset of devices to test against. Anything that
isn't accounted for has a debug log so hopefully we have a bit of future
proofing.
- We still sort iPhones to the top of the device list to preserve the
happy path of `npx expo run:ios -d` + enter -> build on iPhone/iPad.
- We now show compat devices of visionOS and macOS even for iOS-only
schemes. This is how Xcode 15 works.

<!--
How did you build this feature or fix this bug and why?
-->

# Test Plan

- Manually tested by bootstrapping a new project and running `nexpo
run:ios -d` and selecting visionOS simulator, physical Apple Vision Pro
(perhaps I can claim the device as a business expense now), iPhone OTA
and connected, and the new macOS target.
- I added a parsing test for the xcodebuild results. These results are
hard to parse correctly since phone names can contain any number of
characters. I ran the matching through GPT a few times to sanity check
if it was the safest approach. There are unit tests for the most
aggressive behavior I was able to reproduce.

<!--
Please describe how you tested this change and how a reviewer could
reproduce your test, especially if this PR does not include automated
tests! If possible, please also provide terminal output and/or
screenshots demonstrating your test/reproduction.
-->

# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
This is required for changes to Expo modules.
-->

- [ ] Documentation is up to date to reflect these changes (eg:
https://docs.expo.dev and README.md).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).

---------

Co-authored-by: Expo Bot <34669131+expo-bot@users.noreply.github.com>
  • Loading branch information
EvanBacon and expo-bot authored Apr 24, 2024
1 parent 8e346b5 commit 12643a9
Show file tree
Hide file tree
Showing 15 changed files with 826 additions and 51 deletions.
2 changes: 2 additions & 0 deletions packages/@expo/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

### 🎉 New features

- Support building for macOS devices and visionOS simulators with `npx expo run:ios -d`. ([#28430](https://github.com/expo/expo/pull/28430) by [@EvanBacon](https://github.com/EvanBacon))

### 🐛 Bug fixes

- Fix issue with installing OTA on iOS devices. ([#28406](https://github.com/expo/expo/pull/28406) by [@EvanBacon](https://github.com/EvanBacon))
Expand Down
3 changes: 2 additions & 1 deletion packages/@expo/cli/src/run/ios/XcodeBuild.types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { OSType } from '../../start/platforms/ios/simctl';
import { BundlerProps } from '../resolveBundlerProps';

export type XcodeConfiguration = 'Debug' | 'Release';
Expand Down Expand Up @@ -30,7 +31,7 @@ export type BuildProps = {
/** Is the target a simulator. */
isSimulator: boolean;
xcodeProject: ProjectInfo;
device: { name: string; udid: string };
device: { name: string; udid: string; osType: OSType };
configuration: XcodeConfiguration;
/** Disable the initial bundling from the native script. */
shouldSkipInitialBundling: boolean;
Expand Down
28 changes: 28 additions & 0 deletions packages/@expo/cli/src/run/ios/appleDevice/AppleDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Log } from '../../../log';
import { XcodeDeveloperDiskImagePrerequisite } from '../../../start/doctor/apple/XcodeDeveloperDiskImagePrerequisite';
import * as devicectl from '../../../start/platforms/ios/devicectl';
import { launchAppWithDeviceCtl } from '../../../start/platforms/ios/devicectl';
import { OSType } from '../../../start/platforms/ios/simctl';
import { uniqBy } from '../../../utils/array';
import { delayAsync } from '../../../utils/delay';
import { CommandError } from '../../../utils/errors';
Expand All @@ -33,6 +34,8 @@ export interface ConnectedDevice {
connectionType: 'USB' | 'Network';
/** @example `15.4.1` */
osVersion: string;

osType: OSType;
}

async function getConnectedDevicesUsingNativeToolsAsync(): Promise<ConnectedDevice[]> {
Expand All @@ -50,11 +53,35 @@ async function getConnectedDevicesUsingNativeToolsAsync(): Promise<ConnectedDevi
deviceType: 'device',
connectionType:
device.connectionProperties.transportType === 'localNetwork' ? 'Network' : 'USB',
osType: coercePlatformToOsType(device.hardwareProperties.platform),
};
})
);
}

function coercePlatformToOsType(platform: string): OSType {
// The only two devices I have to test against...
switch (platform) {
case 'iOS':
return 'iOS';
case 'xrOS':
return 'xrOS';
default:
debug('Unknown devicectl platform (needs to be added to Expo CLI):', platform);
return platform as OSType;
}
}
function coerceUsbmuxdPlatformToOsType(platform: string): OSType {
// The only connectable device I have to test against...
switch (platform) {
case 'iPhone OS':
return 'iOS';
default:
debug('Unknown usbmuxd platform (needs to be added to Expo CLI):', platform);
return platform as OSType;
}
}

/** @returns a list of connected Apple devices. */
export async function getConnectedDevicesAsync(): Promise<ConnectedDevice[]> {
const devices = await Promise.all([
Expand Down Expand Up @@ -94,6 +121,7 @@ async function getConnectedDevicesUsingCustomToolingAsync(): Promise<ConnectedDe
deviceType: 'device',
connectionType: device.Properties.ConnectionType,
udid: device.Properties.SerialNumber,
osType: coerceUsbmuxdPlatformToOsType(deviceValues.DeviceClass),
};
})
);
Expand Down
20 changes: 13 additions & 7 deletions packages/@expo/cli/src/run/ios/launchApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as XcodeBuild from './XcodeBuild';
import { BuildProps } from './XcodeBuild.types';
import { getAppDeltaDirectory, installOnDeviceAsync } from './appleDevice/installOnDeviceAsync';
import { AppleDeviceManager } from '../../start/platforms/ios/AppleDeviceManager';
import { launchBinaryOnMacAsync } from '../../start/platforms/ios/devicectl';
import { SimulatorLogStreamer } from '../../start/platforms/ios/simctlLogging';
import { DevServerManager } from '../../start/server/DevServerManager';
import { parsePlistAsync } from '../../utils/plist';
Expand All @@ -19,13 +20,18 @@ export async function launchAppAsync(
const appId = await profile(getBundleIdentifierForBinaryAsync)(binaryPath);

if (!props.isSimulator) {
await profile(installOnDeviceAsync)({
bundleIdentifier: appId,
bundle: binaryPath,
appDeltaDirectory: getAppDeltaDirectory(appId),
udid: props.device.udid,
deviceName: props.device.name,
});
if (props.device.osType === 'macOS') {
await launchBinaryOnMacAsync(appId, binaryPath);
} else {
await profile(installOnDeviceAsync)({
bundleIdentifier: appId,
bundle: binaryPath,
appDeltaDirectory: getAppDeltaDirectory(appId),
udid: props.device.udid,
deviceName: props.device.name,
});
}

return;
}

Expand Down
Loading

0 comments on commit 12643a9

Please sign in to comment.