iOS

react-native run-ios内部原理探索

通过react-native命令行学习自动化编译运行iOS项目

Posted by lijiahao on March 5, 2020

最近在为iOS项目开发自动化构建工具时,需要调用react-native run-ios命令,但由于项目react-native版本过低,本地xcode版本已经升至xcode11,不少目录结构和数据格式有变化,官方react-native run-ios命令执行失败,因此不得不从react-native run-ios内部研究iOS打包运行机制,自己重新编写打包脚本进而修复run-ios无法运行的问题。

react-native run-ios执行过程和结果来看,这个命令做了以下工作:

  1. 找到xcode项目(*.xcworkspace或 *.xcodeproj文件);
  2. 找到本地连接的设备或本地可运行模拟器;
  3. 编译项目;
  4. 将项目打包并安装在设备上运行。

下面逐步分析这几个步骤需要使用什么命令执行。

查找xcode项目文件

这一步很简单,只需通过简单的文件遍历,通过判断文件后缀名是否为xcworkspacexcodeproj即可,以下是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提供的命令行工具xcodebuildxcrunxcodebuildxcrun都是来自Command Line Tools,Xcode自带,如果没有可以通过以下命令安装:

xcode-select --install

或者在下面的链接下载安装:

https://developer.apple.com/downloads/

安装完可在以下路径看到这两个工具:

/Applications/Xcode.app/Contents/Developer/usr/bin/

或通过终端执行命令xcodebuild -hxcrun -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命令从编译到安装启动流程如下:

  1. 查找xcworkspace或xcodeproj项目文件;
  2. 通过xcrun查找可运行设备;
  3. 通过xcodebuild编译项目,最后得到编译结果文件xxx.app;
  4. 使用xcrunxxx.app安装到设备,并做启动动作。

另外,本文使用xcodebuildxcrun仅用在开发环境,这两个命令还可以用在生产打包发布自动化,详情可以参考相关文档进行进一步开发。

(完)


原创不易,如果觉得这篇文章对你有帮助,不如赏杯咖啡吧
微信
支付宝