返回博客首页
← 所有文章

在没有Mac的情况下为iOS开发NativeScript应用程序

2022年1月18日 — 作者:Jason Cassidy

显然,在iOS开发中拥有Mac是最好的选择,但如果您想在iOS上试用您的应用程序,并且只拥有Windows机器,那么这篇文章将向您展示一种实现方法。

本指南将使用Azure Devops的免费版本来构建我们的iOS项目,然后将其部署到我们的iPhone。

设置所需的Apple证书/配置文件

您需要注册 Apple Developer Program

创建开发证书

注册后,下一步是创建开发证书。

  • 登录您的Apple开发者帐户
  • 选择证书、ID和配置文件
  • 选择证书
  • 点击+添加新证书。
  • 选择Apple Development,继续

现在苹果需要一个CSR,为此,我们需要一些Windows工具。

  • 下载并安装 openssl 1.X(注意:openssl不提供二进制文件,因此您需要访问该页面上列出的第三方网站之一。)

  • 生成私钥

    openssl genrsa -out mykey.key 2048
    
  • 生成CSR

    openssl req -new -key mykey.key -out CertificateSigning.csr -subj "/[email protected], CN=Jason Cassidy, C=IE"
    
  • 将CSR上传到苹果并下载生成的证书

  • 将证书复制到我们生成CSR和密钥的同一文件夹。

  • 创建.p12(组合私钥和证书)

    转换为PEM格式

    openssl x509 -in development.cer -inform DER -out development.pem -outform PEM
    

    生成.p12

      openssl pkcs12 -export -inkey mykey.key -in development.pem -out development.p12 -legacy
    

这将要求您输入密码,请记住它,因为我们稍后需要它。

在Apple上注册您的设备

  • 登录您的Apple开发者帐户

  • 选择证书、ID和配置文件

  • 选择设备,然后点击+图标

  • 输入设备名称

  • 您现在需要获取您的设备ID,在将您的iPhone连接到PC后运行以下命令。(这可能只在您安装了iTunes的情况下才有效)

     ns device
    

    为您的手机列出的设备标识符是设备ID(UDID),您应该输入它。

  • 在接下来的屏幕中点击继续

创建App Identifier

  • 登录您的Apple开发者帐户
  • 选择证书、ID和配置文件
  • 选择标识符,然后点击+图标
  • 选择App ID,然后点击继续
  • 选择类型App,然后点击继续
  • 输入描述显式捆绑包ID,例如 my.company.testApp
  • 点击继续
  • 点击注册

创建配置文件

  • 登录您的Apple开发者帐户
  • 选择证书、ID和配置文件
  • 选择配置文件,然后点击+图标
  • 选择iOS App Development,然后点击继续
  • 选择您在上一步中创建的App ID(例如 my.company.testApp),然后点击继续
  • 选择您刚刚创建的证书,然后点击继续
  • 选择您之前注册的设备
  • 为配置文件起一个名称,然后点击生成
  • 您现在可以下载配置文件

创建我们的GitHub项目

  • 创建一个您选择的类型的示例NativeScript应用程序。

    例如:

    ns create
    
  • 编辑nativescript.config.ts,将id更改为之前创建的显式捆绑包ID

    例如:

    ...
    id: 'my.company.testApp',
    ...
    
  • Azure使用yaml文件来控制其构建过程,目前我们将在这里创建一个最小的文件作为占位符

    在builds文件夹中创建一个名为build.yaml的新文件,即builds\build.yaml,其中包含

    trigger:
    - main
    
    name: '1.1.$(DayOfYear)$(rev:rr)'
    #variables:
    #  - group: AzureDemoVariables 
    
    jobs:
    - job: BuildApple
      pool:
        vmImage: macOS-latest
      steps:
      - checkout: self
        clean: true 
        path: "TestApp"
    
      - script: |
          echo Saying Hello
        displayName: 'Saying Hello'
    

    这里我们指示azure在mac上运行我们的作业,并使用一个简单的脚本来说hello。

  • 将此项目检入GitHub

在Azure Devops中构建您的应用程序

设置Azure Devops免费帐户

如果您还不是Microsoft Azure Devops用户,可以从 Azure DevOps 设置免费帐户。

为此,我们使用了GitHub凭据。

您将被要求首先创建一个组织,然后它将包含项目。

然后您将被要求创建您的第一个项目,它将包含对您的GitHub项目的引用,并将构建您的代码。

Azure提供自己的git仓库、wiki、问题管理等,但在这个例子中,我们只使用Pipeline功能来构建我们的GitHub仓库。

启用构建

不幸的是,对于免费帐户,您需要请求在azure上构建的能力,您可以在 这里 阅读相关信息,它归结为需要填写这个 表格

创建新的Pipeline

  • 在左侧菜单中点击Pipelines,然后点击Create Pipeline
  • 选择GitHub
  • 选择您的仓库(您可能需要在此处提供GitHub凭据,具体取决于您创建Azure Devops帐户的方式)
  • 批准并安装您的仓库的GitHub App
  • 当被要求配置您的pipeline时,选择Existing Azure Pipelines YAML file
  • Path下拉菜单中选择之前创建的yaml文件,/builds/build.yaml
  • 点击继续
  • 点击Run旁边的向下箭头,然后点击Save
  • 您的pipeline现在应该保存,您可以点击Run Pipeline
  • 这将失败并出现错误No hosted parallelism has been purchased or granted.,直到您的请求被批准。
  • 一旦您的请求被批准,pipeline应该成功运行。

设置所需的Nativescript依赖项

  • 将以下行添加到builds\build.yaml

    
      - task: NodeTool@0 
        inputs:
          versionSpec: '14.x'
      - script: |
          python -m pip install six
        displayName: 'Install six'
    
      - script: |
          npm i -g nativescript
        displayName: 'Install nativescript'   
    
      - script: |
          npm --version
          node --version
    
          ruby --version
          gem query --local
          echo **************************************
          pip --version
          pip show six
    
        displayName: 'Show NPM version'
      - script: |
          ns --version --json
        displayName: 'Show nativescript version'
      - script: |
          ns doctor
        displayName: 'Run ns doctor'
    

    这里我们安装了NativeScript依赖项,这些依赖项还没有安装在Microsoft提供的镜像中。

    最后,我们运行ns doctor来检查一切是否正常。

  • 将其检入,作业应该触发并运行(如果不行,您可以手动运行)

将Secrets添加到Azure项目

我们有3个Secrets,它们必须在mac上可用才能构建我们的应用程序。

  • 开发证书
  • 开发配置文件
  • 证书的密码

我们将它们存储在Pipeline Library

  • 在您的azure项目中,导航到Pipelines -> Library
  • 点击Secure Files
  • 点击+Secure file
  • 浏览到之前步骤中创建的development.p12,然后点击OK上传。
  • 重复此操作并上传之前创建的Profile.mobilprofile)。

现在我们添加一个变量组,用于保存密码和指向这些文件的指针。

  • 在您的azure项目中,导航到Pipelines -> Library
  • 选择Variable groups标签,然后点击+ Variable Group
  • 为它命名为AzureDemoVariables
  • 添加一个名为P12password的新变量,并将值设置为生成的development.p12的密码
  • 点击文本框末端的挂锁,将变量类型更改为secret,这意味着您无法读取该属性的值。

现在我们将添加两个变量,它们保存我们之前添加的secret文件的id。

  • 添加一个名为apple_cert_sercure_file_id的新变量,我们稍后将设置其值

  • 添加一个名为apple_provisioning_profile_file_id的新变量,我们稍后将设置其值

  • 在您的azure项目中,导航到Pipelines -> Library

  • 点击Secure Files

  • 点击development.p12链接

  • 在浏览器url字段中,有一个属性secureField,例如secureFileId=abce9b6a-2470-4258-b6dc-48c26e026c3b,这是secret文件的id,在本例中为abce9b6a-2470-4258-b6dc-48c26e026c3b

  • 将此id复制到我们创建的变量apple_cert_sercure_file_id的值字段

  • 重复此操作,为配置文件设置apple_provisioning_profile_file_id变量的值为其secureFileId

  • 这种获取这些id的方式似乎很疯狂,但我只找到了这种方法

现在我们已经完成了变量组,我们可以将它们安装到构建机器上。

在构建机器上安装证书和配置文件

  • 取消变量部分的注释,即它应该读作

    variables:
      - group: AzureDemoVariables 
    
  • 将以下行添加到builds\build.yaml的末尾

      - task: InstallAppleCertificate@2
        displayName: 'Install an Apple certificate'
        inputs:
          certSecureFile: '$(apple_cert_sercure_file_id)'
          certPwd: '$(P12password)'
    
      - task: InstallAppleProvisioningProfile@1
        displayName: 'Install an Apple provisioning profile'
        inputs:
          provProfileSecureFile: '$(apple_provisioning_profile_file_id)'
    

这将证书和配置文件安装到构建机器上,并在作业完成后将其删除。

  • 将其检入,作业应该触发并运行(如果不行,您可以手动运行)
  • 检查Pipeline作业,如果它要求您使用消息This pipeline needs permission to access 3 resources before this run can continue进行批准,请点击View并授予批准。

构建IPA

  • 将以下行添加到builds\build.yaml的末尾
      - script: |
          ns build ios --bundle --for-device
        displayName: 'Run ns to build'
    
      - task: CopyFiles@2
        displayName: 'Copy Files to: $(build.artifactstagingdirectory)'
        inputs:
          SourceFolder: '$(system.defaultworkingdirectory)'
          Contents: |
            **/*.ipa
          TargetFolder: '$(build.artifactstagingdirectory)'
          flattenFolders: true
          preserveTimestamp: true
          
      - task: PublishBuildArtifacts@1
        displayName: 'Publish Artifact: drop'
        inputs:
          PathtoPublish: '$(build.artifactstagingdirectory)'
    

这里我们正在构建应用程序,然后将应用程序发布到pipeline工件,这使ipa可供下载。

  • 在pipeline运行屏幕上,现在将有一个链接,上面应该写着1 published,点击它将带您到该构建运行的工件页面。
  • 然后您可以下载ipa TestApp.ipas

在iPhone上安装ipa

我们将使用OTA(over the wire)分发来将应用程序安装到iPhone,在这里,我们将应用程序托管在我们自己的网站上,并从该网站下载到手机。

这有一些要求

  • 该网站必须通过https提供服务
  • 手机必须信任为网站签发SSL证书的CA

如果您可以使用权威机构的证书(例如Let's Encrypt),那么您可以使用这些证书,并使用这些证书从网站上提供应用程序,但在这里,我们将从头开始使用我们自己的证书。

创建证书

我们将再次使用openssl创建一个根CA证书,然后使用它来生成一个证书,该证书将用于我们的网站,该网站将把ipa提供给我们的手机。

这要求我们的手机能够与我们的PC通信,并且我们的PC防火墙上必须打开一个端口(8888)。

  • 首先我们需要我们的PC的ip地址,发出命令ipconfig,这将输出各种连接,您需要的是LAN上可访问的连接。(通常是192.168.X.X)。

  • 在项目中创建一个名为serve/certs的文件夹

  • 在该文件夹中创建一个名为'thesite.ext'的文件,其内容为

    authorityKeyIdentifier=keyid,issuer
    basicConstraints=CA:FALSE
    keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
    subjectAltName = @alt_names
    
    [alt_names]
    DNS.1 = thesite.com
    IP.1 = 192.168.178.22
    
  • 将DNS.1的值替换为您的机器FQDN,将IP.1的值替换为您的机器IP地址

  • serve/certs文件夹中创建一个名为createcerts.bat的批处理文件,其内容如下

    rem create the root CA Key
    openssl genrsa -des3 -out myCA.key  -passout pass:capassword 2048
    
    rem create the root CA cert
    openssl req -x509 -new -nodes -key myCA.key -sha256 -days 1825 -out myCA.pem -passin pass:capassword  -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=CA.example.com"
    
    rem create private key for site
    openssl genrsa -out thesite.key -passout pass:sitepassword 2048
    
    rem create CSR for site
    openssl req -new -key thesite.key -out thesite.csr -passin pass:sitepassword -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=site.example.com"
    
    rem create cert for site that serves the ipa
    openssl x509 -req -in thesite.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out thesite.crt -days 825 -sha256 -extfile thesite.ext -passin pass:capassword 
    

    这里我们正在创建一个根CA密钥和证书,然后是一个网站密钥和证书,您可以编辑密码/详细信息,但它们将在演示目的上起作用。

  • serve/certs目录中运行批处理文件。

  • 现在您将拥有从PC上的网站到手机上提供应用程序所需的证书。

  • 将myCA.pem发送到您可以访问的iPhone帐户的电子邮件。

  • 从手机上的电子邮件中,点击证书并安装配置文件。

  • 在手机上进入设置,在顶部附近应该有一个新的选项已下载的配置文件,点击进入它。

  • 点击安装并输入您的密码。

  • 点击安装确认。

  • 导航到设置 -> 通用 -> 关于 -> 证书信任设置

  • 您应该在 启用根证书的完全信任 部分看到 CA.example.com 的条目,将开关切换到开启并点击继续。

您的手机现在将信任来自您新生成的根 CA 的证书。

因此,您应该保管好这些证书,不要将它们检入公共仓库,并在上面的命令中使用您自己的密码。

从您的PC到您的手机提供IPA

  • serve 文件夹中创建一个名为 download.plist 的文件。这是将最初发送到您手机的文件,告诉手机从哪里获取您的应用程序。
  • 添加内容
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>items</key>
        <array>
            <dict>
                <key>assets</key>
                <array>
                    <dict>
                        <key>kind</key>
                        <string>software-package</string>
                        <key>url</key>
                        <string>https://mysite</string>
                    </dict>
                </array>
                <key>metadata</key>
                <dict>
                    <key>bundle-identifier</key>
                    <string>my.app.id</string>
                    <key>bundle-version</key>
                    <string>1.0.0</string>
                    <key>kind</key>
                    <string>software</string>
                    <key>title</key>
                    <string>MyApp</string>
                </dict>
            </dict>
        </array>
    </dict>
    </plist>
    

现在我们将创建一个 node https 服务器来服务 ipa。

  • 创建一个名为 serve\serve.js 的文件。

  • 添加内容

    var http = require('https');
    var fs = require('fs');
    const myArgs = process.argv.slice(2);
    var ipaddress = myArgs[0];
    
    const port = 8888;
    
    const options = {
        key: fs.readFileSync('./serve/certs/thesite.key', 'utf8'),
        cert: fs.readFileSync('./serve/certs/thesite.crt', 'utf8'),
        ca: fs.readFileSync('./serve/certs/myCA.pem')
      };
    
    console.log(options.key);
    
    http.createServer(options, function (request, response) {
        console.log('request starting...');
        var filePath;
        var contentType;
        console.log(request.url);
    
        if(request.url === '/ipa'){
            filePath = './serve/ipa/TestApp.ipa';
            contentType = 'application/octet-stream';
        }else if (request.url === '/plist') {
            contentType = 'application/x-plist';
            filePath = './serve/download.plist';
        }
        if(filePath){
            fs.readFile(filePath, function(error, content) {
                if (error) {
    
                        response.writeHead(500);
                        response.end('An Error Occured: '+error.code+' ..\n');
                        response.end(); 
                }
                else {
                    var contentToWrite;
                    if(contentType == 'application/x-plist'){
                        contentToWrite=content.toString().replace("mysite", ipaddress+':'+port+'/ipa');
                    } else {
                        contentToWrite = content;
                    }
                    response.writeHead(200, { 'Content-Type': contentType });
                    response.end(contentToWrite);
                }
            }
        ) 
    
        } else {
            response.writeHead(200, { 'Content-Type': 'text/html' });
            response.end("hello Click the link to download an app<br><a  href='itms-services://?action=download-manifest&url=https://"+ ipaddress +":"+port+"/plist'>Download    </a>");
        }
      
    }).listen(port);
    

    这将创建一个 https 服务器,它将使用我们生成的证书并将我们的 IPA 传输到手机。

  • 将您下载的 IPA 放置到 serve/ipa/TestApp.ipa

  • 从项目的根目录运行 https 服务器。

    node serve\serve.js <Your Ip Address>
    
  • 从您的手机上,浏览到 https://<您的 IP 地址>:8888

  • 点击下载链接。

  • 系统会提示您安装应用程序。

应用程序在您的iPhone上运行

当您的手机连接到 PC 时,您可以获得来自 iPhone 的日志输出。

  • 在您的手机连接的情况下,再次发出上面使用的命令。

    ns device
    
  • 如果显示设备已连接,您可以运行

    ns device log > LogOutput.txt
    
  • 这将输出很多信息,其中大部分您不需要,但您将看到应用程序中的 控制台日志 消息,以及其他错误等。

    示例输出

    Jan 18 17:52:14 jason-iphone SpringBoard(RunningBoardServices)[50750] : Received state update for 53575
    Jan 18 17:52:14 jason-iphone SpringBoard(RunningBoardServices)[50750] : Received state update for 53533
    Jan 18 17:52:14 jason-iphone TestApp(NativeScript)[53575] : CONSOLE LOG: Angular is running in development mode. Call enableProdMode() to enable production mode.
    Jan 18 17:52:14 jason-iphone kernel[0] : 1609773.409 memorystatus: killing_idle_process pid 53361 [com.apple.WebKit.WebContent] (sustained-memory-pressure 0) 5216KB - memorystatus_available_pages: 66771 compressor_size:120141
    Jan 18 17:52:14 jason-iphone UserEventAgent(MemoryMonitor)[50714] : kernel jetsam snapshot note received
    

最终说明

在 Yaml 中,空格很重要。一个示例项目可在此处 获取,其中包含一个使用上述内容的项目。

如果您已经走到这一步,那么很明显,如果您一直在不断地更改您的应用程序,上面的方法并不适合,因为周转时间太长。但是,云构建至少可以让您在没有 Mac 支出情况下,尝试 iOS 开发。