preface

As a mobile programmer, every feature add or bug fix is packaged and delivered to QA. Previously, the traditional operation is to manually click Xcode -> Product -> Archive -> Organizer -> Distrubute App -> UPLOAD IPA to third-party internal test distribution platform (Dandelion, FIR) -> Manually fill in the update log -> Send the installation link to the department group (dingding or enterprise wechat), it seems very mechanical and cumbersome, and no technical content is it……

It would be nice if you could automate this part of the job, and save a little time each day to daydream. The requirements are roughly like this:

  1. Timing trigger;
  2. Automatic packaging;
  3. Automatically read git commit messge information within a certain period as the update log;
  4. Automatically upload to the third party internal test platform (dandelion, Fir, etc.) read the update log in the previous step and upload it together, obtain the download link after the success, and automatically generate the installation TWO-DIMENSIONAL code;
  5. After uploading, take the update log, download link and TWO-DIMENSIONAL code picture in step 3 and step 4 as the message body, and automatically send the message to the department group (Dingding or enterprise wechat);

implementation

Requirement 1. Scheduled tasks

On research, Mac OS can configure scheduled tasks based on Launchctl. LaunchAgents can be configured to different levels. The differences are as follows:

~/Library/LaunchAgents User-defined task item /Library/LaunchAgents User-defined task item /Library/LaunchDaemons Daemon task item defined by the administrator / System/Library/LaunchAgents by Mac OS X for the user to define the task item/System/Library/LaunchDaemons daemon tasks defined by the Mac OS XCopy the code

Create a new Plist file in the user directory (~/Library/LaunchAgents) in a fixed format. You can see that there are already some third-party tasks in this directory:

My configuration file looks like this:

<plist version="1.0">
<dict>
  <! -- Label unique identifier -->
  <key>Label</key>
  <string>com.autoArchiveTask.plist</string>

  <key>Program</key>
  <string>/Users/username/Desktop/code/Project/run.sh</string>

  <! -- Specify the script to run -->
  <key>ProgramArguments</key>
  <array>
    <string>/Users/username/Desktop/code/Project/run.sh</string>
  </array>
  <! -- Specify the time to run -->
  <key>StartCalendarInterval</key>
  <array>
    <dict>
        <key>Minute</key>
        <integer>00</integer>
        <key>Hour</key>
        <integer>11</integer>
    </dict>
    <dict>
        <key>Minute</key>
        <integer>00</integer>
        <key>Hour</key>
        <integer>16</integer>
    </dict>
  </array>

<! -- Standard output file -->
<key>StandardOutPath</key>
<string>/Users/username/Desktop/code/Project/run.log</string>
<! -- Standard error output file, error log -->
<key>StandardErrorPath</key>
<string>/Users/username/Desktop/code/Project/run.error</string>
</dict>
</plist>
Copy the code

Related fields are explained as follows:

  1. Label: the corresponding need to ensure global uniqueness;
  2. Program: To run a script;
  3. ProgramArguments: Specifies the script to run;
  4. StartCalendarInterval: Specifies the running time. Dict is used for a single time point and Array is used for multiple time points
  5. StartInterval: specifies the time interval used by StartCalendarInterval, in seconds
  6. StandardInPath, StandardOutPath, StandardErrorPath: standard input, output, error file

After the configuration is complete, it can be loaded. After loading, it takes effect.

#The -w option will overwrite invalid keys in the PList file
launchctl load -w xxx.plist

#Delete the task
launchctl unload -w xxx.plist

#To view the task list, use grep'Task part Name'filter
launchctl list | grep 'xxx'

#Perform a task immediately for testing purposes
launchctl start xxx.plist
Copy the code

Requirement 2: Automatic packaging

Use Fastlane for this, which is nice and powerful. For details, see the official website. Brew installation is recommended. Just configure the installation documentation,

Since this is a multi-target project, I may have a little more configuration on my side. My Fastfile configuration is as follows:

default_platform(:ios)

# network request dependency
require 'net/http'
require 'uri'
require 'json'

platform :ios do

  desc "Pack to the Customer"
  lane :customer_hoc do
    # automatically grow bulidNumber
    increment_build_number(xcodeproj: "Project.xcodeproj")
    # add actions here: https://docs.fastlane.tools/actions
    sh "fastlane adhoc --env Customer"
   end

  desc Pack "Driver"
  lane :driver_hoc do
    # automatically grow bulidNumber
    increment_build_number(xcodeproj: "Project.xcodeproj")
    # add actions here: https://docs.fastlane.tools/actions
    sh "fastlane adhoc --env Driver"
  end
     

   desc "Publish the specified Target to Fir. Im"
   lane :adhoc do
   gym(
     clean:true.Clean projects before packing
     workspace: "Project.xcworkspace".export_method: "ad-hoc".# Export method
     scheme: ENV['SCHEME_NAME'].#scheme
     output_name: ENV['SCHEME_NAME'] +".ipa".# ipa filename
     output_directory: "./ipa".# ipA directory
     export_options: {
         provisioningProfiles: {
             "bundleId"= >"CustomerAdhocProfiles"."bundleId"= >"DriverAdhocProfiles"}})Git logs are read from git logs for a specified period as update logs
  Read the last packing time from the cache
  lastArchiveDate = sh("cat /Users/username/Desktop/code/Project/lastArchiveDate.log")
  sh("echo 'lastArchiveDate: #{lastArchiveDate}'")

  # current time
  currentDate = `date`
  sh("echo 'currentDate: #{currentDate}'")

  # update log
  updateLog = sh("git log --after='#{lastArchiveDate}' --before='#{currentDate}' --pretty=format:'%s\n' HEAD")
  sh("echo 'updateLog: \n#{updateLog}'")


  Upload to Fir
  # Go to fir. Im to obtain the API token, place the mouse pointer over the account in the upper right corner, and select the API token in the drop-down window
  # If using dandelion, please go to https://www.pgyer.com/ to see how to upload
  # Firimfile = firim
  # firim(firim_api_token:'xxxx')

  # Multiple arguments can be separated by commas (,)
  answer = fir_cli api_token: "xxx".need_release_id: true.changelog: "#{updateLog}"

  Download the qr code image link
  puts "Upload result:#{answer} "

  Get the download short link
  download_url = "https://fir.im/#{answer[:short]}? release_id=#{answer[:release_id]}"
  puts "Download link:#{download_url} "

  # Generate qr code image
  # pwd = sh("pwd")
  # puts "Current directory: #{PWD}"
  # qrCodeImagePath = "/Users/username/Desktop/code/Project/ipa/" + ENV['SCHEME_NAME']+"qrCode.png"
  # puts "path: #{qrCodeImagePath}"
  # sh("qrencode -o #{qrCodeImagePath} #{download_url}")


  # Nailing robot
  app_patch   = "ipa/" + ENV['SCHEME_NAME'] +".ipa"
  qrCodeImagePath = "/Users/username/Desktop/code/Project/ipa/" + "fir-" + ENV['SCHEME_NAME'] +".png"
  puts "Path:#{qrCodeImagePath} "

  # Base64 encodes images and removes empty lines \n and Spaces
  qrcodeBase64Text = sh("openssl base64 -in #{qrCodeImagePath} | xargs echo -n | tr -d '[:space:]'")
  puts "Base64:#{qrcodeBase64Text} "

  qrcodeBase64 = "data:image/png; base64,#{qrcodeBase64Text}"
  puts "The picture base64:#{qrcodeBase64} "

  app_version = get_ipa_info_plist_value(ipa: app_patch, key: "CFBundleShortVersionString")
  app_build_version = get_ipa_info_plist_value(ipa: app_patch, key: "CFBundleVersion")
  app_name    = get_ipa_info_plist_value(ipa: app_patch, key: "CFBundleDisplayName")
  app_url = download_url

  
  dingTalk_url = "https://oapi.dingtalk.com/robot/send?access_token=xxx"

  Construct the message format
  actionCard = 
  {
    "actionCard": {
        "title": "iOS #{ENV['SCHEME_NAME']} #{app_version} (#{app_build_version}) internal test version"."text": ! "" [downloadQrCode](#{qrcodeBase64}) 
 ### iOS #{ENV['SCHEME_NAME']} #{app_version} (bulid: #{app_build_version}The internal beta has updated the update log \n\n#{updateLog} \n\n "."hideAvatar": "0"."btnOrientation": "0"."singleTitle": "Download address"."singleURL": "#{app_url}"
    }, 
    "msgtype": "actionCard"
  }

puts "Sent pin message:#{actionCard} "



  uri = URI.parse(dingTalk_url)
  https = Net::HTTP.new(uri.host, uri.port)
  https.use_ssl = true

  request = Net::HTTP::Post.new(uri.request_uri)
  request.add_field('Content-Type'.'application/json')
  request.body = actionCard.to_json

  response = https.request(request)
  puts "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"
  puts "Response #{response.code} #{response.message}: #{response.body}"

end
end

Copy the code

Git commit messge

Git logs support log output in a variety of formats, including specific time periods. One line of command:

#Git Shortlog will only output the author and message, and categorize the author by submission, which is exactly what we needGit shortlog --after=" last time "--before=" current time"Copy the code

We also need to save the time when the last package was completed, using the file:

#save
#Save the packaging completion time to the log
echo `date`  > "/Users/username/Desktop/code/Project/lastArchiveDate.log"

#read
#Read git commits for a specified period of timelog
read -r lastArchiveDate < "/Users/username/Desktop/code/Project/lastArchiveDate.log"
Copy the code

But this is executed directly in the shell, so this script is written to the Fastlane packaging script, and Fastlane is based on Ruby, so we have to let Ruby execute this shell, ruby can execute shell in many different ways, You can refer to stackoverflow.com/questions/2… .

Of course, Fastlane provides us with an sh Action to execute. Add this paragraph directly to the fastFile file:

Git logs are read from git logs for a specified period as update logs
Read the last packing time from the cache
lastArchiveDate = sh("cat /Users/username/Desktop/code/Project/lastArchiveDate.log")
sh("echo 'lastArchiveDate: #{lastArchiveDate}'")

# current time
currentDate = `date`
sh("echo 'currentDate: #{currentDate}'")

# update log # update log # update log # update log # update log Specific to see https://stackoverflow.com/questions/12133583/gits-shortlog-command-is-failing-when-run-during-a-pre-commit-hook
updateLog = sh("git shortlog --after='#{lastArchiveDate}' --before='#{currentDate}' HEAD")
sh("echo 'updateLog: \n#{updateLog}'")
Copy the code

Requirement 4. Automatic upload to Fir

This step can be fully based on the Fir command line interface to achieve, the address is here github.com/FIRHQ/fir-c… Fastlane fastlan-plugin-fir_cli Fastlane plugin fastlan-plugin-fir_cli Fastlane plugin fastlan-plugin-fir_cli Fastlane plugin fastlan-plugin-fir_cli Fastlane plugin fastlan-plugin-fir_cli

# Multiple arguments can be separated by commas (,)
answer = fir_cli api_token: "xxxxx".need_release_id: true.changelog: "#{updateLog}"

Get the result after upload
puts "Upload result:#{answer} "

# Generate download short link
download_url = "https://fir.im/#{answer[:short]}? release_id=#{answer[:release_id]}"
puts "Download link:#{download_url} "
Copy the code

However, this plug-in does not support the function of downloading and linking to generate specified TWO-DIMENSIONAL code images for the time being. The native Fir command line is already supported. After investigating here, you can use the command line two-dimensional code generation tool libqrencode to write and specify various parameters for generating images. Related function parameters can refer to the www.linuxintheshell.com/2012/03/01/…

#The basic grammar
qrencode [option] [string]

#Generate the two-dimensional code image of the content text to the current directory, can also specify the directoryQrencode -o xxx.png "contents"Copy the code

Also, since the configuration is in the Fastfile file, sh is called to execute:

# Generated two-dimensional code picture path
qrCodeImagePath = "/Users/username/Desktop/code/Project/ipa/" + ENV['SCHEME_NAME'] +"qrCode.png"
puts "Path:#{qrCodeImagePath} "

sh("qrencode -o #{qrCodeImagePath} #{download_url}")
Copy the code

In fact, fastlane-plugin-fir_cli has been implemented to download the TWO-DIMENSIONAL code image saved locally, in the Project/ IPa directory, as fir scheme. PNG, but also spared a big curve to use shell to generate.

Requirement 5. Automatically send installation messages

I currently use the pin for collaboration, and can use the pin robot to automatically send messages in the relevant work group. Ask the pin group administrator to add a token. You can send this address https://oapi.dingtalk.com/robot/send?access_token=Your Token support text (text), link (Link), MarkDown (Markdown), ActionCard, FeedCard message type format message, can also fill in test girls who need @.

Wechat of other enterprises also seems to be ok, you can check the documents by yourself.

However, there is a point to note here, the image of the pin message is in Markdown format, but it does not support local path, that is to say, the QR code image generated in the previous step passes! [](xxx.png) is invalid. Either upload the image to the image file to obtain a remote link, or base64 can be used to encode the image. MAC OS can use openSSL on the command line to do this.

# Base64 encodes images and removes empty lines \n and Spaces
qrcodeBase64Text = sh("openssl base64 -in #{qrCodeImagePath} | xargs echo -n | tr -d '[:space:]'")
puts "Base64:#{qrcodeBase64Text} "

qrcodeBase64 = "data:image/png; base64,#{qrcodeBase64Text}"
puts "The picture base64:#{qrcodeBase64} "
Copy the code

Then you can insert a picture in the nail message body:! [downloadQrcode](qrcodeBase64)

Hit the pit

Sh: XXX /run.sh: Operation not permitted

The reason:

First I configuration of regular script path in/Users/username/Desktop/code/Project/run. Sh, no and timing task Plist configuration files in a directory, the configuration of the regular script statement is #! /bin/sh, which means using /bin/sh to explain execution, but does not give full disk access.

Solution:

Just give me enough access. System preferences -> Security and Privacy -> Full disk access to check whether ☑️ is selected in the timing script to explain the execution of the shell path, is **#! /bin/** the following is bash, sh, ZSH, etc. If not, add it.

Second, directly in the regular script execution fastlane packaging error command: / Users/username/Desktop/code/Project/run. Sh: fastlane: command not found

The reason:

Although the CD is in the current project directory, it is still not found by Fastlane

Solution:

Run the fastlane command using the full path

/Users/username/.fastlane/bin/fastlane lane
Copy the code

Git shortlog –after=” last package completed time “–before=” current time”

The reason:Stackoverflow.com/questions/1…
Solution:

Add a HEAD after it like this:

sh("git shortlog --after="Time of last packing" --before="The current time" HEAD" ) 
Copy the code

PNG output is disabled at compile time. No output generated.

The reason:

Maybe the brew version is not correct, so libqrencode depends on Libpng is not correct, you can search the source code, it is an error thrown from the else, the condition is HAVE_PNG, so guess, raised the issue, the author also replied, Github.com/fukuchi/lib…

Solution:

Reference macappstore.org/qrencode/, reinstall the brew, and then reinstall the qrencode

#Reinstall the brew
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null

#Reshipment qrencode
brew reinstall qrencode
Copy the code

5. The nailing robot sends text messages without line feeds

The reason:

The git log I read is clearly newline in the command line, but there is no newline in the pin group. Originally, the newline in the pin group requires two \n.

Solution:

Format the log output with –pretty=format and manually concatenate a \n after each Git commit message.

#Format the gitlogGit log --after=' last time '--before=' current time' --pretty=format:"%s\n"Copy the code

conclusion

It took one or two days, and the process was basically smooth. I was generally very happy and had a sense of achievement. Later, I could concentrate on other things. Nice!

The effect is as follows:

Relevant configuration files have been uploaded to GitHub warehouse, the address click here, if you like, you can click star oh.

Refer to the link

  1. On a Mac, run the scheduled task launchctl
  2. Operation not permitted
  3. Fastlane
  4. Git log
  5. Github.com/fukuchi/lib…
  6. www.linuxintheshell.com/2012/03/01/…
  7. Nailing development documents

I would like to share my personal technical learning record, marathon training and reading notes. If you are interested, you can follow my public account “BY in water”.