|
1 | 1 | package scalafix.sbt |
2 | 2 |
|
3 | | -import scala.jdk.CollectionConverters.* |
4 | | -import scala.util.* |
5 | | - |
6 | 3 | import sbt.* |
7 | 4 | import sbt.Keys.* |
8 | | -import sbt.VersionNumber.SemVer |
9 | 5 |
|
10 | 6 | import coursierapi.Repository |
11 | 7 |
|
12 | 8 | import ScalafixPlugin.autoImport.scalafixResolvers |
13 | 9 |
|
14 | | -/** Command to automatically enable semanticdb compiler output for shell session |
15 | | - */ |
| 10 | +/** Command to automatically prepare the build for scalafix invocations */ |
16 | 11 | object ScalafixEnable { |
17 | 12 |
|
18 | | - /** If the provided Scala binary version is supported, return the latest scala |
19 | | - * full version for which the recommended semanticdb-scalac is available |
20 | | - */ |
21 | | - private lazy val recommendedSemanticdbScalacScalaVersion |
22 | | - : PartialFunction[(Long, Long), VersionNumber] = (for { |
23 | | - v <- BuildInfo.supportedScalaVersions |
24 | | - p <- CrossVersion.partialVersion(v).toList |
25 | | - } yield p -> VersionNumber(v)).toMap |
26 | | - |
27 | | - /** If the provided Scala binary version is supported, return the latest scala |
28 | | - * full version for which the recommended semanticdb-scalac is available, or |
29 | | - * None if semanticdb support is built-in in the compiler |
30 | | - */ |
31 | | - private lazy val maybeRecommendedSemanticdbScalacScalaVersion |
32 | | - : PartialFunction[(Long, Long), Option[VersionNumber]] = |
33 | | - recommendedSemanticdbScalacScalaVersion.andThen(Some.apply).orElse { |
34 | | - // semanticdb is built-in in the Scala 3 compiler |
35 | | - case (major, _) if major == 3 => None |
36 | | - } |
| 13 | + private def latestSemanticdbScalac( |
| 14 | + scalaVersion: String, |
| 15 | + repositories: Seq[Repository] |
| 16 | + ): Option[String] = { |
| 17 | + val semanticdbScalacModule = coursierapi.Module.parse( |
| 18 | + "org.scalameta:::semanticdb-scalac", |
| 19 | + coursierapi.ScalaVersion.of(scalaVersion) |
| 20 | + ) |
| 21 | + Option( |
| 22 | + coursierapi.Versions.create |
| 23 | + .withModule(semanticdbScalacModule) |
| 24 | + .withRepositories(repositories*) |
| 25 | + .versions() |
| 26 | + .getMergedListings |
| 27 | + .getRelease |
| 28 | + ).filter(_.nonEmpty) |
| 29 | + } |
37 | 30 |
|
38 | | - /** Collect compatible projects across the entire build */ |
39 | | - private def collectProjects(extracted: Extracted): Seq[CompatibleProject] = |
| 31 | + private def collectProjects(extracted: Extracted): Seq[CompatibleProject] = { |
| 32 | + val repositories = (ThisBuild / scalafixResolvers) |
| 33 | + .get(extracted.structure.data) |
| 34 | + .getOrElse(Seq()) |
40 | 35 | for { |
41 | | - p <- extracted.structure.allProjectRefs |
42 | | - scalaV <- (p / scalaVersion).get(extracted.structure.data).toList |
43 | | - partialVersion <- CrossVersion.partialVersion(scalaV).toList |
44 | | - maybeRecommendedSemanticdbScalacV <- |
45 | | - maybeRecommendedSemanticdbScalacScalaVersion.lift(partialVersion).toList |
46 | | - scalafixResolvers0 <- (p / scalafixResolvers) |
47 | | - .get(extracted.structure.data) |
48 | | - .toList |
49 | | - semanticdbCompilerPlugin0 <- (p / semanticdbCompilerPlugin) |
50 | | - .get(extracted.structure.data) |
51 | | - .toList |
52 | | - } yield CompatibleProject( |
53 | | - p, |
54 | | - VersionNumber(scalaV), |
55 | | - semanticdbCompilerPlugin0, |
56 | | - scalafixResolvers0, |
57 | | - maybeRecommendedSemanticdbScalacV |
58 | | - ) |
| 36 | + (scalaVersion, projectRefs) <- |
| 37 | + extracted.structure.allProjectRefs |
| 38 | + .flatMap { projectRef => |
| 39 | + (projectRef / scalaVersion) |
| 40 | + .get(extracted.structure.data) |
| 41 | + .map(sv => (sv, projectRef)) |
| 42 | + } |
| 43 | + .foldLeft(Map.empty[String, Seq[ProjectRef]]) { |
| 44 | + case (acc, (sv, projectRef)) => |
| 45 | + acc.updated(sv, acc.getOrElse(sv, Seq.empty) :+ projectRef) |
| 46 | + } |
| 47 | + .toSeq |
| 48 | + maybeSemanticdbVersion <- |
| 49 | + if (scalaVersion.startsWith("2.")) |
| 50 | + latestSemanticdbScalac(scalaVersion, repositories) match { |
| 51 | + case None => Seq() // cannot find a plugin for that version, ignore |
| 52 | + case Some(version) => Seq(Some(version)) |
| 53 | + } |
| 54 | + else Seq(None) // no need to set semanticdbVersion for Scala 3 |
| 55 | + projectRef <- projectRefs |
| 56 | + } yield CompatibleProject(projectRef, maybeSemanticdbVersion) |
| 57 | + } |
59 | 58 |
|
60 | 59 | private case class CompatibleProject( |
61 | 60 | ref: ProjectRef, |
62 | | - scalaVersion0: VersionNumber, |
63 | | - semanticdbCompilerPlugin0: ModuleID, |
64 | | - scalafixResolvers0: Seq[Repository], |
65 | | - maybeRecommendedSemanticdbScalacScalaV: Option[VersionNumber] |
| 61 | + semanticdbVersion: Option[String] |
66 | 62 | ) |
67 | 63 |
|
68 | 64 | lazy val command: Command = Command.command( |
69 | 65 | "scalafixEnable", |
70 | 66 | briefHelp = |
71 | 67 | "Configure SemanticdbPlugin for scalafix on supported projects.", |
72 | 68 | detail = """1. set semanticdbEnabled := true |
73 | | - |2. for scala 2.x, |
74 | | - | - set semanticdbCompilerPlugin to the scalameta version tracked by scalafix if available for scalaVersion, |
75 | | - | - otherwise set semanticdbCompilerPlugin to a compatible version available for scalaVersion, |
76 | | - | - otherwise force scalaVersion to the latest version supported by the scalameta version tracked by scalafix.""".stripMargin |
| 69 | + |2. for scala 2.x, set semanticdbVersion to the latest scalameta version""".stripMargin |
77 | 70 | ) { s => |
78 | 71 | val extracted = Project.extract(s) |
79 | 72 | val scalacOptionsSettings = Seq(Compile, Test).flatMap( |
80 | 73 | inConfig(_)(ScalafixPlugin.relaxScalacOptionsConfigSettings) |
81 | 74 | ) |
82 | 75 | val settings = for { |
83 | 76 | project <- collectProjects(extracted) |
84 | | - enableSemanticdbPlugin <- |
85 | | - project.maybeRecommendedSemanticdbScalacScalaV.toList |
86 | | - .flatMap { recommendedSemanticdbScalacScalaV => |
87 | | - |
88 | | - import scalafix.internal.sbt.Implicits._ |
89 | | - val semanticdbScalacModule = |
90 | | - coursierapi.Dependency |
91 | | - .parse( |
92 | | - project.semanticdbCompilerPlugin0.asCoursierCoordinates, |
93 | | - coursierapi.ScalaVersion.of(project.scalaVersion0.toString) |
94 | | - ) |
95 | | - .getModule |
96 | | - val recommendedSemanticdbV = |
97 | | - VersionNumber(BuildInfo.scalametaVersion) |
98 | | - val compatibleSemanticdbVs = Try( |
99 | | - coursierapi.Versions.create |
100 | | - .withRepositories(project.scalafixResolvers0*) |
101 | | - .withModule(semanticdbScalacModule) |
102 | | - .versions() |
103 | | - .getMergedListings |
104 | | - .getAvailable |
105 | | - .asScala |
106 | | - .map(VersionNumber.apply) |
107 | | - // don't use snapshots |
108 | | - .filter(_.extras == Nil) |
109 | | - // https://github.com/scalameta/scalameta/blob/main/COMPATIBILITY.md |
110 | | - .filter(SemVer.isCompatible(_, recommendedSemanticdbV)) |
111 | | - .toList |
112 | | - ) |
113 | | - |
114 | | - compatibleSemanticdbVs match { |
115 | | - case Success(Nil) | Failure(_) => |
116 | | - Seq( |
117 | | - scalaVersion := { |
118 | | - val v = recommendedSemanticdbScalacScalaV.toString |
119 | | - sLog.value.warn( |
120 | | - s"Forcing scalaVersion to $v in project " + |
121 | | - s"${project.ref.project} since no semanticdb-scalac " + |
122 | | - s"version binary-compatible with $recommendedSemanticdbV " + |
123 | | - s"and cross-published for scala " + |
124 | | - s"${project.scalaVersion0.toString} was found - " + |
125 | | - s"consider bumping scala" |
126 | | - ) |
127 | | - v |
128 | | - }, |
129 | | - semanticdbVersion := recommendedSemanticdbV.toString |
130 | | - ) |
131 | | - case Success(available) |
132 | | - if available.contains(recommendedSemanticdbV) => |
133 | | - Seq( |
134 | | - semanticdbVersion := recommendedSemanticdbV.toString |
135 | | - ) |
136 | | - case Success(earliestAvailable :: tail) => |
137 | | - val safeRecommendedSemanticdbV = |
138 | | - if (recommendedSemanticdbV.toString == "4.13.1.1") |
139 | | - VersionNumber("4.13.1") |
140 | | - else recommendedSemanticdbV |
141 | | - |
142 | | - val futureVersion = |
143 | | - SemanticSelector.apply(s">${safeRecommendedSemanticdbV}") |
144 | | - |
145 | | - if (earliestAvailable.matchesSemVer(futureVersion)) { |
146 | | - Seq( |
147 | | - semanticdbVersion := { |
148 | | - val v = earliestAvailable.toString |
149 | | - sLog.value.info( |
150 | | - s"Setting semanticdbVersion to $v in project " + |
151 | | - s"${project.ref.project} since the version " + |
152 | | - s"${recommendedSemanticdbV} tracked by scalafix " + |
153 | | - s"${BuildInfo.scalafixVersion} will not be " + |
154 | | - s"published for scala " + |
155 | | - s"${project.scalaVersion0.toString} - " + |
156 | | - s"consider upgrading sbt-scalafix" |
157 | | - ) |
158 | | - v |
159 | | - } |
160 | | - ) |
161 | | - } else { |
162 | | - val latestAvailable = |
163 | | - tail.lastOption.getOrElse(earliestAvailable) |
164 | | - Seq( |
165 | | - semanticdbVersion := { |
166 | | - val v = latestAvailable.toString |
167 | | - sLog.value.info( |
168 | | - s"Setting semanticdbVersion to $v in project " + |
169 | | - s"${project.ref.project} since the version " + |
170 | | - s"${recommendedSemanticdbV} tracked by scalafix " + |
171 | | - s"${BuildInfo.scalafixVersion} is no longer " + |
172 | | - s"published for scala " + |
173 | | - s"${project.scalaVersion0.toString} - " + |
174 | | - s"consider bumping scala" |
175 | | - ) |
176 | | - v |
177 | | - } |
178 | | - ) |
179 | | - } |
180 | | - } |
181 | | - } :+ (semanticdbEnabled := true) |
182 | | - settings <- |
183 | | - inScope(ThisScope.copy(project = Select(project.ref)))( |
184 | | - scalacOptionsSettings ++ enableSemanticdbPlugin |
185 | | - ) |
| 77 | + semanticdbPluginSettings <- (semanticdbEnabled := true) +: |
| 78 | + project.semanticdbVersion.map { version => |
| 79 | + semanticdbVersion := version |
| 80 | + }.toSeq |
| 81 | + settings <- inScope(ThisScope.copy(project = Select(project.ref)))( |
| 82 | + scalacOptionsSettings ++ semanticdbPluginSettings |
| 83 | + ) |
186 | 84 | } yield settings |
187 | 85 | extracted.appendWithSession(settings, s) |
188 | 86 | } |
|
0 commit comments