preface

Previously, I wrote an article based on Jenkins and Fastlane automatic packaging, which briefly described a lot of environment setup and some problems encountered. The purpose of this article is to use scripts and Jenkins for automatic packaging, why not use the old Fastlane? First of all Fastlane is great and is packaged with the XcodeBuild command, but it involves the ability of a script to migrate to other projects, which I think is not high, at least for Jenkins. Fastlane is built based on XcodeBuild anyway, so why not just write a script for it?

Another problem is that we all know that using Git automatic packaging based on Jenkins can directly achieve automatic packaging on every commit, but using SVN in Jenkins we did not find a plug-in similar to this function. The solution in this paper is a curvilinear way to save the country, which will be described in the second half of the article. At the beginning, I still wanted to realize it through LAN, but later I found it would not work, and I did not feel the convenience brought by automatic packaging. Hence the scheme of this paper.

To prepare

β‘  Please ensure that the environment for packing the PC has been installed with HomeBrew and Jenkins(it is recommended to install Jenkins using BREW and start the PC automatically).

β‘‘ Please make sure that there are certificates and description files for the APP you need to package in the package string

β‘’ Please ensure that the packaging script is used for automatic packaging based on the submission of SVN and Jenkins. Otherwise, the script will be unavailable πŸ˜‚πŸ˜‚

Packaging structure

If there are other types of packaging, you can add a new Plist file to the Plist folder. This file can be exported manually in the folder with xxxexportOptions. Plist file. Then change the type in the configuration file.

foreplay

It is convenient to modify the configuration file to directly control the packaging type. Using JSON files for configuration at first had two disadvantages for me. One is that there is no comment in the JSON file, which makes it difficult for the next person to understand or to add an explanatory document. The second reason is that when using JSON to configure the path, you also need to remove the double quotation marks from the value, which can be cumbersome to implement. The other thing is that the Shell is still a bit cumbersome to parse JSON data. Using JQ parsing is very convenient, but you need to configure the environment. The global.config code is as follows:

	# Do I need to pack
	is_need_package=true

	# Production/development environment certificate Settings
	code_sign_distribution="iPhone Distribution: xxxx xxx (xxx)"
	code_sign_development="iPhone Developer: xxxx xxxx (xxx)"

	# Target /scheme Settings for the project
	package_target_name="xxx"
	package_scheme_name="xxx"

	Package the result root directory
	package_root_path=/Users/xxx/Desktop/Package/
	Package the IPA directory
	package_archive_path=/Users/xxx/Desktop/Package/Archive/

	# pack type AdHoc: 1 AppStore: 2 Enterprise: 3 Development: 4
	package_export_type=1
	# package the xxxexportOptions. plist folder
	package_export_options_dir_path=./AutoPacking/Plist/

	# whether to use workspace for packaging
	package_use_workspace=true
	Whether to use Release mode for packaging
	package_use_release=true

	Record the package prefix
	record_perfix="CEG"
Copy the code

Easy to read, but not very easy to read, there is no highlight in Xcode, all the color is easy to fall asleep. But it’s super easy to use

source global.config
Copy the code

$is_need_package can get the value, as well as the path. So I chose this way to do the configuration file. It has different effects for different people, I am not familiar with the script is really good. Note that only one variable can be written in a line, and there can only be an equal sign between the variable and the value, no space to align 🀑🀑, do not feel even the last landscaping opportunity 😏. In fact, habit is good. πŸ™‚

After the play

Young man don’t stare at the title ha, the thought is very dangerous ha πŸ˜…, I am not to drive, say that finish configuration file should say this package script. Do you remember that we said we were going to do SVN in preparation because we used the Jenkins global build parameter SVN_REVISION, and this script is actually a bit boring because a lot of the code is foreplay πŸ˜‰πŸ˜‰ and the actual XcodeBuild command is at the end. In addition, I like to write scripts are lowercase, all uppercase looking headache ah, so I do not know this character does not conform to the code specification of the script, but feel good 😎😎.

#! /bin/sh
The directory where the current shell is located
shell_dir_path=$(cd "$(dirname "$0")"; pwd)

Globalize the parameters in the configuration file in the script
source $shell_dir_path"/global.config"

echo "Start checking the environment..........."

Create an archive folder if it does not exist
if[!-d $package_root_path ]; then
    mkdir -pv $package_root_path
fi

If no record file exists, add the file content variable to the global variable
if [ -e $package_root_path"result.txt" ]; then
    source $package_root_path"result.txt"
    # Check whether the last information submitted by the SVN has been archived. SVN_REVISION is the number and change amount when the SVN submitted
    # From Jenkins
    if [ $lastestBuildVersion = $SVN_REVISION];then
        echo "This version has already been packaged, please resubmit the record and make sure is_need_package is true"
        exit 1
    else
        To save space, each package removes the previous files before starting
        rm -rf $package_archive_path*
        echo "The removal of the old item is complete and the preparation is complete."
    fi
else
    Create a record document
    touch $package_root_path"/result.txt"
    chmod -R 777 $package_root_path"/result.txt"
    echo lastestBuildVersion=0 >> $package_root_path"/result.txt"

fi

# Check if you need to pack
if ! $is_need_package; then
    exit 1
fi

Check whether target/scheme is set
if test -z $package_target_name; then
	echo "❌ project Target set to empty"
	exit 1
fi

if test -z $package_scheme_name; then
	echo "❌ project Scheme set to empty"
	exit 1
fi

Read configuration file archive type is Release or Debug
if $package_use_release; then
    build_configuration="Release"
else
    build_configuration="Debug"
fi


# AdHoc: 1, AppStore: 2, Enterprise: 3, Development: 4
Export the plist folder of the IPA package, which is generated during packaging
options_dir_path=$package_export_options_dir_path
if [[ $package_export_type -eq1]];then
    export_options_plist_path=$options_dir_path"AdHocExportOptions.plist"
    export_type_name="AdHoc"
elif [[ $package_export_type -eq2]].then
    export_options_plist_path=$options_dir_path"AppStoreExportOptions.plist"
    export_type_name="AppStore"
elif [[ $package_export_type -eq3]].then
    export_options_plist_path=$options_dir_path"EnterpriseExportOptions.plist"
    export_type_name="Enterprise"
elif [[ $package_export_type -eq4]];then
    export_options_plist_path=$options_dir_path"DevelopmentExportOptions.plist"
    export_type_name="Development"
fi

echo "βœ…βœ…βœ… Verification parameters and environment successful"
echo "⚑️ ⚑ ⚑️ Is about to start packing ⚑️ ⚑ sucks sucks"

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # automatic packing part # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

Return to the project directory
cd ../
project_path=`pwd`

Get the project name
project_name=`find . -name *.xcodeproj | awk -F "[/.]" '{print $(NF-1)}'`
# specify info.plist for the project
current_info_plist_name="Info.plist"
# configure the info.plist path
current_info_plist_path="${project_name}/${current_info_plist_name}"
Get the version number of the project
bundle_version=`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${current_info_plist_path}`
Get the compiled version number of the project
bundle_build_version=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" ${current_info_plist_path}`

The current version stores the export file path. You can add different paths according to your needs
currentVersionArchivePath="${package_archive_path}"

# check whether the current version of the archive folder exists, create it if it does not
if[!-d $currentVersionArchivePath ]; then
    mkdir -pv $currentVersionArchivePath
    chmod -R 777 $currentVersionArchivePath
fi

# Archive file path
export_archive_path="${currentVersionArchivePath}${package_scheme_name}.xcarchive"
# ipA export path
export_ipa_path="${currentVersionArchivePath}"
# get time :20190630_1420
# current_date="$(date +%Y%m%d_%H%M)"
The ipA name can be renamed according to the version number
ipa_name="${package_scheme_name}_${SVN_REVISION}.ipa"

echo "Project catalog =${project_path}"
echo "Project info.plist path =${current_info_plist_path}"
echo "Package type =${build_configuration}"
echo "The plist file path used for packaging =${export_options_plist_path}"

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # packaged part # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

echo "πŸ”†πŸ”†πŸ”† is starting packing for you πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€"

# whether to use xxx.xcworkspace project file to package
if $package_use_workspace; then

    if [[ ${build_configuration}= ="Debug"]].then
        # 1. Clean
        xcodebuild clean  -workspace ${project_name}.xcworkspace \
        -scheme ${package_scheme_name} \
        -configuration ${build_configuration}

        # 2. Archive
        xcodebuild archive  -workspace ${project_name}.xcworkspace \
        -scheme ${package_scheme_name} \
        -configuration ${build_configuration} \
        -archivePath ${export_archive_path} \
        CFBundleVersion=${bundle_build_version} \
        -destination generic/platform=ios \

    elif [[ ${build_configuration}= ="Release"]].then

        # 1. Clean
        xcodebuild clean  -workspace ${project_name}.xcworkspace \
        -scheme ${package_scheme_name} \
        -configuration ${build_configuration}

        # 2. Archive
        xcodebuild archive  -workspace ${project_name}.xcworkspace \
        -scheme ${package_scheme_name} \
        -configuration ${build_configuration} \
        -archivePath ${export_archive_path} \
        CFBundleVersion=${bundle_build_version} \
        -destination generic/platform=ios \

    fi

else

    if [[ ${build_configuration}= ="Debug"]].then
        # 1. Clean
        xcodebuild clean  -project ${project_name}.xcodeproj \
        -scheme ${package_scheme_name} \
        -configuration ${build_configuration} \
        -alltargets

        # 2. Archive
        xcodebuild archive  -project ${project_name}.xcodeproj \
        -scheme ${package_scheme_name} \
        -configuration ${build_configuration} \
        -archivePath ${export_archive_path} \
        CFBundleVersion=${bundle_build_version} \
        -destination generic/platform=ios \

    elif [[ ${build_configuration}= ="Release"]].then
        # 1. Clean
        xcodebuild clean  -project ${project_name}.xcodeproj \
        -scheme ${package_scheme_name} \
        -configuration ${build_configuration} \
        -alltargets

        # 2. Archive
        xcodebuild archive  -project ${project_name}.xcodeproj \
        -scheme ${package_scheme_name} \
        -configuration ${build_configuration} \
        -archivePath ${export_archive_path} \
        CFBundleVersion=${bundle_build_version} \
        -destination generic/platform=ios \

    fi
fi

Check if the build is successful
Because XXX. Xcarchive is a folder, not a file
if [ -d ${export_archive_path} ]; then
    echo "πŸš€ πŸš€ πŸš€ project built successfully πŸš€ πŸš€
else
    echo "⚠️ ⚠️ ⚠️ project construction failed ⚠️ ⚠️
    exit 1
fi

echo "Start exporting IPA file"
Export the IPA file
xcodebuild -exportArchive -archivePath ${export_archive_path} \
-exportPath ${export_ipa_path} \
-destination generic/platform=ios \
-exportOptionsPlist ${export_options_plist_path} \
-allowProvisioningUpdates
Export ipA file path by default
export_ipa_name=$export_ipa_path$package_scheme_name".ipa"

Check whether there is an export IPA file
if [ -e $export_ipa_name ]; then
    Ipa Scheme name is the project name, and version is the last version submitted by the SVN
    mv $export_ipa_name $export_ipa_path$ipa_name
    Set the current version to packaged
    echo lastestBuildVersion=$SVN_REVISION > $package_root_path"result.txt"
    echo "πŸŽ‰ πŸŽ‰ πŸŽ‰ Export${ipa_name}Ipa package successful πŸŽ‰ πŸŽ‰ πŸŽ‰"
else
    echo "❌ ❌ ❌ Export${ipa_name}Ipa package failure ❌ ❌ ❌"
fi

Output the total packaging time
echo "Total time of packaging:${SECONDS}s"
Copy the code

Is it a little dizzy wow, I have a headache πŸ€—πŸ€—, played the use of Jenkins script automatic packaging of people should know my curve to save the country way, some of the students must still in 😳😳or πŸ€—. We talk about πŸ€—πŸ€— in the main part.

highlight

I’m not a driver by style, because that’s not foreplay, is it? πŸ€”πŸ€”, start peeling the silk and cocooning.

There is no hook plugin in Jenkins like Git that automatically wraps commit code. The purpose of using Jenkins is to release the programmer’s πŸ‘ and save more time to do the work. If you can’t implement automatic submission packaging, it doesn’t matter whether you have Jenkins or not, why not write a script to package it? What would you have done?

We know Jenkins has an option to perform missions on a regular basis, we just fill in the parameter “H/2 * * * *” and it will perform missions every 2 minutes. Why not set it to 1 minute? But the total can not let this guy every two minutes has been playing it, coverage is good, but every two minutes there is a new installation package uploaded to the test server is not good, the feeling will be tested personnel hit shit 😱😱. Then we need to solve this breaking packing condition πŸ€”. It can’t be set in the project, otherwise Jenkins will overwrite it every time he runs a mission, meaning he can only save one state on the pack computer. So, it’s easy to solve, we just need to create a file in the script to record the last packaged SVN_REVISION, so that the script can read the file record to determine whether the package has not been packaged, and then execute the program, otherwise exit the program. πŸ˜‰πŸ˜‰ now think is not very simple wow, and then you can enjoy the da da da.

What about uploading to the test server using FTP? Because of the variable parameters involved, the script so bad I can only use soil method, a recruit, dry to disobey so far 😁😁. This part of the script is the code after the long string of code above.

# # # # # # # # # # # # # # # # # # # # # # # # # # # # upload part # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function createUploadShell(){
    touch upload.sh
    chmod -R 777 upload.sh
    echo "cd ${package_archive_path}" >> upload.sh
    echo "ftp -i -n -v << !" >> upload.sh
    echo "open 117.xx.xxx.xxx" >> upload.sh
    echo "user oymuzi xxxx" >> upload.sh
    echo "cd ./${upload_dir_path}" >> upload.sh
    current_date="$(date +%Y%m%d%H%M%S)"
    ipa_new_name=$package_scheme_name"_"$current_date".ipa"
    echo "binary" >> upload.sh
    echo "put ${upload_volumes_name}${package_archive_path}${ipa_name}. /${ipa_new_name}" >> upload.sh
    echo "close" >> upload.sh
    echo "bye" >> upload.sh
    echo "!" >> upload.sh
}

cd $package_archive_path
createUploadShell
echo "Upload script created successfully"

echo "πŸš€ πŸš€ πŸš€ start uploading to cloud πŸš€ πŸš€ πŸš€
sh upload.sh
echo "Upload to cloud complete."

rm -f upload.sh
Copy the code

Yeah, that’s how great it feels. The big guy can change the script himself. One of them is that to use the FTP command you must use FTP -i -n -v <

Source code: OMPackaging(Note: this demo is only to show the source code, according to the source code and the article described the way to set up the environment can be normal packaging)

Get to the point

With Jenkins’ dead-execute task feature, a package status is saved locally and the package is automatically packaged every time the code is committed, with a 1-2 minute delay, and the package must be true from is_need_package in the configuration file and committed once. After uploading to the test server, testers can download and install it directly. This solution solves the auto-packaging problem, albeit with some delay.

Reference:

Mac Shell uploads files (using FTP command in shell)

Building from the Command Line with Xcode FAQ