Upload Android Library into Gradle with Artifactory

Bintray 를 서비스하는 JFrog 에서 아키펙트 패키지(Gradle 나 maven, docker 등 저장소)를 관리하는 소프트를 제작하는데, 그것이 바로 Artifactory 이다. 그 중 Pro 버전은 유료지만, oss라 하여 오픈 소스 버전 (즉 커뮤니티 버전) 이 있는데, 이 것을 사용해서 자체적인 Gradle 저장소를 만들 수 있다.

이 글에서는 VPS 환경에서 자체적인 Gradle 저장소를 만들고, 배포하는 방법을 작성하려 한다.

주의할 점으로, 이 글에서는 Docker 설치나 환경 설정에 대해 언급하지는 않는다.

서버 환경

Vultr Cloud Compute의 $10 서버, 즉 40GB SSD / 2GB RAM / 2TB Bandwidth / 1 CPU(Skylake CPU) 를 사용헀고, OS 는 Ubuntu 18.04.1 LTS 이다.

추가적으로, Artifactory 를 안정적으로 Serve 하기 위해 Docker, PostgreSQL 를 사용했다.

Docker-compose 준비

Artifactory 도커 이미지에는 standalone 으로 돌아갈 수 있게 하는 프로그램이 들어가 있기 때문에, 아래의 명령어 1줄이면 쉽게 구동할 수 있다.

docker run --name artifactory -d -p 8081:8081 docker.bintray.io/jfrog/artifactory-oss:latest  

다만 이 방법은 여러 설정을 하려면 명령어가 길어지고, 여러 개의 컨테이너를 동시에 사용해야 될 경우 각각 하나마다 동작 상태를 확인해야 하는 문제가 있어 Docker-compose 를 사용하여 임의의 설정 파일을 만들고 설정 파일을 docker 에 올리는 방법을 사용한다.

여기서 Docker-compose 란 여러 개의 컨테이너를 한 데에 묶어 컨테이너를 순차적으로 생성하는 역할을 한다. Artifcatory 를 구동하기 위해서는 Artifactory 컨테이너와 DB 역할이 되는 PostgreSQL 컨테이너가 필요하기 때문에, 일일히 run 으로 돌리기 보다는 Docker-compose 를 이용해 한번에 관리하려 한다.

다행히도, JFrog가 Github 에 Docker-compose 예제 파일을 올려두어 모든 것을 다 작업할 필요는 없지만, 위 서버 환경에서 적용했을 때 문제가 있어 아래에 정리하려 한다.

Artifactory 구동에 필요한 환경 설정

구동에 필요한 환경 설정 작업은 다음과 같다.

  • 데이터 폴더 생성
  • 기본 설정 파일 복사
  • 권한 설정

데이터 폴더 생성

# mkdir -p /data/postgresql
# mkdir -p /data/artifactory/etc
# mkdir -p /data/nginx/conf.d
# mkdir -p /data/nginx/logs
# mkdir -p /data/nginx/ssl

sudo 권한으로 접근해 위 명령어를 실행하면 된다.

기본 설정 파일 복사

기본 설정 파일에 대해서는 Jfrog/artifactory-docker-examples에 전부 포함되어 있다. 기본 설정 파일 자체를 수정할 필요는 없으니 각각 맞는 경로에 복사하면 된다. 기본 설정 파일의 경로는 이쪽이다.

  1. /data/artifactory 폴더에 access 폴더 안 내용을 삽입한다. 즉, /data/artifactory/access/etc/keys 에 private.key, root.crt 가 있어야 한다.
  2. /data/nginx/conf.d/oss 폴더에 artifactory.conf 를 삽입한다.

권한 설정

Artifactory 6.2.0 부터 artifactory 라는 유저를 생성하여 그 유저만 /data/artifactory 폴더를 접근할 수 있게 변경되었다.

# chown -R 1030:1030 /data/artifactory
# chown -R 104:107 /data/nginx

sudo 권한으로 접근해 위 명령어를 실행하면 된다. 여기서 1030, 104 및 107은 미리 정의된 값으로 사용자에 맞게 수정하면 될 것 같다.

Docker-compose 파일 작성

일반적 문법은 여기서 설명하지 않으나, 기본적인 것을 설명하자면 Docker-compose 의 설정 파일은 yml 확장자를 가지고 있으며, 각 depth 에 컨테이너 이름과 그 정보를 넣는다. 자세한 문법은 Docker-compose version 2 reference 문서를 참고하면 된다.

version: '2'
services:
  postgresql:
    image: library/postgres:latest
    container_name: postgresql
    ports:
     - 5432:5432
    environment:
     - POSTGRES_DB=artifactory
     # The following must match the DB_USER and DB_PASSWORD values passed to Artifactory
     - POSTGRES_USER=artifactory
     - POSTGRES_PASSWORD=password
    volumes:
     - /data/postgresql:/var/lib/postgresql/data
    restart: always
    ulimits:
      nproc: 65535
      nofile:
        soft: 32000
        hard: 40000
  artifactory:
    image: docker.bintray.io/jfrog/artifactory-oss:latest
    container_name: artifactory
    ports:
     - 80:8081
    depends_on:
     - postgresql
    links:
     - postgresql
    volumes:
     - /data/artifactory:/var/opt/jfrog/artifactory
    environment:
     - DB_TYPE=postgresql
     # The following must match the POSTGRES_USER and POSTGRES_PASSWORD values passed to PostgreSQL
     - DB_USER=artifactory
     - DB_PASSWORD=password
     # Add extra Java options by uncommenting the following line
     #- EXTRA_JAVA_OPTIONS=-Xmx4g
    restart: always
    ulimits:
      nproc: 65535
      nofile:
        soft: 32000
        hard: 40000

위 코드를 artifcatory-oss-postgres.yml 라는 이름으로 만들어 서버에 올리면 된다.

Docker-compose 실행

환경 설정도 완료되고, yml 파일도 만들었다면 이제 실행만 하면 된다.

sudo docker-compose -f artifactory-oss-postgresql.yml up -d

위 명령어를 실행하면 postgresql 이미지 와 artifactory 이미지를 pulling 하여 자동으로 받아오고, 실행을 시작한다. up 뒤 -d 옵션을 주는 것으로 사용자가 종료하지 않는 이상 이 컨테이너는 백그라운드에서 계속 구동될 것이다.

이제, 브라우저 등으로 http://localhost 에 접속하면 아래와 같은 창이 나올 것이다.

사진에선 나오지 않아 설명하기 어려우나 처음에 들어가면 초기 설정 팝업이 뜨면서 비밀번호 설정 – 프록시 설정 – 사용 저장소 설정 순서로 나오게 되는데, 프록시 설정은 넘겨도 무방하며, 사용 저장소는 ‘Gradle’ 와 ‘Maven’ 을 선택한다.

여기까지 하면 Gradle 저장소에 대한 구축은 모두 된 것이며, 이제 스튜디오로 넘어와 라이브러리를 업로드하면 된다.

라이브러리 업로드

라이브러리를 업로드하는 것은 다소 어렵지 않게 구성이 가능하다. 업로드 플러그인을 추가하고, 정보를 기재해 명령어를 실행하는 것 뿐이다.

업로드 플러그인 추가

루트 프로젝트의 build.gradle 에 아래 classpath 를 추가한다.

//Check for the latest version here: http://plugins.gradle.org/plugin/com.jfrog.artifactory
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4+"

라이브러리 프로젝트의 build.gradle 에 플러그인을 적용한다.

apply plugin: "com.jfrog.artifactory"
apply plugin: 'maven-publish'

프로젝트 정보 기재

플러그인을 정의한 곳 밑에 변수를 추가한다.

def groupId = "com.github.windsekirun"
def version = "1.0.0"
def artifectId = "AwesomeLibraryForDebug"

def Properties properties = new Properties()
properties.load(project.rootProject.file("local.properties").newDataInputStream())

groupId, version, artifectId 는 각각 그룹, 버전, 아키펙트 이름을 나타낸다. gradle 의존성 문구의 규칙 또한 그룹:아키펙트 이름:버전 이므로 위 정보를 가지고 올리면 com.github.windsekirun:AwesomeLibraryForDebug:1.0.0 이 된다.

그 다음 줄의 Properties 의 경우에 저장소에 올리기 위해 아이디 / 비밀번호를 적어야 하는데, 개인 정보가 코드에 올라가야 되지 않게 해야 하므로 local.properties 에서 불러오도록 코드를 추가하였다. 이와 관련된 자세한 사항은 Insert resValue in gradle file 글에서 볼 수 있다.

플러그인 설정

위에서 프로젝트마다 별도로 설정해야 하는 정보는 모두 기재했으므로, 아래부터는 공통 정보이다.

publishing {
    publications {
        aar(MavenPublication) {
            groupId packageName
            version = libraryVersion
            artifactId projectName

            // Tell maven to prepare the generated "*.aar" file for publishing
            artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
        }
    }
}

artifactory {
    contextUrl = 'ARTIFACTORY URL'
    publish {
        repository {
            // The Artifactory repository key to publish to
            repoKey = 'libs-release-local'

            // personal information will locate at local.properties
            username = properties.getProperty("artifactory.username", "")
            password = properties.getProperty("artifactory.password", "")
        }
        defaults {
            // Tell the Artifactory Plugin which artifacts should be published to Artifactory.
            publications('aar')
            publishArtifacts = true

            // Publish generated POM files to Artifactory (true by default)
            publishPom = true
        }
    }
}

위 코드를 dependencies 문단 밑 (즉 맨 하단)에 넣어주면 된다.

명령어 실행

./gradlew assembleRelease artifactoryPublish

모든 작업이 완료되면, 위 커맨드를 터미널에 실행함으로서 Gradle 저장소에 올라가게 된다.

올라간 정보는 Artifactory 페이지 내 Artifacts 부분에서 찾아볼 수 있다.

위 사진과 같이 aar 와 pom 파일이 같이 올라가고, aar 파일을 클릭했을 때 Dependency Declaration 부분에 정상적으로 나오면 성공이다.

다른 프로젝트에서 불러오기

다른 프로젝트에서 불러오기 위해서는, 모듈의 build.gradle 에 아래 코드를 삽입하면 된다.

repositories {
    maven { url "ARTIFACTORY_URL/list/libs-release-local/" }
}

dependencies {
    implementation "com.github.windsekirun:AwesomeLibraryForDebug:1.0.0"
}

참고로 여기서 예제로 들은 AwesomeLibraryForDebug 는 존재하지 않으므로 시도하지 않아도 된다.

Insert resValue in gradle file

도입

소셜 로그인 라이브러리 (https://github.com/WindSekirun/SocialLogin) 를 업데이트 하면서, 샘플 앱을 구현할 필요가 있었는데 흔히 api 키는 비밀로 유지되어야 할 사항으로 여겨진다.

이를 적당히 우회할 수 있는 방법을 찾아보다가 local.properties 에 적는 형식을 생각해냈고, 이를 사용해서 String.xml 에 새 값을 추가하는 것을 기록해두려 한다.

local.properties에 작성하기

흔히 sdk, ndk 가 설치된 프로젝트의 경우 local.properties 는 다음과 같다.

## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Tue Jan 23 23:17:58 KST 2018
ndk.dir=/Users/Pyxis/Library/Android/sdk/ndk-bundle
sdk.dir=/Users/Pyxis/Library/Android/sdk

이 파일의 마지막 줄에 사용할키=사용할값 순서로 적는다.

default.kakao.api=some_value

build.gradle에서 불러오기

android 블록이 나오기 전에 properties 변수를 불러온다.

def Properties properties = new Properties()
properties.load(project.rootProject.file("local.properties").newDataInputStream())

위 코드를 작성하면 local.properties 에서 속성을 불러올 준비가 다 되었다.
이제 불러오는 코드를 작성하면 되는데, 보통 아래 메서드를 통해 값을 가져온다.
properties.getProperty("default.kakao.api", "")
첫 번째 파라미터는 키, 두 번째 값은 기본값이다.

String.xml에 값 넣기

res 값을 넣기 위해서는 resValue 메서드를 사용한다. 이 메서드는 3개의 파라미터를 가지고 있는데, type 와 name, value 총 3개이다.
name, value 는 이름에서 알 수 있듯이 해당 리소스의 이름, 값이고 type 는 해당 리소스의 속성이다. 흔히 string, color 등이다.
여기에서는 buildType 안에 두는 것이 아닌 defaultConfig 안에 두는데, product flavors 에도 같이 활용이 가능하다.

defaultConfig {
    applicationId "com.github.windsekirun.sociallogintest"
    minSdkVersion 19
    targetSdkVersion 27
    versionCode 1
    versionName "1.0"
    resValue "string", "kakao_api_key", properties.getProperty("default.kakao.api", "")
}

이제 build gradle sync 를 하면 @string/kakao_api_key 를 사용할 수 있게 된다.


추가 (2018. 07. 12)

해당 방법을 사용하고 CircleCI 로 커밋하면 오류가 나는데, https://blog.uzuki.live/resolve-local-properties-in-circleci/ 이 글을 참고하면 된다.