Skip to content

Commit fbaeeb5

Browse files
committed
add overloads to load a custom scalafix version
1 parent b2e3121 commit fbaeeb5

File tree

4 files changed

+146
-48
lines changed

4 files changed

+146
-48
lines changed

scalafix-interfaces/src/main/java/scalafix/interfaces/Scalafix.java

Lines changed: 82 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import coursierapi.Repository;
44
import scalafix.internal.interfaces.ScalafixCoursier;
55
import scalafix.internal.interfaces.ScalafixInterfacesClassloader;
6+
import scalafix.internal.interfaces.ScalafixProperties;
7+
import static scalafix.internal.interfaces.ScalafixProperties.PROPERTIES_PATH;
68

79
import java.io.IOException;
810
import java.io.InputStream;
@@ -136,59 +138,96 @@ static Scalafix fetchAndClassloadInstance(String requestedScalaVersion) throws S
136138
*/
137139
static Scalafix fetchAndClassloadInstance(String requestedScalaVersion, List<Repository> repositories)
138140
throws ScalafixException {
139-
140-
String requestedScalaMajorMinorOrMajorVersion =
141-
requestedScalaVersion.replaceAll("^(\\d+\\.\\d+).*", "$1");
142-
143-
String scalaVersionKey;
144-
if (requestedScalaMajorMinorOrMajorVersion.equals("2.12")) {
145-
scalaVersionKey = "scala212";
146-
} else if (requestedScalaMajorMinorOrMajorVersion.equals("2.13") ||
147-
requestedScalaMajorMinorOrMajorVersion.equals("2")) {
148-
scalaVersionKey = "scala213";
149-
} else if (requestedScalaMajorMinorOrMajorVersion.equals("3.0") ||
150-
requestedScalaMajorMinorOrMajorVersion.equals("3.1") ||
151-
requestedScalaMajorMinorOrMajorVersion.equals("3.2") ||
152-
requestedScalaMajorMinorOrMajorVersion.equals("3.3")) {
153-
scalaVersionKey = "scala33";
154-
} else if (requestedScalaMajorMinorOrMajorVersion.equals("3.5")) {
155-
scalaVersionKey = "scala35";
156-
} else if (requestedScalaMajorMinorOrMajorVersion.equals("3.6")) {
157-
scalaVersionKey = "scala36";
158-
} else if (requestedScalaMajorMinorOrMajorVersion.startsWith("3")) {
159-
scalaVersionKey = "scala3Next";
160-
} else {
161-
throw new IllegalArgumentException("Unsupported scala version " + requestedScalaVersion);
162-
}
163-
164-
Properties properties = new Properties();
165-
String propertiesPath = "scalafix-interfaces.properties";
166141
try {
167-
InputStream stream = Scalafix.class.getClassLoader().getResourceAsStream(propertiesPath);
142+
Properties properties = new Properties();
143+
InputStream stream = Scalafix.class.getClassLoader().getResourceAsStream(PROPERTIES_PATH);
168144
properties.load(stream);
145+
146+
ScalafixProperties scalafixProperties = new ScalafixProperties(properties);
147+
String scalafixVersion = scalafixProperties.scalafixVersion();
148+
String scalaVersion = scalafixProperties.fullScalaVersion(requestedScalaVersion);
149+
150+
List<URL> jars = ScalafixCoursier.scalafixCliJars(repositories, scalafixVersion, scalaVersion);
151+
ClassLoader parent = new ScalafixInterfacesClassloader(Scalafix.class.getClassLoader());
152+
return classloadInstance(new URLClassLoader(jars.stream().toArray(URL[]::new), parent));
169153
} catch (Exception e) {
170154
System.err.println(
171-
"Failed to load '" + propertiesPath + "' from local artifact, " +
155+
"Failed to load '" + PROPERTIES_PATH + "' from local artifact, " +
172156
"falling back to fetching the latest scalafix version...");
173157

158+
String latestVersion;
174159
try {
175-
List<URL> jars = ScalafixCoursier.latestScalafixPropertiesJars(repositories);
176-
URLClassLoader classLoader =
177-
new URLClassLoader(jars.stream().toArray(URL[]::new), null);
178-
179-
InputStream stream = classLoader.getResourceAsStream(propertiesPath);
180-
properties.load(stream);
160+
latestVersion = ScalafixCoursier.latestScalafixProperties(repositories);
181161
} catch (Exception ee) {
182-
throw new ScalafixException(
183-
"Failed to load '" + propertiesPath + "' from local & remote artifacts",
184-
ee);
185-
}
162+
throw new ScalafixException( "Failed to lookup latest scalafix version", ee);
163+
}
164+
return fetchAndClassloadInstance(latestVersion, requestedScalaVersion, repositories);
165+
}
166+
}
167+
168+
/**
169+
* Fetch JARs containing an implementation of {@link Scalafix} using Coursier and classload an instance of it via
170+
* runtime reflection.
171+
* <p>
172+
* The custom classloader optionally provided with {@link ScalafixArguments#withToolClasspath} to compile and
173+
* classload external rules must have the classloader of the returned instance as ancestor to share a common
174+
* loaded instance of `scalafix-core`, and therefore have been compiled against the requested Scala version.
175+
*
176+
* @param scalafixVersion Fetch a specific, implementation of {@link Scalafix}. Must be binary-compatible.
177+
* @param requestedScalaVersion A full Scala version (i.e. "3.3.4") or a major.minor one (i.e. "3.3") to infer
178+
* the major.minor Scala version that should be available in the classloader of the
179+
* returned instance. To be able to run advanced semantic rules using the Scala
180+
* Presentation Compiler such as ExplicitResultTypes, this must be source-compatible
181+
* with the version that the target classpath is built with, as provided with
182+
* {@link ScalafixArguments#withScalaVersion}.
183+
* @return An implementation of the {@link Scalafix} interface.
184+
* @throws ScalafixException in case of errors during artifact resolution/fetching.
185+
*/
186+
static Scalafix fetchAndClassloadInstance(String scalafixVersion, String requestedScalaVersion )
187+
throws ScalafixException {
188+
return fetchAndClassloadInstance(scalafixVersion, requestedScalaVersion, Repository.defaults());
189+
}
190+
191+
/**
192+
* Fetch JARs containing an implementation of {@link Scalafix} from the provided repositories using Coursier and
193+
* classload an instance of it via runtime reflection.
194+
* <p>
195+
* The custom classloader optionally provided with {@link ScalafixArguments#withToolClasspath} to compile and
196+
* classload external rules must have the classloader of the returned instance as ancestor to share a common
197+
* loaded instance of `scalafix-core`, and therefore have been compiled against the requested Scala version.
198+
*
199+
* @param scalafixVersion Fetch a specific, implementation of {@link Scalafix}. Must be binary-compatible.
200+
* @param requestedScalaVersion A full Scala version (i.e. "3.3.4") or a major.minor one (i.e. "3.3") to infer
201+
* the major.minor Scala version that should be available in the classloader of the
202+
* returned instance. To be able to run advanced semantic rules using the Scala
203+
* Presentation Compiler such as ExplicitResultTypes, this must be source-compatible
204+
* with the version that the target classpath is built with, as provided with
205+
* {@link ScalafixArguments#withScalaVersion}.
206+
* @param repositories Maven/Ivy repositories to fetch the JARs from.
207+
* @return An implementation of the {@link Scalafix} interface.
208+
* @throws ScalafixException in case of errors during artifact resolution/fetching.
209+
*/
210+
static Scalafix fetchAndClassloadInstance(
211+
String scalafixVersion,
212+
String requestedScalaVersion,
213+
List<Repository> repositories
214+
) throws ScalafixException {
215+
Properties properties = new Properties();
216+
try {
217+
List<URL> jars = ScalafixCoursier.scalafixPropertiesJars(repositories, scalafixVersion);
218+
URLClassLoader classLoader =
219+
new URLClassLoader(jars.stream().toArray(URL[]::new), null);
220+
221+
InputStream stream = classLoader.getResourceAsStream(PROPERTIES_PATH);
222+
properties.load(stream);
223+
} catch (Exception e) {
224+
throw new ScalafixException(
225+
"Failed to fetch '" + PROPERTIES_PATH + "' for scalafix version " + scalafixVersion,
226+
e);
186227
}
187228

188-
String scalafixVersion = properties.getProperty("scalafixVersion");
189-
String scalaVersion = properties.getProperty(scalaVersionKey);
190-
if (scalafixVersion == null || scalaVersion == null)
191-
throw new ScalafixException("Failed to lookup versions from '" + propertiesPath + "'");
229+
ScalafixProperties scalafixProperties = new ScalafixProperties(properties);
230+
String scalaVersion = scalafixProperties.fullScalaVersion(requestedScalaVersion);
192231

193232
List<URL> jars = ScalafixCoursier.scalafixCliJars(repositories, scalafixVersion, scalaVersion);
194233
ClassLoader parent = new ScalafixInterfacesClassloader(Scalafix.class.getClassLoader());

scalafix-interfaces/src/main/java/scalafix/internal/interfaces/ScalafixCoursier.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
public class ScalafixCoursier {
1717

18+
private static Module PROPERTIES_MODULE = Module.of("ch.epfl.scala", "scalafix-properties");
19+
1820
private static VersionListing versions(
1921
List<Repository> repositories,
2022
coursierapi.Module module
@@ -59,20 +61,24 @@ private static List<URL> toURLs(FetchResult result) throws ScalafixException {
5961
return urls;
6062
}
6163

62-
public static List<URL> latestScalafixPropertiesJars(
64+
public static String latestScalafixProperties(
6365
List<Repository> repositories
6466
) throws ScalafixException {
65-
Module module = Module.of("ch.epfl.scala", "scalafix-properties");
6667
String allowedVersion = System.getProperty("scalafix-properties.version");
67-
String version = versions(repositories, module)
68+
return versions(repositories, PROPERTIES_MODULE)
6869
.getAvailable()
6970
.stream()
7071
// Ignore RC & SNAPSHOT versions, except if explicitly requested
7172
.filter(v -> !v.contains("-") || v.equals(allowedVersion))
7273
.reduce((older, newer) -> newer)
73-
.orElseThrow(() -> new ScalafixException("Could not find any stable version for " + module));
74+
.orElseThrow(() -> new ScalafixException("Could not find any stable version for " + PROPERTIES_MODULE));
75+
}
7476

75-
Dependency scalafixProperties = Dependency.of(module, version);
77+
public static List<URL> scalafixPropertiesJars(
78+
List<Repository> repositories,
79+
String scalafixVersion
80+
) throws ScalafixException {
81+
Dependency scalafixProperties = Dependency.of(PROPERTIES_MODULE, scalafixVersion);
7682
return toURLs(fetch(repositories, Collections.singletonList(scalafixProperties), ResolutionParams.create()));
7783
}
7884

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package scalafix.internal.interfaces;
2+
3+
import java.util.Properties;
4+
5+
public class ScalafixProperties {
6+
7+
public static final String PROPERTIES_PATH = "scalafix-interfaces.properties";
8+
9+
private final Properties properties;
10+
11+
public ScalafixProperties(Properties properties) {
12+
this.properties = properties;
13+
}
14+
15+
public String scalafixVersion() {
16+
return properties.getProperty("scalafixVersion");
17+
}
18+
19+
public String fullScalaVersion(String requestedScalaVersion) {
20+
String requestedScalaMajorMinorOrMajorVersion =
21+
requestedScalaVersion.replaceAll("^(\\d+\\.\\d+).*", "$1");
22+
23+
String scalaVersionKey;
24+
if (requestedScalaMajorMinorOrMajorVersion.equals("2.12")) {
25+
scalaVersionKey = "scala212";
26+
} else if (requestedScalaMajorMinorOrMajorVersion.equals("2.13") ||
27+
requestedScalaMajorMinorOrMajorVersion.equals("2")) {
28+
scalaVersionKey = "scala213";
29+
} else if (requestedScalaMajorMinorOrMajorVersion.equals("3.0") ||
30+
requestedScalaMajorMinorOrMajorVersion.equals("3.1") ||
31+
requestedScalaMajorMinorOrMajorVersion.equals("3.2") ||
32+
requestedScalaMajorMinorOrMajorVersion.equals("3.3")) {
33+
scalaVersionKey = "scala33";
34+
} else if (requestedScalaMajorMinorOrMajorVersion.equals("3.5")) {
35+
scalaVersionKey = "scala35";
36+
} else if (requestedScalaMajorMinorOrMajorVersion.equals("3.6")) {
37+
scalaVersionKey = "scala36";
38+
} else if (requestedScalaMajorMinorOrMajorVersion.startsWith("3")) {
39+
scalaVersionKey = "scala3Next";
40+
} else {
41+
throw new IllegalArgumentException("Unsupported scala version " + requestedScalaVersion);
42+
}
43+
44+
return properties.getProperty(scalaVersionKey);
45+
}
46+
47+
}

scalafix-tests/integration/src/test/scala/scalafix/tests/interfaces/ScalafixSuite.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ class ScalafixSuite extends AnyFunSuite {
145145
assert(scalafixAPI.scalaVersion() == Versions.scala3Next)
146146
}
147147

148+
test("classload a specific scalafix version") {
149+
val v = "0.14.2+48-9b6e03ac-SNAPSHOT" // no stable yet
150+
val scalafixAPI = Scalafix.fetchAndClassloadInstance(v, "3", repositories)
151+
assert(scalafixAPI.scalafixVersion() == v)
152+
}
153+
148154
test("invalid class loader") {
149155
val cl = new URLClassLoader(Array(), null)
150156
val ex = intercept[ScalafixException] {

0 commit comments

Comments
 (0)