Fastlane 实践(一):自动化打包和发布

遇到问题, 多了一个扩展打包失败,顺便看到了这个文章觉得不错,转载记录一下,
xcode 12 自动打包问题,dev 有arm64 兼容问题,切换ad-hoc 自动打包上传解决

From: http://chaosky.tech/2020/05/04/fastlane-in-action-1/

fastlane is the easiest way to automate beta deployments and releases for your iOS and Android apps. 🚀 It handles all tedious tasks, like generating screenshots, dealing with code signing, and releasing your application.

fastlane 是自动化Beta部署和发布iOS和Android应用程序最简单方法。它可以处理所有繁琐的任务,例如生成屏幕截图,处理代码签名以及发布应用程序。

Fastlane 安装

安装 Xcode command line tools

$ xcode-select --install  

安装 Homebrew

$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"  

安装 RVM

$ curl -sSL https://get.rvm.io | bash -s stable --auto-dotfiles  

$ source ~/.rvm/scripts/rvm  

修改 RVM 的 Ruby 安装源到 Ruby China 的 Ruby 镜像服务器,这样能提高安装速度。

$ echo "ruby_url=https://cache.ruby-china.org/pub/ruby" > ~/.rvm/user/db  

安装Ruby 2.6.5

$ rvm install 2.6.5  

$ rvm use 2.6.5 --default  

更新 RubyGems 镜像

$ gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/  

$ gem sources -l  

https://gems.ruby-china.org  

# 确保只有 gems.ruby-china.org  

bundle config mirror.https://rubygems.org https://gems.ruby-china.org  

安装 CocoaPods 和 Fastlane

$ gem install cocoapods  

$ gem install fastlane -NV  

$ gem install bundle  

快速开始

  1. 进入 iOS App 的目录并运行:

    1

fastlane init  

fastlane 会自动自动识别你的项目,并询问任何缺失的信息。

  1. fastlane Getting Started guide for iOS
  1. fastlane Getting Started guide for Android

Fastlane 进阶用法

随着公司项目的增多,每次都运行重复的Fastlane 命令进行配置会低效很多,所以急需一套可以满足所有App需求的配置。

Fastlane 是由Ruby开发,所以也支持 dotenv 的功能。

最终Fastlane生成目录结构如下:

├── .env
├── Appfile
├── Deliverfile
├── Fastfile
├── Matchfile
├── Pluginfile
├── README.md
├── Scanfile
├── metadata
│ ├── app_icon.jpg
│ ├── copyright.txt
│ ├── primary_category.txt
│ ├── primary_first_sub_category.txt
│ ├── primary_second_sub_category.txt
│ ├── review_information
│ │ ├── demo_password.txt
│ │ ├── demo_user.txt
│ │ ├── email_address.txt
│ │ ├── first_name.txt
│ │ ├── last_name.txt
│ │ ├── notes.txt
│ │ └── phone_number.txt
│ ├── secondary_category.txt
│ ├── secondary_first_sub_category.txt
│ ├── secondary_second_sub_category.txt
│ ├── trade_representative_contact_information
│ │ ├── address_line1.txt
│ │ ├── address_line2.txt
│ │ ├── address_line3.txt
│ │ ├── city_name.txt
│ │ ├── country.txt
│ │ ├── email_address.txt
│ │ ├── first_name.txt
│ │ ├── is_displayed_on_app_store.txt
│ │ ├── last_name.txt
│ │ ├── phone_number.txt
│ │ ├── postal_code.txt
│ │ ├── state.txt
│ │ └── trade_name.txt
│ └── zh-Hans
│ ├── apple_tv_privacy_policy.txt
│ ├── description.txt
│ ├── keywords.txt
│ ├── marketing_url.txt
│ ├── name.txt
│ ├── privacy_url.txt
│ ├── promotional_text.txt
│ ├── release_notes.txt
│ ├── subtitle.txt
│ └── support_url.txt
└── pem
├── development_xxx.xxx.xxx.p12
├── development_xxx.xxx.xxx.pem
├── development_xxx.xxx.xxx.pkey
├── production_xxx.xxx.xxx.p12
├── production_xxx.xxx.xxx.pem
├── production_xxx.xxx.xxx.pkey

.env

这个文件中放入的是需要引用的环境变量。

FASTLANE_SKIP_UPDATE_CHECK=true  

FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT=120  



APPLE_ID="xxxx"    

TEAM_ID="xxxx"         

FASTLANE_PASSWORD="xxx"      

FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD="qwwe-tdpp-hdpc-fgzy"   

ITC_TEAM_ID="xxxx"       



APP_IDENTIFIER="xxx.xxx.xxx"  

SCHEME_NAME="XXX"  

WORKSPACE_NAME="XXX.xcworkspace"  

XCODEPROJ_NAME="XXX.xcodeproj"  





DEV_APP_IDENTIFIER="xxx.xxx.dev.xxx"  

DEV_APP_NAME="XXX测试版"  





PROD_APP_IDENTIFIER="xxx.xxx.xxx"  

PROD_APP_NAME="XXX"  



MATCH_GIT_BRANCH="XXX"  



DELIVER_METADATA_PATH="./fastlane/metadata"  

DOWNLOAD_METADATA_PATH="./metadata"  

Appfile

app_identifier "#{ENV["APP_IDENTIFIER"]}"   

apple_id "#{ENV["APPLE_ID"]}"   



team_id "#{ENV["TEAM_ID"]}"   

itc_team_id "#{ENV["ITC_TEAM_ID"]}"   

Deliverfile

app_identifier "#{ENV["APP_IDENTIFIER"]}"   

username "#{ENV["APPLE_ID"]}"   

Fastfile

fastlane_require "spaceship"  



fastlane_version "2.89.0"  



default_platform :ios  



platform :ios do  



  base_path = Pathname::new(File::dirname(__FILE__)).realpath.parent  



  before_all do  







  end  



  desc "生成 adhoc 测试版本,提交到蒲公英,参数 => type:'adhoc/development',默认adhoc"  

  lane :pgyer_beta do |options|  



    type = String(options[:type] || "adhoc")  



    if type == "adhoc"  

      export_method = "ad-hoc"  

      match_type = "adhoc"  

      match_type_name = "AdHoc"  

    else  

      export_method = "development"  

      match_type = "development"  

      match_type_name = "Development"  

    end  



    git_reversion = sh("git log -1 --pretty=format:'%h'")  

    version_number = get_info_plist_value(path: "#{ENV["SCHEME_NAME"]}/Info.plist", key: "CFBundleShortVersionString")  

    build_number = number_of_commits(all: false)  





    git_log = sh("git log --no-merges -1 --pretty=format:'# %ai%n# %B by %an'")  

    build_time = Time.new.strftime("%Y-%m-%d_%H.%M.%S")  





    output_dir = "#{base_path}/Output/adhoc/#{build_time}"  

    output_name = "#{ENV["SCHEME_NAME"]}_v#{version_number}(#{build_number}).ipa"  





    add_badge(shield: "#{version_number}-#{build_number}-orange")  





    increment_build_number(build_number: build_number)  





    update_app_identifier(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      plist_path: "#{ENV["SCHEME_NAME"]}/Info.plist",  

      app_identifier: "#{ENV["DEV_APP_IDENTIFIER"]}"  

    )  



    update_info_plist(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      plist_path: "#{ENV["SCHEME_NAME"]}/Info.plist",  

      block: proc do |plist|  

        plist["CFBundleDisplayName"] = "#{ENV["DEV_APP_NAME"]}"  

        plist["CFBundleName"] = "#{ENV["DEV_APP_NAME"]}"  

        plist["GIT_REVISION"] = git_reversion  

        plist["BUILD_TIME"] = build_time  

        plist["APP_CHANNEL"] = "pgyer"  

        urlScheme = plist["CFBundleURLTypes"].find{|scheme| scheme["CFBundleURLName"] == "weixin"}  

        urlScheme[:CFBundleURLSchemes] = ["#{ENV["DEV_WEIXIN_APPID"]}"]  

      end  

    )  





    update_app_identifier(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      plist_path: "#{ENV["NOTIFICATIONSERVICE_SCHEME_NAME"]}/Info.plist",  

      app_identifier: "#{ENV["DEV_NOTIFICATION_SERVICE"]}"  

    )  



    match(  

      type: "#{match_type}",   

      app_identifier: ["#{ENV["DEV_APP_IDENTIFIER"]}", "#{ENV["DEV_NOTIFICATION_SERVICE"]}"],   

      readonly: true  

    )  



    gym(  

      export_method: "#{export_method}",  

      include_bitcode: false,  

      scheme: "#{ENV["SCHEME_NAME"]}",   

      configuration: "AdHoc",  

      export_options: {  

        compileBitcode: false,  

        uploadBitcode: false,  

        provisioningProfiles: {  

          "#{ENV["DEV_APP_IDENTIFIER"]}" => "match #{match_type_name} #{ENV["DEV_APP_IDENTIFIER"]}",  

          "#{ENV["DEV_NOTIFICATION_SERVICE"]}" => "match #{match_type_name} #{ENV["DEV_NOTIFICATION_SERVICE"]}"  

        }  

      },  

      output_directory: output_dir,  

      output_name: output_name  

    )  



    upload_ipa(type: 'gxm', log: git_log)  





    bugly(app_id: "#{ENV["DEV_BUGLY_APPID"]}",  

      app_key:"#{ENV["DEV_BUGLY_APPKEY"]}",  

      symbol_type: 2,  

      bundle_id: "#{ENV["DEV_APP_IDENTIFIER"]}",  

      product_version: "#{version_number}(#{build_number})",  

      channel: 'pgyer'  

    )  



    copy_dsym(tpye: 'adhoc')  

  end  



  desc "生成 adhoc 预发版本,提交到蒲公英"  

  lane :pgyer_release do  



    git_reversion = sh("git log -1 --pretty=format:'%h'")  

    build_time = Time.new.strftime("%Y-%m-%d_%H.%M.%S")  

    version_number = get_info_plist_value(path: "#{ENV["SCHEME_NAME"]}/Info.plist", key: "CFBundleShortVersionString")  

    build_number = number_of_commits(all: false)  

    git_log = sh("git log --no-merges -1 --pretty=format:'# %ai%n# %B by %an'")  





    output_dir = "#{base_path}/Output/release/#{build_time}"  

    output_name = "#{ENV["SCHEME_NAME"]}_v#{version_number}(#{build_number}).ipa"  





    add_badge(shield: "#{version_number}-#{build_number}-orange", alpha: true)  





    increment_build_number(build_number: build_number)  



    update_app_identifier(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      plist_path: "#{ENV["SCHEME_NAME"]}/Info.plist",  

      app_identifier: "#{ENV["PROD_APP_IDENTIFIER"]}"  

    )  



    update_info_plist(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      plist_path: "#{ENV["SCHEME_NAME"]}/Info.plist",  

      block: proc do |plist|  

        plist["CFBundleDisplayName"] = "#{ENV["PROD_APP_NAME"]}"  

        plist["CFBundleName"] = "#{ENV["PROD_APP_NAME"]}"  

        plist["GIT_REVISION"] = git_reversion  

        plist["BUILD_TIME"] = build_time  

        plist["APP_CHANNEL"] = "pgyer"  

        urlScheme = plist["CFBundleURLTypes"].find{|scheme| scheme["CFBundleURLName"] == "weixin"}  

        urlScheme[:CFBundleURLSchemes] = ["#{ENV["PROD_WEIXIN_APPID"]}"]  

      end  

    )  





    update_app_identifier(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      plist_path: "#{ENV["NOTIFICATIONSERVICE_SCHEME_NAME"]}/Info.plist",  

      app_identifier: "#{ENV["PROD_NOTIFICATION_SERVICE"]}"  

    )  



    match(  

      type: "adhoc",   

      app_identifier: ["#{ENV["PROD_APP_IDENTIFIER"]}", "#{ENV["PROD_NOTIFICATION_SERVICE"]}"],   

      readonly: true  

    )  



    update_project_provisioning(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      target_filter: "#{ENV["SCHEME_NAME"]}",  

      profile:ENV["sigh_#{ENV["PROD_APP_IDENTIFIER"]}_adhoc_profile-path"],  

      build_configuration: "Release"  

    )  



    update_project_provisioning(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      target_filter: "#{ENV["NOTIFICATIONSERVICE_SCHEME_NAME"]}",  

      profile:ENV["sigh_#{ENV["PROD_NOTIFICATION_SERVICE"]}_adhoc_profile-path"],  

      build_configuration: "Release"  

    )  



    gym(  

      export_method: "ad-hoc",   

      scheme: "#{ENV["SCHEME_NAME"]}",   

      configuration: "Release",  

      export_options: {  

        compileBitcode: false,  

        uploadBitcode: false,  

        provisioningProfiles: {  

          "#{ENV["PROD_APP_IDENTIFIER"]}" => "match AdHoc #{ENV["PROD_APP_IDENTIFIER"]}",  

          "#{ENV["PROD_NOTIFICATION_SERVICE"]}" => "match AdHoc #{ENV["PROD_NOTIFICATION_SERVICE"]}"  

        }  

      },  

      output_directory: output_dir,  

      output_name: output_name  

    )  







    upload_ipa(type: 'gxm', log: "App Store 包上传:#{version_number}(#{build_number})")  





    bugly(app_id: "#{ENV["PROD_BUGLY_APPID"]}",  

      app_key:"#{ENV["PROD_BUGLY_APPKEY"]}",  

      symbol_type: 2,  

      bundle_id: "#{ENV["PROD_APP_IDENTIFIER"]}",  

      product_version: "#{version_number}(#{build_number})",  

      channel: 'pgyer'  

    )  



    copy_dsym(tpye: 'release')  

  end  



  desc "生成 appstore 版本,发布到 App Store"  

  lane :appstore_release do  



    git_reversion = sh("git log -1 --pretty=format:'%h'")  

    build_time = Time.new.strftime("%Y-%m-%d_%H.%M.%S")  

    version_number = get_info_plist_value(path: "#{ENV["SCHEME_NAME"]}/Info.plist", key: "CFBundleShortVersionString")  

    build_number = number_of_commits(all: false)  





    output_dir = "#{base_path}/Output/appstore/#{build_time}"  

    output_name = "#{ENV["SCHEME_NAME"]}_v#{version_number}(#{build_number}).ipa"  



    clear_derived_data  





    increment_build_number(build_number: build_number)  



    update_app_identifier(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      plist_path: "#{ENV["SCHEME_NAME"]}/Info.plist",  

      app_identifier: "#{ENV["PROD_APP_IDENTIFIER"]}"  

    )  



    update_info_plist(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      plist_path: "#{ENV["SCHEME_NAME"]}/Info.plist",  

      block: proc do |plist|  

        plist["CFBundleDisplayName"] = "#{ENV["PROD_APP_NAME"]}"  

        plist["CFBundleName"] = "#{ENV["PROD_APP_NAME"]}"  

        plist["GIT_REVISION"] = git_reversion  

        plist["BUILD_TIME"] = build_time  

        plist["APP_CHANNEL"] = "appstore"  

        urlScheme = plist["CFBundleURLTypes"].find{|scheme| scheme["CFBundleURLName"] == "weixin"}  

        urlScheme[:CFBundleURLSchemes] = ["#{ENV["PROD_WEIXIN_APPID"]}"]  

      end  

    )  





    update_app_identifier(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      plist_path: "#{ENV["NOTIFICATIONSERVICE_SCHEME_NAME"]}/Info.plist",  

      app_identifier: "#{ENV["PROD_NOTIFICATION_SERVICE"]}"  

    )  



    match(  

      type: "appstore",   

      app_identifier: ["#{ENV["PROD_APP_IDENTIFIER"]}", "#{ENV["PROD_NOTIFICATION_SERVICE"]}"],   

      readonly: true  

    )  



    update_project_provisioning(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      target_filter: "#{ENV["SCHEME_NAME"]}",  

      profile:ENV["sigh_#{ENV["PROD_APP_IDENTIFIER"]}_appstore_profile-path"],  

      build_configuration: "AppStore"  

    )  



    update_project_provisioning(  

      xcodeproj: "#{ENV["XCODEPROJ_NAME"]}",  

      target_filter: "#{ENV["NOTIFICATIONSERVICE_SCHEME_NAME"]}",  

      profile:ENV["sigh_#{ENV["PROD_NOTIFICATION_SERVICE"]}_appstore_profile-path"],  

      build_configuration: "AppStore"  

    )  







    gym(  

      export_method: "app-store",   

      scheme: "#{ENV["SCHEME_NAME"]}",   

      configuration: "AppStore",  

      export_options: {  

        provisioningProfiles: {  

          "#{ENV["PROD_APP_IDENTIFIER"]}" => "match AppStore #{ENV["PROD_APP_IDENTIFIER"]}",  

          "#{ENV["PROD_NOTIFICATION_SERVICE"]}" => "match AppStore #{ENV["PROD_NOTIFICATION_SERVICE"]}"  

        }  

      },  

      output_directory: output_dir,  

      output_name: output_name  

    )  





    bugly(app_id: "#{ENV["PROD_BUGLY_APPID"]}",  

      app_key:"#{ENV["PROD_BUGLY_APPKEY"]}",  

      symbol_type: 2,  

      bundle_id: "#{ENV["PROD_APP_IDENTIFIER"]}",  

      product_version: "#{version_number}(#{build_number})",  

      channel: 'appstore'  

    )  





    upload_ipa(type: 'gxm', log: "App Store 包上传:#{version_number}(#{build_number})")  



    copy_dsym(type: 'appstore')  



    deliver(  

      metadata_path: "#{ENV["DELIVER_METADATA_PATH"]}",  

      force: true  

    )  



  end  



  desc "上传 AppStore DSYM 文件到 Bugly,参数 => version:[latest]"  

  lane :upload_appstore_dsyms do |options|  

    version = String(options[:version] || "latest")  

    download_dsyms(version: version)  

    dsym_paths = lane_context[SharedValues::DSYM_PATHS]  

    for dsym_path in dsym_paths  



      split_strs = dsym_path.split(/\//).last.split(/-/)  

      version_number = split_strs[1]  

      build_number = split_strs[2].split(/\./)[0]  



      bugly(app_id: "#{ENV["PROD_BUGLY_APPID"]}",  

        app_key:"#{ENV["PROD_BUGLY_APPKEY"]}",  

        symbol_type: 2,  

        bundle_id: "#{ENV["PROD_APP_IDENTIFIER"]}",  

        product_version: "#{version_number}(#{build_number})",  

        channel: 'appstore',  

        dsym: dsym_path  

      )  

    end  

    clean_build_artifacts  

  end  



  desc "手动批量添加设备到profile"  

  lane :add_devices_manual do  



    UI.header "Add Device"  

    device_hash = {}  

    device_sum = UI.input("Device Sum: ").to_i  

    if device_sum == 0  

      next  

    end  

    index = 0  

    while index < device_sum do  

      device_name = UI.input("Device Name: ")  

      device_udid = UI.input("Device UDID: ")  

      device_hash[device_name] = device_udid  

      index += 1  

    end  



    register_devices(  

        devices: device_hash  

    )  

    refresh_profiles  

  end  



  desc "文件批量添加设备到profile"  

  lane :add_devices_file do  

    register_devices(  

      devices_file: "fastlane/devices.txt"  

    )  

    refresh_profiles  

  end  



  desc "批量导出设备"  

  lane :export_devices do  

    password = UI.password("输入 #{ENV["APPLE_ID"]} 账号密码: ")  

    Spaceship::Portal.login("#{ENV["APPLE_ID"]}", password)  

    Spaceship::Portal.select_team(team_id: "#{ENV["TEAM_ID"]}")  

    devices = Spaceship.device.all  

    File.open("#{base_path}/fastlane/devices.txt", "wb") do |f|  

      f.puts "Device ID\tDevice Name"  

      devices.each do |device|  

        f.puts "#{device.udid}\t#{device.name}"  

      end  

    end  

  end  





  desc "更新 provisioning profiles"  

  lane :refresh_profiles do  

    match(  

      type: "development",  

      force: true,  

      force_for_new_devices: true  

    )  

    match(  

      type: "adhoc",  

      force: true,  

      force_for_new_devices: true  

    )  

    match(  

      type: "appstore",  

      force: true,  

      force_for_new_devices: true  

    )  

  end  



  desc "同步 certificates 和 provisioning profiles"  

  lane :sync_cert_profiles do  

    match(  

      type: "development",  

      readonly: true  

    )  

    match(  

      type: "adhoc",  

      readonly: true  

    )  

    match(  

      type: "appstore",  

      readonly: true  

    )  

  end  



  desc "移除本地描述文件"  

  lane :remove_local_profiles do  

    app_identifiers = ["#{ENV["DEV_APP_IDENTIFIER"]}", "#{ENV["DEV_NOTIFICATION_SERVICE"]}", "#{ENV["PROD_APP_IDENTIFIER"]}", "#{ENV["PROD_NOTIFICATION_SERVICE"]}"]  

    types = ["development", "adhoc", "appstore"]  

    app_identifiers.each do |app_identifier|  

      types.each do |type|  

        remove_provisioning_profile(app_identifier: app_identifier, type: type)      

      end  

    end  

  end  



  desc "revoke 证书和描述文件"  

  private_lane :revoke_cert_profiles do  

    ENV["MATCH_SKIP_CONFIRMATION"] = "1"  

    sh("fastlane match nuke development")  

    sh("fastlane match nuke distribution")  

  end  



  desc "生成APNs证书"  

  lane :generate_apns_cert do  

    pem(  

      development: true,   

      force: true,   

      app_identifier: "#{ENV["DEV_APP_IDENTIFIER"]}",   

      p12_password: "GXM", output_path: "fastlane/pem"  

    )  



    pem(  

      development: false,   

      force: true,   

      app_identifier: "#{ENV["DEV_APP_IDENTIFIER"]}",   

      p12_password: "GXM", output_path: "fastlane/pem"  

    )  



    pem(  

      development: true,   

      force: true,   

      app_identifier: "#{ENV["PROD_APP_IDENTIFIER"]}",   

      p12_password: "GXM", output_path: "fastlane/pem"  

    )  



    pem(  

      development: false,   

      force: true,   

      app_identifier: "#{ENV["PROD_APP_IDENTIFIER"]}",   

      p12_password: "GXM", output_path: "fastlane/pem"  

    )  

  end  



  desc "同步 metadata"  

  lane :sync_metadata do  

    ENV["DELIVER_FORCE_OVERWRITE"] = "1"  

    sh("fastlane deliver download_metadata --metadata_path #{ENV["DOWNLOAD_METADATA_PATH"]}")  

  end  



  desc "拷贝 dSYM"  

  private_lane :copy_dsym do |options|  

    type = String(options[:type] || "adhoc")  

    dsym_path = lane_context[SharedValues::DSYM_OUTPUT_PATH]  

    share_dir = File.join(ENV['HOME'],'/Public/iOS', "#{ENV["SCHEME_NAME"]}", "#{type}")  

    FileUtils.mkdir_p(share_dir)  

    FileUtils.cp_r(File.join(dsym_path), share_dir)  

  end  



  desc "上传 ipa,type: [pgyer,gxm], log: desc"  

  private_lane :upload_ipa do |options|  

    type = options[:type] || 'pgyer'  

    log = options[:log] || ''  

    log = String  

    if type == "pgyer"  

      pgyer(  

        api_key: '0098b94391ff417d86837343597789a9',  

        user_key: '4ca1278171177f624ba3f3cc39eb2d73',  

        update_description: log  

      )  

    else  

      sh("curl -X 'POST' 'https://fabu.guoxiaomei.com/api/apps/5dca5121f3920d001f71e42d/upload' -H 'Content-Type: multipart/form-data' -H 'accept: application/json' -H 'apikey: 07a0840834294e7b89c41ab9c302c852' -F 'file=@#{lane_context[SharedValues::IPA_OUTPUT_PATH]}'")  

    end  

  end  



  after_all do |lane|  











  end  



  error do |lane, exception|  









  end  

end  

以上 fastlane 满足基本的功能需求。

Author

陈昭

Posted on

2020-10-24

Updated on

2021-12-27

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

Kommentare

You forgot to set the shortname for Disqus. Please set it in _config.yml.