python自動更新pom檔案
前言
- 專案越來越多,版本管理越來越麻煩,在專案上我使用
maven version
來進行版本管理。主要還是在分散式專案中模組眾多的場景中使用,畢竟各個模組對外的版本需要保持統一。
- 關於這個外掛如何使用呢?也是非常的簡單。只需要在maven檢視中進行設定版本號即可將分模組專案的版本進行升級了。
- 除了idea外掛外,maven本身也提供了一個版本管理工具
versions-maven-plugin
。 具體用法以後有機會在贅述。
自定義實現版本更新
- 作為一個專業懶人,我還是覺得idea的外掛不夠智慧,確切的說還不夠自動化。之前我已經動手實現了防 jenkins 自動打包上傳啟動服務的指令碼的功能了,難道提交合並程式碼這種簡單的事情還需要我自己處理嗎。不得不承認程式碼衝突了的確還是需要認為干涉的,但是在平時開發中有多少概率會發生程式碼衝突呢?我們都是分工合作基本上程式碼衝突概率很低。
- 關於程式碼提交,自己分支如何合併到dev, 如何保證自己分支程式碼最新等等這些場景我們只需要通過指令碼來進行自動化操作就行了。針對這些功能場景我大概寫了兩個指令碼
batfhmerge.sh
和batchgrade.sh
就搞定了。 - 然而我想說的是關於專案的版本如何升級。上面也提到了我們分工合作就必然涉及到別人使用你的jar的場景了。你可以使用
SNAPSHOT
版本來保證別人拉取到你最新的功能程式碼,但是有些公司會要求使用非SNAPSHOT
版本進行管理也就是正式版本,這樣做的好處就是容易找到之前的版本程式碼功能。
SHELL 實現
- 之前用SHELL 實現了自動更新置頂專案的版本號為最新日期字尾。雖然使用起來沒發現有什麼BUG, 但是感覺程式碼實現上還是很弱智的。
# 該指令碼主要用來升級發包期間修改各服務版本 FILEPATH=$1 GROUPID=$2 ARTIFACTID=$3 FILENAME=$4 while getopts ":f:g:a:" opt do case $opt in f) FILENAME=$OPTARG echo "您輸入的檔案配置:$FILENAME" ;; g) GROUPID=$OPTARG echo "您輸入的groupid配置:$GROUPID" ;; a) ARTIFACTID=$OPTARG echo "您輸入的artifactid配置:$ARTIFACTID" ;; ff) FILENAME=$OPTARG echo "您輸入的帶修改檔案為:$FILENAME" ;; ?) echo "未知引數" exit 1;; esac done echo "開始修改版本號" NEWCONTENT=1.2.5.$(date +%Y%m%d) LINE=`cat ${FILENAME} | grep -n -A 1 '<groupId>'"${GROUPID}"'<\/groupId>'| grep -n '<artifactId>'"${ARTIFACTID}"'<\/artifactId>' | awk -F "[:-]+" '{print $2}'` echo 具體行號:$LINE if [[ -z $LINE ]] then echo 未匹配 exit fi VERSIONOLDCONTENT=`sed -n ''"$((LINE+1))"'p' ${FILENAME}| grep '[0-9a-zA-Z\.-]+' -Eo | sed -n '2p'` echo ${VERSIONOLDCONTENT} #gsed -i ''"$((LINE+1))"'c\'"${NEWCONTENT}"'' pom.xml sed -i "" ''"$((LINE+1))"'s/'"${VERSIONOLDCONTENT}"'/'"${NEWCONTENT}"'/' ${FILENAME}
- 其實邏輯很簡單,主要就是尋找
groupId
和artifactId
,最後確定好version
對應的行號將最新的日期字尾版本進行填充進去。 - 填充呢肯定需要三劍客中的
SED
進行操作,那就需要先獲取到以前的舊版本,然後進行替換操作。
為什麼使用SHELL
- shell指令碼作為後端程式猿必備技能選擇他進行實現也是為了溫故下shell的知識。基本上指令碼離不開三劍客,換句話說會了三劍客你就可以做好半個運維工作了。
- 有了這個指令碼我每次在功能開發完成之後,會通過SHELL指令碼進行版本升級以及自己分支合併到dev分支,這樣方便別人獲取到最新的程式碼。
python實現
- SHELL指令碼定製度很高,很難做到自動的相容功能。比如上面我們在定位包的時候是通過grep進行定位的,正常情況下應該是沒什麼問題的,但是當pom.xml 出現被註釋的同名座標時或者說名稱存在其他相似度的情況下很難保證SHELl指令碼還能夠正確的解析出來。
- 除此之外還有一個重要的原因就是SHELL指令碼很難在windows 執行,為了能夠兼顧到windows電腦我決定用python 進行重新實現該功能。
檔案思考
- 與SHELL不同的是 python處理需要考慮檔案格式的問題。SHELL中不管什麼格式都是通過三劍客進行定位處理,這是他的優點也是他的缺點。
- 首先我們得知道
pom.xml
檔案他是一個 XML 格式的檔案, XML=e X tensible M arkup L anguage 。即是一種可擴充套件的標記語言。它與 JSON 一樣主要用來儲存和傳輸資料。在之前的Springboot章節中我們也實現瞭如何實現介面傳遞 XML 資料結構。
常見的 XML 程式設計介面有 DOM 和 SAX,這兩種介面處理 XML 檔案的方式不同,當然使用場合也不同。
Python 有三種方法解析 XML,SAX,DOM,以及 ElementTree:
### 1.SAX (simple API for XML ) Python 標準庫包含 SAX 解析器,SAX 用事件驅動模型,通過在解析XML的過程中觸發一個個的事件並呼叫使用者定義的回撥函式來處理XML檔案。 ### 2.DOM(Document Object Model) 將 XML 資料在記憶體中解析成一個樹,通過對樹的操作來操作XML。 ### 3.ElementTree(元素樹)
- 而我所採用的就是最後一種方式
ElementTree
。
xml.etree.ElementTree
-
ElementTree
在 python3中已經作為標準庫存在了,所以這裡不需要我們額外的安裝。
基於事件和基於文件的APID來解析XML,可以使用XPath表示式搜尋已解析的檔案,具有對文件的增刪改查的功能,該方式需要注意大xml檔案,因為是一次性載入到記憶體,所以如果是大xml檔案,不推薦使用該模組解析,應該使用sax方式
- 不能說最好只能說他是合適的工具,因為
pom.xml
檔案不會很大的。ElementTree
通過XPath
進行節點選擇,所以關於xml 節點查詢我們可以參考 xpath 語法即可。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.github.zxhTom</groupId> <artifactId>bottom</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>bottom</name> <url>http://maven.apache.org</url> <description>最底層的繁瑣封裝</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <log4j2.version>2.10.0</log4j2.version> </properties> <dependencies> <!-- 20180927提供了針對stirng bean list 等判斷的操作。不用我們在詳細的判斷了 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.7</version> </dependency> <!-- 提供了針對list 等判斷的操作。不用我們在詳細的判斷了 --> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> <!-- jsonobeject jar包依賴 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.28</version> </dependency> <!-- 日誌記錄 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>${log4j2.version}</version> </dependency> <!-- 通過反射獲取標有註解的類 --> <dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.10</version> </dependency> </dependencies> </project>
- 上面的
pom.xml
摘自於com.github.zxhTom
的 bottom 專案中。裡面的恰好出現了註釋,方便我們後期測試。
解析xml
import xml.etree.ElementTree as ET with open('pom.xml', 'tr', encoding='utf-8') as rf: tree = ET.parse(rf) print(tree)
- 實際有效的內容就是
ET.parse
即可解析出來 xml 。
檢視pom.xml所有節點標籤名稱
import xml.etree.ElementTree as ET with open('pom.xml', 'tr', encoding='utf-8') as rf: tree = ET.parse(rf) # 根據tree進行遍歷 for node in tree.iter(): print(node.tag)
讀取xml中dependency
- 我想看下所有的 dependency 標籤,只有這樣我才能夠匹配是否是我需要的那個maven座標。
import xml.etree.ElementTree as ET with open('pom.xml', 'tr', encoding='utf-8') as rf: tree = ET.parse(rf) # tree遍歷 for node in tree.findall('.//dependency'): print(node.tag)
- 上述程式碼很明顯是錯誤的,因為我們執行指令碼後沒有任何的輸出,至於為什麼是這樣呢?你可以翻到上一節就可以看到我們在列印所有節點標籤名稱的時候前面好像都多了一串地址。
- 這個地址就是
pom.xml
中的名稱空間,在跟節點 project 標籤中設定的xmlns
屬性。至於為什麼需要這個呢?每個xml 標籤內容都是自定義的,比如你可以將dependency用來做版本號的作用,只要你自己解析的時候注意就行了。而maven中將dependency作為引入座標的概念,每個人的想法不一,所以引入名稱空間,在指定的名稱空間中標籤的作用是唯一的,這就是xmlns
存在的意義。
import xml.etree.ElementTree as ET with open('pom.xml', 'tr', encoding='utf-8') as rf: tree = ET.parse(rf) # tree遍歷 for node in tree.findall('.//{http://maven.apache.org/POM/4.0.0}dependency'): print(node.tag)
讀取com.alibaba.fastjson 的版本號
-
通過上面的內容我們知道在定位節點時候需要加入名稱空間。
-
首先我們知道
com.alibaba.fastjson
的版本號是1.2.28
import xml.etree.ElementTree as ET with open('pom.xml', 'tr', encoding='utf-8') as rf: tree = ET.parse(rf) # tree遍歷 for node in tree.findall('.//{http://maven.apache.org/POM/4.0.0}dependency'): groupIdNode=node.find('.{http://maven.apache.org/POM/4.0.0}groupId') artifactNode=node.find('.{http://maven.apache.org/POM/4.0.0}artifactId') if(artifactNode.text=='fastjson' and groupIdNode.text=='com.alibaba'): print(node.find('.{http://maven.apache.org/POM/4.0.0}version').text)
- 通過
python3 upgrade.py
即可打印出1.2.28
。
儲存xml
- 說了這麼多,還記得我們一開始的任務嗎,沒錯就是修改掉pom.xml 中指定jar的版本號。這裡就將
com.alibaba.fastjson
的版本號升級為1.2.29
吧。
import xml.etree.ElementTree as ET with open('pom.xml', 'tr', encoding='utf-8') as rf: tree = ET.parse(rf) # tree遍歷 for node in tree.findall('.//{http://maven.apache.org/POM/4.0.0}dependency'): groupIdNode=node.find('.{http://maven.apache.org/POM/4.0.0}groupId') artifactNode=node.find('.{http://maven.apache.org/POM/4.0.0}artifactId') if(artifactNode.text=='fastjson' and groupIdNode.text=='com.alibaba'): node.find('.{http://maven.apache.org/POM/4.0.0}version').text='1.2.29' tree.write('pom.xml')
- 修改還是很容易的,我們只需要將node.text進行重新賦值就行了。但是經過對比我發現儲存後的
pom.xml
好像並不是只改了version標籤的內容。- 標籤多了ns0字首。
- 中文亂碼
- 註釋丟失
API追蹤
def write(self, file_or_filename, encoding=None, xml_declaration=None, default_namespace=None, method=None, *, short_empty_elements=True):
- 上述提到了三個問題都是很常見的問題,因為我們開啟
/Lib/xml/etree/ElementTree.py
原始碼就能夠看到在寫會 xml 檔案的時候我們一共有7個引數可選,其中第一個self 沒啥好說的。
屬性 | 作用 |
---|---|
file_or_filename | 檔案 |
encoding | 輸出的編碼格式;預設US-ASCII |
xml_declaration | 將XML宣告新增到檔案中: True新增;False不新增;None在非US-ASCII 或 UTF-8 或 Unicode時新增; 預設None |
default_namespace | 預設的名稱空間 |
method | xml、 html 、 text。預設 xml |
short_empty_elements | 空內容的標籤時的處理 |
修改xml後節點多了字首ns0
- 很明顯是我們沒有指定輸出的預設名稱空間導致程式自動生成一個字首。
tree.write('pom.xml',default_namespace='http://maven.apache.org/POM/4.0.0')
- 當我們指定了名稱空間,這個時候再檢視下檔案節點的字首問題就解決了。
中文亂碼
- 中文亂碼就是我們沒有指定編碼格式。大家都知道預設的 US-ASCII 都是人家老外的東西,我們國內想正常使用肯定還是需要
UTF-8
的。
tree.write('pom.xml',default_namespace='http://maven.apache.org/POM/4.0.0',encoding='UTF-8')
- 這裡我也就不截圖了,筆者親測是可以解決中文亂碼的問題的。
標準化xml
- 這不算個問題,不知道你有沒有發現上面我提供的 pom.xml 嚴格意義上來說不是一個標準的 xml 檔案,就好比你上學不帶紅領巾就不是一個標準學生一樣,上面的pom.xml 確實了xml的標準開頭申明。不過沒關係我們在wirte的時候注意到有一個引數
xml_declaration
就是控制是否生成標準申明的。
修改xml後原來的註釋丟了
- 關於註釋丟了這種問題,怎麼說呢?無關緊要吧但是往往註釋是進行解釋說明的,為了追求完美我還是希望能夠將註釋保留下來。關於註釋的問題我們還得簡單檢視下原始碼說明。
with open('pom.xml', 'tr', encoding='utf-8') as rf: tree = ET.parse(rf)
- parse 對應的就是 ElementTree 的原始碼中 parse 中。
- 到了這裡我們應該不難看出,在渲染的時候主要是通過
TreeBuilder
進行渲染的,其中 CommentHandler 就是 TreeBuilder.comment 進行的。那麼我們繼續檢視下 TreeBuilder 的原始碼來檢視為什麼預設的 parser 沒有保留下注釋。
_comment_factory
class CommentedTreeBuilder (ET.TreeBuilder): def comment(self,data): self.start(ET.Comment,{}) self.data(data) self.end(ET.Comment)
-
上述程式碼類似於 Java中繼承了 TreeBuilder 並且重寫了 comment方法。主要的邏輯就是開頭一個註釋標籤屬性為空,結尾是一個註釋標籤結尾,中間的內容是我們註釋的內容,所以綜上就是保留下來註釋。
-
重寫了類之後我們還需要在ET中指定我們自定義的TreeBuilder 。
parser = ET.XMLParser(target=CommentedTreeBuilder()) tree = ET.parse(xml_path,parser=parser)
- 構建好tree之後其他就是一樣的操作,最終倒出來的檔案就是我們保留註釋的xml 了。
獲取不到子節點
- 就的版本是 getchildren , 但是在新版本中直接廢棄了這個方法。而在新版本中是通過 iter(tag) 進行建立迭代器的。但tag=None或者 * 表示所有子孫節點,其他的情況就只查詢指定tag 名稱的集合。
優雅解析帶名稱空間的xml
- 還記得我們是如何解析獲取到 pom.xml 中的標籤的。
# tree遍歷 for node in tree.findall('.//{http://maven.apache.org/POM/4.0.0}dependency'): print(node.tag)
- 這種方式我只想說NO , 每次在通過xpath 定位節點都需要新增字首,這簡直是個噩夢。
ns = {'real_mvn': 'http://maven.apache.org/POM/4.0.0', 'vistual': 'http://characters.zxhtom.com'} # tree遍歷 for node in tree.findall('.//real_mvn:dependency',ns): print(node.tag)
總結
- 本文主要是通過指令碼實現pom.xml 的版本升級功能。SHELL通過三劍客定位到指定座標進行版本更新,缺點是可能不相容註釋同內容的座標,優點是修改內容範圍小,不會造成大影響
- 而python實現的xml檔案修改。他的優點是能夠精準定位不會產生錯誤定位問題且支援註釋內容保留,缺點是不適合操作大檔案。
檔案 | dependency數量 |
---|---|
knife4j-spring-ui-1.9.6.pom | 0 |
spring-context-support-1.0.6.pom | 3 |
spring-core-5.1.7.RELEASE.pom | 10 |
springfox-swagger-common-3.0.0.pom | 11 |
其他說明
「其他文章」
- 記一次批量更新整型型別的列 → 探究 UPDATE 的使用細節
- 編碼中的Adapter,不僅是一種設計模式,更是一種架構理念與解決方案
- 執行緒池底層原理詳解與原始碼分析
- 30分鐘掌握 Webpack
- 線性迴歸大結局(嶺(Ridge)、 Lasso迴歸原理、公式推導),你想要的這裡都有
- Django 之路由層
- 【前端必會】webpack loader 到底是什麼
- day42-反射01
- 中心化決議管理——雲端分析
- HashMap底層原理及jdk1.8原始碼解讀
- 詳解JS中 call 方法的實現
- 列印 Logger 日誌時,需不需要再封裝一下工具類?
- 初識設計模式 - 代理模式
- 設計模式---享元模式
- 密碼學奇妙之旅、01 CFB密文反饋模式、AES標準、Golang程式碼
- [ML從入門到入門] 支援向量機:從SVM的推導過程到SMO的收斂性討論
- 從應用訪問Pod元資料-DownwardApi的應用
- Springboot之 Mybatis 多資料來源實現
- Java 泛型程式設計
- CAS核心思想、底層實現