From c11b01bfda822a6952bc3509b303ce783480523a Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 12 May 2022 16:19:14 +0200 Subject: [PATCH 1/6] Minor refactos in publish command Paves the ground for subsequent changes --- .../scala/cli/commands/publish/Publish.scala | 258 ++++++++++-------- 1 file changed, 137 insertions(+), 121 deletions(-) diff --git a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala index 05527d81b5..517bfad41d 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala @@ -268,50 +268,21 @@ object Publish extends ScalaCommand[PublishOptions] { } } - val dependencies = build.artifacts.userDependencies - .map(_.toCs(build.artifacts.scalaOpt.map(_.params))) - .sequence - .left.map(CompositeBuildException(_)) - .orExit(logger) - .map { dep0 => - val config = - if (build.scope == Scope.Main) None - else Some(Configuration(build.scope.name)) - (dep0.module.organization, dep0.module.name, dep0.version, config) - } - - val mainClassOpt = build.options.mainClass.orElse { - build.retainedMainClass match { - case Left(_: NoMainClassFoundError) => None - case Left(err) => - logger.debug(s"Error while looking for main class: $err") - None - case Right(cls) => Some(cls) + val mainJar = { + val mainClassOpt = build.options.mainClass.orElse { + build.retainedMainClass match { + case Left(_: NoMainClassFoundError) => None + case Left(err) => + logger.debug(s"Error while looking for main class: $err") + None + case Right(cls) => Some(cls) + } } + val content = Library.libraryJar(build, mainClassOpt) + val dest = workingDir / org / s"$moduleName-$ver.jar" + os.write(dest, content, createFolders = true) + dest } - val mainJarContent = Library.libraryJar(build, mainClassOpt) - val mainJar = workingDir / org / s"$moduleName-$ver.jar" - os.write(mainJar, mainJarContent, createFolders = true) - - val pomContent = Pom.create( - organization = coursier.Organization(org), - moduleName = coursier.ModuleName(moduleName), - version = ver, - packaging = None, - url = publishOptions.url.map(_.value), - name = Some(name), // ? - dependencies = dependencies, - description = publishOptions.description, - license = publishOptions.license.map(_.value).map { l => - Pom.License(l.name, l.url) - }, - scm = publishOptions.versionControl.map { vcs => - Pom.Scm(vcs.url, vcs.connection, vcs.developerConnection) - }, - developers = publishOptions.developers.map { dev => - Pom.Developer(dev.id, dev.name, dev.url, dev.mail) - } - ) val sourceJarOpt = if (publishOptions.sourceJar.getOrElse(true)) { @@ -336,30 +307,70 @@ object Publish extends ScalaCommand[PublishOptions] { else None - val basePath = Path(org.split('.').toSeq ++ Seq(moduleName, ver)) + val pomContent = { + + val dependencies = build.artifacts.userDependencies + .map(_.toCs(build.artifacts.scalaOpt.map(_.params))) + .sequence + .left.map(CompositeBuildException(_)) + .orExit(logger) + .map { dep0 => + val config = + if (build.scope == Scope.Main) None + else Some(Configuration(build.scope.name)) + (dep0.module.organization, dep0.module.name, dep0.version, config) + } - val mainEntries = Seq( - (basePath / s"$moduleName-$ver.pom") -> Content.InMemory( - now, - pomContent.getBytes(StandardCharsets.UTF_8) - ), - (basePath / s"$moduleName-$ver.jar") -> Content.File(mainJar.toNIO) - ) + Pom.create( + organization = coursier.Organization(org), + moduleName = coursier.ModuleName(moduleName), + version = ver, + packaging = None, + url = publishOptions.url.map(_.value), + name = Some(name), // ? + dependencies = dependencies, + description = publishOptions.description, + license = publishOptions.license.map(_.value).map { l => + Pom.License(l.name, l.url) + }, + scm = publishOptions.versionControl.map { vcs => + Pom.Scm(vcs.url, vcs.connection, vcs.developerConnection) + }, + developers = publishOptions.developers.map { dev => + Pom.Developer(dev.id, dev.name, dev.url, dev.mail) + } + ) + } - val sourceJarEntries = sourceJarOpt - .map { sourceJar => - (basePath / s"$moduleName-$ver-sources.jar") -> Content.File(sourceJar.toNIO) - } - .toSeq + val fileSet = { - val docJarEntries = docJarOpt - .map { docJar => - (basePath / s"$moduleName-$ver-javadoc.jar") -> Content.File(docJar.toNIO) - } - .toSeq + val basePath = Path(org.split('.').toSeq ++ Seq(moduleName, ver)) - // TODO version listings, … - (FileSet(mainEntries ++ sourceJarEntries ++ docJarEntries), ver) + val mainEntries = Seq( + (basePath / s"$moduleName-$ver.pom") -> Content.InMemory( + now, + pomContent.getBytes(StandardCharsets.UTF_8) + ), + (basePath / s"$moduleName-$ver.jar") -> Content.File(mainJar.toNIO) + ) + + val sourceJarEntries = sourceJarOpt + .map { sourceJar => + (basePath / s"$moduleName-$ver-sources.jar") -> Content.File(sourceJar.toNIO) + } + .toSeq + + val docJarEntries = docJarOpt + .map { docJar => + (basePath / s"$moduleName-$ver-javadoc.jar") -> Content.File(docJar.toNIO) + } + .toSeq + + // TODO version listings, … + FileSet(mainEntries ++ sourceJarEntries ++ docJarEntries) + } + + (fileSet, ver) } private def doPublish( @@ -376,6 +387,70 @@ object Publish extends ScalaCommand[PublishOptions] { else docBuilds.iterator.map(Some(_)) } + val publishOptions = ConfigMonoid.sum( + builds.map(_.options.notForBloopOptions.publishOptions) + ) + + val ec = builds.head.options.finalCache.ec + + val (repo, hooks) = { + + lazy val es = + Executors.newSingleThreadScheduledExecutor(Util.daemonThreadFactory("publish-retry")) + + lazy val authOpt = { + val userOpt = publishOptions.repoUser + val passwordOpt = publishOptions.repoPassword.map(_.get()) + passwordOpt.map { password => + Authentication(userOpt.fold("")(_.get().value), password.value) + } + } + + def centralRepo(base: String) = { + val repo0 = { + val r = PublishRepository.Sonatype(MavenRepository(base)) + authOpt.fold(r)(r.withAuthentication) + } + val backend = ScalaCliSttpBackend.httpURLConnection(logger) + val api = SonatypeApi(backend, base + "/service/local", authOpt, logger.verbosity) + val hooks0 = Hooks.sonatype( + repo0, + api, + logger.compilerOutputStream, // meh + logger.verbosity, + batch = coursier.paths.Util.useAnsiOutput(), // FIXME Get via logger + es + ) + (repo0, hooks0) + } + + publishOptions.repository match { + case None => + value(Left(new MissingPublishOptionError( + "repository", + "--publish-repository", + "publish.repository" + ))) + case Some("central" | "maven-central" | "mvn-central") => + centralRepo("https://oss.sonatype.org") + case Some("central-s01" | "maven-central-s01" | "mvn-central-s01") => + centralRepo("https://s01.oss.sonatype.org") + case Some(repoStr) => + val repo0 = RepositoryParser.repositoryOpt(repoStr) + .collect { + case m: MavenRepository => + m + } + .getOrElse { + val url = + if (repoStr.contains("://")) repoStr + else os.Path(repoStr, Os.pwd).toNIO.toUri.toASCIIString + MavenRepository(url) + } + (PublishRepository.Simple(repo0), Hooks.dummy) + } + } + val now = Instant.now() val (fileSet0, versionOpt) = value { it @@ -393,13 +468,6 @@ object Publish extends ScalaCommand[PublishOptions] { } } - val ec = builds.head.options.finalCache.ec - lazy val es = - Executors.newSingleThreadScheduledExecutor(Util.daemonThreadFactory("publish-retry")) - - val publishOptions = ConfigMonoid.sum( - builds.map(_.options.notForBloopOptions.publishOptions) - ) val signerOpt = publishOptions.signer.orElse { if (publishOptions.secretKey.isDefined) Some(PSigner.BouncyCastle) else if (publishOptions.gpgSignatureId.isDefined) Some(PSigner.Gpg) @@ -474,58 +542,6 @@ object Publish extends ScalaCommand[PublishOptions] { val finalFileSet = fileSet2.order(ec).unsafeRun()(ec) - lazy val authOpt = { - val userOpt = publishOptions.repoUser - val passwordOpt = publishOptions.repoPassword.map(_.get()) - passwordOpt.map { password => - Authentication(userOpt.fold("")(_.get().value), password.value) - } - } - - def centralRepo(base: String) = { - val repo0 = { - val r = PublishRepository.Sonatype(MavenRepository(base)) - authOpt.fold(r)(r.withAuthentication) - } - val backend = ScalaCliSttpBackend.httpURLConnection(logger) - val api = SonatypeApi(backend, base + "/service/local", authOpt, logger.verbosity) - val hooks0 = Hooks.sonatype( - repo0, - api, - logger.compilerOutputStream, // meh - logger.verbosity, - batch = coursier.paths.Util.useAnsiOutput(), // FIXME Get via logger - es - ) - (repo0, hooks0) - } - - val (repo, hooks) = publishOptions.repository match { - case None => - value(Left(new MissingPublishOptionError( - "repository", - "--publish-repository", - "publish.repository" - ))) - case Some("central" | "maven-central" | "mvn-central") => - centralRepo("https://oss.sonatype.org") - case Some("central-s01" | "maven-central-s01" | "mvn-central-s01") => - centralRepo("https://s01.oss.sonatype.org") - case Some(repoStr) => - val repo0 = RepositoryParser.repositoryOpt(repoStr) - .collect { - case m: MavenRepository => - m - } - .getOrElse { - val url = - if (repoStr.contains("://")) repoStr - else os.Path(repoStr, Os.pwd).toNIO.toUri.toASCIIString - MavenRepository(url) - } - (PublishRepository.Simple(repo0), Hooks.dummy) - } - val isSnapshot0 = versionOpt.exists(_.endsWith("SNAPSHOT")) val hooksData = hooks.beforeUpload(finalFileSet, isSnapshot0).unsafeRun()(ec) From d3af22255282b385063f830e9147743369ce3a66 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 12 May 2022 16:19:27 +0200 Subject: [PATCH 2/6] Add --ivy2-local-like option to publish --- .../cli/commands/publish/PublishOptions.scala | 6 +- .../scala/cli/commands/publish/Ivy.scala | 130 +++++++++++++++ .../scala/cli/commands/publish/Publish.scala | 149 +++++++++++++----- .../scala/build/options/PublishOptions.scala | 1 + website/docs/reference/cli-options.md | 2 + 5 files changed, 247 insertions(+), 41 deletions(-) create mode 100644 modules/cli/src/main/scala/scala/cli/commands/publish/Ivy.scala diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishOptions.scala index 8477909e2c..4726db341c 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishOptions.scala @@ -61,7 +61,11 @@ final case class PublishOptions( @ValueDescription("argument") @ExtraName("G") @ExtraName("gpgOpt") - gpgOption: List[String] = Nil + gpgOption: List[String] = Nil, + + @Group("Publishing") + @Hidden + ivy2LocalLike: Option[Boolean] = None ) // format: on diff --git a/modules/cli/src/main/scala/scala/cli/commands/publish/Ivy.scala b/modules/cli/src/main/scala/scala/cli/commands/publish/Ivy.scala new file mode 100644 index 0000000000..590ae1e0a5 --- /dev/null +++ b/modules/cli/src/main/scala/scala/cli/commands/publish/Ivy.scala @@ -0,0 +1,130 @@ +package scala.cli.commands.publish + +import coursier.core.{Configuration, ModuleName, Organization, Type} +import coursier.publish.Pom +import coursier.publish.Pom.{Developer, License, Scm} + +import java.time.format.DateTimeFormatterBuilder +import java.time.temporal.ChronoField +import java.time.{LocalDateTime, ZoneOffset} + +import scala.collection.mutable +import scala.xml.NodeSeq + +object Ivy { + + private lazy val dateFormatter = new DateTimeFormatterBuilder() + .appendValue(ChronoField.YEAR, 4) + .appendValue(ChronoField.MONTH_OF_YEAR, 2) + .appendValue(ChronoField.DAY_OF_MONTH, 2) + .appendValue(ChronoField.HOUR_OF_DAY, 2) + .appendValue(ChronoField.MINUTE_OF_HOUR, 2) + .appendValue(ChronoField.SECOND_OF_MINUTE, 2) + .toFormatter + + def create( + organization: Organization, + moduleName: ModuleName, + version: String, + packaging: Option[Type] = None, + description: Option[String] = None, + url: Option[String] = None, + name: Option[String] = None, + // TODO Accept full-fledged coursier.Dependency + dependencies: Seq[(Organization, ModuleName, String, Option[Configuration])] = Nil, + license: Option[License] = None, + scm: Option[Scm] = None, + developers: Seq[Developer] = Nil, + time: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC), + hasPom: Boolean = true, + hasDoc: Boolean = true, + hasSources: Boolean = true + ): String = { + + val nodes = new mutable.ListBuffer[NodeSeq] + + nodes += { + val desc = (description, url) match { + case (Some(d), Some(u)) => + Seq({d}) + case (Some(d), None) => + Seq({d}) + case (None, Some(u)) => + Seq() + case (None, None) => + Nil + } + + {desc} + + } + + nodes += { + + val docConf = + if (hasDoc) Seq() + else Nil + val sourcesConf = + if (hasSources) Seq() + else Nil + val pomConf = + if (hasPom) Seq() + else Nil + + + + + + + {docConf} + {sourcesConf} + {pomConf} + + } + + nodes += { + + val docPub = + if (hasDoc) Seq() + else Nil + val sourcesPub = + if (hasSources) Seq() + else Nil + val pomPub = + if (hasPom) Seq() + else Nil + + + + {docPub} + {sourcesPub} + {pomPub} + + } + + nodes += { + val depNodes = dependencies.map { + case (org, name, ver, confOpt) => + val conf = confOpt.map(_.value).getOrElse("compile") + val confSpec = s"$conf->default(compile)" + + } + + {depNodes} + + } + + Pom.print( + + {nodes.result()} + + ) + } + +} diff --git a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala index 517bfad41d..73ecd36e87 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala @@ -18,7 +18,7 @@ import java.io.OutputStreamWriter import java.net.URI import java.nio.charset.StandardCharsets import java.nio.file.{Path => NioPath, Paths} -import java.time.Instant +import java.time.{Instant, LocalDateTime, ZoneOffset} import java.util.concurrent.Executors import java.util.function.Supplier @@ -88,6 +88,7 @@ object Publish extends ScalaCommand[PublishOptions] { scalaVersionSuffix = scalaVersionSuffix.map(_.trim), scalaPlatformSuffix = scalaPlatformSuffix.map(_.trim), repository = sharedPublish.publishRepository.filter(_.trim.nonEmpty), + repositoryIsIvy2LocalLike = ivy2LocalLike, sourceJar = sources, docJar = doc, gpgSignatureId = gpgKey.map(_.trim).filter(_.nonEmpty), @@ -223,6 +224,7 @@ object Publish extends ScalaCommand[PublishOptions] { docBuildOpt: Option[Build.Successful], workingDir: os.Path, now: Instant, + isIvy2LocalLike: Boolean, logger: Logger ): Either[BuildException, (FileSet, String)] = either { @@ -307,42 +309,62 @@ object Publish extends ScalaCommand[PublishOptions] { else None - val pomContent = { - - val dependencies = build.artifacts.userDependencies - .map(_.toCs(build.artifacts.scalaOpt.map(_.params))) - .sequence - .left.map(CompositeBuildException(_)) - .orExit(logger) - .map { dep0 => - val config = - if (build.scope == Scope.Main) None - else Some(Configuration(build.scope.name)) - (dep0.module.organization, dep0.module.name, dep0.version, config) - } - - Pom.create( - organization = coursier.Organization(org), - moduleName = coursier.ModuleName(moduleName), - version = ver, - packaging = None, - url = publishOptions.url.map(_.value), - name = Some(name), // ? - dependencies = dependencies, - description = publishOptions.description, - license = publishOptions.license.map(_.value).map { l => - Pom.License(l.name, l.url) - }, - scm = publishOptions.versionControl.map { vcs => - Pom.Scm(vcs.url, vcs.connection, vcs.developerConnection) - }, - developers = publishOptions.developers.map { dev => - Pom.Developer(dev.id, dev.name, dev.url, dev.mail) - } - ) + val dependencies = build.artifacts.userDependencies + .map(_.toCs(build.artifacts.scalaOpt.map(_.params))) + .sequence + .left.map(CompositeBuildException(_)) + .orExit(logger) + .map { dep0 => + val config = + if (build.scope == Scope.Main) None + else Some(Configuration(build.scope.name)) + (dep0.module.organization, dep0.module.name, dep0.version, config) + } + val url = publishOptions.url.map(_.value) + val license = publishOptions.license.map(_.value).map { l => + Pom.License(l.name, l.url) } + val scm = publishOptions.versionControl.map { vcs => + Pom.Scm(vcs.url, vcs.connection, vcs.developerConnection) + } + val developers = publishOptions.developers.map { dev => + Pom.Developer(dev.id, dev.name, dev.url, dev.mail) + } + val description = publishOptions.description.getOrElse(moduleName) + + val pomContent = Pom.create( + organization = coursier.Organization(org), + moduleName = coursier.ModuleName(moduleName), + version = ver, + packaging = None, + url = url, + name = Some(moduleName), // ? + dependencies = dependencies, + description = Some(description), + license = license, + scm = scm, + developers = developers + ) + + def ivyContent = Ivy.create( + organization = coursier.Organization(org), + moduleName = coursier.ModuleName(moduleName), + version = ver, + packaging = None, + url = url, + name = Some(moduleName), // ? + dependencies = dependencies, + description = Some(description), + license = license, + scm = scm, + developers = developers, + time = LocalDateTime.ofInstant(now, ZoneOffset.UTC), + hasPom = true, + hasDoc = docJarOpt.isDefined, + hasSources = sourceJarOpt.isDefined + ) - val fileSet = { + def mavenFileSet = { val basePath = Path(org.split('.').toSeq ++ Seq(moduleName, ver)) @@ -370,6 +392,40 @@ object Publish extends ScalaCommand[PublishOptions] { FileSet(mainEntries ++ sourceJarEntries ++ docJarEntries) } + def ivy2LocalLikeFileSet = { + + val basePath = Path(Seq(org, moduleName, ver)) + + val mainEntries = Seq( + (basePath / "poms" / s"$moduleName.pom") -> Content.InMemory( + now, + pomContent.getBytes(StandardCharsets.UTF_8) + ), + (basePath / "ivys" / "ivy.xml") -> Content.InMemory( + now, + ivyContent.getBytes(StandardCharsets.UTF_8) + ), + (basePath / "jars" / s"$moduleName.jar") -> Content.File(mainJar.toNIO) + ) + + val sourceJarEntries = sourceJarOpt + .map { sourceJar => + (basePath / "srcs" / s"$moduleName-sources.jar") -> Content.File(sourceJar.toNIO) + } + .toSeq + + val docJarEntries = docJarOpt + .map { docJar => + (basePath / "docs" / s"$moduleName-javadoc.jar") -> Content.File(docJar.toNIO) + } + .toSeq + + FileSet(mainEntries ++ sourceJarEntries ++ docJarEntries) + } + + val fileSet = + if (isIvy2LocalLike) ivy2LocalLikeFileSet else mavenFileSet + (fileSet, ver) } @@ -393,7 +449,7 @@ object Publish extends ScalaCommand[PublishOptions] { val ec = builds.head.options.finalCache.ec - val (repo, hooks) = { + val (repo, hooks, isIvy2LocalLike) = { lazy val es = Executors.newSingleThreadScheduledExecutor(Util.daemonThreadFactory("publish-retry")) @@ -421,7 +477,7 @@ object Publish extends ScalaCommand[PublishOptions] { batch = coursier.paths.Util.useAnsiOutput(), // FIXME Get via logger es ) - (repo0, hooks0) + (repo0, hooks0, false) } publishOptions.repository match { @@ -447,7 +503,11 @@ object Publish extends ScalaCommand[PublishOptions] { else os.Path(repoStr, Os.pwd).toNIO.toUri.toASCIIString MavenRepository(url) } - (PublishRepository.Simple(repo0), Hooks.dummy) + ( + PublishRepository.Simple(repo0), + Hooks.dummy, + publishOptions.repositoryIsIvy2LocalLike.getOrElse(false) + ) } } @@ -458,7 +518,14 @@ object Publish extends ScalaCommand[PublishOptions] { .filter(_._1.scope != Scope.Test) .map { case (build, docBuildOpt) => - buildFileSet(build, docBuildOpt, workingDir, now, logger) + buildFileSet( + build, + docBuildOpt, + workingDir, + now, + isIvy2LocalLike = isIvy2LocalLike, + logger + ) } .sequence0 .map { l => @@ -540,7 +607,9 @@ object Publish extends ScalaCommand[PublishOptions] { ).unsafeRun()(ec) val fileSet2 = fileSet1 ++ checksums - val finalFileSet = fileSet2.order(ec).unsafeRun()(ec) + val finalFileSet = + if (isIvy2LocalLike) fileSet2 + else fileSet2.order(ec).unsafeRun()(ec) val isSnapshot0 = versionOpt.exists(_.endsWith("SNAPSHOT")) val hooksData = hooks.beforeUpload(finalFileSet, isSnapshot0).unsafeRun()(ec) diff --git a/modules/options/src/main/scala/scala/build/options/PublishOptions.scala b/modules/options/src/main/scala/scala/build/options/PublishOptions.scala index 46b9c48939..08ccd74078 100644 --- a/modules/options/src/main/scala/scala/build/options/PublishOptions.scala +++ b/modules/options/src/main/scala/scala/build/options/PublishOptions.scala @@ -17,6 +17,7 @@ final case class PublishOptions( scalaVersionSuffix: Option[String] = None, scalaPlatformSuffix: Option[String] = None, repository: Option[String] = None, + repositoryIsIvy2LocalLike: Option[Boolean] = None, sourceJar: Option[Boolean] = None, docJar: Option[Boolean] = None, gpgSignatureId: Option[String] = None, diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index dfd519ecba..ed34099f36 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -1083,6 +1083,8 @@ Aliases: `-G`, `--gpg-opt` gpg command-line options +#### `--ivy2-local-like` + ## Repl options Available in commands: From cad026133a7489af6ba5f397d15d91b6c8272622 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 12 May 2022 16:19:40 +0200 Subject: [PATCH 3/6] Split publish options more --- .../cli/commands/publish/PublishOptions.scala | 50 +-------- .../publish/PublishParamsOptions.scala | 61 +++++++++++ .../publish/PublishRepositoryOptions.scala | 35 ++++++ .../publish/SharedPublishOptions.scala | 79 ++++++-------- .../scala/cli/commands/publish/Publish.scala | 46 ++++---- website/docs/reference/cli-options.md | 102 ++++++++++-------- website/docs/reference/commands.md | 2 + 7 files changed, 214 insertions(+), 161 deletions(-) create mode 100644 modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishParamsOptions.scala create mode 100644 modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishRepositoryOptions.scala diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishOptions.scala index 4726db341c..cb00587652 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishOptions.scala @@ -3,8 +3,6 @@ package scala.cli.commands.publish import caseapp._ import scala.cli.commands.{CompileCrossOptions, MainClassOptions, SharedOptions, SharedWatchOptions} -import scala.cli.signing.shared.PasswordOption -import scala.cli.signing.util.ArgParsers._ // format: off final case class PublishOptions( @@ -16,53 +14,13 @@ final case class PublishOptions( compileCross: CompileCrossOptions = CompileCrossOptions(), @Recurse mainClass: MainClassOptions = MainClassOptions(), + @Recurse + publishParams: PublishParamsOptions = PublishParamsOptions(), + @Recurse + publishRepo: PublishRepositoryOptions = PublishRepositoryOptions(), @Recurse sharedPublish: SharedPublishOptions = SharedPublishOptions(), - @Group("Publishing") - @HelpMessage("Directory where temporary files for publishing should be written") - @Hidden - workingDir: Option[String] = None, - - @Group("Publishing") - @Hidden - @HelpMessage("Scala version suffix to append to the module name, like \"_2.13\" or \"_3\"") - @ValueDescription("suffix") - scalaVersionSuffix: Option[String] = None, - @Group("Publishing") - @Hidden - @HelpMessage("Scala platform suffix to append to the module name, like \"_sjs1\" or \"_native0.4\"") - @ValueDescription("suffix") - scalaPlatformSuffix: Option[String] = None, - - @Group("Publishing") - @HelpMessage("Whether to build and publish source JARs") - sources: Option[Boolean] = None, - - @Group("Publishing") - @HelpMessage("Whether to build and publish doc JARs") - @ExtraName("scaladoc") - @ExtraName("javadoc") - doc: Option[Boolean] = None, - - @Group("Publishing") - @HelpMessage("ID of the GPG key to use to sign artifacts") - @ValueDescription("key-id") - @ExtraName("K") - gpgKey: Option[String] = None, - - @Group("Publishing") - @HelpMessage("Method to use to sign artifacts") - @ValueDescription("gpg|bc") - signer: Option[String] = None, - - @Group("Publishing") - @HelpMessage("gpg command-line options") - @ValueDescription("argument") - @ExtraName("G") - @ExtraName("gpgOpt") - gpgOption: List[String] = Nil, - @Group("Publishing") @Hidden ivy2LocalLike: Option[Boolean] = None diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishParamsOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishParamsOptions.scala new file mode 100644 index 0000000000..5fa9b66347 --- /dev/null +++ b/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishParamsOptions.scala @@ -0,0 +1,61 @@ +package scala.cli.commands.publish + +import caseapp._ + +import scala.cli.signing.shared.PasswordOption +import scala.cli.signing.util.ArgParsers._ + +// format: off +final case class PublishParamsOptions( + + @Group("Publishing") + @HelpMessage("Organization to publish artifacts under") + organization: Option[String] = None, + @Group("Publishing") + @HelpMessage("Name to publish artifacts as") + name: Option[String] = None, + @Group("Publishing") + @HelpMessage("Final name to publish artifacts as, including Scala version and platform suffixes if any") + moduleName: Option[String] = None, + @Group("Publishing") + @HelpMessage("Version to publish artifacts as") + version: Option[String] = None, + @Group("Publishing") + @HelpMessage("How to compute the version to publish artifacts as") + computeVersion: Option[String] = None, + @Group("Publishing") + @HelpMessage("URL to put in publishing metadata") + url: Option[String] = None, + @Group("Publishing") + @HelpMessage("License to put in publishing metadata") + @ValueDescription("name:URL") + license: Option[String] = None, + @Group("Publishing") + @HelpMessage("VCS information to put in publishing metadata") + vcs: Option[String] = None, + @Group("Publishing") + @HelpMessage("Description to put in publishing metadata") + description: Option[String] = None, + @Group("Publishing") + @HelpMessage("Developer(s) to add in publishing metadata, like \"alex|Alex|https://alex.info\" or \"alex|Alex|https://alex.info|alex@alex.me\"") + @ValueDescription("id|name|URL|email") + developer: List[String] = Nil, + + @Group("Publishing") + @HelpMessage("Secret key to use to sign artifacts with BouncyCastle") + secretKey: Option[PasswordOption] = None, + + @Group("Publishing") + @HelpMessage("Password of secret key to use to sign artifacts with BouncyCastle") + @ValueDescription("value:…") + @ExtraName("secretKeyPass") + secretKeyPassword: Option[PasswordOption] = None + +) +// format: on + +object PublishParamsOptions { + lazy val parser: Parser[PublishParamsOptions] = Parser.derive + implicit lazy val parserAux: Parser.Aux[PublishParamsOptions, parser.D] = parser + implicit lazy val help: Help[PublishParamsOptions] = Help.derive +} diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishRepositoryOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishRepositoryOptions.scala new file mode 100644 index 0000000000..eb2ec13843 --- /dev/null +++ b/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishRepositoryOptions.scala @@ -0,0 +1,35 @@ +package scala.cli.commands.publish + +import caseapp._ + +import scala.cli.signing.shared.PasswordOption +import scala.cli.signing.util.ArgParsers._ + +// format: off +final case class PublishRepositoryOptions( + + @Group("Publishing") + @HelpMessage("Repository to publish to") + @ValueDescription("URL or path") + @ExtraName("R") + @ExtraName("publishRepo") + publishRepository: Option[String] = None, + + @Group("Publishing") + @HelpMessage("User to use with publishing repository") + @ValueDescription("user") + user: Option[PasswordOption] = None, + + @Group("Publishing") + @HelpMessage("Password to use with publishing repository") + @ValueDescription("value:…") + password: Option[PasswordOption] = None + +) +// format: on + +object PublishRepositoryOptions { + lazy val parser: Parser[PublishRepositoryOptions] = Parser.derive + implicit lazy val parserAux: Parser.Aux[PublishRepositoryOptions, parser.D] = parser + implicit lazy val help: Help[PublishRepositoryOptions] = Help.derive +} diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/publish/SharedPublishOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/publish/SharedPublishOptions.scala index 47d2905301..80461899d5 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/publish/SharedPublishOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/publish/SharedPublishOptions.scala @@ -2,71 +2,52 @@ package scala.cli.commands.publish import caseapp._ -import scala.cli.signing.shared.PasswordOption -import scala.cli.signing.util.ArgParsers._ - // format: off final case class SharedPublishOptions( @Group("Publishing") - @HelpMessage("Organization to publish artifacts under") - organization: Option[String] = None, - @Group("Publishing") - @HelpMessage("Name to publish artifacts as") - name: Option[String] = None, - @Group("Publishing") - @HelpMessage("Final name to publish artifacts as, including Scala version and platform suffixes if any") - moduleName: Option[String] = None, - @Group("Publishing") - @HelpMessage("Version to publish artifacts as") - version: Option[String] = None, - @Group("Publishing") - @HelpMessage("How to compute the version to publish artifacts as") - computeVersion: Option[String] = None, - @Group("Publishing") - @HelpMessage("URL to put in publishing metadata") - url: Option[String] = None, - @Group("Publishing") - @HelpMessage("License to put in publishing metadata") - @ValueDescription("name:URL") - license: Option[String] = None, - @Group("Publishing") - @HelpMessage("VCS information to put in publishing metadata") - vcs: Option[String] = None, + @HelpMessage("Directory where temporary files for publishing should be written") + @Hidden + workingDir: Option[String] = None, + @Group("Publishing") - @HelpMessage("Description to put in publishing metadata") - description: Option[String] = None, + @Hidden + @HelpMessage("Scala version suffix to append to the module name, like \"_2.13\" or \"_3\"") + @ValueDescription("suffix") + scalaVersionSuffix: Option[String] = None, @Group("Publishing") - @HelpMessage("Developer(s) to add in publishing metadata, like \"alex|Alex|https://alex.info\" or \"alex|Alex|https://alex.info|alex@alex.me\"") - @ValueDescription("id|name|URL|email") - developer: List[String] = Nil, + @Hidden + @HelpMessage("Scala platform suffix to append to the module name, like \"_sjs1\" or \"_native0.4\"") + @ValueDescription("suffix") + scalaPlatformSuffix: Option[String] = None, @Group("Publishing") - @HelpMessage("Repository to publish to") - @ValueDescription("URL or path") - @ExtraName("R") - @ExtraName("publishRepo") - publishRepository: Option[String] = None, + @HelpMessage("Whether to build and publish source JARs") + sources: Option[Boolean] = None, @Group("Publishing") - @HelpMessage("User to use with publishing repository") - @ValueDescription("user") - user: Option[PasswordOption] = None, + @HelpMessage("Whether to build and publish doc JARs") + @ExtraName("scaladoc") + @ExtraName("javadoc") + doc: Option[Boolean] = None, @Group("Publishing") - @HelpMessage("Password to use with publishing repository") - @ValueDescription("value:…") - password: Option[PasswordOption] = None, + @HelpMessage("ID of the GPG key to use to sign artifacts") + @ValueDescription("key-id") + @ExtraName("K") + gpgKey: Option[String] = None, @Group("Publishing") - @HelpMessage("Secret key to use to sign artifacts with BouncyCastle") - secretKey: Option[PasswordOption] = None, + @HelpMessage("Method to use to sign artifacts") + @ValueDescription("gpg|bc") + signer: Option[String] = None, @Group("Publishing") - @HelpMessage("Password of secret key to use to sign artifacts with BouncyCastle") - @ValueDescription("value:…") - @ExtraName("secretKeyPass") - secretKeyPassword: Option[PasswordOption] = None + @HelpMessage("gpg command-line options") + @ValueDescription("argument") + @ExtraName("G") + @ExtraName("gpgOpt") + gpgOption: List[String] = Nil ) // format: on diff --git a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala index 73ecd36e87..4b3a4157f7 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala @@ -53,58 +53,58 @@ object Publish extends ScalaCommand[PublishOptions] { mainClass = mainClass.mainClass.filter(_.nonEmpty), notForBloopOptions = baseOptions.notForBloopOptions.copy( publishOptions = baseOptions.notForBloopOptions.publishOptions.copy( - organization = sharedPublish.organization.map(_.trim).filter(_.nonEmpty).map( + organization = publishParams.organization.map(_.trim).filter(_.nonEmpty).map( Positioned.commandLine(_) ), - name = sharedPublish.name.map(_.trim).filter(_.nonEmpty).map(Positioned.commandLine(_)), + name = publishParams.name.map(_.trim).filter(_.nonEmpty).map(Positioned.commandLine(_)), moduleName = - sharedPublish.moduleName.map(_.trim).filter(_.nonEmpty).map(Positioned.commandLine(_)), + publishParams.moduleName.map(_.trim).filter(_.nonEmpty).map(Positioned.commandLine(_)), version = - sharedPublish.version.map(_.trim).filter(_.nonEmpty).map(Positioned.commandLine(_)), - url = sharedPublish.url.map(_.trim).filter(_.nonEmpty).map(Positioned.commandLine(_)), + publishParams.version.map(_.trim).filter(_.nonEmpty).map(Positioned.commandLine(_)), + url = publishParams.url.map(_.trim).filter(_.nonEmpty).map(Positioned.commandLine(_)), license = value { - sharedPublish.license + publishParams.license .map(_.trim).filter(_.nonEmpty) .map(Positioned.commandLine(_)) .map(License.parse(_)) .sequence }, versionControl = value { - sharedPublish.vcs + publishParams.vcs .map(_.trim).filter(_.nonEmpty) .map(Positioned.commandLine(_)) .map(Vcs.parse(_)) .sequence }, - description = sharedPublish.description.map(_.trim).filter(_.nonEmpty), + description = publishParams.description.map(_.trim).filter(_.nonEmpty), developers = value { - sharedPublish.developer + publishParams.developer .filter(_.trim.nonEmpty) .map(Positioned.commandLine(_)) .map(Developer.parse(_)) .sequence .left.map(CompositeBuildException(_)) }, - scalaVersionSuffix = scalaVersionSuffix.map(_.trim), - scalaPlatformSuffix = scalaPlatformSuffix.map(_.trim), - repository = sharedPublish.publishRepository.filter(_.trim.nonEmpty), + scalaVersionSuffix = sharedPublish.scalaVersionSuffix.map(_.trim), + scalaPlatformSuffix = sharedPublish.scalaPlatformSuffix.map(_.trim), + repository = publishRepo.publishRepository.filter(_.trim.nonEmpty), repositoryIsIvy2LocalLike = ivy2LocalLike, - sourceJar = sources, - docJar = doc, - gpgSignatureId = gpgKey.map(_.trim).filter(_.nonEmpty), - gpgOptions = gpgOption, - secretKey = sharedPublish.secretKey, - secretKeyPassword = sharedPublish.secretKeyPassword, - repoUser = sharedPublish.user, - repoPassword = sharedPublish.password, + sourceJar = sharedPublish.sources, + docJar = sharedPublish.doc, + gpgSignatureId = sharedPublish.gpgKey.map(_.trim).filter(_.nonEmpty), + gpgOptions = sharedPublish.gpgOption, + secretKey = publishParams.secretKey, + secretKeyPassword = publishParams.secretKeyPassword, + repoUser = publishRepo.user, + repoPassword = publishRepo.password, signer = value { - signer + sharedPublish.signer .map(Positioned.commandLine(_)) .map(PSigner.parse(_)) .sequence }, computeVersion = value { - sharedPublish.computeVersion + publishParams.computeVersion .map(Positioned.commandLine(_)) .map(ComputeVersion.parse(_)) .sequence @@ -129,7 +129,7 @@ object Publish extends ScalaCommand[PublishOptions] { val cross = options.compileCross.cross.getOrElse(false) - lazy val workingDir = options.workingDir + lazy val workingDir = options.sharedPublish.workingDir .filter(_.trim.nonEmpty) .map(os.Path(_, Os.pwd)) .getOrElse { diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index ed34099f36..1065ad992e 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -979,6 +979,54 @@ Available in commands: - [`publish`](./commands.md#publish) + + +#### `--working-dir` + +Directory where temporary files for publishing should be written + +#### `--scala-version-suffix` + +Scala version suffix to append to the module name, like "_2.13" or "_3" + +#### `--scala-platform-suffix` + +Scala platform suffix to append to the module name, like "_sjs1" or "_native0.4" + +#### `--sources` + +Whether to build and publish source JARs + +#### `--doc` + +Aliases: `--scaladoc`, `--javadoc` + +Whether to build and publish doc JARs + +#### `--gpg-key` + +Aliases: `-K` + +ID of the GPG key to use to sign artifacts + +#### `--signer` + +Method to use to sign artifacts + +#### `--gpg-option` + +Aliases: `-G`, `--gpg-opt` + +gpg command-line options + +#### `--ivy2-local-like` + +## Publish params options + +Available in commands: +- [`publish`](./commands.md#publish) + + #### `--organization` @@ -1021,20 +1069,6 @@ Description to put in publishing metadata Developer(s) to add in publishing metadata, like "alex|Alex|https://alex.info" or "alex|Alex|https://alex.info|alex@alex.me" -#### `--publish-repository` - -Aliases: `-R`, `--publish-repo` - -Repository to publish to - -#### `--user` - -User to use with publishing repository - -#### `--password` - -Password to use with publishing repository - #### `--secret-key` Secret key to use to sign artifacts with BouncyCastle @@ -1045,45 +1079,27 @@ Aliases: `--secret-key-pass` Password of secret key to use to sign artifacts with BouncyCastle -#### `--working-dir` - -Directory where temporary files for publishing should be written - -#### `--scala-version-suffix` - -Scala version suffix to append to the module name, like "_2.13" or "_3" - -#### `--scala-platform-suffix` - -Scala platform suffix to append to the module name, like "_sjs1" or "_native0.4" - -#### `--sources` - -Whether to build and publish source JARs - -#### `--doc` +## Publish repository options -Aliases: `--scaladoc`, `--javadoc` - -Whether to build and publish doc JARs +Available in commands: +- [`publish`](./commands.md#publish) -#### `--gpg-key` -Aliases: `-K` + -ID of the GPG key to use to sign artifacts +#### `--publish-repository` -#### `--signer` +Aliases: `-R`, `--publish-repo` -Method to use to sign artifacts +Repository to publish to -#### `--gpg-option` +#### `--user` -Aliases: `-G`, `--gpg-opt` +User to use with publishing repository -gpg command-line options +#### `--password` -#### `--ivy2-local-like` +Password to use with publishing repository ## Repl options diff --git a/website/docs/reference/commands.md b/website/docs/reference/commands.md index 1470d434a8..f8e73a3611 100644 --- a/website/docs/reference/commands.md +++ b/website/docs/reference/commands.md @@ -197,6 +197,8 @@ Accepts options: - [logging](./cli-options.md#logging-options) - [main class](./cli-options.md#main-class-options) - [publish](./cli-options.md#publish-options) +- [publish params](./cli-options.md#publish-params-options) +- [publish repository](./cli-options.md#publish-repository-options) - [Scala.js](./cli-options.md#scalajs-options) - [Scala Native](./cli-options.md#scala-native-options) - [scalac](./cli-options.md#scalac-options) From 5a3cd41a3f0bd1c4ba083c5ea467135090dfbfab Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 12 May 2022 16:19:52 +0200 Subject: [PATCH 4/6] Make Publish expose some of its internal stuff For the upcoming "publish local" command --- .../scala/cli/commands/publish/Publish.scala | 61 ++++++++++++++++--- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala index 4b3a4157f7..4372eeaeaf 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala @@ -24,17 +24,25 @@ import java.util.function.Supplier import scala.build.EitherCps.{either, value} import scala.build.Ops._ +import scala.build._ +import scala.build.compiler.ScalaCompilerMaker import scala.build.errors.{BuildException, CompositeBuildException, NoMainClassFoundError} import scala.build.internal.Util import scala.build.internal.Util.ScalaDependencyOps import scala.build.options.publish.{ComputeVersion, Developer, License, Signer => PSigner, Vcs} import scala.build.options.{BuildOptions, ConfigMonoid, Scope} -import scala.build.{Build, BuildThreads, Builds, Logger, Os, Positioned} import scala.cli.CurrentParams import scala.cli.commands.pgp.PgpExternalCommand +import scala.cli.commands.publish.{PublishParamsOptions, PublishRepositoryOptions} import scala.cli.commands.util.ScalaCliSttpBackend import scala.cli.commands.util.SharedOptionsUtil._ -import scala.cli.commands.{Package => PackageCmd, ScalaCommand, WatchUtil} +import scala.cli.commands.{ + MainClassOptions, + Package => PackageCmd, + ScalaCommand, + SharedOptions, + WatchUtil +} import scala.cli.errors.{FailedToSignFileError, MissingPublishOptionError, UploadError} import scala.cli.packaging.Library import scala.cli.publish.BouncycastleSignerMaker @@ -46,8 +54,14 @@ object Publish extends ScalaCommand[PublishOptions] { override def sharedOptions(options: PublishOptions) = Some(options.shared) - def mkBuildOptions(ops: PublishOptions): Either[BuildException, BuildOptions] = either { - import ops._ + def mkBuildOptions( + shared: SharedOptions, + publishParams: PublishParamsOptions, + sharedPublish: SharedPublishOptions, + publishRepo: PublishRepositoryOptions, + mainClass: MainClassOptions, + ivy2LocalLike: Option[Boolean] + ): Either[BuildException, BuildOptions] = either { val baseOptions = shared.buildOptions(enableJmh = false, jmhVersion = None) baseOptions.copy( mainClass = mainClass.mainClass.filter(_.nonEmpty), @@ -116,13 +130,21 @@ object Publish extends ScalaCommand[PublishOptions] { def run(options: PublishOptions, args: RemainingArgs): Unit = { maybePrintGroupHelp(options) + CurrentParams.verbosity = options.shared.logging.verbosity val inputs = options.shared.inputsOrExit(args) CurrentParams.workspaceOpt = Some(inputs.workspace) - val logger = options.shared.logger - val initialBuildOptions = mkBuildOptions(options).orExit(logger) - val threads = BuildThreads.create() + val logger = options.shared.logger + val initialBuildOptions = mkBuildOptions( + options.shared, + options.publishParams, + options.sharedPublish, + options.publishRepo, + options.mainClass, + options.ivy2LocalLike + ).orExit(logger) + val threads = BuildThreads.create() val compilerMaker = options.shared.compilerMaker(threads) val docCompilerMaker = options.shared.compilerMaker(threads, scaladoc = true) @@ -139,7 +161,30 @@ object Publish extends ScalaCommand[PublishOptions] { ) } - if (options.watch.watch) { + doRun( + inputs, + logger, + initialBuildOptions, + compilerMaker, + docCompilerMaker, + cross, + workingDir, + options.watch.watch + ) + } + + def doRun( + inputs: Inputs, + logger: Logger, + initialBuildOptions: BuildOptions, + compilerMaker: ScalaCompilerMaker, + docCompilerMaker: ScalaCompilerMaker, + cross: Boolean, + workingDir: => os.Path, + watch: Boolean + ): Unit = { + + if (watch) { val watcher = Build.watch( inputs, initialBuildOptions, From 6d484f21b3d5b096d9e6701b434cf47f77477050 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 12 May 2022 16:20:04 +0200 Subject: [PATCH 5/6] Add ivy2-local repository support in publish --- .../publish/SharedPublishOptions.scala | 7 ++++- .../scala/cli/commands/publish/Publish.scala | 27 ++++++++++++++++--- website/docs/reference/cli-options.md | 4 +++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/publish/SharedPublishOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/publish/SharedPublishOptions.scala index 80461899d5..9e75f9bb7e 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/publish/SharedPublishOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/publish/SharedPublishOptions.scala @@ -47,7 +47,12 @@ final case class SharedPublishOptions( @ValueDescription("argument") @ExtraName("G") @ExtraName("gpgOpt") - gpgOption: List[String] = Nil + gpgOption: List[String] = Nil, + + @Group("Publishing") + @HelpMessage("Set Ivy 2 home directory") + @ValueDescription("path") + ivy2Home: Option[String] = None ) // format: on diff --git a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala index 4372eeaeaf..814aae2730 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala @@ -161,6 +161,10 @@ object Publish extends ScalaCommand[PublishOptions] { ) } + val ivy2HomeOpt = options.sharedPublish.ivy2Home + .filter(_.trim.nonEmpty) + .map(os.Path(_, os.pwd)) + doRun( inputs, logger, @@ -169,6 +173,7 @@ object Publish extends ScalaCommand[PublishOptions] { docCompilerMaker, cross, workingDir, + ivy2HomeOpt, options.watch.watch ) } @@ -181,6 +186,7 @@ object Publish extends ScalaCommand[PublishOptions] { docCompilerMaker: ScalaCompilerMaker, cross: Boolean, workingDir: => os.Path, + ivy2HomeOpt: Option[os.Path], watch: Boolean ): Unit = { @@ -197,7 +203,7 @@ object Publish extends ScalaCommand[PublishOptions] { postAction = () => WatchUtil.printWatchMessage() ) { res => res.orReport(logger).foreach { builds => - maybePublish(builds, workingDir, logger, allowExit = false) + maybePublish(builds, workingDir, ivy2HomeOpt, logger, allowExit = false) } } try WatchUtil.waitForCtrlC() @@ -215,7 +221,7 @@ object Publish extends ScalaCommand[PublishOptions] { buildTests = false, partial = None ).orExit(logger) - maybePublish(builds, workingDir, logger, allowExit = true) + maybePublish(builds, workingDir, ivy2HomeOpt, logger, allowExit = true) } } @@ -229,6 +235,7 @@ object Publish extends ScalaCommand[PublishOptions] { private def maybePublish( builds: Builds, workingDir: os.Path, + ivy2HomeOpt: Option[os.Path], logger: Logger, allowExit: Boolean ): Unit = { @@ -250,7 +257,7 @@ object Publish extends ScalaCommand[PublishOptions] { val docBuilds0 = builds.allDoc.collect { case s: Build.Successful => s } - val res = doPublish(builds0, docBuilds0, workingDir, logger) + val res = doPublish(builds0, docBuilds0, workingDir, ivy2HomeOpt, logger) if (allowExit) res.orExit(logger) else @@ -478,6 +485,7 @@ object Publish extends ScalaCommand[PublishOptions] { builds: Seq[Build.Successful], docBuilds: Seq[Build.Successful], workingDir: os.Path, + ivy2HomeOpt: Option[os.Path], logger: Logger ): Either[BuildException, Unit] = either { @@ -525,6 +533,17 @@ object Publish extends ScalaCommand[PublishOptions] { (repo0, hooks0, false) } + def ivy2Local = { + val home = ivy2HomeOpt.getOrElse(os.home / ".ivy2") + val base = home / "local" + // not really a Maven repo… + ( + PublishRepository.Simple(MavenRepository(base.toNIO.toUri.toASCIIString)), + Hooks.dummy, + true + ) + } + publishOptions.repository match { case None => value(Left(new MissingPublishOptionError( @@ -532,6 +551,8 @@ object Publish extends ScalaCommand[PublishOptions] { "--publish-repository", "publish.repository" ))) + case Some("ivy2-local") => + ivy2Local case Some("central" | "maven-central" | "mvn-central") => centralRepo("https://oss.sonatype.org") case Some("central-s01" | "maven-central-s01" | "mvn-central-s01") => diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index 1065ad992e..6a20a27cb9 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -1019,6 +1019,10 @@ Aliases: `-G`, `--gpg-opt` gpg command-line options +#### `--ivy2-home` + +Set Ivy 2 home directory + #### `--ivy2-local-like` ## Publish params options From adf14dc0044473621715dd7076f7bf0396e762df Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 12 May 2022 16:20:16 +0200 Subject: [PATCH 6/6] Add "publish local" command --- .../publish/PublishLocalOptions.scala | 27 +++++++ .../scala/scala/cli/ScalaCliCommands.scala | 3 +- .../scala/cli/commands/publish/Publish.scala | 77 ++++++++++--------- .../cli/commands/publish/PublishLocal.scala | 71 +++++++++++++++++ .../cli/integration/PublishTestsDefault.scala | 62 +++++++++++++++ website/docs/reference/cli-options.md | 19 +++++ website/docs/reference/commands.md | 22 ++++++ 7 files changed, 245 insertions(+), 36 deletions(-) create mode 100644 modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishLocalOptions.scala create mode 100644 modules/cli/src/main/scala/scala/cli/commands/publish/PublishLocal.scala diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishLocalOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishLocalOptions.scala new file mode 100644 index 0000000000..c1b621d521 --- /dev/null +++ b/modules/cli-options/src/main/scala/scala/cli/commands/publish/PublishLocalOptions.scala @@ -0,0 +1,27 @@ +package scala.cli.commands.publish + +import caseapp._ + +import scala.cli.commands.{CompileCrossOptions, MainClassOptions, SharedOptions, SharedWatchOptions} + +// format: off +final case class PublishLocalOptions( + @Recurse + shared: SharedOptions = SharedOptions(), + @Recurse + watch: SharedWatchOptions = SharedWatchOptions(), + @Recurse + compileCross: CompileCrossOptions = CompileCrossOptions(), + @Recurse + mainClass: MainClassOptions = MainClassOptions(), + @Recurse + publishParams: PublishParamsOptions = PublishParamsOptions(), + @Recurse + sharedPublish: SharedPublishOptions = SharedPublishOptions() +) +// format: on + +object PublishLocalOptions { + implicit lazy val parser: Parser[PublishLocalOptions] = Parser.derive + implicit lazy val help: Help[PublishLocalOptions] = Help.derive +} diff --git a/modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala b/modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala index 49597e8b72..345677bc37 100644 --- a/modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala +++ b/modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala @@ -9,7 +9,7 @@ import scala.cli.commands._ import scala.cli.commands.bloop.BloopOutput import scala.cli.commands.github.{SecretCreate, SecretList} import scala.cli.commands.pgp.{PgpCommands, PgpCommandsSubst} -import scala.cli.commands.publish.Publish +import scala.cli.commands.publish.{Publish, PublishLocal} class ScalaCliCommands( val progName: String, @@ -45,6 +45,7 @@ class ScalaCliCommands( Repl, Package, Publish, + PublishLocal, Run, SecretCreate, SecretList, diff --git a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala index 814aae2730..30e4bb30db 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala @@ -153,7 +153,7 @@ object Publish extends ScalaCommand[PublishOptions] { lazy val workingDir = options.sharedPublish.workingDir .filter(_.trim.nonEmpty) - .map(os.Path(_, Os.pwd)) + .map(os.Path(_, os.pwd)) .getOrElse { os.temp.dir( prefix = "scala-cli-publish-", @@ -174,6 +174,7 @@ object Publish extends ScalaCommand[PublishOptions] { cross, workingDir, ivy2HomeOpt, + publishLocal = false, options.watch.watch ) } @@ -187,6 +188,7 @@ object Publish extends ScalaCommand[PublishOptions] { cross: Boolean, workingDir: => os.Path, ivy2HomeOpt: Option[os.Path], + publishLocal: Boolean, watch: Boolean ): Unit = { @@ -203,7 +205,7 @@ object Publish extends ScalaCommand[PublishOptions] { postAction = () => WatchUtil.printWatchMessage() ) { res => res.orReport(logger).foreach { builds => - maybePublish(builds, workingDir, ivy2HomeOpt, logger, allowExit = false) + maybePublish(builds, workingDir, ivy2HomeOpt, publishLocal, logger, allowExit = false) } } try WatchUtil.waitForCtrlC() @@ -221,7 +223,7 @@ object Publish extends ScalaCommand[PublishOptions] { buildTests = false, partial = None ).orExit(logger) - maybePublish(builds, workingDir, ivy2HomeOpt, logger, allowExit = true) + maybePublish(builds, workingDir, ivy2HomeOpt, publishLocal, logger, allowExit = true) } } @@ -236,6 +238,7 @@ object Publish extends ScalaCommand[PublishOptions] { builds: Builds, workingDir: os.Path, ivy2HomeOpt: Option[os.Path], + publishLocal: Boolean, logger: Logger, allowExit: Boolean ): Unit = { @@ -257,7 +260,7 @@ object Publish extends ScalaCommand[PublishOptions] { val docBuilds0 = builds.allDoc.collect { case s: Build.Successful => s } - val res = doPublish(builds0, docBuilds0, workingDir, ivy2HomeOpt, logger) + val res = doPublish(builds0, docBuilds0, workingDir, ivy2HomeOpt, publishLocal, logger) if (allowExit) res.orExit(logger) else @@ -486,6 +489,7 @@ object Publish extends ScalaCommand[PublishOptions] { docBuilds: Seq[Build.Successful], workingDir: os.Path, ivy2HomeOpt: Option[os.Path], + publishLocal: Boolean, logger: Logger ): Either[BuildException, Unit] = either { @@ -544,37 +548,40 @@ object Publish extends ScalaCommand[PublishOptions] { ) } - publishOptions.repository match { - case None => - value(Left(new MissingPublishOptionError( - "repository", - "--publish-repository", - "publish.repository" - ))) - case Some("ivy2-local") => - ivy2Local - case Some("central" | "maven-central" | "mvn-central") => - centralRepo("https://oss.sonatype.org") - case Some("central-s01" | "maven-central-s01" | "mvn-central-s01") => - centralRepo("https://s01.oss.sonatype.org") - case Some(repoStr) => - val repo0 = RepositoryParser.repositoryOpt(repoStr) - .collect { - case m: MavenRepository => - m - } - .getOrElse { - val url = - if (repoStr.contains("://")) repoStr - else os.Path(repoStr, Os.pwd).toNIO.toUri.toASCIIString - MavenRepository(url) - } - ( - PublishRepository.Simple(repo0), - Hooks.dummy, - publishOptions.repositoryIsIvy2LocalLike.getOrElse(false) - ) - } + if (publishLocal) + ivy2Local + else + publishOptions.repository match { + case None => + value(Left(new MissingPublishOptionError( + "repository", + "--publish-repository", + "publish.repository" + ))) + case Some("ivy2-local") => + ivy2Local + case Some("central" | "maven-central" | "mvn-central") => + centralRepo("https://oss.sonatype.org") + case Some("central-s01" | "maven-central-s01" | "mvn-central-s01") => + centralRepo("https://s01.oss.sonatype.org") + case Some(repoStr) => + val repo0 = RepositoryParser.repositoryOpt(repoStr) + .collect { + case m: MavenRepository => + m + } + .getOrElse { + val url = + if (repoStr.contains("://")) repoStr + else os.Path(repoStr, Os.pwd).toNIO.toUri.toASCIIString + MavenRepository(url) + } + ( + PublishRepository.Simple(repo0), + Hooks.dummy, + publishOptions.repositoryIsIvy2LocalLike.getOrElse(false) + ) + } } val now = Instant.now() diff --git a/modules/cli/src/main/scala/scala/cli/commands/publish/PublishLocal.scala b/modules/cli/src/main/scala/scala/cli/commands/publish/PublishLocal.scala new file mode 100644 index 0000000000..e813bd347c --- /dev/null +++ b/modules/cli/src/main/scala/scala/cli/commands/publish/PublishLocal.scala @@ -0,0 +1,71 @@ +package scala.cli.commands.publish + +import caseapp.core.RemainingArgs + +import scala.build.BuildThreads +import scala.cli.CurrentParams +import scala.cli.commands.ScalaCommand +import scala.cli.commands.util.SharedOptionsUtil._ + +object PublishLocal extends ScalaCommand[PublishLocalOptions] { + + override def group = "Main" + override def inSipScala = false + override def sharedOptions(options: PublishLocalOptions) = + Some(options.shared) + + override def names = List( + List("publish", "local") + ) + + def run(options: PublishLocalOptions, args: RemainingArgs): Unit = { + maybePrintGroupHelp(options) + + CurrentParams.verbosity = options.shared.logging.verbosity + val inputs = options.shared.inputsOrExit(args) + CurrentParams.workspaceOpt = Some(inputs.workspace) + + val logger = options.shared.logger + val initialBuildOptions = Publish.mkBuildOptions( + options.shared, + options.publishParams, + options.sharedPublish, + PublishRepositoryOptions(), + options.mainClass, + None + ).orExit(logger) + val threads = BuildThreads.create() + + val compilerMaker = options.shared.compilerMaker(threads) + val docCompilerMaker = options.shared.compilerMaker(threads, scaladoc = true) + + val cross = options.compileCross.cross.getOrElse(false) + + lazy val workingDir = options.sharedPublish.workingDir + .filter(_.trim.nonEmpty) + .map(os.Path(_, os.pwd)) + .getOrElse { + os.temp.dir( + prefix = "scala-cli-publish-", + deleteOnExit = true + ) + } + + val ivy2HomeOpt = options.sharedPublish.ivy2Home + .filter(_.trim.nonEmpty) + .map(os.Path(_, os.pwd)) + + Publish.doRun( + inputs, + logger, + initialBuildOptions, + compilerMaker, + docCompilerMaker, + cross, + workingDir, + ivy2HomeOpt, + publishLocal = true, + options.watch.watch + ) + } +} diff --git a/modules/integration/src/test/scala/scala/cli/integration/PublishTestsDefault.scala b/modules/integration/src/test/scala/scala/cli/integration/PublishTestsDefault.scala index cf3c41134f..e85afc4cb8 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/PublishTestsDefault.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/PublishTestsDefault.scala @@ -4,6 +4,68 @@ import com.eed3si9n.expecty.Expecty.expect class PublishTestsDefault extends PublishTestDefinitions(scalaVersionOpt = None) { + test("publish local") { + val testOrg = "test-local-org.sth" + val testName = "my-proj" + val testVersion = "1.5.6" + val inputs = TestInputs( + Seq( + os.rel / "Project.scala" -> + s"""//> using publish.organization "$testOrg" + |//> using publish.name "$testName" + |//> using publish.version "$testVersion" + | + |//> using scala "2.13" + |//> using lib "com.lihaoyi::os-lib:0.8.1" + | + |object Project { + | def message = "Hello" + | + | def main(args: Array[String]): Unit = + | println(message) + |} + |""".stripMargin + ) + ) + + val expectedFiles = { + val modName = s"${testName}_2.13" + val base = os.rel / testOrg / modName / testVersion + val baseFiles = Seq( + base / "jars" / s"$modName.jar", + base / "docs" / s"$modName-javadoc.jar", + base / "srcs" / s"$modName-sources.jar", + base / "poms" / s"$modName.pom", + base / "ivys" / "ivy.xml" + ) + baseFiles + .flatMap { f => + val md5 = f / os.up / s"${f.last}.md5" + val sha1 = f / os.up / s"${f.last}.sha1" + Seq(f, md5, sha1) + } + .toSet + } + + inputs.fromRoot { root => + os.proc(TestUtil.cli, "publish", "local", "Project.scala", "--ivy2-home", os.rel / "ivy2") + .call(cwd = root) + val ivy2Local = root / "ivy2" / "local" + val foundFiles = os.walk(ivy2Local) + .filter(os.isFile(_)) + .map(_.relativeTo(ivy2Local)) + .toSet + val missingFiles = expectedFiles -- foundFiles + val unexpectedFiles = foundFiles -- expectedFiles + if (missingFiles.nonEmpty) + pprint.err.log(missingFiles) + if (unexpectedFiles.nonEmpty) + pprint.err.log(unexpectedFiles) + expect(missingFiles.isEmpty) + expect(unexpectedFiles.isEmpty) + } + } + test("Pure Java") { val testOrg = "test-org.foo" val testName = "foo" diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index 6a20a27cb9..e43676b982 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -105,6 +105,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`setup-ide`](./commands.md#setup-ide) @@ -201,6 +202,7 @@ Compile test scope Available in commands: - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`shebang`](./commands.md#shebang) @@ -228,6 +230,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`github secret create` / `gh secret create`](./commands.md#github-secret-create) @@ -273,6 +276,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`setup-ide`](./commands.md#setup-ide) @@ -317,6 +321,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`setup-ide`](./commands.md#setup-ide) @@ -442,6 +447,7 @@ Available in commands: - [`pgp sign`](./commands.md#pgp-sign) - [`pgp verify`](./commands.md#pgp-verify) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`github secret create` / `gh secret create`](./commands.md#github-secret-create) @@ -481,6 +487,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`setup-ide`](./commands.md#setup-ide) @@ -595,6 +602,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`setup-ide`](./commands.md#setup-ide) @@ -657,6 +665,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`github secret create` / `gh secret create`](./commands.md#github-secret-create) @@ -683,6 +692,7 @@ Available in commands: - [`export`](./commands.md#export) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`run`](./commands.md#run) - [`shebang`](./commands.md#shebang) @@ -977,6 +987,7 @@ Available in commands: Available in commands: - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) @@ -1029,6 +1040,7 @@ Set Ivy 2 home directory Available in commands: - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) @@ -1144,6 +1156,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`setup-ide`](./commands.md#setup-ide) @@ -1238,6 +1251,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`setup-ide`](./commands.md#setup-ide) @@ -1298,6 +1312,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`setup-ide`](./commands.md#setup-ide) @@ -1367,6 +1382,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`setup-ide`](./commands.md#setup-ide) @@ -1494,6 +1510,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`github secret create` / `gh secret create`](./commands.md#github-secret-create) @@ -1518,6 +1535,7 @@ Available in commands: - [`compile`](./commands.md#compile) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`shebang`](./commands.md#shebang) @@ -1550,6 +1568,7 @@ Available in commands: - [`browse` / `metabrowse`](./commands.md#browse) - [`package`](./commands.md#package) - [`publish`](./commands.md#publish) +- [`publish local`](./commands.md#publish-local) - [`console` / `repl`](./commands.md#console) - [`run`](./commands.md#run) - [`setup-ide`](./commands.md#setup-ide) diff --git a/website/docs/reference/commands.md b/website/docs/reference/commands.md index f8e73a3611..1e9afca216 100644 --- a/website/docs/reference/commands.md +++ b/website/docs/reference/commands.md @@ -207,6 +207,28 @@ Accepts options: - [watch](./cli-options.md#watch-options) - [workspace](./cli-options.md#workspace-options) +## `publish local` + +Accepts options: +- [compilation server](./cli-options.md#compilation-server-options) +- [compile cross](./cli-options.md#compile-cross-options) +- [coursier](./cli-options.md#coursier-options) +- [dependency](./cli-options.md#dependency-options) +- [directories](./cli-options.md#directories-options) +- [help group](./cli-options.md#help-group-options) +- [jvm](./cli-options.md#jvm-options) +- [logging](./cli-options.md#logging-options) +- [main class](./cli-options.md#main-class-options) +- [publish](./cli-options.md#publish-options) +- [publish params](./cli-options.md#publish-params-options) +- [Scala.js](./cli-options.md#scalajs-options) +- [Scala Native](./cli-options.md#scala-native-options) +- [scalac](./cli-options.md#scalac-options) +- [shared](./cli-options.md#shared-options) +- [verbosity](./cli-options.md#verbosity-options) +- [watch](./cli-options.md#watch-options) +- [workspace](./cli-options.md#workspace-options) + ## `run` Compile and run Scala code.