diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 83bd178..11c819a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -177,8 +177,8 @@ jobs: echo "result=$VERSION" >>$GITHUB_OUTPUT - name: Build run: | - #export GPG_TTY=$(tty) ./gradlew uberJar + java -jar ./build/libs/mavence.uber.jar --version mv ./build/libs/mavence.uber.jar ./build/libs/mavence.jar env: MAVEN_GPG_KEY: ${{ secrets.MAVEN_GPG_KEY }} diff --git a/README.md b/README.md index 823f5c7..547604b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ This essentially does the same thing as the [Signing](https://docs.gradle.org/current/userguide/signing_plugin.html) and [Nexus](https://github.com/gradle-nexus/publish-plugin) plugins. -
Why not use plugins? +
Why not publish with plugins? + - Building locally - Publishing somewhere @@ -15,16 +16,13 @@ the [Signing](https://docs.gradle.org/current/userguide/signing_plugin.html) and These tasks are almost unrelated. By placing publishing logic in a build script, you make the foundation of the -project complex, big and ugly. - -However, we still use some Gradle plugins. The `maven-publish` creates, i.e. -builds a local copy of the Maven package. +project complex and ugly.
-## Install and run +# Install and run -### Command line +## Command line ``` wget https://github.com/rtmigo/mavence/releases/latest/download/mavence.jar @@ -36,8 +34,7 @@ Run: java -jar mavence.jar ``` - -### Manually +## Manually Just get the latest [mavence.jar](https://github.com/rtmigo/mavence/releases/latest/download/mavence.jar) @@ -49,23 +46,39 @@ Run: java -jar ~/Downloads/mavence.jar ``` - - -## Setting the environment +# Setting the environment Before publishing, you will need to set the following four environment variables: | variable | wtf | |----------------------|-----------------------------------------------------------| -| `MAVEN_GPG_KEY` | Locally generated private key in ASCII armor | -| `MAVEN_GPG_PASSWORD` | Password protecting the private key | | `SONATYPE_USERNAME` | Username for Sonatype JIRA (optionally replaced by token) | | `SONATYPE_PASSWORD` | Password for Sonatype JIRA (optionally replaced by token) | +| `MAVEN_GPG_KEY` | Locally generated private key in ASCII armor | +| `MAVEN_GPG_PASSWORD` | Password protecting the private key | + +
Where to get Sonatype variables + +[Register](https://getstream.io/blog/publishing-libraries-to-mavencentral-2021/#registering-a-sonatype-account) +on the [Sonatype Jira](https://issues.sonatype.org/secure/Dashboard.jspa) +and chat with bots, 🤪 until they **verify** that you can publish a package. +That gives you `SONATYPE_USERNAME` and `SONATYPE_PASSWORD` you can use for +publishing. + +Additionally, you +can [generate tokens](https://central.sonatype.org/publish/manage-user/#generate-token-on-nxrm-servers) +to use them instead of the username and password (tokens are safer). The +tokens can be placed in the same `SONATYPE_USERNAME` and `SONATYPE_PASSWORD` and +do not require other changes. + +**May the Google be with you.** + +
Where to get GPG variables -### Generate key +## Generate key It gives you `MAVEN_GPG_PASSWORD`. @@ -76,7 +89,7 @@ $ gpg --gen-key `gpg` will interactively prompt you to choose a password for the new key. It is this password that should later be placed in the variable `MAVEN_GPG_PASSWORD`. -### See your private key +## See your private key It gives you `MAVEN_GPG_KEY`. @@ -113,7 +126,7 @@ $ MAVEN_GPG_KEY=$(gpg --export-secret-keys --armor 1292EC426424C9BA0A581EE060C99 $ export MAVEN_GPG_KEY ``` -### Send the public key to [a keyserver](https://unix.stackexchange.com/a/692097) +## Send the public key to [a keyserver](https://unix.stackexchange.com/a/692097) You won't come back to this again, but it will be important for the servers when publishing the package. @@ -136,36 +149,14 @@ $ gpg --keyserver hkps://keys.openpgp.org --send-keys 1292EC426424C9BA0A581EE060 Some servers will just store the key. Some may require prior email verification. Some servers disappear. You have to choose the right one for the moment. - - -
-
Where to get Sonatype variables - -You need -to [register](https://getstream.io/blog/publishing-libraries-to-mavencentral-2021/#registering-a-sonatype-account) -on the [Sonatype Jira](https://issues.sonatype.org/secure/Dashboard.jspa) -and chat with bots, until they **verify** that you can publish a package. -That gives you `SONATYPE_USERNAME` and `SONATYPE_PASSWORD` you can use for -publishing. - -If you have enough nerve for one more step, you -can [generate tokens](https://central.sonatype.org/publish/manage-user/#generate-token-on-nxrm-servers) -. -The tokens also can be placed in the `SONATYPE_USERNAME` and `SONATYPE_PASSWORD` -. In some circumstances it is safer. - - - -
- -## Minimal configuration +# Minimal configuration We're using Gradle configuration to build a Maven package, but not push it Central. Creating in this way seems like a reasonable compromise. -#### build.gradle.kts +### build.gradle.kts ```kotlin plugins { @@ -199,7 +190,10 @@ publishing { } } scm { - connection.set(github.replace("https:", "scm:git:") + ".git") + connection.set( + github.replace( + "https:", + "scm:git:") + ".git") url.set(github) } licenses { @@ -214,13 +208,13 @@ publishing { } ``` -#### settings.gradle.kts +### settings.gradle.kts ```kotlin rootProject.name = "thelib" ``` -### Package name +## Package name The published package will have a version like `my.domain:thelib:0.1.2`. @@ -247,7 +241,7 @@ It is the second part of `my.domain:thelib:0.1.2`, i.e. `thelib`. from [archivesBaseName](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:archivesBaseName) Gradle property. -#### If we release the root project: +### If we release the root project: ``` thelib/ <<< dir name will be the artifact name @@ -264,7 +258,7 @@ The redefine the root project name, add the following: rootProject.name = "newname" ``` -#### If we release a subproject: +### If we release a subproject: ``` myrootproject/ @@ -276,18 +270,23 @@ myrootproject/
-## Keep in mind + + +# Publishing + +
Keep in mind When publishing, the servers may not return meaningful error responses. They often return a generic "500 Internal Server Error" code, or accept the file, but never publish it as a maven package. If publishing a package fails for any reason, the problem may be in the meta -data. Something does not match with something: package name, authors, signatures. +data. Something does not match with something: package name, authors, +signatures. -## Publishing +
-### Publish to Maven Central +## Publish to Maven Central Set environment variables `MAVEN_GPG_KEY`, `MAVEN_GPG_PASSWORD` , `SONATYPE_USERNAME`, `SONATYPE_PASSWORD` and run: @@ -311,6 +310,20 @@ cd /path/to/thelib java -jar mavence.jar local ``` +
stdout + +``` +{ + "group": "my.domain", + "artifact": "thelib", + "version": "0.1.2", + "notation": "my.domain:thelib:0.1.2", + "mavenRepo": "file:///home/doe/.m2/repository" +} +``` + +
+ ### Publish to Staging Set environment variables `MAVEN_GPG_KEY`, `MAVEN_GPG_PASSWORD` @@ -356,7 +369,7 @@ Using this data, you can test the package before it is sent. I usually use Python and [tempground](https://pypi.org/project/tempground/) for such testing. -## License +# License Copyright © 2022 [Artsiom iG](https://github.com/rtmigo). Released under the [ISC License](LICENSE). \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 5a1ec5f..380b74e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "io.github.rtmigo" -version = "0.2.0" // -SNAPSHOT +version = "0.3.0" // -SNAPSHOT repositories { mavenCentral() diff --git a/src/main/kotlin/Build.kt b/src/main/kotlin/Build.kt index 0ca8505..f78d114 100644 --- a/src/main/kotlin/Build.kt +++ b/src/main/kotlin/Build.kt @@ -1,5 +1,5 @@ // DO NOT EDIT. Generated by Gradle task "generateBuildKt" object Build { - const val version = "0.2.0" - const val date = "2022-10-20" + const val version = "0.3.0" + const val date = "2022-10-21" } \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 6fa03a2..70ab487 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -12,12 +12,15 @@ import stages.build.* import stages.sign.* import stages.upload.* import java.nio.file.* +import kotlin.io.path.absolute class Cli : NoOpCliktCommand( name = "mavence", help = "Publishes Gradle projects to Maven Central\n\nSee: https://github.com/rtmigo/mavence#readme" ) { + private val trace by option("--trace", help = "Show full stack traces on errors").flag() + init { versionOption(Build.version) { "$commandName $it (${Build.date})\n" + @@ -26,21 +29,20 @@ class Cli : NoOpCliktCommand( } } - override fun run() = Unit + override fun run() { + currentContext.findOrSetObject { CliConfig(trace = this.trace) } + } } private suspend fun gaa(): GroupArtifact { - val ad = ArtifactDir(Paths.get(".")) + val ad = ArtifactDir(Paths.get(".").absolute()) return GroupArtifact(ad.group(), ad.artifact()) } -open class Local(help: String="Build, publish to $m2str") : CliktCommand(help = help) { - //val groupAndArtifact by argument("") +data class CliConfig(val trace: Boolean) - override fun run() = runBlocking { - //val gw = Paths.get(".").toGradlew() - //gw. - //val ad = ArtifactDir(Paths.get(".")) +open class Local(help: String = "Build, publish to $m2str") : CliktCommand(help = help) { + override fun run() = catchingCommand(this) { cmdLocal(gaa(), isFinal = true) Unit } @@ -58,7 +60,7 @@ open class Stage(help: String = "Build, sign, publish to OSSRH Staging") : "--sonatype-password", envvar = "SONATYPE_PASSWORD").required() - override fun run() = runBlocking { + override fun run() = catchingCommand(this) { cmdSign( cmdLocal(gaa()), key = GpgPrivateKey(gpgKey), @@ -74,7 +76,7 @@ open class Stage(help: String = "Build, sign, publish to OSSRH Staging") : } class Central : Stage(help = "Build, sign, publish to OSSRH Staging, release to Central") { - override fun run() = runBlocking { + override fun run() = catchingCommand(this) { cmdSign( cmdLocal(gaa()), key = GpgPrivateKey(gpgKey), @@ -90,6 +92,21 @@ class Central : Stage(help = "Build, sign, publish to OSSRH Staging, release to } } +fun catchingCommand(cmd: CliktCommand, block: suspend () -> Unit) { + try { + runBlocking { + block() + } + } catch (e: Exception) { + if (cmd.currentContext.findObject()!!.trace) + e.printStackTrace() + else + System.err.println("ERROR: $e") + System.err.println("Run with --trace to see full stack trace.") + } + +} + fun main(args: Array) { Cli() .subcommands(Local(), Stage(), Central()) diff --git a/src/main/kotlin/stages/build/Building.kt b/src/main/kotlin/stages/build/Building.kt index 6a00cbf..f628177 100644 --- a/src/main/kotlin/stages/build/Building.kt +++ b/src/main/kotlin/stages/build/Building.kt @@ -13,7 +13,7 @@ import kotlin.io.path.* suspend fun cmdLocal(ga: GroupArtifact, isFinal: Boolean = false): MavenArtifactDir { eprintHeader("Publishing to $m2str") - val f = Paths.get(".") + val f = Paths.get(".").absolute() .toGradlew().publishAndDetect(ga,null) eprint() @@ -22,7 +22,7 @@ suspend fun cmdLocal(ga: GroupArtifact, isFinal: Boolean = false): MavenArtifact fun debugReplace(fn: String) { // In case Sonatype freezes or gives uninformative errors, we can try to debug by // replacing files one at a time. Normally it does not run - val src = Paths.get("...") + val src = Paths.get("---") println("HACKY REPLACE $fn") val srcFile = src.resolve(fn) val dstFile = mad.path.resolve(fn) diff --git a/src/main/kotlin/stages/build/GradleTypes.kt b/src/main/kotlin/stages/build/GradleTypes.kt index 2c49084..a96d94a 100644 --- a/src/main/kotlin/stages/build/GradleTypes.kt +++ b/src/main/kotlin/stages/build/GradleTypes.kt @@ -6,6 +6,10 @@ import java.nio.file.Path import kotlin.io.path.* +//class AbsPath(src: Path) { +// val path: Path = src.absolute() +//} + @JvmInline value class ProjectRootDir(val path: Path) { init { @@ -16,6 +20,7 @@ value class ProjectRootDir(val path: Path) { @JvmInline value class ArtifactDir(val path: Path) { init { + require(path.isAbsolute) require( path.resolve("build.gradle.kts").exists() || path.resolve("build.gradle").exists()) { diff --git a/src/main/kotlin/stages/sign/GpgTty.kt b/src/main/kotlin/stages/sign/GpgTty.kt index 03b0474..21a6eb5 100644 --- a/src/main/kotlin/stages/sign/GpgTty.kt +++ b/src/main/kotlin/stages/sign/GpgTty.kt @@ -1,3 +1,8 @@ +/** + * SPDX-FileCopyrightText: (c) 2022 Artsiom iG (rtmigo.github.io) + * SPDX-License-Identifier: ISC + **/ + package stages.sign import com.github.pgreze.process.process diff --git a/src/main/kotlin/stages/upload/Bundle.kt b/src/main/kotlin/stages/upload/Bundle.kt index eb280f8..7f25d0f 100644 --- a/src/main/kotlin/stages/upload/Bundle.kt +++ b/src/main/kotlin/stages/upload/Bundle.kt @@ -42,4 +42,3 @@ class SignedBundle(val jar: Path, private val tempDir: CloseableTempDir) : Close tempDir.close() } } - diff --git a/src/main/kotlin/stages/upload/Http.kt b/src/main/kotlin/stages/upload/Http.kt index 6405ffd..41acb64 100644 --- a/src/main/kotlin/stages/upload/Http.kt +++ b/src/main/kotlin/stages/upload/Http.kt @@ -190,6 +190,12 @@ suspend fun HttpClient.waitForUri( private val json = Json { encodeDefaults = true } suspend fun HttpClient.promoteToCentral(uri: StagingUri) { + + // иногда тут бывает + // Error: Exception in thread "main" java.lang.IllegalStateException: Failed to promote: + // 500 Server Error + // {"errors":[{"id":"*","msg":"Unhandled: Staging repository is already transitioning: iogithubrtmigo-1183"}]} + eprintHeader("Promoting Staging to Release") this.post("https://s01.oss.sonatype.org/service/local/staging/bulk/promote") { setBody( diff --git a/src/main/kotlin/tools/Thrower.kt b/src/main/kotlin/tools/Thrower.kt index a17e241..31e30e0 100644 --- a/src/main/kotlin/tools/Thrower.kt +++ b/src/main/kotlin/tools/Thrower.kt @@ -1,25 +1,12 @@ package tools -import java.lang.IllegalStateException - -//inline fun rethrowing( -// error: () -> Throwable, -// call: () -> R, -//): R { -// try { -// return call() -// } catch (e: Throwable) { -// throw error() -// } -//} - inline fun rethrowingState( error: () -> String, call: () -> R, ): R { try { return call() - } catch (e: Throwable) { + } catch (e: Exception) { throw IllegalStateException(error(), e) } } \ No newline at end of file diff --git a/src/test/kotlin/GradleTest.kt b/src/test/kotlin/GradleTest.kt index 8983202..aec6028 100644 --- a/src/test/kotlin/GradleTest.kt +++ b/src/test/kotlin/GradleTest.kt @@ -4,21 +4,22 @@ import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Test import stages.build.* import java.nio.file.* +import kotlin.io.path.absolute class GradleTest { @Test fun testVersion() = runBlocking { - Paths.get(".").toGradlew().getGradleVersion()[0].isDigit().shouldBeTrue() + Paths.get(".").absolute().toGradlew().getGradleVersion()[0].isDigit().shouldBeTrue() } @Test fun testProperties() = runBlocking { - ArtifactDir(Paths.get(".")).gradleVersion()[0].isDigit().shouldBeTrue() + ArtifactDir(Paths.get(".").absolute()).gradleVersion()[0].isDigit().shouldBeTrue() } @Test fun testOwnDependencies() = runBlocking { - val x = ArtifactDir(Paths.get(".")).dependencies("runtimeClasspath") + val x = ArtifactDir(Paths.get(".").absolute()).dependencies("runtimeClasspath") x.filter { it.notation.artifact.string=="kotlin-stdlib-jdk8" }.size.shouldBe(1) } } \ No newline at end of file