Skip to content

fix: use “simctl list” to fetch device types, not file i/o #246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 3, 2025
Merged
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
81 changes: 29 additions & 52 deletions lib/xcode.js
Original file line number Diff line number Diff line change
Expand Up @@ -576,64 +576,41 @@ exports.detect = function detect(options, callback) {
}
});

var deviceTypesPaths = [
path.join(xc.path, 'Platforms', 'iPhoneSimulator.platform', 'Developer', 'Library', 'CoreSimulator', 'Profiles'),
path.join(xc.path, 'Platforms', 'WatchSimulator.platform', 'Developer', 'Library', 'CoreSimulator', 'Profiles'),

// Xcode 9 moved CoreSimulator into the "OS" directory instead of the "Simulator" directory
path.join(xc.path, 'Platforms', 'iPhoneOS.platform', 'Developer', 'Library', 'CoreSimulator', 'Profiles'),
path.join(xc.path, 'Platforms', 'WatchOS.platform', 'Developer', 'Library', 'CoreSimulator', 'Profiles'),

// Xcode 11 moved CoreSimulator into the "Library/Developer" directory instead of the "Developer/Library" directory
path.join(xc.path, 'Platforms', 'iPhoneOS.platform', 'Library', 'Developer', 'CoreSimulator', 'Profiles'),
path.join(xc.path, 'Platforms', 'WatchOS.platform', 'Library', 'Developer', 'CoreSimulator', 'Profiles')
];

deviceTypesPaths.forEach(function (deviceTypePath) {
// read in the device types
var deviceTypesDir = path.join(deviceTypePath, 'DeviceTypes');
fs.existsSync(deviceTypesDir) && fs.readdirSync(deviceTypesDir).forEach(function (name) {
var plist = readPlist(path.join(deviceTypesDir, name, 'Contents', 'Info.plist')),
devId = plist && plist.CFBundleIdentifier;
if (plist) {
var deviceType = xc.simDeviceTypes[devId] = {
name: plist.CFBundleName,
model: 'unknown',
supportsWatch: false
};

plist = readPlist(path.join(deviceTypesDir, name, 'Contents', 'Resources', 'profile.plist'));
if (plist) {
deviceType.model = plist.modelIdentifier;
}
// Read the device types and devices in one call using the `xcrun simctl list --json`
// command. This not only improves performance (no device I/O required), but also combines
// two command (`simctl list` and `simctl list devices`) into one.
simctl.list({ simctl: xc.executables.simctl }, function (err, info) {
if (err) {
return next(err);
}

plist = readPlist(path.join(deviceTypesDir, name, 'Contents', 'Resources', 'capabilities.plist'));
if (plist) {
deviceType.supportsWatch = !!plist.capabilities['watch-companion'];
}
const devices = info.devices;
const deviceTypes = info.devicetypes;

deviceTypes.forEach(function(deviceType) {
if (!xc.simDeviceTypes[deviceType.identifier]) {
xc.simDeviceTypes[deviceType.identifier] = {
name: deviceType.name,
model: deviceType.modelIdentifier || 'unknown',
// Assume devices with Watch in name or model support watch pairing
supportsWatch: /watch/i.test(deviceType.name) ? false : true
};
}
});

simctl.listDevices({ simctl: xc.executables.simctl }, function (err, info) {
if (err) {
return next(err);
}

// Map the platform and version from CoreSimulator string like:
// - com.apple.CoreSimulator.SimRuntime.iOS-17-0
// - com.apple.CoreSimulator.SimRuntime.watchOS-10-0
for (const key of Object.keys(info.devices)) {
const [_, platform, rawVersion] = key.match(/\.SimRuntime\.(.*?)\-(.*)$/);
const version = rawVersion.replace(/-/g, '.');

const mapping = {
name: `${platform} ${version}`,
version
}
appc.util.mix(xc.simRuntimes, { [key]: mapping });
// Map the platform and version from CoreSimulator string like:
// - com.apple.CoreSimulator.SimRuntime.iOS-17-0
// - com.apple.CoreSimulator.SimRuntime.watchOS-10-0
for (const key of Object.keys(devices)) {
const [_, platform, rawVersion] = key.match(/\.SimRuntime\.(.*?)\-(.*)$/);
const version = rawVersion.replace(/-/g, '.');

const mapping = {
name: `${platform} ${version}`,
version
}
});
appc.util.mix(xc.simRuntimes, { [key]: mapping });
}
});

['Simulator', 'iOS Simulator'].some(function (name) {
Expand Down