@@ -12,6 +12,7 @@ import java.util.concurrent.ScheduledExecutorService
1212import scala .build .EitherCps .{either , value }
1313import scala .build .Logger
1414import scala .build .errors .BuildException
15+ import scala .build .internals .ConsoleUtils .ScalaCliConsole .warnPrefix
1516import scala .cli .commands .util .ScalaCliSttpBackend
1617
1718final case class RepoParams (
@@ -25,6 +26,8 @@ final case class RepoParams(
2526 shouldSign : Boolean ,
2627 shouldAuthenticate : Boolean
2728) {
29+ import RepoParams .*
30+
2831 def withAuth (auth : Authentication ): RepoParams =
2932 copy(
3033 repo = repo.withAuthentication(auth),
@@ -41,14 +44,25 @@ final case class RepoParams(
4144 )
4245 def withAuth (authOpt : Option [Authentication ]): RepoParams = authOpt.fold(this )(withAuth)
4346
44- lazy val isLegacySonatype : Boolean =
47+ lazy val isSonatype : Boolean =
4548 Option (new URI (repo.snapshotRepo.root))
4649 .filter(_.getScheme == " https" )
4750 .map(_.getHost)
48- .exists(host => host == " oss.sonatype.org " || host.endsWith( " .oss.sonatype.org " ) )
51+ .exists(sonatypeHosts.contains )
4952}
5053
5154object RepoParams {
55+ private val sonatypeOssrhStagingApiBase = " https://ossrh-staging-api.central.sonatype.com"
56+ private val sonatypeSnapshotsBase = " https://central.sonatype.com/repository/maven-snapshots/"
57+ private val sonatypeLegacyBase = " https://oss.sonatype.org"
58+ private val sonatypeS01LegacyBase = " https://s01.oss.sonatype.org"
59+ private def sonatypeHosts : Seq [String ] =
60+ Seq (
61+ sonatypeLegacyBase,
62+ sonatypeSnapshotsBase,
63+ sonatypeS01LegacyBase,
64+ sonatypeOssrhStagingApiBase
65+ ).map(new URI (_).getHost)
5266
5367 def apply (
5468 repo : String ,
@@ -67,24 +81,42 @@ object RepoParams {
6781 case " ivy2-local" =>
6882 RepoParams .ivy2Local(ivy2HomeOpt)
6983 case " sonatype" | " central" | " maven-central" | " mvn-central" =>
84+ logger.message(s " Using Portal OSSRH Staging API: $sonatypeOssrhStagingApiBase" )
85+ RepoParams .centralRepo(
86+ base = sonatypeOssrhStagingApiBase,
87+ useLegacySnapshots = false ,
88+ connectionTimeoutRetries = connectionTimeoutRetries,
89+ connectionTimeoutSeconds = connectionTimeoutSeconds,
90+ stagingRepoRetries = stagingRepoRetries,
91+ stagingRepoWaitTimeMilis = stagingRepoWaitTimeMilis,
92+ es = es,
93+ logger = logger
94+ )
95+ case " sonatype-legacy" | " central-legacy" | " maven-central-legacy" | " mvn-central-legacy" =>
96+ logger.message(s " $warnPrefix $sonatypeLegacyBase is EOL since 2025-06-30. " )
97+ logger.message(s " $warnPrefix $sonatypeLegacyBase publishing is expected to fail. " )
7098 RepoParams .centralRepo(
71- " https://oss.sonatype.org" ,
72- connectionTimeoutRetries,
73- connectionTimeoutSeconds,
74- stagingRepoRetries,
75- stagingRepoWaitTimeMilis,
76- es,
77- logger
99+ base = sonatypeLegacyBase,
100+ useLegacySnapshots = true ,
101+ connectionTimeoutRetries = connectionTimeoutRetries,
102+ connectionTimeoutSeconds = connectionTimeoutSeconds,
103+ stagingRepoRetries = stagingRepoRetries,
104+ stagingRepoWaitTimeMilis = stagingRepoWaitTimeMilis,
105+ es = es,
106+ logger = logger
78107 )
79108 case " sonatype-s01" | " central-s01" | " maven-central-s01" | " mvn-central-s01" =>
109+ logger.message(s " $warnPrefix $sonatypeS01LegacyBase is EOL since 2025-06-30. " )
110+ logger.message(s " $warnPrefix it's expected publishing will fail. " )
80111 RepoParams .centralRepo(
81- " https://s01.oss.sonatype.org" ,
82- connectionTimeoutRetries,
83- connectionTimeoutSeconds,
84- stagingRepoRetries,
85- stagingRepoWaitTimeMilis,
86- es,
87- logger
112+ base = sonatypeS01LegacyBase,
113+ useLegacySnapshots = true ,
114+ connectionTimeoutRetries = connectionTimeoutRetries,
115+ connectionTimeoutSeconds = connectionTimeoutSeconds,
116+ stagingRepoRetries = stagingRepoRetries,
117+ stagingRepoWaitTimeMilis = stagingRepoWaitTimeMilis,
118+ es = es,
119+ logger = logger
88120 )
89121 case " github" =>
90122 value(RepoParams .gitHubRepo(vcsUrlOpt, workspace, logger))
@@ -103,77 +135,86 @@ object RepoParams {
103135 }
104136
105137 RepoParams (
106- PublishRepository .Simple (repo0),
107- None ,
108- Hooks .dummy,
109- isIvy2LocalLike,
110- true ,
111- true ,
112- true ,
113- false ,
114- false
138+ repo = PublishRepository .Simple (repo0),
139+ targetRepoOpt = None ,
140+ hooks = Hooks .dummy,
141+ isIvy2LocalLike = isIvy2LocalLike ,
142+ defaultParallelUpload = true ,
143+ supportsSig = true ,
144+ acceptsChecksums = true ,
145+ shouldSign = false ,
146+ shouldAuthenticate = false
115147 )
116148 }
117149 }
118150
119151 def centralRepo (
120152 base : String ,
153+ useLegacySnapshots : Boolean ,
121154 connectionTimeoutRetries : Option [Int ],
122155 connectionTimeoutSeconds : Option [Int ],
123156 stagingRepoRetries : Option [Int ],
124157 stagingRepoWaitTimeMilis : Option [Int ],
125158 es : ScheduledExecutorService ,
126159 logger : Logger
127- ) = {
128- val repo0 = PublishRepository .Sonatype (MavenRepository (base))
160+ ): RepoParams = {
161+ val repo0 = PublishRepository .Sonatype (
162+ base = MavenRepository (base),
163+ useLegacySnapshots = useLegacySnapshots
164+ )
129165 val backend = ScalaCliSttpBackend .httpURLConnection(logger, connectionTimeoutSeconds)
130166 val api = SonatypeApi (
131- backend,
132- base + " /service/local" ,
133- None ,
134- logger.verbosity,
167+ backend = backend ,
168+ base = base + " /service/local" ,
169+ authentication = None ,
170+ verbosity = logger.verbosity,
135171 retryOnTimeout = connectionTimeoutRetries.getOrElse(3 ),
136- stagingRepoRetryParams = EmaRetryParams (
137- stagingRepoRetries.getOrElse(3 ),
138- stagingRepoWaitTimeMilis.getOrElse(10 * 1000 ),
139- 2.0f
140- )
172+ stagingRepoRetryParams =
173+ EmaRetryParams (
174+ attempts = stagingRepoRetries.getOrElse(3 ),
175+ initialWaitDurationMs = stagingRepoWaitTimeMilis.getOrElse(10 * 1000 ),
176+ factor = 2.0f
177+ )
141178 )
142179 val hooks0 = Hooks .sonatype(
143- repo0,
144- api,
145- logger.compilerOutputStream, // meh
146- logger.verbosity,
180+ repo = repo0,
181+ api = api ,
182+ out = logger.compilerOutputStream, // meh
183+ verbosity = logger.verbosity,
147184 batch = coursier.paths.Util .useAnsiOutput(), // FIXME Get via logger
148- es
185+ es = es
149186 )
150187 RepoParams (
151- repo0,
152- Some (" https://repo1.maven.org/maven2" ),
153- hooks0,
154- false ,
155- true ,
156- true ,
157- true ,
158- true ,
159- true
188+ repo = repo0,
189+ targetRepoOpt = Some (" https://repo1.maven.org/maven2" ),
190+ hooks = hooks0,
191+ isIvy2LocalLike = false ,
192+ defaultParallelUpload = true ,
193+ supportsSig = true ,
194+ acceptsChecksums = true ,
195+ shouldSign = true ,
196+ shouldAuthenticate = true
160197 )
161198 }
162199
163- def gitHubRepoFor (org : String , name : String ) =
200+ def gitHubRepoFor (org : String , name : String ): RepoParams =
164201 RepoParams (
165- PublishRepository .Simple (MavenRepository (s " https://maven.pkg.github.com/ $org/ $name" )),
166- None ,
167- Hooks .dummy,
168- false ,
169- false ,
170- false ,
171- false ,
172- false ,
173- true
202+ repo = PublishRepository .Simple (MavenRepository (s " https://maven.pkg.github.com/ $org/ $name" )),
203+ targetRepoOpt = None ,
204+ hooks = Hooks .dummy,
205+ isIvy2LocalLike = false ,
206+ defaultParallelUpload = false ,
207+ supportsSig = false ,
208+ acceptsChecksums = false ,
209+ shouldSign = false ,
210+ shouldAuthenticate = true
174211 )
175212
176- def gitHubRepo (vcsUrlOpt : Option [String ], workspace : os.Path , logger : Logger ) = either {
213+ def gitHubRepo (
214+ vcsUrlOpt : Option [String ],
215+ workspace : os.Path ,
216+ logger : Logger
217+ ): Either [BuildException , RepoParams ] = either {
177218 val orgNameFromVcsOpt = vcsUrlOpt.flatMap(GitRepo .maybeGhOrgName)
178219
179220 val (org, name) = orgNameFromVcsOpt match {
@@ -184,23 +225,23 @@ object RepoParams {
184225 gitHubRepoFor(org, name)
185226 }
186227
187- def ivy2Local (ivy2HomeOpt : Option [os.Path ]) = {
228+ def ivy2Local (ivy2HomeOpt : Option [os.Path ]): RepoParams = {
188229 val home = ivy2HomeOpt
189230 .orElse(sys.props.get(" ivy.home" ).map(prop => os.Path (prop)))
190231 .orElse(sys.props.get(" user.home" ).map(prop => os.Path (prop) / " .ivy2" ))
191232 .getOrElse(os.home / " .ivy2" )
192233 val base = home / " local"
193234 // not really a Maven repo…
194235 RepoParams (
195- PublishRepository .Simple (MavenRepository (base.toNIO.toUri.toASCIIString)),
196- None ,
197- Hooks .dummy,
198- true ,
199- true ,
200- true ,
201- true ,
202- false ,
203- false
236+ repo = PublishRepository .Simple (MavenRepository (base.toNIO.toUri.toASCIIString)),
237+ targetRepoOpt = None ,
238+ hooks = Hooks .dummy,
239+ isIvy2LocalLike = true ,
240+ defaultParallelUpload = true ,
241+ supportsSig = true ,
242+ acceptsChecksums = true ,
243+ shouldSign = false ,
244+ shouldAuthenticate = false
204245 )
205246 }
206247
0 commit comments