|
5 | 5 | import java.nio.file.Path; |
6 | 6 | import java.nio.file.Paths; |
7 | 7 | import java.util.ArrayList; |
8 | | -import java.util.HashMap; |
9 | 8 | import java.util.HashSet; |
10 | 9 | import java.util.List; |
11 | 10 | import java.util.Map; |
|
21 | 20 | import org.gradle.api.tasks.Input; |
22 | 21 | import org.gradle.api.tasks.SourceSet; |
23 | 22 | import org.gradle.api.tasks.SourceSetContainer; |
| 23 | +import org.gradle.api.tasks.TaskProvider; |
| 24 | +import org.gradle.api.tasks.TaskState; |
24 | 25 | import org.gradle.api.tasks.compile.CompileOptions; |
25 | 26 | import org.gradle.api.tasks.compile.JavaCompile; |
26 | 27 | import org.gradle.process.CommandLineArgumentProvider; |
@@ -138,75 +139,41 @@ private void configureProject(Project project) { |
138 | 139 | } |
139 | 140 | } |
140 | 141 |
|
141 | | - // Kotlin sources are not indexed inside kotlinc: after each Kotlin compile |
142 | | - // task, its sources and classpath are dumped for the scip-java CLI to index |
143 | | - // after the build, decoupling indexing from the build's Kotlin compiler |
144 | | - // version. The dump is a finalizer rather than an action of the compile |
145 | | - // task, because Gradle skips the compile task's actions when it is up to |
| 142 | + // Kotlin sources are not indexed inside kotlinc: after the Kotlin compile |
| 143 | + // tasks, their sources and classpath are dumped for the scip-java CLI to |
| 144 | + // index after the build, decoupling indexing from the build's Kotlin |
| 145 | + // compiler version. The dump is a finalizer rather than an action of the |
| 146 | + // compile tasks, because Gradle skips their actions when they are up to |
146 | 147 | // date or restored from the build cache, while the CLI deletes the |
147 | 148 | // targetroot before every build. The finalizer declares no outputs, so |
148 | | - // Gradle runs it every time. A rule creates it on demand, because tasks |
149 | | - // can't be registered from configureEach, and it captures only file |
150 | | - // collections rather than the compile task, so it works with the |
151 | | - // configuration cache. |
152 | | - Map<String, String> kotlinConfigTasks = new HashMap<>(); |
153 | | - project |
154 | | - .getTasks() |
155 | | - .addRule( |
156 | | - "Pattern: scipKotlinConfig<Task>: dumps a Kotlin compile task's configuration", |
157 | | - name -> { |
158 | | - String compileName = kotlinConfigTasks.get(name); |
159 | | - if (compileName == null) { |
160 | | - return; |
161 | | - } |
162 | | - Task compile = project.getTasks().getByName(compileName); |
163 | | - String compilePath = compile.getPath(); |
164 | | - // Referring to KotlinCompile directly triggers NoClassDefFoundError - |
165 | | - // the plugin classpath is murky and we deliberately don't bundle the |
166 | | - // Kotlin Gradle plugin. So we commit the sins of reflection. |
167 | | - FileCollection libraries = fileCollection(compile, "getLibraries"); |
168 | | - if (libraries == null) { |
169 | | - libraries = fileCollection(compile, "getClasspath"); |
170 | | - } |
171 | | - if (libraries == null) { |
172 | | - throw new IllegalStateException( |
173 | | - "scip-java: failed to extract Kotlin compile configuration for task '" |
174 | | - + compileName |
175 | | - + "': no libraries or classpath"); |
176 | | - } |
177 | | - FileCollection classpath = libraries; |
178 | | - FileCollection sources = compile.getInputs().getSourceFiles(); |
179 | | - // Kotlin Gradle Plugin without javaSources (pre 1.7) has none. |
180 | | - FileCollection javaSources = fileCollection(compile, "getJavaSources"); |
181 | | - FileCollection allJavaSources = javaSources != null ? javaSources : project.files(); |
182 | | - project |
183 | | - .getTasks() |
184 | | - .create( |
185 | | - name, |
186 | | - export -> |
187 | | - export.doLast( |
188 | | - ignored -> |
189 | | - writeKotlinConfig( |
190 | | - compilePath, |
191 | | - classpath, |
192 | | - sources, |
193 | | - allJavaSources, |
194 | | - sourceRoot, |
195 | | - targetRoot))); |
196 | | - }); |
| 149 | + // Gradle runs it every time. |
| 150 | + List<Task> kotlinCompileTasks = new ArrayList<>(); |
| 151 | + TaskProvider<Task> kotlinConfigs = |
| 152 | + project |
| 153 | + .getTasks() |
| 154 | + .register( |
| 155 | + "scipKotlinConfigs", |
| 156 | + export -> |
| 157 | + export.doLast( |
| 158 | + ignored -> { |
| 159 | + for (Task task : kotlinCompileTasks) { |
| 160 | + // Only tasks of this build that succeeded; up-to-date |
| 161 | + // and cached tasks count as executed. |
| 162 | + TaskState state = task.getState(); |
| 163 | + if (state.getExecuted() && state.getFailure() == null) { |
| 164 | + writeKotlinConfig(task, sourceRoot, targetRoot); |
| 165 | + } |
| 166 | + } |
| 167 | + })); |
197 | 168 | // Not limited to the plugin checks above: AGP 9's built-in Kotlin adds |
198 | 169 | // Kotlin compile tasks to Android modules without applying a Kotlin plugin. |
199 | 170 | project |
200 | 171 | .getTasks() |
201 | 172 | .configureEach( |
202 | 173 | task -> { |
203 | 174 | if (task.getClass().getSimpleName().contains("KotlinCompile")) { |
204 | | - String name = |
205 | | - "scipKotlinConfig" |
206 | | - + Character.toUpperCase(task.getName().charAt(0)) |
207 | | - + task.getName().substring(1); |
208 | | - kotlinConfigTasks.put(name, task.getName()); |
209 | | - task.finalizedBy(name); |
| 175 | + kotlinCompileTasks.add(task); |
| 176 | + task.finalizedBy(kotlinConfigs); |
210 | 177 | } |
211 | 178 | }); |
212 | 179 |
|
@@ -303,57 +270,59 @@ private static Set<String> addJavacPlugin(Project project, Object javacPluginDep |
303 | 270 | return pluginTasks; |
304 | 271 | } |
305 | 272 |
|
306 | | - private static FileCollection fileCollection(Task task, String getter) { |
307 | | - try { |
308 | | - return (FileCollection) task.getClass().getMethod(getter).invoke(task); |
309 | | - } catch (ReflectiveOperationException exc) { |
310 | | - return null; |
311 | | - } |
312 | | - } |
313 | | - |
314 | 273 | /** |
315 | 274 | * Dumps the sources and classpath of a Kotlin compile task to {@code kotlin-configs/<task>.txt}. |
316 | 275 | */ |
317 | | - private static void writeKotlinConfig( |
318 | | - String taskPath, |
319 | | - FileCollection libraries, |
320 | | - FileCollection kotlinSources, |
321 | | - FileCollection javaSources, |
322 | | - String sourceRoot, |
323 | | - String targetRoot) { |
| 276 | + private static void writeKotlinConfig(Task task, String sourceRoot, String targetRoot) { |
324 | 277 | try { |
325 | 278 | List<String> lines = new ArrayList<>(); |
326 | 279 | lines.add("sourceroot " + sourceRoot); |
| 280 | + |
| 281 | + // Referring to KotlinCompile directly triggers NoClassDefFoundError - |
| 282 | + // the plugin classpath is murky and we deliberately don't bundle the |
| 283 | + // Kotlin Gradle plugin. So we commit the sins of reflection. |
| 284 | + FileCollection libraries; |
| 285 | + try { |
| 286 | + libraries = (FileCollection) task.getClass().getMethod("getLibraries").invoke(task); |
| 287 | + } catch (ReflectiveOperationException exc) { |
| 288 | + libraries = (FileCollection) task.getClass().getMethod("getClasspath").invoke(task); |
| 289 | + } |
327 | 290 | for (File entry : libraries.getFiles()) { |
328 | 291 | lines.add("classpath " + entry.getAbsolutePath()); |
329 | 292 | } |
330 | 293 |
|
331 | 294 | int sources = 0; |
332 | | - for (File file : kotlinSources.getFiles()) { |
| 295 | + for (File file : task.getInputs().getSourceFiles().getFiles()) { |
333 | 296 | if (file.getName().endsWith(".kt") || file.getName().endsWith(".kts")) { |
334 | 297 | lines.add("source " + file.getAbsolutePath()); |
335 | 298 | sources++; |
336 | 299 | } |
337 | 300 | } |
338 | 301 | // Java sources of the same module are listed too, so Kotlin |
339 | 302 | // references to them resolve. The indexer only emits Kotlin files. |
340 | | - for (File file : javaSources.getFiles()) { |
341 | | - if (file.getName().endsWith(".java")) { |
342 | | - lines.add("source " + file.getAbsolutePath()); |
| 303 | + try { |
| 304 | + FileCollection javaSources = |
| 305 | + (FileCollection) task.getClass().getMethod("getJavaSources").invoke(task); |
| 306 | + for (File file : javaSources.getFiles()) { |
| 307 | + if (file.getName().endsWith(".java")) { |
| 308 | + lines.add("source " + file.getAbsolutePath()); |
| 309 | + } |
343 | 310 | } |
| 311 | + } catch (ReflectiveOperationException exc) { |
| 312 | + // Kotlin Gradle Plugin without javaSources (pre 1.7). |
344 | 313 | } |
345 | 314 | if (sources == 0) { |
346 | 315 | return; |
347 | 316 | } |
348 | 317 |
|
349 | 318 | Path configDir = Paths.get(targetRoot, "kotlin-configs"); |
350 | 319 | Files.createDirectories(configDir); |
351 | | - String fileName = taskPath.replaceAll("[^A-Za-z0-9]", "-") + ".txt"; |
| 320 | + String fileName = task.getPath().replaceAll("[^A-Za-z0-9]", "-") + ".txt"; |
352 | 321 | Files.write(configDir.resolve(fileName), lines); |
353 | 322 | } catch (Exception exc) { |
354 | 323 | throw new RuntimeException( |
355 | 324 | "scip-java: failed to extract Kotlin compile configuration for task '" |
356 | | - + taskPath |
| 325 | + + task.getName() |
357 | 326 | + "': " |
358 | 327 | + exc, |
359 | 328 | exc); |
|
0 commit comments