|
1 | 1 | package scalafix.interfaces;
|
2 | 2 |
|
| 3 | +import coursierapi.Repository; |
| 4 | +import scalafix.internal.interfaces.ScalafixCoursier; |
| 5 | +import scalafix.internal.interfaces.ScalafixInterfacesClassloader; |
3 | 6 |
|
| 7 | +import java.io.IOException; |
| 8 | +import java.io.InputStream; |
4 | 9 | import java.lang.reflect.Constructor;
|
5 | 10 | import java.lang.reflect.InvocationTargetException;
|
| 11 | +import java.net.URL; |
| 12 | +import java.net.URLClassLoader; |
| 13 | +import java.util.List; |
| 14 | +import java.util.Properties; |
6 | 15 |
|
7 | 16 | /**
|
8 | 17 | * Public API for reflectively invoking Scalafix from a build tool or IDE integration.
|
9 |
| - * |
10 |
| - * To obtain an instance of Scalafix, use {@link Scalafix#classloadInstance(ClassLoader)}. |
| 18 | + * <p> |
| 19 | + * To obtain an instance of Scalafix, use one of the static factory methods. |
11 | 20 | *
|
12 | 21 | * @implNote This interface is not intended to be extended, the only implementation of this interface
|
13 | 22 | * should live in the Scalafix repository.
|
14 | 23 | */
|
15 | 24 | public interface Scalafix {
|
16 | 25 |
|
17 | 26 | /**
|
18 |
| - * @return Construct a new instance of {@link ScalafixArguments} that can be later passed to {@link #runMain(ScalafixArguments) }. |
| 27 | + * @return Construct a new instance of {@link ScalafixArguments}. |
19 | 28 | */
|
20 | 29 | ScalafixArguments newArguments();
|
21 | 30 |
|
@@ -58,15 +67,96 @@ public interface Scalafix {
|
58 | 67 | */
|
59 | 68 | String scala213();
|
60 | 69 |
|
| 70 | + /** |
| 71 | + * Fetch JARs containing an implementation of {@link Scalafix} using Coursier and classload an instance of it via |
| 72 | + * runtime reflection. |
| 73 | + * <p> |
| 74 | + * The custom classloader optionally provided with {@link ScalafixArguments#withToolClasspath} to compile and |
| 75 | + * classload external rules must have the classloader of the returned instance as ancestor to share a common |
| 76 | + * loaded instance of `scalafix-core`, and therefore have been compiled against the requested Scala binary version. |
| 77 | + * |
| 78 | + * @param scalaBinaryVersion The Scala binary version ("2.13" for example) available in the classloader of the |
| 79 | + * returned instance. To be able to run advanced semantic rules using the Scala |
| 80 | + * Presentation Compiler such as ExplicitResultTypes, this must match the binary |
| 81 | + * version that the target classpath was built with, as provided with |
| 82 | + * {@link ScalafixArguments#withScalaVersion}. |
| 83 | + * @return An implementation of the {@link Scalafix} interface. |
| 84 | + * @throws ScalafixException in case of errors during artifact resolution/fetching. |
| 85 | + */ |
| 86 | + static Scalafix fetchAndClassloadInstance(String scalaBinaryVersion) throws ScalafixException { |
| 87 | + return fetchAndClassloadInstance(scalaBinaryVersion, Repository.defaults()); |
| 88 | + } |
| 89 | + |
| 90 | + /** |
| 91 | + * Fetch JARs containing an implementation of {@link Scalafix} from the provided repositories using Coursier and |
| 92 | + * classload an instance of it via runtime reflection. |
| 93 | + * <p> |
| 94 | + * The custom classloader optionally provided with {@link ScalafixArguments#withToolClasspath} to compile and |
| 95 | + * classload external rules must have the classloader of the returned instance as ancestor to share a common |
| 96 | + * loaded instance of `scalafix-core`, and therefore have been compiled against the requested Scala binary version. |
| 97 | + * |
| 98 | + * @param scalaBinaryVersion The Scala binary version ("2.13" for example) available in the classloader of the |
| 99 | + * returned instance. To be able to run advanced semantic rules using the Scala |
| 100 | + * Presentation Compiler such as ExplicitResultTypes, this must match the binary |
| 101 | + * version that the target classpath was built with, as provided with |
| 102 | + * {@link ScalafixArguments#withScalaVersion}. |
| 103 | + * @param repositories Maven/Ivy repositories to fetch the JARs from. |
| 104 | + * @return An implementation of the {@link Scalafix} interface. |
| 105 | + * @throws ScalafixException in case of errors during artifact resolution/fetching. |
| 106 | + */ |
| 107 | + static Scalafix fetchAndClassloadInstance(String scalaBinaryVersion, List<Repository> repositories) |
| 108 | + throws ScalafixException { |
| 109 | + |
| 110 | + String scalaVersionKey; |
| 111 | + switch (scalaBinaryVersion) { |
| 112 | + case "2.11": |
| 113 | + scalaVersionKey = "scala211"; |
| 114 | + break; |
| 115 | + case "2.12": |
| 116 | + scalaVersionKey = "scala212"; |
| 117 | + break; |
| 118 | + case "2.13": |
| 119 | + scalaVersionKey = "scala213"; |
| 120 | + break; |
| 121 | + default: |
| 122 | + throw new IllegalArgumentException("Unsupported scala version " + scalaBinaryVersion); |
| 123 | + } |
61 | 124 |
|
| 125 | + Properties properties = new Properties(); |
| 126 | + String propertiesPath = "scalafix-interfaces.properties"; |
| 127 | + InputStream stream = Scalafix.class.getClassLoader().getResourceAsStream(propertiesPath); |
| 128 | + try { |
| 129 | + properties.load(stream); |
| 130 | + } catch (IOException | NullPointerException e) { |
| 131 | + throw new ScalafixException("Failed to load '" + propertiesPath + "' to lookup versions", e); |
| 132 | + } |
| 133 | + |
| 134 | + String scalafixVersion = properties.getProperty("scalafixVersion"); |
| 135 | + String scalaVersion = properties.getProperty(scalaVersionKey); |
| 136 | + if (scalafixVersion == null || scalaVersion == null) |
| 137 | + throw new ScalafixException("Failed to lookup versions from '" + propertiesPath + "'"); |
| 138 | + |
| 139 | + List<URL> jars = ScalafixCoursier.scalafixCliJars(repositories, scalafixVersion, scalaVersion); |
| 140 | + ClassLoader parent = new ScalafixInterfacesClassloader(Scalafix.class.getClassLoader()); |
| 141 | + return classloadInstance(new URLClassLoader(jars.stream().toArray(URL[]::new), parent)); |
| 142 | + } |
62 | 143 |
|
63 | 144 | /**
|
64 | 145 | * JVM runtime reflection method helper to classload an instance of {@link Scalafix}.
|
| 146 | + * <p> |
| 147 | + * The custom classloader optionally provided with {@link ScalafixArguments#withToolClasspath} to compile and |
| 148 | + * classload external rules must have the provided classloader as ancestor to share a common loaded instance |
| 149 | + * of `scalafix-core`, and therefore must have been compiled against the same Scala binary version as |
| 150 | + * the one in the classLoader provided here. |
65 | 151 | *
|
66 |
| - * @param classLoader Classloader containing the full Scalafix classpath, including the scalafix-cli module. |
| 152 | + * @param classLoader Classloader containing the full Scalafix classpath, including the scalafix-cli module. To be |
| 153 | + * able to run advanced semantic rules using the Scala Presentation Compiler such as |
| 154 | + * ExplicitResultTypes, this Scala binary version in that classloader should match the one that |
| 155 | + * the target classpath was built with, as provided with |
| 156 | + * {@link ScalafixArguments#withScalaVersion}. |
67 | 157 | * @return An implementation of the {@link Scalafix} interface.
|
68 | 158 | * @throws ScalafixException in case of errors during classloading, most likely caused
|
69 |
| - * by an incorrect classloader argument. |
| 159 | + * by an incorrect classloader argument. |
70 | 160 | */
|
71 | 161 | static Scalafix classloadInstance(ClassLoader classLoader) throws ScalafixException {
|
72 | 162 | try {
|
|
0 commit comments