In Jenkins (1) and Jenkins (2) of iOS Sustainable Integration, Jenkins environment building and automatic packaging and distribution of APP with plug-ins were introduced. However, I think it is still not cool enough to use. So there is an article that uses Jenkins+shell+ Python to achieve more free and persistent integration automation

1. Jenkins Optional parameters

This is not the focus, I just take an example here, you can customize according to your own needs, such as TAGE, Debug, Release, etc., even if you simply modify the shell script in different projects to achieve the purpose, the ok option parameter is filled in the option box, and each option is filled in line. The effect is to make the following build procedures more flexible, as shown in the figure below, especially with the value ${name}, such as ${Archive}

Shell & Python

For reuse in other projects, and for Python to read SVN logs later, it is recommended that you create a directory in the root directory of the project to store shells, Python scripts, and other items needed by the project, for example

Ad-hoc is a configuration file that you can export manually using Xcode. In the exported directory, exportOptions. plist is the configuration file we want. Autopackage. sh package script, used by SVNlog. py to read Jenkins logs, code below

Important points

The certificate management in Xcode must be manually managed, then select Debug, Release certificate, certificate to The Apple developer website to generate and install to the computer

2.1 the shell

Steps: Build -> Add build steps -> Shell

${Archive} ${Archive} ${Archive} ${Archive} ${Archive

export LANG="en_US.UTF-8"


# # # # # # # # # # # # # # # # # # # # parameters and environment variables defined # # # # # # # # # # # # # # # # # # # # # # # # #
# Engineering project path
projectPath="$(pwd)"
# Project name
projectName="xxxx"
# Engineering project packaging mode
buildConfiguration="Release"
IPA configuration file
exportOptionsPlist="${projectPath}/Package/${Archive}.plist"

# certificate
ADHOCCODE_SIGN_IDENTITY="iPhone Distribution: xxxx"
DEVELOPMENT_TEAM="The value in parentheses following iPhone Distribution: XXXX"
# description file
Main_Provisioning_Profile="xxxx-xxxx-xxxx-xxxx-xxxxx"
Extension_Provisioning_Profile="xxxx-xxxx-xxxx-xxxx-xxxxx"

#build file path
buildPath="${projectPath}/build"
Publish file path
releasePath="${projectPath}/build/Release-iphoneos"
#archive Save path
archivePath="${projectPath}/archive"
archiveName="${projectName}.xcarchive"
archiveFilePath="${archivePath}/${archiveName}"

# IPA save path
ipaPath="${projectPath}/ipa"
#log Log path
logfilePath="${projectPath}/ChangeLog"



Delete the existing file directory first
rm -rdf "$buildPath"
rm -rdf "$archivePath"
rm -rdf "$ipaPath"
rm -rdf "${logfilePath}"
Create a new file directory
mkdir "$buildPath"
mkdir "$releasePath"
mkdir "$archivePath"
mkdir "$ipaPath"
touch "${logfilePath}"

echo "* * * * * * * * * * * * * * * * * * * * * * * parameters, environment variables, * * * * * * * * * * * * * * * * * * * * * * *"
echo "Current directory path -------->${projectPath}"
echo 'Packaging Mode:'$buildConfiguration
echo 'Project Catalogue:'$projectPath
echo Project Name:$projectName
echo 'Installation package path'$archiveFilePath 
echo '\n'

echo "* * * * * * * * * * * * * * * * * * * * * * * began to build archive app file * * * * * * * * * * * * * * * * * * * * * * *"


The package command
xcodebuild -workspace "${projectPath}/${projectName}.xcworkspace" -scheme "$projectName" -configuration ${buildConfiguration} -archivePath "${archiveFilePath}" CONFIGURATION_BUILD_DIR="${releasePath}" DEVELOPMENT_TEAM="${DEVELOPMENT_TEAM}" CODE_SIGN_IDENTITY="${ADHOCCODE_SIGN_IDENTITY}" APP_PROFILE="${Main_Provisioning_Profile}" EXTENSION_PROFILE="${Extension_Provisioning_Profile}" clean archive


EXCODE=$?
if [ "$EXCODE"= ="0" ]; then
echo "O.K"
else
echo "* * * * * * * * * * * * * * * * * * * * * * * compile * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *"
exit 1
fi

Export the IPA file
xcodebuild -exportArchive -archivePath ${archiveFilePath} -exportPath ${ipaPath} -exportOptionsPlist $exportOptionsPlist

echo "* * * * * * * * * * * * * * * * * * * * * * * end of build archive app file * * * * * * * * * * * * * * * * * * * * * * *"

echo "* * * * * * * * * * * * * * * * * * * * * * * set package name information * * * * * * * * * * * * * * * * * * * * * * *"
#app file location and name
appPath="${archiveFilePath}/Products/Applications"
appFile="${appPath}/${projectName}.app"
# info.plist file path in app file
appInfoPlistPath=$appFile/Info.plist
Get the version number
version=$(/usr/libexec/PlistBuddy -c "print CFBundleShortVersionString" ${appInfoPlistPath})
# # for the Build
buildNo=$(/usr/libexec/PlistBuddy -c "print CFBundleVersion" ${appInfoPlistPath})
Take the bundle id #
bundleId=$(/usr/libexec/PlistBuddy -c "print CFBundleIdentifier" ${appInfoPlistPath})
Select the name of the application
appName=$(/usr/libexec/PlistBuddy -c "print CFBundleDisplayName" ${appInfoPlistPath})
# Package compile type (ad-hoc, Enterprise...)
buildMethod=$(/usr/libexec/PlistBuddy -c "print method" ${exportOptionsPlist})
# Time to pack
date=$(date +%Y%m%d%H%M)
Check whether the directory for storing ipA packets exists
destinationPath="{fill in the last directory you want to save}/${buildMethod}/${projectName}/${version}"
if[!-d "$destinationPath" ]; then
    mkdir -p "$destinationPath"
fi
ipaFile="${projectName}_${buildMethod}_${version}(${date}).ipa"
dSYMFile="${projectName}_${buildMethod}_${version}(${date}).app.dSYM"
ipaFilePath="${destinationPath}/${ipaFile}"
dSYMFilePath="${destinationPath}/${dSYMFile}"

Move ipA and dSYM to the specified directory
mv -f "${releasePath}/${projectName}.ipa" $ipaFilePath
mv -f "${releasePath}/${projectName}.app.dSYM" $dSYMFilePath
echo "** Final directory of installation package -->${ipaFilePath}* *"

echo "* * * * * * * * * * * * * * * * * * * * * * * * * start uploaded to fir * * * * * * * * * * * * * * * * * * * * * * * * * *"

fir login "Fir login token"

fir me


if[!-f "$logfileDir" ]; then
fir publish ${ipaFilePath} -c "No update record"
else
fir publish ${ipaFilePath} -c ${logfileDir}
fi
echo "* * * * * * * * * * * * * * * * * * * * * * * * * end to fir * * * * * * * * * * * * * * * * * * * * * * * * * *"

echo "* * * * * * * * * * * * * * * * * * * * * * * * * began to upload the dandelion * * * * * * * * * * * * * * * * * * * * * * * * * *"

curl -F "file=${ipaFilePath}" \
 -F "updateDescription=${logfileDir}" \
-F "UKey = Dandelion account center userkey" \
-F "_API_key = Dandelion Account center APIkey" \
https://www.pgyer.com/apiv1/app/upload
echo "* * * * * * * * * * * * * * * * * * * * * * * * * end of upload the dandelion * * * * * * * * * * * * * * * * * * * * * * * * * *"
# Remove log files
rm -rdf "${logfileDir}"
exit

Copy the code

Tip, FIR and dandelion need to install the environment first, specifically refer to the FIR official website official documents, dandelion official website documents

python

As a matter of fact, after Jenkins’ construction, the compile-related files of the corresponding project will be generated in jobs of the directory where Jenkins was installed, including SVN logs

We’ll read the file and save it to the directory we specify, and then upload it to a third-party hosting platform like Dandelion. This is written at the end of the shell script above

from xml.dom.minidom import parse
import xml.dom.minidom,sys,os

# Related Contents

numbulindline = open('/ Users/Shared/Jenkins/jobs/project name/nextBuildNumber'.'r').readline()
needNumbulindline = int(numbulindline)-1
xmlPath = '/ Users/Shared/Jenkins/jobs/project name)/builds / % d/changelog. The XML'%needNumbulindline
The name of the saved file
txtPath = 'ChangeLog'

# File write encoding
reload(sys)
sys.setdefaultencoding('utf8')

Write log to TXT
def text_write(text):
    # Save path
    logPath = "./%s"%txtPath
    file = open(logPath,'a')
    file.write(text)
    file.close()

Get the XML node value method
def get_xmlnode(node, name):
    return node.getElementsByTagName(name) if node else []
try:
    DOMTree = xml.dom.minidom.parse("%s"%xmlPath)
    collection = DOMTree.documentElement
    logentry = collection.getElementsByTagName("logentry")
    text_write("=============================\n")
    for index in range(len(logentry)):
        print "==========log Log writing ========="
        logentrysub = logentry[index]
        author = get_xmlnode(logentrysub,'author')[0].firstChild.nodeValue
        date = get_xmlnode(logentrysub,'date')[0].firstChild.nodeValue [0:10]
        msgdom = get_xmlnode(logentrysub,'msg')[0].firstChild
        ifmsgdom ! = None: msg = msgdom.dataelse:
            msg = "Empty"
        text_write(author+""+date+"\n"+msg+"\n\n")
    text_write("= = = = = = = = = = = = = = = = = = = = = = = = = = = = =")
    print "==========log writing completed ========="
    print "==========log Log content ========="
    f = open("./%s"%txtPath,'r')
    lines = f.readlines()
    for line in lines:
        print line
    f.close()
except Exception,e:
    print "========== XML file is invalid ==========%s"%e

Copy the code

At the end, if you don’t want to use this method, you can also use a third-party tool such as Fastlane, which has a more mature packaging solution.