Jenkins配置-Android自動化打包-Mac版

Jenkins是一款開源CI$CD軟件芹关,用于自動化各種任務(wù),包括構(gòu)建行瑞、測試和部署軟件
優(yōu)點:
持續(xù)的軟件版本發(fā)布奸腺、測試項目
監(jiān)控外部調(diào)用執(zhí)行的工作
對于移動端開發(fā)來說,使用Jenkins持續(xù)化集成血久,可以幫助開發(fā)人員縮短開發(fā)周期突照,開發(fā)人員只需要關(guān)注開發(fā)任務(wù),像給產(chǎn)品氧吐、測試人員打包時讹蘑,這些任務(wù)就可以交給Jenkins來做,測試人員可只需要掃描一下二維碼安裝即可筑舅。
先來一張效果圖


效果圖.jpg

安裝

這里我們通過homebrew安裝座慰,如果未安裝Homebrew,先安裝Homebrew翠拣,詳見Homebrew安裝和使用
Homebrew安裝完成后版仔,執(zhí)行以下命令安裝Jenkins

brew install jenkins

安裝完成后,執(zhí)行war包

java -jar /usr/local/Cellar/jenkins/2.183/libexec/jenkins.war

這里Jenkins版本號可根據(jù)自己的Jenkins版本進(jìn)行更換
另附啟動和關(guān)閉Jenkins命令:
啟動

jenkins -h

關(guān)閉
control + c 快捷鍵關(guān)閉
啟動后误墓,先不要急著打開Jenkins的web容器蛮粮,先去/Library/LaunchDaemons目錄下新建一個org.jenkins-ci.plist文件,文件內(nèi)容如下(可直接拷貝修改JENKINS_HOME值為你自己的路徑)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>StandardOutPath</key>
    <string>/var/log/jenkins/jenkins.log</string>
    <key>StandardErrorPath</key>
    <string>/var/log/jenkins/jenkins.log</string>
    <key>EnvironmentVariables</key>
    <dict>
      <key>JENKINS_HOME</key>
      <string>/Users/aladin/Documents/Jenkins/Home</string>
    </dict>
    <key>GroupName</key>
    <string>daemon</string>
    <key>KeepAlive</key>
    <true/>
    <key>Label</key>
    <string>org.jenkins-ci</string>
    <key>ProgramArguments</key>
    <array>
      <string>/bin/bash</string>
      <string>/Library/Application Support/Jenkins/jenkins-runner.sh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>UserName</key>
    <string>jenkins</string>
    <key>SessionCreate</key>
    <true/>
  </dict>
</plist>

啟動Jenkins
為避免權(quán)限問題谜慌,先執(zhí)行下面的命令:

sudo chown root /usr/local/Cellar/jenkins/2.122/homebrew.mxcl.jenkins.plist

啟動完成后然想,打開瀏覽器,輸入http://localhost:8080會出現(xiàn)如下頁面:

jenkins_install_finish.jpg

稍等幾分鐘欣范,會出現(xiàn)如下頁面
unlock_jenkins.jpg

進(jìn)入到Jenkins提示的目錄(我這里是/Users/Shared/Jenkins/Home/secrets/initialAdminPassword)獲取管理員密碼变泄,在該路徑中,非Jenkins用戶secrets目錄和initialAdminPassword文件時沒有讀寫權(quán)限的熙卡,將該權(quán)限改成只讀或讀和寫:
secret.jpg

edit.jpg

獲取到密碼后杖刷,記得備份下励饵,后期可能會用到

配置

1驳癌、安裝插件

可以按照推薦的插件安裝,也可以自己選擇

20190701100537.jpg

這里我們選擇安裝推薦的插件
install_plugins.jpg

安裝完成后役听,會提示我們創(chuàng)建用戶
創(chuàng)建用戶.jpg

創(chuàng)建完成后颓鲜,會提示我們配置Jenkins URL,這個可以根據(jù)自身情況進(jìn)行修改典予,這里我們先用默認(rèn)設(shè)置http://localhost:8080/
Jenkins插件安裝
因為公司項目托管在gitlab上甜滨,所以這里需要安裝gitlab插件,同時安裝Git Parameter插件和Build Name and Description Setter(用于參數(shù)化構(gòu)建)
manage plugins.jpg

plugins install.jpg

安裝Dynamic Parameter插件
由于Dynamic Parameter插件有漏洞瘤袖,在Jenkins中搜索不到衣摩,這里給出下載地址,經(jīng)測試目前只有0.1.1版本能用,該插件下載完成后需要在插件管理-高級-上傳插件中進(jìn)行安裝
插件安裝完成后捂敌,我們就來進(jìn)行基本環(huán)境的配置

2艾扮、環(huán)境配置
2.1系統(tǒng)配置:
Manage Jenkins.jpg

增加環(huán)境變量
進(jìn)入Manage Jenkins -> Configure System -> 全局屬性既琴,勾選Environment variables,增加一對鍵值


PATH.jpg

其中PATH的值為本機(jī)的環(huán)境變量泡嘴,可以在終端執(zhí)行以下命令查看甫恩,為一堆路徑:

echo $PATH

Android sdk配置:


Android sdk.jpg

SDK配置中的鍵必須是ANDROID_HOME,值為你本機(jī)的Android SDK目錄酌予,這里要注意SDK目錄的權(quán)限問題磺箕,沒有權(quán)限的話,可能會導(dǎo)致后期構(gòu)建的時候提示找不到SDK路徑抛虫。
JDK松靡、Git、Gradle配置:


Global Tool Configuration.jpg

jdk git gradle.jpg
2.2Gradle配置

接下來看下build.gradle中的部分配置

apply plugin: 'com.android.application'

def fileArray = []

def getBuildTime() {
    return new Date().format("yyyy-MM-dd-HH-mm")
}

//是否Jenkins打包
def isJenkins() {
    return "true".equals(IS_JENKINS)
}
//是否是Google渠道
def isGpChannel() {
    return "gp".equals(JENKINS_CHANNEL)
}

//獲取Channel
def getJenkinsChannel() {
//    def channels = System.getenv("JENKINS_CHANNEL")
    def channels = JENKINS_CHANNEL
    println("多渠道:" + channels)
//    String channels = "yingyongbao"
    channels.toString().tokenize(',').each { channelItem ->
        android.productFlavors.create(channelItem, {
            manifestPlaceholders = [
                    APP_NAME     : APP_NAME,
                    CHANNEL_VALUE: channelItem,
                    API_DOMAIN   : API_DOMAIN
            ]
            println("當(dāng)前渠道:" + channelItem)
        })
//        android.sourceSets.main.manifest.srcFile 'src/main/AndroidManifest.xml'
    }
}

android {
    compileSdkVersion 28
    buildToolsVersion "29.0.0"
    defaultConfig {
        String packageName = "com.ywd.jenkinsbuildtest"
        if (isJenkins()) {
            packageName = PACKAGE_NAME
        }
        applicationId packageName
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 100
        versionName "1.0.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        //確保所有的flavors都屬于同一維度
        flavorDimensions "default"

//        sourceSets.main {
//            jni.srcDirs = []
//            //LOCAL_LDFLAGS += -fuse-ld=bfd
//            //jni.srcDirs 'src/main/jni'
//            jniLibs.srcDir 'src/main/libs'
//        }
    }

    buildTypes {
        debug {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        preview {
            minifyEnabled false
            zipAlignEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        release {
            minifyEnabled true
            zipAlignEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    getJenkinsChannel()

//    productFlavors {
//        def channel = "gp"
//        println("productFlavors_isJenkins":isJenkins())
//        if(isJenkins()){
//            channel = CHANNEL_VALUE
//        }
//        println(channel)
//        app {
//            manifestPlaceholders = [CHANNEL_VALUE: channel]
//        }
//    }

    applicationVariants.all { variant ->
        variant.outputs.all { output ->
            def appVersion = variant.versionName //版本號
            def buildType = "" //構(gòu)建類型
            def buildTime = getBuildTime() //構(gòu)建時間
            println("IS_JENKINS_${IS_JENKINS}")

            if ("true".equals(IS_JENKINS)) {
                appVersion = APP_VERSION
                buildTime = BUILD_TIME
            }

            //構(gòu)建類型
            if ("debug".equals(variant.buildType.name)) {
                buildType = "Debug"
            } else if ("preview".equals(variant.buildType.name)) {
                buildType = "Preview"
            } else {
                buildType = "Release"
            }

            def fileName = "${appVersion}_${variant.productFlavors[0].name}_${buildTime}_${buildType}.apk"
            def outFile = output.outputFile
            if (outFile != null && outFile.name.endsWith('.apk')) {
                outputFileName = fileName
            }
            fileArray.add(outFile.parentFile.absolutePath + File.separator + fileName)
        }
    }

    //根據(jù)不同場景配置不同的AndroidManifest.xml文件
    sourceSets {
        println("==============sourceSets==============")
        main {
            if (isGpChannel()) {
                manifest.srcFile 'src/main/gp/AndroidManifest.xml'
                println("使用Google配置")
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                println("使用默認(rèn)配置")
            }
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

afterEvaluate {
    //只有Jenkins打包才復(fù)制建椰,此處復(fù)制到文件下載路徑
    if (isJenkins()) {
        assembleDebug.doLast {
            forEachFile(fileArray)
        }

        assemblePreview.doLast {
            forEachFile(fileArray)
        }

        assembleRelease.doLast {
            forEachFile(fileArray)
        }
    }
}

def forEachFile(fileArray) {
    fileArray.forEach { file ->
        renameAndMoveoutApk(file)
    }
}

def renameAndMoveoutApk(orignalFile) {
    //此處路徑根據(jù)實際情況設(shè)置
    def intoFile = rootDir.parentFile.parentFile.parentFile.parentFile.getAbsolutePath() + File.separator + "Shared/apache-tomcat-9.0.21/webapps/Jenkins_apk"
    copy {
        println("開始復(fù)制:目標(biāo)路徑:${intoFile}")
        from orignalFile
        into intoFile
//        rename("${android.defaultConfig.versionName}_${android.defaultConfig.versionCode}_","")
    }
}

gradle.properties

IS_JENKINS = false
BUILD_TIME = 2019-09-04
APP_NAME = JenkinsBuildTest
APP_VERSION = 1.0.0
PACKAGE_NAME = com.ywd.jenkinsbuildtest1
# 渠道
JENKINS_CHANNEL = app
# 接口域名
API_DOMAIN = ""
2.3Jenkins項目配置

新建項目


create new project.jpg

填寫項目名稱击困,這里我們選擇構(gòu)建一個自由風(fēng)格的軟件項目


create 2.jpg

創(chuàng)建好后,進(jìn)入項目配置
項目配置.jpg

選擇參數(shù)化構(gòu)建广凸,添加參數(shù)GitParameter


參數(shù)化構(gòu)建_git.jpg

這里變量名隨意阅茶,參數(shù)類型選擇Branch or Tag
select branch.jpg

添加參數(shù),選擇Choice Parameter谅海,這里參數(shù)名為IS_JENKINS脸哀,注意這里參數(shù)名要和gradle.properties中定義的相同
IS_JENKINS.jpg

創(chuàng)建參數(shù)BUILD_TYPE,這里名字可以隨意扭吁,參數(shù)根據(jù)自己項目中定義
BUILD_TYPE.jpg

添加Dynamic Parameter
Dynamic Parameter.jpg

創(chuàng)建參數(shù)BUILD_TIME撞蜂,注意這里參數(shù)名要和gradle.properties中定義的相同
并且Dynamic Parameter使用的是Groovy Script


BUILD_TIME.jpg

創(chuàng)建
源碼管理
這里我們使用的是Git
添加gitlab賬號.jpg

輸入倉庫地址后,點擊添加侥袜,添加認(rèn)證
gitlab_jenkins.jpg

創(chuàng)建完成后蝌诡,選擇剛剛創(chuàng)建的用戶憑據(jù),并填寫上面參數(shù)化構(gòu)建填好的分支變量名枫吧,注意變量名前要加$
源碼管理.jpg

選擇構(gòu)建插件
Android使用的是Gradle構(gòu)建浦旱,這里我們選擇之前配置好的Gradle版本,并輸入以下命令
clean assemble${BUILD_TYPE} --stacktrace

然后勾選Pass all job parameters as Project properties九杂,舊版本是勾選Pass job parameters as Gradle properties


select gradle version.jpg

配置完成后颁湖,點擊保存,回到項目首頁


項目列表.jpg

可以看到例隆,原先的立即構(gòu)建已經(jīng)變成了Build with Parameters
Build with Parameters.jpg

配置完成后甥捺,點擊開始構(gòu)建,這里我們可以查看控制臺輸出


控制臺輸出.jpg

在控制臺我們可以看到和Android Studio打包同樣的輸出結(jié)果镀层,最后顯示構(gòu)建成功镰禾。
Build Successful.jpg

構(gòu)建完成后,我們可以在項目目錄找到打好的包
build apk.jpg

構(gòu)建名稱
原本的構(gòu)建名稱只是一個編號,對于使用人員來講吴侦,沒有辨識度谷饿,我們可以在項目的構(gòu)建環(huán)境中進(jìn)行配置,更改名稱妈倔,具體操作如下博投,在構(gòu)建環(huán)境中勾選Set Build Name,并填入上文配置的參數(shù)名稱
set build name 0.jpg

保存后盯蝴,我們再次構(gòu)建查看下結(jié)果毅哗,構(gòu)建名稱已經(jīng)改變了


set build name.jpg

經(jīng)過如上配置,我們的Jenkins打包就可以正常工作了捧挺,但是構(gòu)建完成的包虑绵,測試人員該怎么安裝呢,不能每次打完包還要我們?nèi)ロ椖磕夸浵抡业桨l(fā)給他們吧闽烙,這是不可能的翅睛,讓測試區(qū)工作區(qū)自己找?可不太可能黑竞。捕发。。接下來我們卡一下如何將打完的包生成二維碼并展示
3很魂、生成二維碼并展示
3.1 Tomcat安裝及配置

安裝并配置Tomcat扎酷,詳見Mac安裝Tomcat
修改配置文件conf/web.xml

<init-param>
    <param-name>listings</param-name>
    <param-value>true</param-value>
</init-param>

把原來的false改為true,此時在webapps下新建個目錄遏匆,如download法挨,就可以通過瀏覽器訪問里面的內(nèi)容


download app.jpg
3.2 Python安裝及配置

安裝Python和pip詳見Mac安裝Python和pip
安裝Pillow
輸入命令sudo pip install Pillow,出現(xiàn)如下提示說明已經(jīng)安裝完成

install pillow

3.3 qrcode安裝及配置

輸入以下命令

pip3 install myqr
install myqr.jpg

以上配置完成后幅聘,打開Jenkins凡纳,進(jìn)入Manage Jenkins -> 全局屬性,然后新增屬性帝蒿,添加Python全局變量


python env.jpg
3.4 生成二維碼

進(jìn)入項目 -> 配置 -> 構(gòu)建荐糜,增加構(gòu)建步驟


Execute shell.jpg

填寫如下命令

myqr http://172.20.41.235:8888/Jenkins_apk/${APP_VERSION}_${JENKINS_CHANNEL}_${BUILD_TIME}_${BUILD_TYPE}.apk -n ${APP_VERSION}_${JENKINS_CHANNEL}_${BUILD_TIME}_${BUILD_TYPE}.png -v 1 -l L -d /Users/Shared/apache-tomcat-9.0.21/webapps/Jenkins_apk

其中路徑和${}里面的參數(shù)根據(jù)自己的實際情況進(jìn)行配置
關(guān)于qrcode的詳細(xì)使用,詳見Github

3.5 展示二維碼

通過myqr命令會在Tomcat下載目錄生成一張二維碼圖片陵叽,接下來我們要把二維碼圖片顯示在Jenkins上:
安裝插件description setter plugin狞尔,安裝好后丛版,進(jìn)入項目->配置->構(gòu)建后操作巩掺,增加構(gòu)建后操作步驟,選擇Set build description

select set build description.jpg

我這里已經(jīng)設(shè)置過了build description页畦,所以是灰色的
選擇完成后胖替,描述可以添加HTML標(biāo)簽,所以我們可以將<img src='' />標(biāo)簽加入到描述中,不過這里有個問題独令,加了img標(biāo)簽后端朵,Jenkins并不會顯示二維碼圖片,這是因為Jenkins出于安全考慮燃箭,所有描述信息的Markup Formatter默認(rèn)是采用Plain text模式冲呢,這種模式不會對描述信息中的HTML編碼進(jìn)行解析。
我們可以在Manage Jenkins -> Configure Global Security招狸,將Markup Formatter的設(shè)置改為Safe HTML即可敬拓。
Description中的描述

<img src='http://172.20.41.235:8888/Jenkins_apk/${APP_VERSION}_${JENKINS_CHANNEL}_${BUILD_TIME}_${BUILD_TYPE}.png' height="200" width="200" /><br><a >下載連接</a>

其中地址和參數(shù)可根據(jù)自身實際情況進(jìn)行配置。


build description.jpg
4裙戏、常見問題

1乘凸、

Caused by: java.lang.RuntimeException: The SDK directory '/Users/aladin/Library/Android/sdk' does not exist.

這個路徑是SDK默認(rèn)的路徑,剛開始是以為jenkins沒有把local.properties文件拉下來累榜,結(jié)果這個文件拉下來之后营勤,還是報這個錯,后來估計是權(quán)限問題壹罚,然后將everyone只讀權(quán)限放在Library目錄下就沒問題了

參考文章:
Android-解放雙手告別測試-使用Jenkins自動化打包
Android使用Jenkins持續(xù)集成
Jenkins本地搭建遇到的問題 for Mac

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末葛作,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子猖凛,更是在濱河造成了極大的恐慌进鸠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件形病,死亡現(xiàn)場離奇詭異客年,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)漠吻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門量瓜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人途乃,你說我怎么就攤上這事绍傲。” “怎么了耍共?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵烫饼,是天一觀的道長。 經(jīng)常有香客問我试读,道長杠纵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任钩骇,我火速辦了婚禮比藻,結(jié)果婚禮上铝量,老公的妹妹穿的比我還像新娘。我一直安慰自己银亲,他們只是感情好慢叨,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著务蝠,像睡著了一般拍谐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上馏段,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天赠尾,我揣著相機(jī)與錄音,去河邊找鬼毅弧。 笑死气嫁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的够坐。 我是一名探鬼主播寸宵,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼元咙!你這毒婦竟也來了梯影?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤庶香,失蹤者是張志新(化名)和其女友劉穎甲棍,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赶掖,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡感猛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奢赂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陪白。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖膳灶,靈堂內(nèi)的尸體忽然破棺而出咱士,到底是詐尸還是另有隱情,我是刑警寧澤轧钓,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布序厉,位于F島的核電站,受9級特大地震影響毕箍,放射性物質(zhì)發(fā)生泄漏欠母。R本人自食惡果不足惜判耕,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一胚股、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捞奕,春花似錦牺堰、人聲如沸拄轻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恨搓。三九已至,卻和暖如春筏养,著一層夾襖步出監(jiān)牢的瞬間斧抱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工渐溶, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留辉浦,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓茎辐,卻偏偏與公主長得像宪郊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拖陆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354