Думаю, что у многих возникала необходимость повторно использовать отдельные модули своего Android приложения в другом проекте. Для решения этой задачи есть простые пути, например, публично разместить Android библиотеку с помощью JitPack. Решение отличное, но если нужно разместить библиотеку приватно, то придется заплатить, а тариф чаще всего будет зависеть от количества размещаемых артефактов. Данное решение подойдет далеко не всем.

Ниже я предложу простой вариант решения данной проблемы с использованием Sonatype Nexus Repository OSS.



Введение


Статья не претендует на полноту и отражает лишь базовое решение. В итоге в вашем распоряжении будет установленное ПО для управления артефактами на стороне какого-либо хоста, скрипт для публикации Android библиотеки, возможность реализовать зависимости в своём приложении из локального maven репозитория. Возможно, публикации подобного рода уже есть на Хабре, но понятного туториала для Android разработчика я не нашел, что и побудило меня поделиться с вами информацией. Буду рад, если она окажется действительно полезной в работе.

Шаг 1. Подготовка


Для начала необходимо определиться с тем куда устанавливать Nexus. Логично, если это будет сервер, к которому у вас всегда будет доступ при разработке. В данной статье мы будем устанавливать Nexus на тот же хост, на котором ведем разработку. Отличия при установке на сервер будут лишь в ip адресе, по которому мы будем обращаться к репозиториям.

Перед установкой Nexus, нужно скачать и установить JRE. Подойдёт только Oracle JRE и только версии 1.8 (пробовал на 10, но Nexus ругался), это явно указано в документации. Не забудьте добавить в PATH путь к исполняемым файлам JRE.

Далее нужно скачать Nexus с официального сайта. На момент написания статьи это версия 3.12

Шаг 2. Установка и настройка Nexus


Скачанный архив достаточно распаковать там, где вам удобно. После чего можно запускать программу и начинать работать. Для долгосрочного и удобного использования нужно зарегистрировать Nexus как службу и запускать её при старте ОС. Как это сделать — достаточно понятно описано в документации. В нашем же случае достаточно перейти в папку "nexus-3.12/bin/" и запустить из командной строки файл nexus.

В случае c Windows:

nexus.exe /run

В случае с Unix:

./nexus run

В результате вы должны увидеть оповещение об успешном старте, выглядит оно примерно так:

Image


Пришло время проверить работоспособность Nexus. Для этого достаточно перейти по ссылке http://localhost:8081. Как вы понимаете, в случае с сервером вместо "localhost" нужно указать ip адрес вашего сервера, а Nexus для работы использует порт 8081. Вы увидите такую страницу:

Image


Первым делом нужно авторизоваться, жмём "Sign in" справа вверху и вводим логин (admin) и пароль (admin123) администратора. После этого появляется иконка шестеренки, по нажатию на которую мы попадём в настройки.

Image


На экране настроек вы увидите ссылки на Blob Stores и Repositories. Blob Stores — это хранилища для данных ваших репозиториев. В настройках каждого репозитория можно будет указать конкретный Blob Store для хранения информации. Нам будет достаточно одного, созданного по умолчанию. Repositories — это интересующие нас репозитории, в которых будут храниться библиотеки.

Но перед тем, как перейти к описанию репозиториев, стоит сделать основные настройки для пользователей. Перейдем в раздел "Roles".

Image


Помимо роли администратора и анонимного пользователя я предлагаю выделить роли типа "Downloader" — для загрузки зависимостей в проекты, "Contributor" — для отправки библиотек в Nexus.

Жмем «Create role», затем выбираем «Nexus role», вводим данные для «Downloader» как на скрине ниже:

Image


О том, какие привилегии за что отвечают — подробно описано в документации. Нам нужно выбрать привилегию с флагом "repository-view" (отвечает за работу с данными именно репозиториев), привилегия должна также давать право на чтение данных репозитория, это флаг "read", и так как в Android мы работаем с maven репозиториями, то стоит остановиться на флаге "maven2", чтобы не давать пользователю работать с другими типами репозиториев. Жмём "Create role".

По аналогии создаем роль "Contributor". Разница лишь в том, что право чтения унаследуем от роли "Downloader" (настройка в нижней части страницы), а руками дадим привилегии на редактирование, добавление артефактов в репозитории, а также на просмотр данных репозиториев через web интерфейс.

Image


Далее создадим пользователей, которым назначим настроенные роли. Заходим в раздел "Users" (в левой части окна) и нажимаем "Create local user". Для проверки можно задать пароли аналогичные стандартному паролю админа, то есть "downloader123" и "contributor123". Образец заполнения на скрине:

Image


Так как наша цель — приватная работа, то стоит отключить доступ к данным репозиториев для анонимных пользователей, для этого заходим в пользователя "anonimous" и меняем его статус с "Active" на "Disabled". В этом случае нельзя будет анонимно получить зависимости в Android проекте, а только с указанием определенного пользователя и пароля, но скачивать данные через web интерфейс всё ещё будет возможно.

Для того, чтобы запретить просматривать содержимое репозиториев через web интерфейс анонимным пользователям — перейдём в раздел "Anonimous" и снимем флаг с опции "Allow anonymous users to access the server". Не забудьте сохранить изменения.

Остался последний этап — настройка репозиториев. Переходим в раздел "Repositories" и видим там созданные по умолчанию репозитории. Среди них есть репозитории "maven2", если присмотреться, то можно увидеть разные типы: «proxy», «group», «hosted». «Proxy» репозиторий просто пробрасывает при обращении к себе пользователя на другой репозиторий, в случае с преднастроенным "maven-central" это отсылка на https://repo1.maven.org/maven2/, «group» может включать в себя несколько репозиториев, а «hosted» — это уже конкретный репозиторий, хранящийся в Nexus. Они-то нам сейчас и пригодятся.

Создадим репозитории для «Release» и «Snapshot» версий артефактов. Жмем "Create repository", выбираем "maven2 (hosted)". Первый репозиторий назовём «android» и просто сохраним не меняя настройки:

Image


Второй репозиторий назовём «android_snapshot», поменяем "Version policy" на «Spapshot» и "Deployment policy" на «Allow redeploy».

Image


Если не понятно на что это влияет — можно прочитать здесь. Фактически для репозитория «android» будет недоступна возможность загрузить повторно одну и ту же версию библиотеки, это привычное поведение при общении, например, с maven-central. Для репозитория «android_snapshot» наименование версии обязательно должно будет оканчиваться на «SNAPSHOT» и будет доступна загрузка одной и той же версии повторно.

Также обратите внимание на то, что в списке репозиториев есть колонка «URL» с кнопками «Copy», эти ссылки понадобятся нам в дальнейшем для доступа к репозиториям из проектов.

Поздравляю! Базовая настройка Nexus сделана, пора приступить к созданию и публикации библиотеки.

Шаг 3. Создание и публикация Android библиотеки


Создаём Android проект, назовём его «TestLibrary». Далее в «Project View» нажимаем на корень проекта правой кнопкой мыши выбираем «New» и «Module». В открывшемся окне выбираем «Android library» и называем библиотеку «HelloLibrary». В итоге вы увидите рядом с модулем «app» модуль нашей новой библиотеки.

Image


Добавим в библиотеку класс с функцией приветствия:

package com.example.nexus.hellolibrary

fun helloFromOurLibrary() {
    println("### Hello World! This is our new Android library!")
}

Библиотека готова, пора публиковать. В «Project view» в модуль «hellolibrary» добавляем файл и называем его «publisher.gradle». В нём будет храниться скрипт для публикации библиотеки.

// Подключаем плагин для публикации
apply plugin: 'maven-publish'

ext {
    // URL для Release репозитория
    repositoryReleaseUrl = "http://localhost:8081/repository/android/"
    // URL для Snapshot репозитория
    repositorySnapshotUrl = "http://localhost:8081/repository/android_snapshot/"

    // Логин пользователя, от которого будет производиться загрузка
    contributorUserName = "contributor"
    // Пароль пользователя, от которого будет производиться загрузка
    contributorPassword = "contributor123"

    // Группа для артефакта
    libraryGroupId = "com.example.nexus"
    // Имя артефакта
    libraryArtifact = "hellolibrary"
    // Текущая версия
    libraryVersion = "0.0.1"
}

// Публикуем
publishing {

    // В список репозиториев
    repositories {
        // Подключаем наш репозиторий
        maven {
            // Указываем данные для авторизации
            credentials {
                username contributorUserName
                password contributorPassword
            }
            // И ссылку на репозиторий в зависимости от того Snapshot это версия или нет
            url libraryVersion.endsWith("SNAPSHOT") ? repositorySnapshotUrl : repositoryReleaseUrl
        }
    }

    // Настраиваем задание публикации для maven-publish плагина
    publications {
        // AndroidLibrary - это просто имя, на основании которого 
        // сформируется задание, оно может быть любым
        AndroidLibrary(MavenPublication) {

            // Настраиваем параметры публикации артефакта
            groupId libraryGroupId
            artifactId libraryArtifact
            version libraryVersion

            // Указываем универсальный путь к артефакту
            artifact "$buildDir/outputs/aar/${project.getName()}-release.aar"

            pom {
                // Добавляем все зависимости первого уровня в публикацию для того,
                // чтобы избежать ClassNotFoundException при использовании
                // библиотеки.
                withXml {
                    def dependencies = asNode().appendNode("dependencies")
                    configurations.getByName("releaseCompileClasspath")
                            .getResolvedConfiguration()
                            .getFirstLevelModuleDependencies()
                            .each {
                        def dependency = dependencies.appendNode("dependency")
                        dependency.appendNode("groupId", it.moduleGroup)
                        dependency.appendNode("artifactId", it.moduleName)
                        dependency.appendNode("version", it.moduleVersion)
                    }
                } // withXml
            } // pom
        } // AndroidLibrary
    } // publications
} //publishing

Далее переходим в «build.gradle» нашей библиотеки, применяем к нему наш publisher и очищаем от лишних зависимостей:

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

// Подключаем publisher
apply from: "publisher.gradle"

android {
    compileSdkVersion 28
    defaultConfig {
        minSdkVersion 18
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

repositories {
    mavenCentral()
}

После чего переходим в список gradle заданий и выполняем "assemble" для нашей библиотеки, это необходимо делать перед публикацией для того, чтобы собрать aar файл библиотеки.

Image


Теперь можем публиковать, просто выполнив задание "publishAndroidLibraryPublicationToMavenRepository".

Библиотека опубликована, поздравляю! Проверим результат в web интерфейсе нашего Nexus. Переходим в пункт «Browse» и выбираем репозиторий «android». Увидеть вы должны следующее:

Image


Шаг 4. Использование зависимостей из Nexus


Для теста предлагаю создать ещё один пустой Android проект.

Для начала подключим репозиторий к проекту, просто добавив данные в «build.gradle» файл уровня проекта:

buildscript {
    ext.kotlin_version = '1.2.50'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.3'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        google()
        jcenter()

        // Подключаем локальный репозиторий
        maven {
            credentials {
                username "downloader"
                password "downloader123"
            }
            url "http://localhost:8081/repository/android"
        }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

А теперь как и с любой другой библиотекой получим зависимость на нашу «hellolibrary» через implementation в «build.gradle» файле уровня «app».

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.nexus.fff"
        minSdkVersion 18
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:28.0.0-alpha3'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    // Получаем зависимость
    implementation "com.example.nexus:hellolibrary:0.0.1"
}

Путь к зависимости прописывается как groupId:artifactId:version.

Теперь вы можете вызвать функцию helloFromOurLibrary() в своём Android проекте и протестировать работоспособность всей схемы. Поздравляю!

Резюме


Теперь у вас есть возможность повторно использовать приватные Android библиотеки в проектах. Жду замечаний!

Надеюсь вы не зря потратили своё время, спасибо!

Комментарии (9)


  1. ser-mk
    12.07.2018 00:17

    Сказать честно слишком много действий для повторного использования кода, ну прям чересчур)
    Для гита можно использовать, субмодули, а саму библиотеку выделить в отдельный репозиторий, пусть даже локальный.


    1. tehreh1uneh Автор
      12.07.2018 08:20

      Не работал с подмодулями, поэтому внятно ответить не смогу пока не разберусь. При использовании подмодулей возможность версионирования библиотеки присутствует? Буду благодарен за ссылки на статьи по этой теме, кроме документации.


      1. ser-mk
        12.07.2018 13:32

        об субмодулях кто только не писал)
        habr.com/post/60860
        habr.com/post/75964
        О каком версионировании вы говорите? Через гит или gradle?


        1. tehreh1uneh Автор
          12.07.2018 16:47

          В maven репозиторий можно выгружать библиотеки под разными версиями и загружать в свой проект тоже любую версию по усмотрению. Интересует наличие аналогии при использовании подмодулей git. И спасибо за ссылки.


          1. ser-mk
            12.07.2018 17:07

            В гите все аналогично, checkout делаешь на любой тег или ветку и вот нужная версия. все отличие в том что все это делает гит а не система сборки.
            maven в этом плане наверно удобнее будет, все в одном месте через одну систему. Только жаль что под андройд используется gradle по умолчанию.


    1. Alexei38
      12.07.2018 09:25

      Для одного разработчика может и чересчур, но когда работает группа разработки, зависимостей очень много. Да и если есть переиспользование другими приложениями во время сборки. То этот способ просто необходим


  1. Telmah
    12.07.2018 01:04

    maven install умеет складировать библиотеки и метаданные в локальный репозиторий — директорию на диске… в вашем случае по моему тоже должен быть некий способ


    1. tehreh1uneh Автор
      12.07.2018 08:18

      Локально, к сожалению, не выход, так как использовать библиотеки нужно нескольким разработчикам и как с рабочего места, так и, например, из дома. Поэтому и была вся эта морока с Nexus. В итоге поставили на сервер и пока нареканий нет.


  1. Implozia
    12.07.2018 16:39

    Спасибо за статью