什么是版本管理首先,這里說的版本管理(version management)不是指版本控制(version control),但是本文假設(shè)你擁有基本的版本控制的知識(shí),了解subversion的基本用法。版本管理中說得版本是指構(gòu)件(artifact)的版本,而非源碼的版本(如subversion中常見的rXXX,或者git中一次提交都有個(gè)sha1的commit號(hào))。 比如我有一個(gè)項(xiàng)目,其artifactId為myapp,隨著項(xiàng)目的進(jìn)展,我們會(huì)生成這樣一些jar:myapp-1.0-SNAPSHOT.jar,myapp-1.0.jar,myapp-1.1-SNAPSHOT.jar,myapp-1.0.1.jar等等。你可能會(huì)說,這很簡(jiǎn)單啊,我在POM中改個(gè)version,mvn clean install不就完了?但這只是表面,本文我將講述,snapshot和release版本的區(qū)別,如何自動(dòng)化版本發(fā)布(如果你的項(xiàng)目有幾十個(gè)module,你就會(huì)覺得手工改POM來升級(jí)版本是很痛苦的事情),結(jié)合自動(dòng)化發(fā)布的過程,這里還會(huì)介紹maven-release-plugin。此外,一些scm概念也會(huì)被涉及到,比如tag和branch。
前提:版本控制不管怎樣,我們都需要建立一個(gè)項(xiàng)目并提交到SCM中,這里我以subversion為例。你得有一個(gè)配置好的subversion repository,這里我建立了一個(gè)空的svn倉(cāng)庫(kù),其地址為:https://192.168.1.100:8443/svn/myapp/ 現(xiàn)在,該目錄下只有三個(gè)空的典型的子目錄:/trunk/, branches/, tags/。分別用來存放主干,分支,以及標(biāo)簽。 接著將項(xiàng)目導(dǎo)入到svn倉(cāng)庫(kù)中,到項(xiàng)目根目錄,運(yùn)行如下命令: svn import -m 'project initialization' https://192.168.1.100:8443/svn/myapp/trunk
目前,我們將項(xiàng)目的版本設(shè)置為1.0-SNAPSHOT。
為什么用SNAPSHOT?我先說說如果沒有SNAPSHOT會(huì)是什么樣子。假設(shè)你的項(xiàng)目有2個(gè)模塊,A,B,其中A依賴B。這三個(gè)模塊分別由甲,乙兩個(gè)個(gè)人負(fù)責(zé)開發(fā)。在開發(fā)過程中,因?yàn)锳是依賴于B的,因此乙每次做一個(gè)改動(dòng)都會(huì)影響到甲,于是,乙提交了一些更改后,需要讓甲看到。這個(gè)時(shí)候,怎么做呢?乙對(duì)甲說,“你簽出我的代碼,build一下就OK了”,甲有點(diǎn)不情愿,但還是照做了,簽出代碼,svn clean install,然后,發(fā)現(xiàn)build出錯(cuò)了,有個(gè)測(cè)試沒有pass。甲郁悶了,對(duì)乙說,“你的代碼根本不能用,我不想build,你build好了給我”,乙看了看確實(shí)自己的代碼build不過,于是回去解決了,然后打了個(gè)jar包,扔給甲,甲對(duì)了對(duì)groupId,artifactId,放到了自己的.m2/repository/目錄下,OK,能用了。 于是乙每次更新都這樣做,打包,復(fù)制,然后甲粘貼,使用……漸漸的,大家發(fā)現(xiàn)這是個(gè)很笨的辦法,這是純手工勞動(dòng)阿,程序員最BS的就是重復(fù)勞動(dòng)。一天,甲對(duì)乙說,“你知道nexus么?你把你的jar發(fā)布到nexus上就可以了,我要用就自動(dòng)去下載,這多棒!”乙說“哦?有這好東西,我去看看”于是乙發(fā)現(xiàn)了nexus這塊新大陸,并成功的發(fā)布了B到nexus上。(見,Nexus入門指南,(圖文) )。 但是,請(qǐng)注意,我們這里的一切都假設(shè)沒有SNAPSHOT,因此如果乙不更改版本,甲下載一次如B-1.0.jar之后,maven認(rèn)為它已經(jīng)有了正確的B的版本,就不會(huì)再重新下載。甲發(fā)現(xiàn)了這個(gè)問題,對(duì)乙說“你的更新我看不到,你更新了么?”乙說“不可能!我看看”,于是檢查一下甲下載的C-1.0.jar,發(fā)現(xiàn)那是幾天前的。乙一拍腦袋,說“這簡(jiǎn)單,我更新一下我的版本就好了,我發(fā)布個(gè)B-1.1.jar上去,你更新下依賴版本”,甲照做了,似乎這么做是可行的。 這里有一個(gè)問題,一次提交就更新一個(gè)版本,這明顯不是正確的管理辦法,此外,乙得不停的通知甲更新對(duì)B的依賴版本,累不累阿?1.0,或者說1.1,2.0,都代表了穩(wěn)定,這樣隨隨便便的改版本,能穩(wěn)定么? 所以Maven有SNAPSHOT版本的概念,它與release版本對(duì)應(yīng),后者是指1.0,1.1,2.0這樣穩(wěn)定的發(fā)布版本。 現(xiàn)在乙可以將B的版本設(shè)置成1.0-SNAPSHOT,每次更改后,都mvn deploy到nexus中,每次deploy,maven都會(huì)將SNAPSHOT改成一個(gè)當(dāng)前時(shí)間的timestamp,比如B-1.0-SNAPSHOT.jar到nexus中后,會(huì)成為這個(gè)樣子:B-1.0-20081017-020325-13.jar。Maven在處理A中對(duì)于B的SNAPSHOT依賴時(shí),會(huì)根據(jù)這樣的timestamp下載最新的jar,默認(rèn)Maven每天 更新一次,如果你想讓Maven強(qiáng)制更新,可以使用-U參數(shù),如:mvn clean install -U 。 現(xiàn)在事情簡(jiǎn)化成了這個(gè)樣子:乙做更改,然后mvn deploy,甲用最簡(jiǎn)單的maven命令就能得到最新的B。
從1.0-SNAPSHOT到1.0到1.1-SNAPSHOTSNAPSHOT是快照的意思,項(xiàng)目到一個(gè)階段后,就需要發(fā)布一個(gè)正式的版本(release版本)。一次正式的發(fā)布需要這樣一些工作:
你可以手工一步步的做這些事情,無非就是一些svn操作,一些pom編輯,還有一些mvn操作。但是你應(yīng)該明白,手工做這些事情,一來繁瑣,而來容易出錯(cuò)。因此這里我介紹使用maven插件來自動(dòng)化這一系列動(dòng)作。 SCM首先我們需要在POM中加入scm信息,這樣Maven才能夠替你完成svn操作,這里我的配置如下:
需要注意的是,很多windows使用的tortoiseSVN客戶端,而沒有svn命令行客戶端,這會(huì)導(dǎo)致Maven所有svn相關(guān)的工作失敗,因此,你首先確保svn --version能夠運(yùn)行。 分發(fā)倉(cāng)庫(kù)想要讓Maven幫我們自動(dòng)發(fā)布,首先我們需要配置好分發(fā)倉(cāng)庫(kù)。關(guān)于這一點(diǎn),見Maven最佳實(shí)踐:Maven倉(cāng)庫(kù) ——分發(fā)構(gòu)件至遠(yuǎn)程倉(cāng)庫(kù)。 maven-release-plugin緊接著,我們需要配置maven-release-plugin,這個(gè)插件會(huì)幫助我們升級(jí)pom版本,提交,打tag,然后再升級(jí)版本,再提交,等等?;九渲萌缦拢?/p>
GAV我就不多解釋了,這里我們需要注意的是configuration元素下的tagBase元素,它代表了我們svn中的tag目錄,也就是說,maven-release-plugin幫我們打tag的時(shí)候,其基礎(chǔ)目錄是什么。這里,我填寫了svn倉(cāng)庫(kù)中的標(biāo)準(zhǔn)的tags目錄。 提交代碼接著,確保你的所有代碼都提交了,如果你有未提交代碼,release插件會(huì)報(bào)錯(cuò),既然你要發(fā)布版本了,就表示代碼是穩(wěn)定的,所以要么要么把代碼提交了,要么把本地的更改拋棄了。 開始工作現(xiàn)在,屏住呼吸,執(zhí)行: mvn release:prepare 執(zhí)行過程中,你會(huì)遇到這樣的提示: What is the release version for "Unnamed - org.myorg:myapp:jar:1.0-SNAPSHOT"? (org.myorg:myapp) 1.0: : ——“你想將1.0-SNAPSHOT發(fā)布為什么版本?默認(rèn)是1.0。”我要的就是1.0,直接回車。 What is SCM release tag or label for "Unnamed - org.myorg:myapp:jar:1.0-SNAPSHOT"? (org.myorg:myapp) myapp-1.0: : ——“發(fā)布的tag標(biāo)簽名稱是什么?默認(rèn)為myapp-1.0?!蔽疫€是要默認(rèn)值,直接回車。 What is the new development version for "Unnamed - org.myorg:myapp:jar:1.0-SNAPSHOT"? (org.myorg:myapp) 1.1-SNAPSHOT: : ——“主干上新的版本是什么?默認(rèn)為1.1-SNAPSHOT?!惫瑀elease插件會(huì)自動(dòng)幫我更新版本到1.1-SNAPSHOT,很好,直接回車。 然后屏幕刷阿刷,maven在build我們的項(xiàng)目,并進(jìn)行了一些svn操作,你可以仔細(xì)查看下日志。 那么結(jié)果是什么呢?你可以瀏覽下svn倉(cāng)庫(kù):
這不正是我們想要的么?等等,好像缺了點(diǎn)什么,對(duì)了,1.0還沒有發(fā)布到倉(cāng)庫(kù)中呢。 再一次屏住呼吸,執(zhí)行: mvn release:perform maven-release-plugin會(huì)自動(dòng)幫我們簽出剛才打的tag,然后打包,分發(fā)到遠(yuǎn)程Maven倉(cāng)庫(kù)中,至此,整個(gè)版本的升級(jí),打標(biāo)簽,發(fā)布等工作全部完成。我們可以在遠(yuǎn)程Maven倉(cāng)庫(kù)中看到正式發(fā)布的1.0版本。 這可是自動(dòng)化的 ,正式的 版本發(fā)布!
Maven的版本規(guī)則前面我們提到了SNAPSHOT和Release版本的區(qū)別,現(xiàn)在看一下,為什么要有1.0,1.1,1.1.1這樣的版本,這里的規(guī)則是什么。 Maven主要是這樣定義版本規(guī)則的: <主版本>.<次版本>.<增量版本> 比如說1.2.3,主版本是1,次版本是2,增量版本是3。 主版本一般來說代表了項(xiàng)目的重大的架構(gòu)變更,比如說Maven 1和Maven 2,在架構(gòu)上已經(jīng)兩樣了,將來的Maven 3和Maven 2也會(huì)有很大的變化。次版本一般代表了一些功能的增加或變化,但沒有架構(gòu)的變化,比如說Nexus 1.3較之于Nexus 1.2來說,增加了一系列新的或者改進(jìn)的功能(倉(cāng)庫(kù)鏡像支持,改進(jìn)的倉(cāng)庫(kù)管理界面等等),但從大的架構(gòu)上來說,1.3和1.2沒什么區(qū)別。至于增量版本,一般是一些小的bug fix,不會(huì)有重大的功能變化。 一般來說,在我們發(fā)布一次重要的版本之后,隨之會(huì)開發(fā)新的版本,比如說,myapp-1.1發(fā)布之后,就著手開發(fā)myapp-1.2了。由于myapp-1.2有新的主要功能的添加和變化,在發(fā)布測(cè)試前,它會(huì)變得不穩(wěn)定,而myapp-1.1是一個(gè)比較穩(wěn)定的版本,現(xiàn)在的問題是,我們?cè)趍yapp-1.1中發(fā)現(xiàn)了一些bug(當(dāng)然在1.2中也存在),為了能夠在段時(shí)間內(nèi)修復(fù)bug并仍然發(fā)布穩(wěn)定的版本,我們就會(huì)用到分支(branch),我們基于1.1開啟一個(gè)分支1.1.1,在這個(gè)分支中修復(fù)bug,并快速發(fā)布。這既保證了版本的穩(wěn)定,也能夠使bug得到快速修復(fù),也不同停止1.2的開發(fā)。只是,每次修復(fù)分支1.1.1中的bug后,需要merge代碼到1.2(主干)中。 上面講的就是我們?yōu)槭裁匆迷隽堪姹尽?/p>
實(shí)戰(zhàn)分支目前我們trunk的版本是1.1-SNAPSHOT,其實(shí)按照前面解釋的版本規(guī)則,應(yīng)該是1.1.0-SNAPSHOT。 現(xiàn)在我們想要發(fā)布1.1.0,然后將主干升級(jí)為1.2.0-SNAPSHOT,同時(shí)開啟一個(gè)1.1.x的分支,用來修復(fù)1.1.0中的bug。 首先,在發(fā)布1.1.0之前,我們創(chuàng)建1.1.x分支,運(yùn)行如下命令: mvn release:branch -DbranchName=1.1.x -DupdateBranchVersions=true -DupdateWorkingCopyVersions=false 這是maven-release-plugin的branch目標(biāo),我們指定branch的名稱為1.1.x,表示這里會(huì)有版本1.1.1, 1.1.2等等。updateBranchVersions=true的意思是在分支中更新版本,而updateWorkingCopyVersions=false是指不更改當(dāng)前工作目錄(這里是trunk)的版本。 在運(yùn)行該命令后,我們會(huì)遇到這樣的提示: What is the branch version for "Unnamed - org.myorg:myapp:jar:1.1-SNAPSHOT"? (org.myorg:myapp) 1.1-SNAPSHOT: : ——"分支中的版本號(hào)是多少?默認(rèn)為1.1-SNAPSHOT" 這時(shí)我們想要的版本是1.1.1-SNAPSHOT,因此輸入1.1.1-SNAPSHOT,回車,maven繼續(xù)執(zhí)行直至結(jié)束。 接著,我們?yōu)g覽svn倉(cāng)庫(kù),會(huì)看到這樣的目錄:https://192.168.1.100:8443/svn/myapp/branches/1.1.x/,打開其中的POM文件,其版本已經(jīng)是1.1.1-SNAPSHOT。 分支創(chuàng)建好了,就可以使用release:prepare和release:perform為1.1.0打標(biāo)簽,升級(jí)trunk至1.2.0-SNAPSHOT,然后分發(fā)1.1.0。 至此,一切OK。
小結(jié)本文講述了如何使用Maven結(jié)合svn進(jìn)行版本管理。解釋了Maven中SNAPSHOT版本的來由,以及Maven管理版本的規(guī)則。并結(jié)合SCM的tag和branch概念展示了如何使用maven-release-plugin發(fā)布版本,以及創(chuàng)建分支。本文涉及的內(nèi)容比較多,且略顯復(fù)雜,不過掌握版本管理的技巧對(duì)于項(xiàng)目的正規(guī)化管理來說十分重要。Maven為我們提供了一些一套比較成熟的機(jī)制,值得掌握。 |
|