最近在为iOS项目开发自动化构建工具时,需要调用react-native run-ios
命令,但由于项目react-native版本过低,本地xcode版本已经升至xcode11,不少目录结构和数据格式有变化,官方react-native run-ios
命令执行失败,因此不得不从react-native run-ios
内部研究iOS打包运行机制,自己重新编写打包脚本进而修复run-ios
无法运行的问题。
从react-native run-ios
执行过程和结果来看,这个命令做了以下工作:
- 找到xcode项目(*.xcworkspace或 *.xcodeproj文件);
- 找到本地连接的设备或本地可运行模拟器;
- 编译项目;
- 将项目打包并安装在设备上运行。
下面逐步分析这几个步骤需要使用什么命令执行。
查找xcode项目文件
这一步很简单,只需通过简单的文件遍历,通过判断文件后缀名是否为xcworkspace
或xcodeproj
即可,以下是react-native/local-cli/runIOS
查找项目文件的方法:
const path = require('path');
function findXcodeProject(files) {
const sortedFiles = files.sort();
for (let i = sortedFiles.length - 1; i >= 0; i--) {
const fileName = files[i];
const ext = path.extname(fileName);
if (ext === '.xcworkspace') {
return {
name: fileName,
isWorkspace: true,
};
}
if (ext === '.xcodeproj') {
return {
name: fileName,
isWorkspace: false,
};
}
}
return null;
}
命令行工具准备
接下来设备查找、工程编译、打包安装需要使用xcode提供的命令行工具xcodebuild
和xcrun
。xcodebuild
和xcrun
都是来自Command Line Tools,Xcode自带,如果没有可以通过以下命令安装:
xcode-select --install
或者在下面的链接下载安装:
https://developer.apple.com/downloads/
安装完可在以下路径看到这两个工具:
/Applications/Xcode.app/Contents/Developer/usr/bin/
或通过终端执行命令xcodebuild -h
和xcrun -h
确认命令帮助信息。这两个命令大致功能如下:
- xcodebuild: 主要是用来编译,打包成Archive和导出ipa包。
- xcrun: 通过app文件,来生成ipa文件(包含了签名的过程),同时也可以查找本地设备。
查找本地真机设备和模拟器
查找本地设备可以直接通过xcrun instruments -s
命令查看,输出如下:
$ xcrun instruments -s
李佳浩的 iPhone (13.3.1) [cbfadfgfafasdfaec3f54fde43314e9366bb813764]
iPhone 11 (13.3) [8AKJGFY4F-298F-4CC9-B6D1-73TEWRQW7F2DE] (Simulator)
iPhone 11 Pro (13.3) [25832163B-C30E-41C1-A79D-CCMKNIHUQW7D830] (Simulator)
...
可以看到这个命令会把本地真机设备跟模拟器全部列出,包含设备的udid,设备名称,后续打包安装时就可以通过这两个参数把APP安装到指定设备。
不过在执行react-native run-ios
时,当不指定真机时,这个命令会自动帮我们启动本地模拟器,如果当前有启动的模拟器,那么就会在已经启动的模拟器上执行相关动作,但刚才那个命令上并没有显示模拟器运行的相关信息,模拟器的具体信息可以通过xcrun simctl list --json devices
获得:
$ xcrun simctl list --json devices
{
"devices" : {
"com.apple.CoreSimulator.SimRuntime.watchOS-5-2" : [
{
"state" : "Shutdown",
"isAvailable" : false,
"name" : "Apple Watch Series 4 - 44mm",
"udid" : "CE3F-4419-BA2D-13686D1B165C",
"availabilityError" : "runtime profile not found"
}
],
"com.apple.CoreSimulator.SimRuntime.tvOS-12-2" : [
{
"state" : "Shutdown",
"isAvailable" : false,
"name" : "Apple TV 4K (at 1080p)",
"udid" : "A76FD1E6-BD11-4A74DE533C",
"availabilityError" : "runtime profile not found"
}
],
"com.apple.CoreSimulator.SimRuntime.watchOS-6-1" : [
{
"state" : "Shutdown",
"isAvailable" : true,
"name" : "Apple Watch Series 5 - 44mm",
"udid" : "D525C-95C2-ACE975745824"
}
],
"com.apple.CoreSimulator.SimRuntime.iOS-13-3" : [
{
"state" : "Booted",
"isAvailable" : true,
"name" : "iPhone 8",
"udid" : "79C4E2-AB95-33C35757BF1E"
},
{
"state" : "Shutdown",
"isAvailable" : true,
"name" : "iPhone 8 Plus",
"udid" : "5B5579-AA1D38931332"
},
]
}
}
这里以json格式展示了当前设备上已经安装的模拟器,包含了watchOS、iOS、tvOS设备的全部信息(系统版本、开启状态state、可用状态isAvailable以及上述设备名称name和设备udid),通过state状态找到已经启动的模拟器,然后通过xcode-select -p
查找模拟器在本地的安装位置:
$ xcode-select -p
/Applications/Xcode.app/Contents/Developer
通过udid打开模拟器:
$ /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app --args -CurrentDeviceUDID 79C4E2-AB95-33C35757BF1E
打开模拟器后还要执行模拟器的“开机”动作,即如果模拟器的state状态不为”Booted”时启动:
$ xcrun instruments -w [udid]
编译项目
编译项目需要通过xcodebuild
在项目文件夹下(xcworkspace或xcodeproj所在文件夹)执行,执行参数如下:
xcodebuild (-workspace或-project) [xcodeProject_name] -configuration [configuration] -scheme [scheme] -destination id=[udid]
- configuration:为编译时自定义配置,可查看xcodebuild -h查看具体配置,具体项目里我这里写了空也没问题;
- scheme:必填参数,可通过执行
xcodebuild -list
查看当前项目的scheme,通常填的是项目的名称; - udid:设备的udid
react-native
官方的buildProject
源码关键点如下:
function buildProject(
xcodeProject,
udid,
scheme,
args,
) {
return new Promise((resolve, reject) => {
const xcodebuildArgs = [
xcodeProject.isWorkspace ? '-workspace' : '-project',
xcodeProject.name,
'-configuration',
args.configuration,
'-scheme',
scheme,
'-destination',
`id=${udid}`,
];
console.log(
`Building ${`(using "xcodebuild ${xcodebuildArgs.join(' ')}")`}`,
);
const buildProcess = child_process.spawn(
'xcodebuild',
xcodebuildArgs,
getProcessOptions(args),
);
// ...
// 后面是命令执行输出内容,省略
})
}
编译执行结束后,会输出一段内容,里面有一段TARGET_BUILD_DIR = ...
的内容这个就是最后的编译结果输出目录,那么最后就使用xcrun
命令,把编译结果装到设备上。
项目打包并安装到设备
项目打包安装主要靠xcrun
命令完成:
$ xcrun simctl install [udid] [appPath]
如通过上一步最后编译得到的TARGET_BUILD_DIR
为:/Desktop/build,APP项目名字为test,执行如下命令:
xcrun simctl install 79C4E2-AB95-33C35757BF1E /Desktop/build/test.app
这时,就可以在设备上看到APP的图标了,最后我们再让APP自己启动,使用xcrun simctl launch
命令:
xcrun simctl launch [udid] [bundleID]
- udid:设备id
- bundleID:APP的bundleID可以通过
/usr/libexec/PlistBuddy -c Print:CFBundleIdentifier
命令访问项目下Info.plist
获得,react-native run-ios
源码通过如下方法获得bundleID:
const bundleID = child_process
.execFileSync(
'/usr/libexec/PlistBuddy',
['-c', 'Print:CFBundleIdentifier', path.join(appPath, 'Info.plist')],
{encoding: 'utf8'},
)
.trim();
console.log(`Launching "${bundleID}"`);
总结
最后,react-native run-ios
命令从编译到安装启动流程如下:
- 查找xcworkspace或xcodeproj项目文件;
- 通过
xcrun
查找可运行设备; - 通过
xcodebuild
编译项目,最后得到编译结果文件xxx.app; - 使用
xcrun
将xxx.app
安装到设备,并做启动动作。
另外,本文使用xcodebuild
和xcrun
仅用在开发环境,这两个命令还可以用在生产打包发布自动化,详情可以参考相关文档进行进一步开发。
(完)