最近、MavenのRepositoryとしてSonatype Nexus 3を使っているのですが、2の頃と違ってリポジトリの構成がファイルシステム
そのものではなくて、バイナリな感じになりましたね?
これにより、Maven Centralに置いていないようなライブラリは、Nexusに個別にdeploy-fileする必要がありました。
Sonatype Nexus 3で、Third PartyのMavenアーティファクトをアップロードする(+リポジトリについて少し) - CLOVER
これはこれで仕方がなさそうですが、ライブラリが大量にあるとちょっと困ります。これをなんとかしようかなぁと。
目標とゴール感
要するに、Nexusに突っ込みたいライブラリを、依存ライブラリ含めて一式突っ込みたいわけです。
ですが、deploy-fileだと個別にファイルを指定する必要があります。とても面倒なうえに、そもそも依存関係が
わかりません。
で、どうしようかなぁと。
dependency:treeの結果を使おうとも思ったのですが、これだとpackagingがpomのものが入りません。それで困るの?
と聞かれれば、困ります。BOMや親pomが解決できませんからね。
※実際、そのパターンも作ってpomがどうにもならないことに気づいてやめました
とすると、ローカルリポジトリをごそっとアップロードできる方法を考えた方が良さそうです。
やったこと
$HOME/.m2/repositoryにあるファイルは、deploy:deploy-fileでは直接デプロイできないので、まずは依存ライブラリを別のローカルリポジトリに
集める方法を考えます。
「-Dmaven.repo.local」を使ってごまかしましょう。
ソースコードの収集。
$ mvn dependency:sources -Dmaven.repo.local=localrepos
Javadocの収集。
$ mvn dependency:resolve -Dclassifier=javadoc -Dmaven.repo.local=localrepos
go-offline。
$ mvn dependency:go-offline -Dclassifier=javadoc -Dmaven.repo.local=localrepos
あとは、このローカルリポジトリからひたすらデプロイしていくスクリプトを書きます。Groovyですが。
deploy-local-repository.groovy
@Grab('org.apache.maven:maven-model:3.5.0') import org.apache.maven.model.io.xpp3.MavenXpp3Reader def localRepositoryDir = args[0] def repositoryId = 'my-maven-hosted-repo' def repositoryUrl = 'http://localhost:8081/repository/my-maven-hosted-repo/' def deployFailedArtifacts = [] def select = { artifactId, groupId, version, packaging -> true } new File(localRepositoryDir).eachFileRecurse { file -> if (file.directory) { return } if (!file.name.endsWith('pom')) { return } def reader = new MavenXpp3Reader() file.withReader('UTF-8') { r -> def model = reader.read(r) def parent = model.parent def artifactId = model.artifactId def groupId = model.groupId ?: parent.groupId def version = model.version ?: parent.version def packaging = model.packaging if (!select(artifactId, groupId, version, packaging)) { return } def artifactBasePath = file.parent def artifactFilePath = null def javadocFilePath = null def sourcesFilePath = null new File(artifactBasePath).eachFile { f -> if (f.name.endsWith('-javadoc.jar')) { javadocFilePath = f.path } else if (f.name.endsWith('-sources.jar')) { sourcesFilePath = f.path } else if (f.name.endsWith(packaging)) { artifactFilePath = f.path } } if (artifactFilePath == null) { artifactFilePath = file.path } def command = [ 'mvn deploy:deploy-file', "-Dfile=${artifactFilePath}", "-DrepositoryId=${repositoryId}", "-Durl=${repositoryUrl}", "-DpomFile=${artifactBasePath}/${artifactId}-${version}.pom" ] if (javadocFilePath && new File(javadocFilePath).exists()) { command << "-Djavadoc=${javadocFilePath}" } if (sourcesFilePath && new File(sourcesFilePath).exists()) { command << "-Dsources=${sourcesFilePath}" } def process = command.join(' ').execute() println('=================================================================') println("command: ${command.join(' ')}") println("Result:") println(process.text) try { def result = process.waitFor() if (result != 0) { deployFailedArtifacts << "${groupId}:${artifactId}:${version}" } } finally { process.destroy() } } } if (deployFailedArtifacts) { println("Deploy Failed Artifacts: ${deployFailedArtifacts.size()}") println("===== detail =====") deployFailedArtifacts.each { println(it) } }
指定されたローカルリポジトリにあるpomから、アーティファクトの情報を取り出し、実際にローカルリポジトリに置かれているファイルと
合わせてdeploy:deploy-file向けのコマンドを作り出します。
こんな感じで使います。
$ groovy deploy-local-repository.groovy localrepos
デプロイ先のリポジトリは、先頭に定義してあるので適当に変えてください。
def repositoryId = 'my-maven-hosted-repo' def repositoryUrl = 'http://localhost:8081/repository/my-maven-hosted-repo/'
new File(localRepositoryDir).eachFileRecurse { file ->
pomからアーティファクトの情報を作り出します。
def reader = new MavenXpp3Reader() file.withReader('UTF-8') { r -> def model = reader.read(r) def parent = model.parent def artifactId = model.artifactId def groupId = model.groupId ?: parent.groupId def version = model.version ?: parent.version def packaging = model.packaging
親のgroupIdやversionを引き継いでいる場合は、pomの解析結果がnullとなってしまうので、その場合は親の情報で補完するようにしています。
あとは、同じディレクトリ内にあるファイル群から、Javadocやソースコード、アーティファクトそのものを探します。
def artifactBasePath = file.parent def artifactFilePath = null def javadocFilePath = null def sourcesFilePath = null new File(artifactBasePath).eachFile { f -> if (f.name.endsWith('-javadoc.jar')) { javadocFilePath = f.path } else if (f.name.endsWith('-sources.jar')) { sourcesFilePath = f.path } else if (f.name.endsWith(packaging)) { artifactFilePath = f.path } } if (artifactFilePath == null) { artifactFilePath = file.path }
最後に、これらをつなげて「mvn deploy:deploy-file」にして実行。
def command = [ 'mvn deploy:deploy-file', "-Dfile=${artifactFilePath}", "-DrepositoryId=${repositoryId}", "-Durl=${repositoryUrl}", "-DpomFile=${artifactBasePath}/${artifactId}-${version}.pom" ] if (javadocFilePath && new File(javadocFilePath).exists()) { command << "-Djavadoc=${javadocFilePath}" } if (sourcesFilePath && new File(sourcesFilePath).exists()) { command << "-Dsources=${sourcesFilePath}" } def process = command.join(' ').execute()
デプロイに失敗したアーティファクトは、最後にまとめて出力するようにしています。
この実装だと、ローカルリポジトリにあるすべてのpomを対象にしてしまうので、それなりに時間がかかります。
対象を絞りたい場合は、フィルタリングして対象を絞るとよいでしょう。今回は、テンプレート的にこのように作りました。
def select = { artifactId, groupId, version, packaging -> true }
この関数がtrueを返すと、デプロイ対象となります。
if (!select(artifactId, groupId, version, packaging)) { return }
ローカルリポジトリにJARがダウンロードできていなければ失敗しますし、classfierなどすべての組み合わせで動く保証などはありませんが、
とりあえずこんな感じかなぁと。
なにかおかしいところがあったら、ちょこちょこと修正していくと思います。