ขั้นตอนการ Upload Android Library ไปที่ Maven Central

ขั้นตอนการ Upload Android Library ไปที่ Maven Central Cover Image

วันนี้ผมได้ทำการทดสอบ อัพโหลด 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

ขั้นตอนแรกสุดเลย ต้องทำการสมัครสมาชิกกับทาง Nexus Sonatype ก่อน หลังจากนั้นก็ทำการโพส Issue ขึ้นใหม่ โดยให้ใส่รายละเอียดว่าเรากำลังจะทำโปรเจ็คอะไร

Create new 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 ชม. ถึงจะมีทีมงานมาตอบ)

Config

โอเค เมื่อทีมงานมาตอบแล้ว เค้าก็จะแนะนำ ว่าเราจะต้องอัพโหลด 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:[email protected]:devahoy/shared.git
POM_SCM_DEV_CONNECTION=scm:[email protected]: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) <[email protected]>"

Real name: DevAhoy
Email address: [email protected]
Comment: 
You selected this USER-ID:
    "DevAhoy <[email protected]>"

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 <[email protected]>

ใช้รหัส ที่ได้ 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 ก็คือ รหัสที่ได้จาก GPG
  • signing.password : Passphrase ที่ตั้งไว้
  • signing.secretKeyRingFile : ที่อยู่ของไฟล์ secretKey อยู่ในโฟลเดอร์ .gnupg ของ User อันนี้น่าจะเหมือนกัน
  • nexusUsername : ชื่อ Username ของ Nexus Sonatype ที่สมัครใน Step 1
  • nexusPassword : 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 ล่างๆสุดเลย ดูเวลาเปรียบเทียบเอาครับ ล่าสุดจะอยู่ล่างสุด

Close

ทำการเลือก 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

Chai Chai Phonbopit : Software Engineer @Nextzy • ผู้ชายธรรมดาๆ ที่ชื่นชอบ Android, JavaScript (Node.js) และ Open Source มีงานอดิเรกเป็น Acoustic Guitar และ Football

บทความล่าสุด