16
16
17
17
package org .scalasteward .core .coursier
18
18
19
+ import cats .Parallel
19
20
import cats .effect .Async
20
21
import cats .implicits ._
21
- import cats .{Applicative , Parallel }
22
22
import coursier .cache .{CachePolicy , FileCache }
23
23
import coursier .core .{Authentication , Project }
24
- import coursier .{Fetch , Info , Module , ModuleName , Organization }
25
- import org .http4s .Uri
24
+ import coursier .{Fetch , Module , ModuleName , Organization }
26
25
import org .scalasteward .core .data .Resolver .Credentials
27
- import org .scalasteward .core .data .{Dependency , Resolver , Scope , Version }
26
+ import org .scalasteward .core .data .{Dependency , Resolver , Version }
28
27
import org .scalasteward .core .util .uri
29
28
import org .typelevel .log4cats .Logger
30
29
31
30
/** An interface to [[https://get-coursier.io Coursier ]] used for fetching dependency versions and
32
31
* metadata.
33
32
*/
34
33
trait CoursierAlg [F [_]] {
35
- def getArtifactUrl (dependency : Scope . Dependency ): F [Option [ Uri ] ]
34
+ def getMetadata (dependency : Dependency , resolvers : List [ Resolver ] ): F [DependencyMetadata ]
36
35
37
36
def getVersions (dependency : Dependency , resolver : Resolver ): F [List [Version ]]
38
-
39
- final def getArtifactIdUrlMapping (dependencies : Scope .Dependencies )(implicit
40
- F : Applicative [F ]
41
- ): F [Map [String , Uri ]] =
42
- dependencies.sequence
43
- .traverseFilter(dep => getArtifactUrl(dep).map(_.map(dep.value.artifactId.name -> _)))
44
- .map(_.toMap)
45
37
}
46
38
47
39
object CoursierAlg {
@@ -50,64 +42,68 @@ object CoursierAlg {
50
42
parallel : Parallel [F ],
51
43
F : Async [F ]
52
44
): CoursierAlg [F ] = {
53
- val fetch : Fetch [F ] = Fetch [F ](FileCache [F ]())
45
+ val fetch : Fetch [F ] =
46
+ Fetch [F ](FileCache [F ]())
54
47
55
48
val cacheNoTtl : FileCache [F ] =
56
49
FileCache [F ]().withTtl(None ).withCachePolicies(List (CachePolicy .Update ))
57
50
58
51
new CoursierAlg [F ] {
59
- override def getArtifactUrl (dependency : Scope .Dependency ): F [Option [Uri ]] =
60
- convertToCoursierTypes(dependency).flatMap((getArtifactUrlImpl _).tupled)
52
+ override def getMetadata (
53
+ dependency : Dependency ,
54
+ resolvers : List [Resolver ]
55
+ ): F [DependencyMetadata ] =
56
+ resolvers.traverseFilter(convertResolver(_).attempt.map(_.toOption)).flatMap {
57
+ repositories =>
58
+ val csrDependency = toCoursierDependency(dependency)
59
+ getMetadataImpl(csrDependency, repositories, DependencyMetadata .empty)
60
+ }
61
61
62
- private def getArtifactUrlImpl (
62
+ private def getMetadataImpl (
63
63
dependency : coursier.Dependency ,
64
- repositories : List [coursier.Repository ]
65
- ): F [Option [Uri ]] = {
64
+ repositories : List [coursier.Repository ],
65
+ acc : DependencyMetadata
66
+ ): F [DependencyMetadata ] = {
66
67
val fetchArtifacts = fetch
67
68
.withArtifactTypes(Set (coursier.Type .pom, coursier.Type .ivy))
68
69
.withDependencies(List (dependency))
69
70
.withRepositories(repositories)
71
+
70
72
fetchArtifacts.ioResult.attempt.flatMap {
71
73
case Left (throwable) =>
72
- logger.debug(throwable)(s " Failed to fetch artifacts of $dependency" ).as(None )
74
+ logger.debug(throwable)(s " Failed to fetch artifacts of $dependency" ).as(acc )
73
75
case Right (result) =>
74
76
val maybeProject = result.resolution.projectCache
75
77
.get(dependency.moduleVersion)
76
78
.map { case (_, project) => project }
77
- maybeProject.traverseFilter { project =>
78
- getScmUrlOrHomePage(project.info) match {
79
- case Some (url) => F .pure(Some (url))
80
- case None =>
81
- getParentDependency(project).traverseFilter(getArtifactUrlImpl(_, repositories))
79
+
80
+ maybeProject.fold(F .pure(acc)) { project =>
81
+ val metadata = acc.enrichWith(metadataFrom(project))
82
+ val recurse = Option .when(metadata.repoUrl.isEmpty)(())
83
+ (recurse >> parentOf(project)).fold(F .pure(metadata)) { parent =>
84
+ getMetadataImpl(parent, repositories, metadata)
82
85
}
83
86
}
84
87
}
85
88
}
86
89
87
90
override def getVersions (dependency : Dependency , resolver : Resolver ): F [List [Version ]] =
88
- toCoursierRepository(resolver) match {
89
- case Left (message) =>
90
- logger.error(message) >> F .raiseError(new Throwable (message))
91
- case Right (repository) =>
92
- val module = toCoursierModule(dependency)
93
- repository.versions(module, cacheNoTtl.fetch).run.flatMap {
94
- case Left (message) =>
95
- logger.debug(message) >> F .raiseError(new Throwable (message))
96
- case Right ((versions, _)) => F .pure(versions.available.map(Version .apply).sorted)
97
- }
91
+ convertResolver(resolver).flatMap { repository =>
92
+ val module = toCoursierModule(dependency)
93
+ repository.versions(module, cacheNoTtl.fetch).run.flatMap {
94
+ case Left (message) =>
95
+ logger.debug(message) >> F .raiseError[List [Version ]](new Throwable (message))
96
+ case Right ((versions, _)) =>
97
+ F .pure(versions.available.map(Version .apply).sorted)
98
+ }
98
99
}
99
100
100
- private def convertToCoursierTypes (
101
- dependency : Scope .Dependency
102
- ): F [(coursier.Dependency , List [coursier.Repository ])] =
103
- dependency.resolvers.traverseFilter(convertResolver).map { repositories =>
104
- (toCoursierDependency(dependency.value), repositories)
105
- }
106
-
107
- private def convertResolver (resolver : Resolver ): F [Option [coursier.Repository ]] =
101
+ private def convertResolver (resolver : Resolver ): F [coursier.Repository ] =
108
102
toCoursierRepository(resolver) match {
109
- case Right (repository) => F .pure(Some (repository))
110
- case Left (message) => logger.error(s " Failed to convert $resolver: $message" ).as(None )
103
+ case Right (repository) => F .pure(repository)
104
+ case Left (message) =>
105
+ logger.error(s " Failed to convert $resolver: $message" ) >>
106
+ F .raiseError[coursier.Repository ](new Throwable (message))
111
107
}
112
108
}
113
109
}
@@ -127,40 +123,40 @@ object CoursierAlg {
127
123
private def toCoursierRepository (resolver : Resolver ): Either [String , coursier.Repository ] =
128
124
resolver match {
129
125
case Resolver .MavenRepository (_, location, creds, headers) =>
130
- Right (
131
- coursier.maven.MavenRepository
132
- .apply(location, toCoursierAuthentication(creds, headers))
133
- )
126
+ val authentication = toCoursierAuthentication(creds, headers)
127
+ Right (coursier.maven.MavenRepository .apply(location, authentication))
134
128
case Resolver .IvyRepository (_, pattern, creds, headers) =>
135
- coursier.ivy. IvyRepository
136
- . parse(pattern, authentication = toCoursierAuthentication(creds, headers) )
129
+ val authentication = toCoursierAuthentication(creds, headers)
130
+ coursier.ivy. IvyRepository . parse(pattern, authentication = authentication )
137
131
}
138
132
139
133
private def toCoursierAuthentication (
140
134
credentials : Option [Credentials ],
141
135
headers : List [Resolver .Header ]
142
136
): Option [Authentication ] =
143
- if (credentials.isEmpty && headers.isEmpty) {
144
- None
145
- } else {
146
- Some (
147
- new Authentication (
148
- credentials.fold(" " )(_.user),
149
- credentials.map(_.pass),
150
- headers.map(h => (h.key, h.value)),
151
- optional = false ,
152
- None ,
153
- httpsOnly = true ,
154
- passOnRedirect = false
155
- )
137
+ Option .when(credentials.nonEmpty || headers.nonEmpty) {
138
+ new Authentication (
139
+ credentials.fold(" " )(_.user),
140
+ credentials.map(_.pass),
141
+ headers.map(h => (h.key, h.value)),
142
+ optional = false ,
143
+ realmOpt = None ,
144
+ httpsOnly = true ,
145
+ passOnRedirect = false
156
146
)
157
147
}
158
148
159
- private def getParentDependency (project : Project ): Option [coursier.Dependency ] =
149
+ private def metadataFrom (project : Project ): DependencyMetadata =
150
+ DependencyMetadata (
151
+ homePage = uri.fromStringWithScheme(project.info.homePage),
152
+ scmUrl = project.info.scm.flatMap(_.url).flatMap(uri.fromStringWithScheme),
153
+ releaseNotesUrl = project.properties
154
+ .collectFirst { case (key, value) if key.equalsIgnoreCase(" info.releaseNotesUrl" ) => value }
155
+ .flatMap(uri.fromStringWithScheme)
156
+ )
157
+
158
+ private def parentOf (project : Project ): Option [coursier.Dependency ] =
160
159
project.parent.map { case (module, version) =>
161
160
coursier.Dependency (module, version).withTransitive(false )
162
161
}
163
-
164
- private def getScmUrlOrHomePage (info : Info ): Option [Uri ] =
165
- uri.findBrowsableUrl(info.scm.flatMap(_.url).toList :+ info.homePage)
166
162
}
0 commit comments