iOS-fastlane

1,202 阅读12分钟

1、安装

  1. 安装ruby
    // 查看 Ruby 版本
    % ruby -v
    // 查看 gem 的source 
    % gem sources
    // 安装RVM工具
    % RVMcurl -L get.rvm.io | bash -s stable
    // 列出可安装的ruby版本(有原始的ruby也有其他版本的版本)
    % rvm list known
    // 根据刚才列出的ruby版本,安装一个ruby版本
    % rvm install ruby-xxxxx(xxx为版本号) 
    
  2. 安装Xcode命令行工具
    % xcode-select --install
    
  3. 安装fastlane
    // 常规安装
    % sudo gem install fastlane --verbose
    // 如果安装时出现错误无法安装,就使用以下命令安装
    % sudo gem install -n /usr/local/bin fastlane
    
  4. 验证安装结果
    % fastlane --version
    
    image.png

2、使用

  1. cd到 项目的根目录,初始化fastlane配置:

    % fastlane init
    
  2. 出现选项,常用可选择打包至TestFlightApple Store自定义平台(蒲公英) image.png

  3. 还需要选择要打包的scheme image.png

  4. 过程中还需要输入 Apple账号与密码,这个信息会存储在钥匙串中,后续使用无需再输入密码(过程中还会检测当前项目的 App 与 App Identifier 是否已经在 iTunes Connect 和 Apple Developer 中) image.png

  5. 初始化完成后,项目工程目录下会生成 fastlane文件夹,里面是 Fastlane 的一些配置文件

    • .env文件:根据需要手动创建,作用是 配置环境变量,这是个隐藏文件,需要Command + Shift + .查看隐藏文件
      % touch .env
      
      # // bundle id
      App_Identifier       = "com.xxx.xxx"
      # // 开发者账号
      Apple_Id             = "xxx@126.com"
      # // 开发者App Store Connect Team ID(如果找不到,fastlane init选3能直接拉下来)
      Itc_Team_Id          = "xxxx33366"
      # // 开发者TeamId
      Team_Id              = "xxxxxYYA67"
      # // project的target scheme名称
      Scheme               = "D&P patient"
      # // ipa的名称
      ipa                  = "LZProject.ipa"
      # // 项目xcodeproj
      Xcodeproj            = "D&P patient.xcodeproj"
      # // 项目xcworkspace
      Workspace            = "D&P patient.xcworkspace"
      # // ipa输出路径 -- Appstore
      Appstore_Output_Path = "build/appstore"
      # // ipa输出路径 -- Firim
      Firim_Output_Path    = "build/firim"
      # // ipa输出路径 -- 蒲公英
      Pgy_Output_Path      = "build/pgy"
      
      # // firim 的token
      firim_api_token = "d58c5754d2aff40b4d883ddfefa0d079"
      # // 蒲公英 的api_key
      api_key         = "c00de532996b9c848b40fb602d3xxxxx"
      # // 蒲公英 的user_key
      user_Key        = "273c8527d579ef18d7939a2fd76xxxxx"
      
    • Appfile:存放了 App 的基本信息,主要包括 App_IdentifierAppIDTeam_ID等:(通过ENV['Key']使用 .env文件image.png
    • Fastfile:编写和定制我们打包脚本的一个文件,所有自定义的功能都写在这里

2.1、Fastfile

2.1.1、action、lane
  • fastfile中通过lane任务实现逻辑功能,其中包含多个action还可以调用别的lane),诸如cer()sigh()gym()都是 action

    查看所有Action
    fastlane actions
    查看某个Action的参数说明
    fastlane action [action_name]如(fastlane action gym)

  • fastfile中的action:(详细参阅fastlane官方文档

    action作用
    cert创建和维护证书签名
    sigh配置文件(force: true 每次运行时重新生成配置文件,这将导致总是使用安装在本地机器上的正确签名证书)
    gym(build_app命令的别名)构建打包应用程序,会自动处理打包、签名等操作
    deliver(upload_to_app_store的别名)上传应用程序和屏幕截图到App Store Connect
    pilot(upload_to_testflight的别名)TestFlight上传构建并处理其管理
    scan自动化测试
    match团队中同步证书和配置文件
    pen管理推送配置文件
    produce在App Store Connect创建新的App
  • fastfile中固有一些lane:

    固有lane作用
    before_all执行一次脚本之前首先执行的代码,我们可以在这里面执行一些公共的东西,比如git_pull,cocoapods
    after_all执行结束后,处理共有的后置逻辑
    before_each每次执行 lane 之前都会执行一次
    after_each每次执行 lane 之后都会执行一次
    error在执行上述情况任意环境报错都会中止并执行一次
2.1.2、打包至蒲公英

image.png 首先需要安装蒲公英的 Fastlane 插件:

% fastlane add_plugin pgyer

修改fastlane文件:

default_platform(:ios)

platform :ios do

    desc "以 ad-hoc 方式打包并上传到蒲公英"
    lane :ad_pgy do
        configuration = "Debug"

        puts "自动生成 Provisioning Profiles 文件"
        sigh(
            # // 每次运行时重新生成配置文件
            force: true,
            # // 指定输出的文件夹地址
            output_path: "./fastlane/archive/sign",
            # // 是否为 AdHoc 证书(设为 false 或不写默认为 AppStore 证书)
            adhoc: true
        )

        puts "以 ad-hoc 方式打包"
        gym(
            clean: true,
            # // 指定打包所使用的输出方式 (可选: app-store, package, ad-hoc, enterprise, development)
            export_method: "ad-hoc",
            # // 指定打包方式 (可选: Release, Debug)
            configuration: configuration,
            # // 归档文件路径
            archive_path:"./fastlane/Archive",
            # // 指定输出ipa包的文件夹地址
            output_directory: "./fastlane/archive/" + Time.new.strftime("%Y-%m-%d-%H:%M:%S"),
            # // 输出ipa的文件名为当前的build号
            output_name:get_build_number(),
            # // 指定项目的 scheme 名称
            scheme: ENV['Scheme'],
            # // ipa文件是否包含symbols
            include_symbols:true
            # sdk:"iOS 12.0",
            
            # export_options: { 
               # provisioningProfiles: { 
                  # "com.example.bundleid" => "Provisioning Profile Name", 
                  # "com.example.bundleid2" => "Provisioning Profile Name 2" 
              # } 
            # }
        )

        puts "上传 ipa 包到蒲公英"
        pgyer(
            # 蒲公英 API KEY
            api_key: ENV['Api_key'],
            # 蒲公英 USER KEY
            # user_key: ENV['user_Key']
        )
    end
end
gym参数作用
puts在终端中打印
配置项 clean每次执行前是否清空工程
export_method用于导出archive的方法,有效值为:app-storead-hocdevelopment、validation、package、enterprise、developer-id和mac-application
configuration构建应用程序时要使用的配置,默认为Release
output_directory输出ipa文件目录
output_name输出的ipa名字
schemeproject的Scheme,确保它被Mark为Share
export_options输出配置项、Plist,及发布证书相关(如果使用了fastlane的match,就不需要配置该项,除非你传递一个plist文件到export_options:export_options("./ExportOptions.plist")
  • 记得到Apple账户中创建并下载安装ad-hoc证书

  • 命令行执行% fastlane ad_pgy即可完成上传至蒲公英(ad_pgy是我们自定的lane名称

  • 蒲公英支持Webhook机制,可以将事件消息通知分发出去;所以当我们发布新版本时可以给钉钉或微信发送消息,通知App版本更新

  • 如果一直卡在% bundle update,那么可以尝试更换RubyGems镜像

    // 替换镜像
    % gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/
    // 执行查看当前镜像,是否替换成功
    $ gem sources -l https://gems.ruby-china.com
    
2.1.3、打包至TestFlight
  • 打包至TestFlight,主要需要4步:
    1. gym:打包
    2. app_store_connect_api_keyAPI密钥配置、认证和使用一个或多个 Apple 服务,其中就包括TestFlight
      • 注:设置了这个就不能再设置 usernameapp_identifier两个参数了,否则打包时报错
    3. upload_to_testflight:上传TestFlight
    4. notification:通知

完整配置示例

default_platform(:ios)

platform :ios do

    desc "Push a new release build to the App Store"
    # // 生产环境打包
    lane :release do |options|
    desc "Release>>>>>>>App开始打包..."

    // 打包
    gym(
        clean: true,
        output_directory: './fastlane/release',
        output_name:"dp.ipa",
        scheme: 'D&P patient',
        configuration: 'Release',
        # // include_bitcode: true,
        include_symbols: true,
        codesigning_identity:"iPhone Distribution: Harmonious Home (Beijing) xxxxx xxxxxxx Co., Ltd. (xxxxxYYA67)",
        # // export_xcargs: "-allowProvisioningUpdates"
        export_options: {
            method: 'app-store',
            provisioningProfiles: {
                "com.xxx.xxxxx" => "QZKProvisioning"
            },
        }
    )

    notification(app_icon:"./fastlane/icon.png",title:"LZManager",subtitle: "打包成功,已导出安装包>>>>>>>>", message: "准备发布中....")
    
    // API密钥
    api_key = app_store_connect_api_key(
        key_id: "xxxxx99597",
        issuer_id: "xxxxxxxx-xxxx-xxxx-e053-5b8c7c11a4d1",
        # // key_filepath: "./fastlane/xxxxx99597.p8",
        key_content: "-----BEGIN PRIVATE KEY-----\nxxxxxxxxxxxxxxxxxxxxAgEGCCqGSM49AwEHBHkwdwIBAQQggF/JW87NuzDW0FeLGU3FE/PboJO5I660PdyFdViZm8SgCgYIKoZIzj0DAQehRANCAARnglciq//2VNJDel9gfhqIzE4+q/quQ8xeHFcVt0NiYYYh7iSHhNM+DHJ2I/LFVfUDazS/P9Cl06+oeZsBxu7j\n-----END PRIVATE KEY-----",
        duration: 1200, # optional (maximum 1200)
        in_house: false # optional but may be required if using match/sigh
    )

    // 将打包的ipa上传至TestFlight
    upload_to_testflight(
        api_key: api_key,
        ipa: "./fastlane/release/dp.ipa",
        skip_waiting_for_build_processing: true,
        skip_submission:true
        // 设置了app_store_connect_api_key,因此这两个参数注释掉
        # // username: "xxxxx@126.com",
        # // app_identifier: "com.xxx.xxxxxxx", 
    )
    
    notification(app_icon:"icon.png",title:"LZManager",subtitle: "IPA上传成功", message: "自动打包完成!")
    end
end

等待一段时间后成功显示上传成功: image.png

gym参数作用
export_xcargs自动管理证书、PP文件(我们不进行自动管理,要去掉Automatically manage signing的勾选
codesigning_identity代码签名标识名称,必须和名字完全匹配
export_optionsprovisioningProfiles中存放手动创建并下载安装的PP文件(不带.mobileprovision的后缀)

image.png

image.png

app_store_connect_api_key参数作用
key_id密钥ID,在 AppStore Connect -> 用户和访问 -> 密钥 中创建并获取
issuer_id标识创建认证令牌的发放者
key_filePathp8文件的路径
key_contentp8文件的内容,未编码直接提供,换行处需要加入\n(只提供一次查看密钥的机会,会弹出网页展示密钥,注意保存!,-----BEGIN PRIVATE KEY-----\nxxxxx\n-----END PRIVATE KEY-----)
is_key_content_base64是否key的内容经过base64编码
in_house是app store还是 enterprise

image.png

upload_to_testflight参数作用
api_key传入 app_store_connect_api_key
ipa上传至TestFlight的ipa包路径,与gym中的output对应上
skip_waiting_for_build_processing如果设置为true, distribute_external选项将不起作用,也不会向测试人员分发任何构建
skip_submission跳过pilot的分发动作,只上传ipa文件
2.1.4、打包至App Store
default_platform(:ios)

platform :ios do
    # ======================== 执行lane前的操作 ========================
    # // 所有lane执行之前,可以做如执行cocoapods的pod install
    before_all do |lane, options|
        # // 更新pod
        # cocoapods(use_bundle_exec: FALSE)
        # // 清理缓存
        xcclean(scheme: "D&P patient")
    end

    # // 将正式应用打包并上传到App Store,release是自己取的名字,因为是发布正式版,所以取名叫 release
    desc "Push a new release build to the App Store"
    lane :release do
        # // 打包之前,先将build号递增
        increment_build_number(xcodeproj: "D&P patient.xcodeproj")

        # // 对应用进行打包
        build_app(
            clean: true,
            output_directory: './fastlane/upload',
            output_name:"dp.ipa",
            workspace: "D&P patient.xcworkspace",
            scheme: "D&P patient",
            configuration: 'Release',
            include_symbols: true,
            export_method: "app-store",
            codesigning_identity:"iPhone Distribution: Harmonious Home (Beijing) xxxxx xxxxxxx Co., Ltd. (xxxxxYYA67)",
            export_options: {
                method: 'app-store',
                provisioningProfiles: {
                    "com.qzk.patient" => "QZKProvisioning"
                },
            } 
        )

        # // 将打包完的应用上传到AppStore
        upload_to_app_store(
            ipa: "./fastlane/upload/dp.ipa",
            username: "xxxxx@126.com", 
            app_identifier: "com.xxx.xxxxxxx", 
            app_version: "1.0.5",
            # build_number: "10",
            force: true,  #跳过HTML报告验证
            submit_for_review: true, # 上传完所有内容后,提交新版本以供审核
            automatic_release: true, # 应用程序通过审核后是否应该自动发布?(不能与auto_release_date一起使用)
            skip_metadata: true,
            skip_screenshots: true,
            submission_information: {
                add_id_info_uses_idfa: false,
                add_id_info_serves_ads: false,
                add_id_info_tracks_install: false,
                add_id_info_tracks_action: false,
                add_id_info_limits_tracking: false
            }
        )
    end

    # ======================== 执行lane成功后的操作 ========================
    # // 所有lane完成之后,可以使用参数lane来区分
    after_all do |lane, options|
        puts "所有lane执行完毕"
    end

    # ======================== 执行lane失败后的操作 ========================
    # // 所有lane失败之后,可以使用参数lane来区分
    error do |lane, options|
        puts "lane执行失败"
    end
end
upload_to_app_store参数作用
force跳过HTML报告验证
submit_for_review上传完所有内容后,提交新版本以供审核
automatic_release应用程序通过审核后是否应该自动发布?(不能与auto_release_date一起使用)
submission_information设置提交时的一连串询问,包括是否使用IDFA、广告之类的
  • 不同于TestFlight使用了API密钥而必须省略username和app_identifier,我们这里写上能避免一些错误

2.2、报错、警告

2.2.1、Cannot set build number with plist path containing $(SRCROOT)

image.png

报错原因

自增build号increment_build_number方法不能包含 $(SRCROOT)

解决方案

Build Settings 中将info.plist file的路径由原来的 $(SRCROOT)/Info.plist 改为相对路径(如果改错了Build Settings左边的Info就没有东西) image.png

2.2.2、warning: The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0
/Users/lz/Downloads/DPSourceTree/QZ patient /Pods/Pods.xcodeproj: warning: The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 11.0 to 16.0.99. (in target 'IQKeyboardManager' from project 'Pods')

报错原因

项目部署版本与Pod库部署版本不匹配,可能会有非常多的库报这个问题 image.png

解决方案

podfile末端添加以下代码:

post_install do |installer|
    installer.pods_project.build_configurations.each do |config|
        config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
    end
    installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
        end
    end
end
2.2.3、Exit status: 65

image.png 报错原因

归档失败,明显是gym步骤中出现问题,查看图中提示的日志可以帮助我们分析报错

解决方案

借助日志查看gym中配置问题;我犯的错误是:

  • codesigning_identity 配置错误
  • 使用了自动证书管理 Automatically manage signing
// 改后的
gym( 
    ......
    codesigning_identity:"iPhone Distribution: Harmonious Home (Beijing) xxxxx xxxxxxx Co., Ltd. (xxxxxYYA67)",
    # // export_xcargs: "-allowProvisioningUpdates" 
    export_options: { 
        method: 'app-store', 
        provisioningProfiles: { 
            "com.xxx.xxxxx" => "QZKProvisioning" 
        }, 
    } 
)
2.2.4、Exit status: 70

image.png 报错原因

找不到PP文件,因为多写入了 .mobileprovision 后缀

2.2.5、invalid curve name(上传TestFlight过程报错)

image.png 报错原因

上传 TestFlight过程中配置API密钥key_filepath 读取出错

解决方案

改用 key_content 直接存放 API密钥

// API密钥
api_key = app_store_connect_api_key(
    ......
    # // key_filepath: "./fastlane/xxxxx99597.p8",
    key_content: "-----BEGIN PRIVATE KEY-----\nxxxxxxxxxxxxxxxxxxxxAgEGCCqGSM49AwEHBHkwdwIBAQQggF/JW87NuzDW0FeLGU3FE/PboJO5I660PdyFdViZm8SgCgYIKoZIzj0DAQehRANCAARnglciq//2VNJDel9gfhqIzE4+q/quQ8xeHFcVt0NiYYYh7iSHhNM+DHJ2I/LFVfUDazS/P9Cl06+oeZsBxu7j\n-----END PRIVATE KEY-----",
)
2.2.6、Use of Advertising Identifier (IDFA) is required to submit(打包发布值Apple Store过程报错)

image.png 报错原因

手动发布时会询问的那些是否使用IDFA之类的问题没设置

解决方案

使用submission_information将这些问题统一设置(报错已经给出了提示)

submission_information: {
    add_id_info_uses_idfa: false,
    add_id_info_serves_ads: false,
    add_id_info_tracks_install: false,
    add_id_info_tracks_action: false,
    add_id_info_limits_tracking: false
}
2.2.7、Error: Unable to validate your application
Error: Unable to validate your application. Sign in with the app-specific password you generated. 
If you forgot the app-specific password or need to create a new one, go to appleid.apple.com

报错原因

苹果开发者账号开启了双重认证,所以仅有账号和密码还是不能够成功登录账号的

解决方案

使用App专用密码,并把 keychain下开发者账号对应的密码 改为该 application specific password image.png image.png

  1. 执行命令,生成FASTLANE_SESSION,将其备份
    % fastlane spaceauth -u xxxxxxx@126.com
    
  2. .bash_profile中将 专用密码FASTLANE_SESSION 配置为环境变量: image.png
    export FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-ezag
    export FASTLANE_SESSION=value: DAWTKNV323952cf8084a204fb20ab2508441a07d02d3f1dd928eb250cc23560916dcd3b2552522601f91a8f412f4aa7efcc50d86d4ce59767c7f8a905f984825f851abedd8905106e156fc61823ae3795e32d85fad02324d33a7ac3a907b4c66faa512180b0f9bddbe88ac50e8cfa3ec5481a2224210d33561e51ddba3dabeae5cd2d25cac2142a745a8077cc4f822dfdb335bf94a58835c73024eba1c0d9188c45532d699036051bffde871a0fb167d984aeb333cef0ea90e9d2ba52284df2e2e72def2cfa997fcebdfb3702b0798621284f87db1881391ff6014acbf097622429e5a0dc591d32cb7d49ad799e5a3d482024457995abf7cab5d90292fd92fd57f093ef6a0af006a0d8aac42aa9a1d680c01a69d0c6771b4636581f4dfb393e5b90a4dc5b5e2807d759505ffd4d2b753c936c5dd06602dc9207d2f31946cf8cce8459b3ac6ad939e0f737eeda771b1c8e6238a1cac1ee0529b02c4d9487426cfb15d74dc4b1f141c12d085b12e3cff62ef72e521578c68c3f5507f3e11cda4bd8bb93cacb4afac28b8ff8eb9fae0cdde4e8445374305d843abd10aa09ca2b38a7966ab3a8c6a45066632f6dce724fdd0714b1edda686bf2e1cc352c6f520f5f00eab7ec5668ad49e8214cdc2a101c2fecc541992d8d95cf6a01e3e5f4f2d03fe2d5cda106768914a74e4101e100a57c028dade97fc2fab9ae9ee641c3b95d5bfxxxxxxxxxxxxxxxxxxx\n
    
  3. 执行下边命令使新增的环境变量生效,分别执行以下命令查看环境变量是否设置成功
    // 使环境变量配置生效
    % source ~/.bash_profile
    // 检测配置能否成功打印出来
    % echo $FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
    % echo $FASTLANE_SESSION
    

    注意:

    1. 生成的FASTLANE_SESSION,真正的实体要从第一个value处开始到第一个value结束,不要全部一股脑全部配置到环境变量文件中,否则会因为格式问题导致变量设置失败,=号左右不留空格,否则也会出问题
    2. 如官网提示,使用application specific password的情况下,deliver不要做上传二进制之外的其他操作,比如设置元数据等,否则可能会导致方案失效
    3. application specific passwordFASTLANE_SESSION的有效期为一个月左右,所以切记要及时对环境变量和钥匙串对应的密码进行更新
  4. 使用CredentialsManager重设钥匙串对应的开发者账号密码,密码是上边的 App专用密码 image.png

    如果设置后又要执行% fastlane spaceauth -u xxxxxxx@126.com获取FASTLANE_SESSION,会报错获取不到,但系统会提示要你先执行% unset FASTLANE_SESSION

    image.png

3、进阶使用

3.1、options参数传递

在执行shell脚本之类的都是可以传递一些参数的,fastlane也是有的,options就是存储了我们在命令行中执行lane时传递的参数的字典,在befor_allafter_all、各种lane里都可以使用这个options

# 使用key:value来传递一组对应的参数
fastlane laneName key:value key2:value2

3.2、接收options参数

platform :ios do 
  before_all do |lane, options|
    #options参数
    value  = options[:key]
    value2 = options[:key2]
  end
  
  lane :laneName do |options|
    ...
  end
end

3.3、private_lane

私有lane,命令行无法调用,只能fastfile中使用

platform :ios do 
  # 相当于全局变量
  build_config = "debug"

  before_all do |lane, options|
    # 调用私有lane deal_param 并将options传递过去
    deal_param options
  end
  
  lane :laneName do |options|
    ...
  end
  
  # 私有lane,比如把传入的build参数进行一下处理
  private_lane :deal_param do |options|
    build_config = build_config ? build_config : "debug"
    build_config.capitalize!
  end
end

结语

本文尽量列出了笔者使用中遇到的各种问题,但这只是fastlane丰富功能中的冰山一角,使用中还需要查看 Fastlane官网,它真的太好用了!下篇再结合Jenkins出篇文章

童鞋们也可以参考下面的文章丰富了解:自动化打包之fastlane