Bintray簡介
Bintray是一個(gè)提供軟件的發(fā)布、存儲(chǔ)、推廣、分布等功能的云平臺(tái),對下列開發(fā)技術(shù)都提供了強(qiáng)大的支持:
- Docker
- Debian
- Maven
- RPM
- npm
- NuGet
- Vagrant
- Opkg
在現(xiàn)在的Gradle文件中,常見以下配置:
repositories {
jcenter()
}
這里的jcenter(),就是指從Bintray的JCenter這里獲取依賴:
https://bintray.com/bintray/jcenter/
有時(shí)是mavenCentral(),指的是Maven這個(gè)構(gòu)建軟件的官方倉庫。
用上面列出的Maven支持,可以方便地發(fā)布軟件到上述的兩個(gè)地方。
作為類庫的使用者,只需要在Gradle中加入一行代碼即可,甚至不需要知道JCenter在哪;而作為類庫的發(fā)布者,Bintray是一個(gè)非常復(fù)雜的網(wǎng)絡(luò)服務(wù)系統(tǒng),需要花很多時(shí)間去實(shí)際操作,才能逐漸熟悉。
(另外,Bintray部分頁面因使用Google的analytic與map的API而加載緩慢。為避免等待幾分鐘才能看到頁面,最好相信科學(xué)再上網(wǎng)。)
準(zhǔn)備

要通過Bintray發(fā)布一個(gè)jar(或aar),需要以下幾個(gè)步驟:
- 用GitHub賬戶注冊、登錄Bintray。
- 建立一個(gè)Maven庫。
- 從GitHub導(dǎo)入要發(fā)布的庫。
- 新增Version,在Version中上傳要發(fā)布的文件。
- 在項(xiàng)目主頁點(diǎn)擊【Add to JCenter】。
- 同步到Maven Central(略)。
與GitHub不同,Bintray的repository(庫)的概念,是指一些package(包)的集合,而Bintray的package的概念,才對應(yīng)GitHub的repository。一個(gè)在GitHub上為user/name的庫,要在建立好的Bintray庫(比如叫repo)中才能導(dǎo)入,最后名稱為user/repo/name。
而Bintray的version(版本)的概念,與GitHub的Releases就比較類似。可以基于git tag,也可以與tag無關(guān)。
純手工操作Bintray發(fā)布,由于需要填的信息眾多,需要上傳的文件不少,所以是一件很麻煩的事。官方發(fā)布了一個(gè)Gradle插件,可以進(jìn)行自動(dòng)化:
https://github.com/bintray/gradle-bintray-plugin
(很多文章都介紹過如何使用這個(gè)插件,見本文的參考。)
這里,上傳要發(fā)布的文件,是通過Travis來實(shí)現(xiàn)的。有三種實(shí)現(xiàn)方式。
第一種方式
對于已經(jīng)非常熟練使用官方Gradle插件的人來說,在Travis的配置里加入./gradlew bintrayUpload就好,思路非常簡單。
唯一的麻煩是,要對Bintray的API KEY做兼容配置,既不在Git庫中,又能同時(shí)在本地和Travis使用。這有多種方法可以實(shí)現(xiàn),無非是加密、生成、環(huán)境變量這些手段,不詳述。
我沒有這樣做。
我希望,發(fā)布這個(gè)操作,只在Travis上進(jìn)行,而不是兩個(gè)地方都可以。如果在本地也可以發(fā)布,那么我為什么非要去Travis上搞?(是啊,為什么呢?)
一個(gè)人玩的時(shí)候還好,多人開發(fā)時(shí),如果大家都在本地?fù)屩l(fā)布版本,這就太傷和氣了。在我實(shí)際的工作環(huán)境中,是利用Jenkins來做統(tǒng)一的編譯和發(fā)布,普通開發(fā)者根本沒有發(fā)布版本的權(quán)利。我也希望利用Travis,把發(fā)布統(tǒng)一管理。
第二種方式
根據(jù)Travis的文檔《Bintray Deployment》,可以通過在.travis.yml里添加一個(gè)provider,另外再添加一個(gè)JSON的方式來發(fā)布。
.travis.yml后半部分如下:
deploy:
- provider: releases
skip_cleanup: true
file: $artifacts
api_key:
secure: "KmMdcwTWGubXVRu93/lY1NtyHxrjHK4TzCfemgwjsYzPcZuPmEA+pz+umQBN\n1ZhzUHZwDNsDd2VnBgYq27ZdcS2cRvtyI/IFuM/xJoRi0jpdTn/KsXR47zeE\nr2bFxRqrdY0fERVHSMkBiBrN/KV5T70js4Y6FydsWaQgXCg+WEU="
on:
tags: true
jdk: oraclejdk8
- provider: bintray
skip_cleanup: true
file: "build/descriptor.json"
user: USER
on:
tags: true
jdk: oraclejdk8
key:
secure: "1ZhzUHZwDNsDd2VnBgYq27ZdcS2cRvtyI/IFuM/xJoRi0jpdTn/KsXR47zeE\nKmMdcwTWGubXVRu93/lY1NtyHxrjHK4TzCfemgwjsYzPcZuPmEA+pz+umQBN\nr2bFxRqrdY0fERVHSMkBiBrN/KV5T70js4Y6FydsWaQgXCg+WEU="
provider: releases就是上篇介紹的,上傳到GitHub Releases。provider: bintray的手段也很類似,特有的key需要加密;user需要額外指定,因?yàn)椴皇荁intray賬號(hào)未必與GitHub相同。
最大的不同是,前者的file是指定上傳文件,后者的file是指定JSON配置文件。示例如下:
{
"package": {
"name": "PROJECT",
"repo": "REPO NAME IN BINTRAY",
"subject": "USER",
"desc": "Description for this package.",
"website_url": "https://github.com/USER/PROJECT",
"issue_tracker_url": "https://github.com/USER/PROJECT/issues",
"vcs_url": "https://github.com/USER/PROJECT.git",
"github_use_tag_release_notes": true,
"github_release_notes_file": "RELEASE_NOTE.md",
"labels": ["AS", "YOU", "LIKE"],
"public_download_numbers": true,
"public_stats": true
},
"version": {
"name": "VERSION",
"desc": "Description for this version.",
"released": "yyyy-MM-dd",
"vcs_tag": "GIT TAG",
"gpgSign": true
},
"files": [
{
"includePattern": "build/libs/(.*\\.jar)",
"uploadPattern": "GROUP/ID/ARTIFACT/ID/VERSION/$1",
"matrixParams": { "override": 1 }
},
{
"includePattern": "build/(.*\\.pom)",
"uploadPattern": "GROUP/ID/ARTIFACT/ID/VERSION/$1",
"matrixParams": { "override": 1 }
}
],
"publish": true
}
乍然一看,這是一個(gè)非常復(fù)雜的配置。其實(shí)這與Travis沒有太大關(guān)系,是Bintray自身的復(fù)雜性所致。
以下做一些解釋(沒解釋的基本都可省略):
- package
指定一些關(guān)于Bintray package的信息,以便于確定網(wǎng)絡(luò)位置,以及頁面展示。- name
包名,也是GitHub上的庫名。 - repo
庫名,是Bintray獨(dú)有的那一層。通常,按類型命名,全小寫。這里一般命名為maven。 - subject
用戶名,也是GitHub上的用戶名。 - desc
對package的描述。
以下都是些顯示的信息,可以去網(wǎng)站上填寫,略。
- name
- version
- name
版本名。通常是0.1.0什么的。 - desc
對此版本的描述,每個(gè)版本都可以不同。 - released
發(fā)布時(shí)間。 - vcs_tag
最新的git tag。如果設(shè)為按tag發(fā)布,那么與版本號(hào)相同。 - gpgSign
上傳文件后,自動(dòng)用gpg簽名。需要先把gpg的公鑰填到Bintray的用戶信息中。
- name
- files
這是指定上傳文件,以及上傳后的路徑。
這是一個(gè)列表,可以指定多個(gè)文件或模式。- includePattern
本地需要上傳的文件,其路徑模式,遵循Ruby的正則表達(dá)式規(guī)則。
一般也就是*.jar之類的,依葫蘆畫瓢即可。 - uploadPattern
遠(yuǎn)程的文件位置及命名。如果前面includePattern使用了正則表達(dá)式,那么用$1來代表匹配的內(nèi)容。
- includePattern
其實(shí),這是一種不可接受的方式。
信息寫死為JSON,把很多經(jīng)常變化的東西納入git的版本控制,發(fā)布前需要花費(fèi)許多精力去更新。而且,很多地方都需要與Gralde的編譯配置保持一致,否則會(huì)有難以預(yù)知的錯(cuò)誤。
于是,為了實(shí)現(xiàn)一些信息的自動(dòng)變化,我利用build.gradle來生成這個(gè)JSON——這就是第三種方式。
第三種方式
用Groovy來生成JSON,有一個(gè)方便的方法:JsonBuilder。
def date = new Date()
def website = 'https://github.com/USER/PROJECT'
def packageDir = "${group.replace('.', '/')}/${project.name}"
task writeDescriptor << {
def builder = new JsonBuilder()
def root = builder {
'package' {
name 'PROJECT'
repo 'REPO NAME IN BINTRAY'
subject 'USER'
desc 'Description for this package.'
website_url website
issue_tracker_url "$website/issues"
vcs_url "${website}.git"
github_use_tag_release_notes true
github_release_notes_file 'RELEASE_NOTE.md'
labels 'AS', 'YOU', 'LIKE'
public_download_numbers true
}
'version' {
name version
desc 'Description for this version.'
released date.format('yyyy-MM-dd')
vcs_tag tag
gpgSign true
}
publish true
}
root.files = []
root.files.add([
'includePattern': 'build/libs/(.*\\.jar)',
'uploadPattern': "$packageDir/$version/\$1",
'matrixParams': ['override': 1]
])
root.files.add([
'includePattern': 'build/(.*\\.pom)',
'uploadPattern': "$packageDir/$version/\$1",
'matrixParams': ['override': 1]
])
def jsonFile = new File("$buildDir/descriptor.json")
jsonFile.write(builder.toPrettyString())
}
build.dependsOn('writeDescriptor')
可惜的是,JsonBuilder雖然方便,局限也非常大。它只支持一些比較簡單的JSON,稍微復(fù)雜點(diǎn)就只能上其它語法了。
最終,這個(gè)task生成的JSON文件,與前面的那個(gè)基本一致。
其實(shí),我最后發(fā)現(xiàn),第三種方式與第一種沒太大區(qū)別,尤其是在build.gradle文件的修改。
o(╯□╰)o
這也許就是傳說中的返璞歸真(走火入魔)吧。
發(fā)布失敗
點(diǎn)擊【Add to JCenter】時(shí),總是被檢查出錯(cuò)誤。我看過的所有相關(guān)文章,都沒有描述過這類事情。在他們那里,點(diǎn)擊【Add to JCenter】,彈出個(gè)框,然后再點(diǎn)發(fā)送,剩下的就只有等了。
然而,我卻在見到那個(gè)發(fā)送框之前,遇到了兩類錯(cuò)誤。
缺少POM文件
一開始是缺少*.pom文件。但是,POM是什么?
POM是Project Object Model的縮寫,是Maven對單一項(xiàng)目的描述,是Maven得以正常運(yùn)作的核心。
我玩的不是Gradle嗎,和Maven有什么關(guān)系?
相比Maven,Gradle的改進(jìn)主要在配置文件用Groovy、編譯過程可定制化等。(詳見官方對比《Gradle vs Maven Feature Comparison》。)Gradle的遠(yuǎn)程庫,仍然使用的是Maven的那一套。Maven Central和JCenter,原本都是給Maven使用的,只是Gradle(和Ivy),涎著臉一起蹭著用而已。所以,所謂發(fā)布,其實(shí)是面向Maven的發(fā)布。
Gradle如何生成Maven的POM文件?
要蹭東西當(dāng)然得有所準(zhǔn)備。對此,官方文檔《Chapter 32. The Maven Plugin》中有詳細(xì)描述。
我的配置如下,僅供參考:
apply plugin: 'maven'
def date = new Date()
def website = 'https://github.com/USER/PROJECT'
task writePom << {
pomDir = "$buildDir/PROJECT-${version}.pom"
pom {
project {
inceptionYear date.format('yyyy')
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution 'repo'
}
}
developers {
developer {
id 'USER'
name 'USER NAME'
email 'USER@EMAIL.com'
url 'https://github.com/USER'
timezone '+8'
}
}
issueManagement {
system 'github'
url "$website/issues"
}
}
}.writeTo(pomDir)
}
build.dependsOn('writePom')
這樣,在執(zhí)行./gradlew build后,build/目錄下就有POM文件了。上述inceptionYear、licenses、developers、issueManagement等配置,在pom中基本上都可缺省。最重要的groupId、artifactId、version、dependencies等,都由plugin maven自動(dòng)生成。
缺少maven-metadata.xml
The POM is invalid or was uploaded to the wrong coordinates.
解決了前一個(gè)問題后,點(diǎn)擊【Add to JCenter】仍然提示上述錯(cuò)誤。
并且,由于在Bintray上缺少maven-metadata.xml,所以仍然無法提供給他人使用。
maven-metadata.xml是Maven遠(yuǎn)程庫的描述,而POM則是這個(gè)庫中一個(gè)版本的描述。(在本地Maven庫中,這個(gè)文件一般名為maven-metadata-local.xml。)它們都是xml形式的配置文件,內(nèi)容也差不多。
在使用遠(yuǎn)程Maven庫時(shí),有大約兩種形式。
一種是指定版本的,這時(shí)構(gòu)建工具會(huì)去直接根據(jù)約定目錄結(jié)構(gòu),查找該版本的POM。
compile 'org.codehaus.groovy:groovy-all:2.4.7'
另一種是用符號(hào)+,指定最新版本的。例如:
compile 'org.codehaus.groovy:groovy-all:2.4.+'
compile 'org.codehaus.groovy:groovy-all:+'
2.4.+是指定2.4這個(gè)大版本里最新的小版本;+是指定整個(gè)庫中最新的版本。
由于所有的版本,都在maven-metadata.xml的記錄中,上述功能才得以實(shí)現(xiàn)。由此可見,maven-metadata.xml和POM的正常,是這個(gè)Maven庫是否成功建立的標(biāo)志。
本來我想自己生成個(gè)maven-metadata.xml上傳,并且自行更新。都卷起袖子,研究Metadata的配置含義,準(zhǔn)備開始自己寫了,但轉(zhuǎn)念一想,這樣是不是太不尊重前輩。Maven的創(chuàng)造者們都是程序員中的佼佼者,這么麻煩的事,理應(yīng)自動(dòng)化了才對。
最終發(fā)現(xiàn),這是由于我的groupId、artifactId與上傳目錄不匹配所致。
Maven倉庫的約定目錄結(jié)構(gòu)是:MAVEN_REPO/groupId/artifactId/version/。其中,MAVEN_REPO代表Maven倉庫的位置,本地默認(rèn)為$HOME/.m2/repository/,Bintray則是https://dl.bintray.com/USER/REPO;groupId和artifactId都需要把.換成/;version是版本號(hào)。另外,似乎大小寫敏感。
由于在Travis里,上傳位置是自行指定的,并無限制,因此我查了很久都沒發(fā)現(xiàn)這個(gè)錯(cuò)誤。天坑一個(gè)!在上傳文件目錄正確后,遠(yuǎn)程Maven庫的mave-metadata.xml自動(dòng)生成,已經(jīng)可以作為私有庫使用了。
接下來點(diǎn)擊【Add to JCenter】,再無問題,靜候佳音。
參考
- bintray官方文檔:
https://bintray.com/docs/usermanual/index.html - bintray官方Gradle插件文檔:
https://github.com/bintray/gradle-bintray-plugin#overview - Maven pom文檔:https://maven.apache.org/pom.html
- 《如何使用Android Studio把自己的Android library分發(fā)到j(luò)Center和Maven Central》
- 《Android 快速發(fā)布開源項(xiàng)目到j(luò)center》
- 《Locally release an Android Library for JCenter or Maven Central inclusion》
- 《Automatic deployments to JFrog OSS and Bintray/jCenter/Maven Central via Travis CI from SBT》
由于.travis.yml越來越復(fù)雜,為避免一些錯(cuò)誤,需要對YAML有更深入的了解。關(guān)于YAML的資料,最新的是spec 1.2。
YAML官網(wǎng):http://yaml.org/
相關(guān)文章:
- 第一篇:《從GitHub到Travis》
- 上一篇:《從GitHub到Travis》
后記
匿:一套折騰下來,腎都虧了!
蟒:確實(shí)花了不少時(shí)間。其實(shí)原先只是有一個(gè)簡單樸素的想法,要發(fā)布一個(gè)別人也能用的jar給自己用。受Android的影響,于是就選了Bintray。
匿:然而,為了自動(dòng)化,花了數(shù)百倍的時(shí)間。如果在本地編譯上傳,早就搞定了!
蟒:為了可以懶一點(diǎn),我也真是夠勤奮的。