|
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 |
| -import ScalafixPlugin.autoImport.scalafixResolvers |
| 8 | +import ScalafixPlugin.autoImport.* |
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.empty) |
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 => |
| 52 | + Seq.empty // don't activate semanticdb |
| 53 | + case Some(version) => |
| 54 | + Seq(Some(version)) // activate semanticdb with plugin |
| 55 | + } |
| 56 | + else Seq(None) // activate semanticdb without plugin |
| 57 | + projectRef <- projectRefs |
| 58 | + } yield CompatibleProject(projectRef, maybeSemanticdbVersion) |
| 59 | + } |
59 | 60 |
|
60 | 61 | private case class CompatibleProject(
|
61 | 62 | ref: ProjectRef,
|
62 |
| - scalaVersion0: VersionNumber, |
63 |
| - semanticdbCompilerPlugin0: ModuleID, |
64 |
| - scalafixResolvers0: Seq[Repository], |
65 |
| - maybeRecommendedSemanticdbScalacScalaV: Option[VersionNumber] |
| 63 | + semanticdbVersion: Option[String] |
66 | 64 | )
|
67 | 65 |
|
68 | 66 | lazy val command: Command = Command.command(
|
69 | 67 | "scalafixEnable",
|
70 | 68 | briefHelp =
|
71 | 69 | "Configure SemanticdbPlugin for scalafix on supported projects.",
|
72 |
| - 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 |
| 70 | + detail = """1. set scalafixAllowDynamicFallback := true |
| 71 | + |2. set semanticdbEnabled := true |
| 72 | + |3. for scala 2.x, set semanticdbVersion to the latest scalameta version""".stripMargin |
77 | 73 | ) { s =>
|
78 | 74 | val extracted = Project.extract(s)
|
79 | 75 | val scalacOptionsSettings = Seq(Compile, Test).flatMap(
|
80 | 76 | inConfig(_)(ScalafixPlugin.relaxScalacOptionsConfigSettings)
|
81 | 77 | )
|
| 78 | + val dynamicFallbackSetting = |
| 79 | + scalafixAllowDynamicFallback := true |
82 | 80 | val settings = for {
|
83 | 81 | 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 |
| - ) |
| 82 | + semanticdbPluginSettings <- (semanticdbEnabled := true) +: |
| 83 | + project.semanticdbVersion.map { version => |
| 84 | + semanticdbVersion := version |
| 85 | + }.toSeq |
| 86 | + settings <- inScope(ThisScope.copy(project = Select(project.ref)))( |
| 87 | + scalacOptionsSettings ++ semanticdbPluginSettings :+ dynamicFallbackSetting |
| 88 | + ) |
186 | 89 | } yield settings
|
187 | 90 | extracted.appendWithSession(settings, s)
|
188 | 91 | }
|
|
0 commit comments