ขั้นตอนการ Upload Android Library ไปที่ Maven Central
วันนี้ผมได้ทำการทดสอบ อัพโหลด Library ขึ้นไปไว้ที่ Maven Central ครั้งแรกครับ ก็เลยนำประสบการณ์มาแชร์เผื่อมีใครที่กำลังจะทำ หรืออยากทำ เชื่อว่ามีนะ เห็นมาถามๆผมอยู่ :D Library ที่ผมทำการ Upload ขึ้นไป คือ Shared เป็น Android SharedPreferences แบบง่ายๆครับ
System & Tools
สำหรับเครื่องมือและระบบปฎิบัติการที่ผมใช้ ก็มีดังนี้ หากใครใช้อย่างอื่น ก็ไปประยุกต์ใช้กันเองนะ
- Ubuntu 14.04
- Android Studio 1.0 RC1
- Gradle 2.2
- Android Gradle Plugin 0.14.4
ส่วนขั้นตอนการ Upload มีอะไรบ้าง ดูด้านล่างเลย
Table of Contents
- Step 1 : Register Sonatype Account
- Step 2 : Create Gradle Script
- Step 3 : Create Module Properties
- Step 4 : Signing Artifacts with GPG
- Step 5 : Upload to Maven Central
Step 1 : Register Sonatype Account
ขั้นตอนแรกสุดเลย ต้องทำการสมัครสมาชิกกับทาง Nexus Sonatype ก่อน หลังจากนั้นก็ทำการโพส Issue ขึ้นใหม่ โดยให้ใส่รายละเอียดว่าเรากำลังจะทำโปรเจ็คอะไร
- Summary : กรอกชื่อ Library/Project ของเรา
- Description : ใส่รายละเอียดซักนิด เพื่อให้รู้ว่าคือโปรเจ็คอะไร
- Group Id : ใส่ groupId ที่ไม่ให้ซ้ำกัน คล้ายๆกับการตั้งชื่อ package name แต่ควรจะใช้ชื่อโดเมนของเราแบบ Reverse ถ้าไม่มีโดเมนก็ใช้โดเมนเดียวกันกับที่เราฝาก repository ไว้ รายละเอียดเพิ่มเติมและตัวอย่างมีเขียนไว้แล้ว
- Project URL : ใส่ที่อยู่ของ Library ของเรา ในตัวอย่างผมใช้ repository ที่ฝากไว้ที่ Github
- SCM url : ที่อยู่ของ source control system เหมือนกับด้านบน แค่เพิ่ม
.git
เข้าไป - Already Synced to Central : ถ้ายังไม่เคย Sync ไรเลย ก็เลือก None หรือ No ไป
พร้อมแล้วก็กด Create เลยครับ เมื่อ Issue ของเราถูกสร้าง ก็จะมีทีมงานของ Sonanype มาอนุมัติไม่ใช่ออโต้ ฉะนั้นก็ต้องรอประมาณ 1-2 วัน หรือโชคดีหน่อยก็อาจจะได้เร็ว (ของผมใช้เวลา 10 ชม. ถึงจะมีทีมงานมาตอบ)
โอเค เมื่อทีมงานมาตอบแล้ว เค้าก็จะแนะนำ ว่าเราจะต้องอัพโหลด Snapshot , Release, Staged Artifacts ไปที่ URL ไหนบ้าง แล้วเมื่ออัพโหลดเสร็จแล้ว ก็มาคอมเม้นตอบเค้าด้วย
หากใครทำแล้ว ยังไม่มีคนตอบ ก็ข้ามไปทำ Step อื่นได้นะครับ แต่อย่าเพิ่ง Upload อะไรขึ้นไป (ผมไม่แน่ใจว่าจะเป็นไรไหม เพราะไม่เคยทำก่อน :D )
Step 2 : Create Gradle Script
มาถึงขั้นตอนสร้าง Gradle Script อันนี้ผมอ้างอิงจาก Publish an aar file to Maven Central with Gradle และก็ดูพวกตัวอย่าง Library อื่นๆ ประกอบ เห็นว่าแต่ละอันก็เหมือนกัน ฉะนั้นก็เลยก็อปมาซะเลย
ผมทำการสร้างไฟล์ขึ้นมาใหม่ ชื่อว่า maven_push.gradle
ใส่ไว้ที่ Root Project เลย
apply plugin: 'maven'
apply plugin: 'signing'
def sonatypeRepositoryUrl
if (isReleaseBuild()) {
println 'RELEASE BUILD'
sonatypeRepositoryUrl = hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
} else {
println 'DEBUG BUILD'
sonatypeRepositoryUrl = hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
: "https://oss.sonatype.org/content/repositories/snapshots/"
}
def getRepositoryUsername() {
return hasProperty('nexusUsername') ? nexusUsername : ""
}
def getRepositoryPassword() {
return hasProperty('nexusPassword') ? nexusPassword : ""
}
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
pom.artifactId = POM_ARTIFACT_ID
repository(url: sonatypeRepositoryUrl) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
pom.project {
name POM_NAME
packaging POM_PACKAGING
description POM_DESCRIPTION
url POM_URL
scm {
url POM_SCM_URL
connection POM_SCM_CONNECTION
developerConnection POM_SCM_DEV_CONNECTION
}
licenses {
license {
name POM_LICENCE_NAME
url POM_LICENCE_URL
distribution POM_LICENCE_DIST
}
}
developers {
developer {
id POM_DEVELOPER_ID
name POM_DEVELOPER_NAME
}
}
}
}
}
}
signing {
required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
task androidJavadocs(type: Javadoc) {
source = android.sourceSets.main.java.sourceFiles
}
task androidJavadocsJar(type: Jar) {
classifier = 'javadoc'
//basename = artifact_id
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
//basename = artifact_id
from android.sourceSets.main.java.sourceFiles
}
artifacts {
//archives packageReleaseJar
archives androidSourcesJar
archives androidJavadocsJar
}
}
จากด้านบน ก็มีในส่วนการใช้ Plugin Maven และ Signing
apply plugin: 'maven'
apply plugin: 'signing'
และเมื่อสังเกต Task uploadArchives
จะเห็น sonatypeRepository ก็คือ URL ที่ทาง Sonatype บอกให้เราทำการ Upload ไว้ในคอมเม้นใน Step 1 แต่ว่าตัว maven_push.gradle
ตั้งค่าไว้ให้แล้ว ที่เหลือเราก็แค่ไปเปลี่ยนค่าในไฟล์ gradle.properties
เท่านั้นเอง เพราะเห็นว่าค่าบางตัว มันคือตัวแปลที่กำหนดไว้ในไฟล์ gradle.properties
ฉะนั้นเปิดไฟล์ gradle.properties
ตัวที่อยู่ที่ Root Project นะไม่ใช่ใน Module
VERSION_NAME=0.0.1
VERSION_CODE=1
GROUP=com.devahoy
POM_DESCRIPTION=A Simple Android SharedPreferences
POM_URL=https://github.com/devahoy/shared
POM_SCM_URL=https://github.com/devahoy/shared
POM_SCM_CONNECTION=scm:git@github.com:devahoy/shared.git
POM_SCM_DEV_CONNECTION=scm:git@github.com:devahoy/shared.git
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=phonbopit
POM_DEVELOPER_NAME=Chai Phonbopit
ด้านบนเป็นตัวอย่างของผม ท่านก็ต้องไปเปลี่ยนเป็นรายละเอียดของท่านเอาเอง
ดูรายละเอียดเปรียบเทียบกับไฟล์ของผมได้ที่นี่ maven_push.gradle และ gradle.properties
Step 3 : Create Module Properties
หลักจากเปลี่ยนแปลงค่าใน Root Project เรียบร้อยแล้ว ต่อมาก็เปลี่ยนแปลงค่าใน Module กันบ้าง ซึ่ง Module มันก็คือ Library Project ของเราใช่มั้ยละ อย่างตอนนี้ File Structure ของผมเป็นแบบนี้
├── build
│ └── intermediates
├── build.gradle
├── gradle
│ └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library
│ ├── build.gradle
│ ├── gradle.properties
│ ├── library.iml
│ ├── libs
│ ├── proguard-rules.pro
│ └── src
├── LICENSE.txt
├── local.properties
├── maven_push.gradle
├── README.md
├── settings.gradle
└── Shared.iml
ทำการเปิดไฟล์ gradle.properties
ใน Module ซึ่งของผมคือในโฟลเดอร์ /library/gradle.properties
จากนั้นก็เพิ่มรายละเอียดของ POM ลงไป
POM_NAME=Shared
POM_ARTIFACT_ID=shared
POM_PACKAGING=aar
เปลี่ยน POM_NAME
และ POM_ARTIFACT_ID
เป็นของท่านด้วยนะ เอาแบบ จำง่ายๆก็ได้ เวลาคนอื่นเอาไปใช้จะได้จำง่ายๆ ฟอแมตมันคือ
dependencies {
compile 'GROUP_ID:POM_ARTIFACT_ID:VERSION_CODE'
}
ต่อมา เปิดไฟล์ build.gradle
ใน Module ซึ่งของผมก็ยังอยู่ในโฟลเดอร์ /library/build.gradle
จากนั้นทำการเพิ่มโค๊ดนี้ลงไปบรรทัดล่างสุดเลย
apply from: '../maven_push.gradle'
ดูรายละเอียดเปรียบเทียบกับไฟล์ของผมได้ที่นี่ build.gradle และ gradle.properties
Step 4 : Signing Artifacts with GPG
ขั้นตอนนี้ต้องทำการ Siging ด้วย GPG ก่อน ผมทำบน Ubuntu ส่วน Mac OS X หรือ Windows วิธีการทำไม่รู้ว่าเป็นยังไง ต้องไปหาดูเอาเองนะครับ แต่คิดว่าวิธีคล้ายๆกัน คือต้องการ pub ID
และการ Publish Key ไปที่ Server ถ้าท่านไม่ใช่ Ubuntu ก็ข้ามขั้นนี้ไปเลยครับ
ขั้นแรก ติดตั้ง gnugpg
sudo apt-get install gnupg
ทำการ Gen Key
gpg --gen-key
จากนั้นก็ทำการขั้นตอนตาม Command Line เลย
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0) 0
Key does not expire at all
Is this correct? (y/N) y
You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"
Real name: DevAhoy
Email address: hello@devahoy.com
Comment:
You selected this USER-ID:
"DevAhoy <hello@devahoy.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
You need a Passphrase to protect your secret key.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
เมื่อกรอกรายละเอียดเสร็จ ก็ให้ใส่ Passphrase หรือรหัสที่เราจะใช้ จากนั้น ก็รอมัน Generate (แนะนำว่าหาอะไรทำ ขยับเมาท์ นั่งพิมพ์ หรือเปิด Browser อะไรไปก็ได้ ไม่งั้นมันจะ Gen ไม่ได้)
หาก Gen เสร็จแล้ว ให้เราทำการเช็คว่าโอเคไหม
gpg --list-keys
ที่เราจะใช้ก็คือรหัส ประมาณนี้
pub 4096R/XXXXXXXX 2014-11-25
uid DevAhoy <hello@devahoy.com>
ใช้รหัส ที่ได้ Publish ไปที่ Server (ส่วนนี้จะเอาไว้ยืนยันตัวตน เวลาเรา Publish ไปที่ Maven แบบ Released version)
gpg --keyserver hkp://keyserver.ubuntu.com --send-keys XXXXXXXX
$ gpg --keyserver hkp://pgp.mit.edu --send-keys XXXXXXXX
จำรหัสในตำแหน่ง XXXXXXXX
เพราะเราจะเอาไว้ใช้ใน Step 5 อีกที
Step 5 : Upload to Maven Central
มาถึงขั้นตอนสุดท้ายละ คราวนี้เปิดไฟล์ gradle.properties
ของ Gradle Home คนละตัวกับในโปรเจ็คและใน Module นะ อันนี้จะเป็นตัว Global เลย ปกติน่าจะอยู่ที่ ~/.gradle/
ถ้าใน Windows น่าจะประมาณ C:\Users\UserName\.gradle
ถ้าจำไม่ผิด
จากนั้นก็ใส่รายละเอียดของเราลงไป ส่วนนี้เป็นข้อมูลที่เป็นความลับนะ อย่าเผลอติดมากับ Repository ละ
signing.keyId=PUB_ID
signing.password=YOUR_PASSPHRASE
signing.secretKeyRingFile=/home/UserName/.gnupg/secring.gpg
nexusUsername=YOUR_NEXUS_USERNAME
nexusPassword=YOUR_NEXUS_PASSWORD
เปลี่ยนรายละเอียดด้านบน เป็นของท่านเอง
signing.keyId
ก็คือ รหัสที่ได้จาก GPGsigning.password
: Passphrase ที่ตั้งไว้signing.secretKeyRingFile
: ที่อยู่ของไฟล์secretKey
อยู่ในโฟลเดอร์.gnupg
ของ User อันนี้น่าจะเหมือนกันnexusUsername
: ชื่อ Username ของ Nexus Sonatype ที่สมัครใน Step 1nexusPassword
: password ที่ใช้ของ Nexus Sonatype เช่นกัน ได้จาก Step 1
OK เตรียมพร้อมทุกอย่างเรียบร้อยแล้ว ต่อมาเปิด Terminal ใน Android Studio ก็ได้ แล้วรันคำสั่ง
gradle uploadArchives
รออัพโหลดขึ้น Maven ซักพัก ก็จะได้ข้อความด้านล่าง ว่าอัพเสร็จแล้ว (ตื่นเต้นดี :D)
Uploading: com/devahoy/library/0.0.1/library-0.0.1-20141125.161932-1.aar to repository remote at https://oss.sonatype.org/content/repositories/snapshots/
Transferring 52K from remote
Uploaded 52K
Uploading: com/devahoy/library/0.0.1/library-0.0.1-20141125.161932-1-sources.jar.asc to repository remote at https://oss.sonatype.org/content/repositories/snapshots/
Transferring 0K from remote
Uploaded 0K
Uploading: com/devahoy/library/0.0.1/library-0.0.1-20141125.161932-1.aar.asc to repository remote at https://oss.sonatype.org/content/repositories/snapshots/
Transferring 0K from remote
Uploaded 0K
Uploading: com/devahoy/library/0.0.1/library-0.0.1-20141125.161932-1-javadoc.jar.asc to repository remote at https://oss.sonatype.org/content/repositories/snapshots/
Transferring 0K from remote
Uploaded 0K
Uploading: com/devahoy/library/0.0.1/library-0.0.1-20141125.161932-1-javadoc.jar to repository remote at https://oss.sonatype.org/content/repositories/snapshots/
Transferring 0K from remote
Uploaded 0K
Uploading: com/devahoy/library/0.0.1/library-0.0.1-20141125.161932-1-sources.jar to repository remote at https://oss.sonatype.org/content/repositories/snapshots/
Transferring 1K from remote
Uploaded 1K
Uploading: com/devahoy/library/0.0.1/library-0.0.1-20141125.161932-1.pom.asc to repository remote at https://oss.sonatype.org/content/repositories/snapshots/
Transferring 0K from remote
Uploaded 0K
BUILD SUCCESSFUL
เมื่ออัพโหลด เสร็จเรียบร้อยแล้ว ตรวจเช็ค Library ของเราในเว็บ OSSRH web UI ด้วย จะอยู่ List ล่างๆสุดเลย ดูเวลาเปรียบเทียบเอาครับ ล่าสุดจะอยู่ล่างสุด
ทำการเลือก Library ของเรา แล้วกดปุ่ม Close ครับ เพื่อพร้อมสำหรับ Release Version หากมีข้อผิดพลาดมันก็จะบอก ว่าเพราะอะไร เช่น ครั้งแรกผมลืม Publish Pub ID ไปที่ Server ทำให้ไม่มี Permission ในการ Release
แต่หากใครที่ Close เรียบร้อยไม่มีปัญหา ก็จะมีอีกปุ่มขึ้นมา คือ Release กดเลย เป็นอันเรียบร้อย Library เราลืมตาดูโลกแล้ว
ขั้นตอนสุดท้าย กลับไปคอมเม้น Issue ที่เราสร้างไว้ใน Step 1 ด้วยนะครับ อย่าลืม!
ตอนนี้ผมสามารถเรียกใช้ Library ของผม ได้เก๋ไก๋ แบบคนอื่นเค้าแล้ว แบบนี้
dependencies {
compile 'com.devahoy:shared:0.0.1'
}
แล้วก็ ถ้าหากใครอยากให้ค้นหาเจอใน Maven Search ก็รอทางทีมงานดำเนินการอีกราวๆ 3-4 ชม. Library เราก็ไปอยู่ใน Maven Search แล้ว ใน Gradle Please ก็อัพเดทเช่นเดียวกัน :)
สุดท้ายดู บันทึก Issue ของผม ได้ครับ ว่าทางทีมงานเค้าแนะนำ และบอกขั้นตอน จนเราอัพ Library ได้แหละครับ
หวังว่าคงมีประโยชน์นะครับ
Referenes
- Authors
- Name
- Chai Phonbopit
- Website
- @Phonbopit