diff --git a/CHANGELOG.md b/CHANGELOG.md index d5eb2e3..a33e81b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # AtPlug releases ## [Unreleased] +### Added +- Add methods for taking `KClass` instead of just `Class` to prepare for Kotlin Multiplatform. ([#8](https://github.com/diffplug/atplug/pull/8)) ## [1.0.1] - 2022-12-31 ### Fixed diff --git a/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/PlugGeneratorJavaExecable.java b/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/PlugGeneratorJavaExecable.java deleted file mode 100644 index 176d348..0000000 --- a/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/PlugGeneratorJavaExecable.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2022 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.atplug.tooling; - - -import com.diffplug.atplug.tooling.gradle.JavaExecable; -import java.io.File; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.SortedMap; - -/** - * {@link PlugGenerator#PlugGenerator(List, Set)} in a {@link JavaExecable} form. - */ -public class PlugGeneratorJavaExecable implements JavaExecable { - // inputs - List toSearch; - Set toLinkAgainst; - - public PlugGeneratorJavaExecable(List toSearch, Set toLinkAgainst) { - this.toSearch = new ArrayList<>(toSearch); - this.toLinkAgainst = new LinkedHashSet<>(toLinkAgainst); - } - - // outputs - SortedMap atplugInf; - - public SortedMap getAtplugInf() { - return atplugInf; - } - - @Override - public void run() { - PlugGenerator metadataGen = new PlugGenerator(toSearch, toLinkAgainst); - atplugInf = metadataGen.atplugInf; - } -} diff --git a/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/PlugGeneratorJavaExecable.kt b/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/PlugGeneratorJavaExecable.kt new file mode 100644 index 0000000..a380f68 --- /dev/null +++ b/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/PlugGeneratorJavaExecable.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.atplug.tooling + +import com.diffplug.atplug.tooling.gradle.JavaExecable +import java.io.File +import java.util.* + +/** [PlugGenerator.PlugGenerator] in a [JavaExecable] form. */ +class PlugGeneratorJavaExecable(toSearch: List?, toLinkAgainst: Set?) : JavaExecable { + // inputs + var toSearch: List + var toLinkAgainst: Set + + // outputs + @JvmField var atplugInf: SortedMap? = null + + init { + this.toSearch = ArrayList(toSearch) + this.toLinkAgainst = LinkedHashSet(toLinkAgainst) + } + + override fun run() { + val metadataGen = PlugGenerator(toSearch, toLinkAgainst) + atplugInf = metadataGen.atplugInf + } +} diff --git a/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugGenerateTask.java b/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugGenerateTask.java deleted file mode 100644 index b182a10..0000000 --- a/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugGenerateTask.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2020-2022 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.atplug.tooling.gradle; - -import com.diffplug.atplug.tooling.PlugGeneratorJavaExecable; -import com.diffplug.gradle.FileMisc; -import com.diffplug.gradle.JRE; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.SortedMap; -import java.util.function.Consumer; -import java.util.jar.Attributes; -import java.util.jar.Manifest; -import java.util.stream.Collectors; -import javax.inject.Inject; -import org.gradle.api.DefaultTask; -import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.file.FileCollection; -import org.gradle.api.model.ObjectFactory; -import org.gradle.api.plugins.JavaPluginExtension; -import org.gradle.api.provider.Property; -import org.gradle.api.tasks.Classpath; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.Internal; -import org.gradle.api.tasks.JavaExec; -import org.gradle.api.tasks.Nested; -import org.gradle.api.tasks.Optional; -import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.TaskAction; -import org.gradle.jvm.toolchain.JavaLauncher; -import org.gradle.jvm.toolchain.JavaToolchainService; -import org.gradle.jvm.toolchain.JavaToolchainSpec; -import org.gradle.workers.WorkQueue; -import org.gradle.workers.WorkerExecutor; - -public abstract class PlugGenerateTask extends DefaultTask { - public PlugGenerateTask() { - this.getOutputs().upToDateWhen(unused -> { - Manifest manifest = loadManifest(); - String componentsCmd = atplugComponents(); - String componentsActual = manifest.getMainAttributes().getValue(PlugPlugin.SERVICE_COMPONENT); - return Objects.equals(componentsActual, componentsCmd); - }); - - JavaToolchainSpec spec = getProject().getExtensions().getByType(JavaPluginExtension.class).getToolchain(); - JavaToolchainService service = getProject().getExtensions().getByType(JavaToolchainService.class); - getLauncher().set(service.launcherFor(spec)); - } - - @Nested - @Optional - abstract Property getLauncher(); - - @Inject - abstract public WorkerExecutor getWorkerExecutor(); - - @Inject - abstract public ObjectFactory getFS(); - - @Classpath - @InputFiles - public abstract ConfigurableFileCollection getJarsToLinkAgainst(); - - public File resourcesFolder; - - @Internal - public File getResourcesFolder() { - return resourcesFolder; - } - - @OutputDirectory - public File getAtplugInfFolder() { - return new File(resourcesFolder, PlugPlugin.ATPLUG_INF); - } - - @InputFiles - FileCollection classesFolders; - - public FileCollection getClassesFolders() { - return classesFolders; - } - - void setClassesFolders(Iterable files) { - // if we don't copy, Gradle finds an implicit dependency which - // forces us to depend on `classes` even though we don't - List copy = new ArrayList<>(); - for (File file : files) { - copy.add(file); - } - classesFolders = getProject().files(copy); - } - - @TaskAction - public void build() throws Throwable { - // generate the metadata - SortedMap result = generate(); - - // clean out the ATPLUG-INF folder, and put the map's content into the folder - FileMisc.cleanDir(getAtplugInfFolder()); - for (Map.Entry entry : result.entrySet()) { - File serviceFile = new File(getAtplugInfFolder(), entry.getKey() + PlugPlugin.DOT_JSON); - Files.write(serviceFile.toPath(), entry.getValue().getBytes(StandardCharsets.UTF_8)); - } - - // the resources directory *needs* the Service-Component entry of the manifest to exist in order for tests to work - // so we'll get a manifest (empty if necessary, but preferably we'll load what already exists) - Manifest manifest = loadManifest(); - String componentsCmd = atplugComponents(); - String componentsActual = manifest.getMainAttributes().getValue(PlugPlugin.SERVICE_COMPONENT); - if (Objects.equals(componentsActual, componentsCmd)) { - return; - } - // make sure there is a MANIFEST_VERSION, because otherwise the manifest won't write *anything* - if (manifest.getMainAttributes().getValue(Attributes.Name.MANIFEST_VERSION) == null) { - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - } - // set the Service-Component entry - if (componentsCmd == null) { - manifest.getMainAttributes().remove(new Attributes.Name(PlugPlugin.SERVICE_COMPONENT)); - } else { - manifest.getMainAttributes().putValue(PlugPlugin.SERVICE_COMPONENT, componentsCmd); - } - // and write out the manifest - saveManifest(manifest); - } - - private File manifestFile() { - return new File(resourcesFolder, "META-INF/MANIFEST.MF"); - } - - private Manifest loadManifest() { - Manifest manifest = new Manifest(); - if (manifestFile().isFile()) { - try (InputStream input = new BufferedInputStream(Files.newInputStream(manifestFile().toPath()))) { - manifest.read(input); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - return manifest; - } - - private void saveManifest(Manifest manifest) { - FileMisc.mkdirs(manifestFile().getParentFile()); - try (OutputStream output = new BufferedOutputStream(Files.newOutputStream(manifestFile().toPath()))) { - manifest.write(output); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private SortedMap generate() { - PlugGeneratorJavaExecable input = new PlugGeneratorJavaExecable(new ArrayList<>(getClassesFolders().getFiles()), getJarsToLinkAgainst().getFiles()); - if (getLauncher().isPresent()) { - WorkQueue workQueue = getWorkerExecutor().processIsolation(workerSpec -> { - workerSpec.getClasspath().from(fromLocalClassloader()); - workerSpec.forkOptions(options -> { - options.setExecutable(getLauncher().get().getExecutablePath()); - }); - }); - return JavaExecable.exec(workQueue, input).getAtplugInf(); - } else { - input.run(); - return input.getAtplugInf(); - } - } - - private String atplugComponents() { - return atplugComponents(getAtplugInfFolder()); - } - - static String atplugComponents(File atplugInf) { - if (!atplugInf.isDirectory()) { - return null; - } else { - List serviceComponents = new ArrayList<>(); - for (File file : FileMisc.list(atplugInf)) { - if (file.getName().endsWith(PlugPlugin.DOT_JSON)) { - serviceComponents.add(PlugPlugin.ATPLUG_INF + file.getName()); - } - } - Collections.sort(serviceComponents); - return serviceComponents.stream().collect(Collectors.joining(",")); - } - } - - static Set fromLocalClassloader() { - Set files = new LinkedHashSet<>(); - Consumer> addPeerClasses = clazz -> { - try { - for (URL url : JRE.getClasspath(clazz.getClassLoader())) { - String name = url.getFile(); - if (name != null) { - files.add(new File(name)); - } - } - } catch (Exception e) { - throw new RuntimeException(e); - } - }; - // add the classes that we need - addPeerClasses.accept(PlugGeneratorJavaExecable.class); - // add the gradle API - addPeerClasses.accept(JavaExec.class); - // Needed because of Gradle API classloader hierarchy changes with 2c5adc8 in Gradle 6.7+ - addPeerClasses.accept(FileCollection.class); - return files; - } -} diff --git a/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugGenerateTask.kt b/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugGenerateTask.kt new file mode 100644 index 0000000..2990f22 --- /dev/null +++ b/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugGenerateTask.kt @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2020-2023 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.atplug.tooling.gradle + +import com.diffplug.atplug.tooling.PlugGeneratorJavaExecable +import com.diffplug.atplug.tooling.gradle.JavaExecable.Companion.exec +import com.diffplug.gradle.FileMisc +import com.diffplug.gradle.JRE +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import java.io.File +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.util.SortedMap +import java.util.jar.Attributes +import java.util.jar.Manifest +import javax.inject.Inject +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.FileCollection +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.JavaExec +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import org.gradle.jvm.toolchain.JavaLauncher +import org.gradle.jvm.toolchain.JavaToolchainService +import org.gradle.process.JavaForkOptions +import org.gradle.workers.ProcessWorkerSpec +import org.gradle.workers.WorkerExecutor + +abstract class PlugGenerateTask : DefaultTask() { + @get:Optional @get:Nested abstract val launcher: Property + + @get:Inject abstract val workerExecutor: WorkerExecutor + + @get:InputFiles @get:Classpath abstract val jarsToLinkAgainst: ConfigurableFileCollection + + @get:Internal var resourcesFolder: File? = null + + @get:OutputDirectory + val atplugInfFolder: File + get() = File(resourcesFolder, PlugPlugin.ATPLUG_INF) + + @InputFiles var classesFolders: FileCollection? = null + + init { + this.outputs.upToDateWhen { + val manifest = loadManifest() + val componentsCmd = atplugComponents(atplugInfFolder) + val componentsActual = manifest.mainAttributes.getValue(PlugPlugin.SERVICE_COMPONENT) + componentsActual == componentsCmd + } + val spec = project.extensions.getByType(JavaPluginExtension::class.java).toolchain + val service = project.extensions.getByType(JavaToolchainService::class.java) + launcher.set(service.launcherFor(spec)) + } + + fun setClassesFolders(files: Iterable) { + // if we don't copy, Gradle finds an implicit dependency which + // forces us to depend on `classes` even though we don't + val copy: MutableList = ArrayList() + for (file in files) { + copy.add(file) + } + classesFolders = project.files(copy) + } + + @TaskAction + fun build() { + // generate the metadata + val result = generate() + + // clean out the ATPLUG-INF folder, and put the map's content into the folder + FileMisc.cleanDir(atplugInfFolder) + for ((key, value) in result) { + val serviceFile = File(atplugInfFolder, key + PlugPlugin.DOT_JSON) + Files.write(serviceFile.toPath(), value.toByteArray(StandardCharsets.UTF_8)) + } + + // the resources directory *needs* the Service-Component entry of the manifest to exist in order + // for tests to work + // so we'll get a manifest (empty if necessary, but preferably we'll load what already exists) + val manifest = loadManifest() + val componentsCmd = atplugComponents(atplugInfFolder) + val componentsActual = manifest.mainAttributes.getValue(PlugPlugin.SERVICE_COMPONENT) + if (componentsActual == componentsCmd) { + return + } + // make sure there is a MANIFEST_VERSION, because otherwise the manifest won't write *anything* + if (manifest.mainAttributes.getValue(Attributes.Name.MANIFEST_VERSION) == null) { + manifest.mainAttributes[Attributes.Name.MANIFEST_VERSION] = "1.0" + } + // set the Service-Component entry + if (componentsCmd == null) { + manifest.mainAttributes.remove(Attributes.Name(PlugPlugin.SERVICE_COMPONENT)) + } else { + manifest.mainAttributes.putValue(PlugPlugin.SERVICE_COMPONENT, componentsCmd) + } + // and write out the manifest + saveManifest(manifest) + } + + private fun manifestFile(): File = File(resourcesFolder, "META-INF/MANIFEST.MF") + + private fun loadManifest(): Manifest { + val manifest = Manifest() + if (manifestFile().isFile) { + BufferedInputStream(Files.newInputStream(manifestFile().toPath())).use { input -> + manifest.read(input) + } + } + return manifest + } + + private fun saveManifest(manifest: Manifest) { + FileMisc.mkdirs(manifestFile().parentFile) + BufferedOutputStream(Files.newOutputStream(manifestFile().toPath())).use { output -> + manifest.write(output) + } + } + + private fun generate(): SortedMap { + val input = + PlugGeneratorJavaExecable(ArrayList(classesFolders!!.files), jarsToLinkAgainst.files) + return if (launcher.isPresent) { + val workQueue = + workerExecutor.processIsolation { workerSpec: ProcessWorkerSpec -> + workerSpec.classpath.from(fromLocalClassloader()) + workerSpec.forkOptions { options: JavaForkOptions -> + options.setExecutable(launcher.get().executablePath) + } + } + exec(workQueue, input).atplugInf!! + } else { + input.run() + input.atplugInf!! + } + } + + companion object { + fun atplugComponents(atplugInf: File): String? { + return if (!atplugInf.isDirectory) null + else { + val serviceComponents: MutableList = ArrayList() + for (file in FileMisc.list(atplugInf)) { + if (file.name.endsWith(PlugPlugin.DOT_JSON)) { + serviceComponents.add(PlugPlugin.ATPLUG_INF + file.name) + } + } + serviceComponents.sort() + serviceComponents.joinToString(",") + } + } + + fun fromLocalClassloader(): Set { + val files = mutableSetOf() + val addPeerClasses = { clazz: Class<*> -> + for (url in JRE.getClasspath(clazz.classLoader)) { + val name = url.file + if (name != null) { + files.add(File(name)) + } + } + } + // add the classes that we need + addPeerClasses(PlugGeneratorJavaExecable::class.java) + // add the gradle API + addPeerClasses(JavaExec::class.java) + // Needed because of Gradle API classloader hierarchy changes with 2c5adc8 in Gradle 6.7+ + addPeerClasses(FileCollection::class.java) + return files + } + } +} diff --git a/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugPlugin.java b/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugPlugin.java deleted file mode 100644 index 929a69a..0000000 --- a/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugPlugin.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2020-2022 DiffPlug - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.diffplug.atplug.tooling.gradle; - -import java.io.File; -import java.io.Serializable; -import java.util.Arrays; -import org.gradle.api.Action; -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.UnknownTaskException; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.plugins.JavaPluginExtension; -import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.TaskProvider; -import org.gradle.jvm.tasks.Jar; - -/** - * `plugGenerate` task uses `@Plug` to generate files - * in `src/main/resources/ATPLUG-INF` as a dependency of - * `processResources`. - */ -public class PlugPlugin implements Plugin { - static final String GENERATE = "plugGenerate"; - - static final String SERVICE_COMPONENT = "AtPlug-Component"; - static final String DOT_JSON = ".json"; - static final String ATPLUG_INF = "ATPLUG-INF/"; - - @Override - public void apply(Project project) { - // get the classes we're compiling - project.getPlugins().apply(JavaPlugin.class); - JavaPluginExtension javaExtension = project.getExtensions().getByType(JavaPluginExtension.class); - SourceSet main = javaExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); - - Dependency dep = project.getDependencies().create("org.jetbrains.kotlin:kotlin-reflect:1.8.0"); - Configuration plugGenConfig = project.getConfigurations().create("plugGenerate", plugGen -> { - plugGen.extendsFrom(project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); - plugGen.getDependencies().add(dep); - }); - - // jar --dependsOn--> plugGenerate - TaskProvider generateTask = project.getTasks().register(GENERATE, PlugGenerateTask.class, task -> { - task.setClassesFolders(main.getOutput().getClassesDirs()); - task.getJarsToLinkAgainst().setFrom(plugGenConfig); - task.resourcesFolder = main.getResources().getSourceDirectories().getSingleFile(); - // dep on java - for (String taskName : Arrays.asList( - "compileJava", - "compileKotlin")) { - try { - task.dependsOn(project.getTasks().named(taskName)); - } catch (UnknownTaskException e) { - // not a problem - } - } - }); - project.getTasks().named(JavaPlugin.JAR_TASK_NAME).configure(jarTaskUntyped -> { - Jar jarTask = (Jar) jarTaskUntyped; - PlugGenerateTask metadataTask = generateTask.get(); - jarTask.getInputs().dir(metadataTask.getAtplugInfFolder()); - jarTask.doFirst("Set " + PlugPlugin.SERVICE_COMPONENT + " header", new SetServiceComponentHeader(metadataTask.getAtplugInfFolder())); - }); - project.getTasks().named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME).configure(t -> t.dependsOn(generateTask)); - } - - static class SetServiceComponentHeader implements Serializable, Action { - private final File atplugInfFolder; - - SetServiceComponentHeader(File atplugInfFolder) { - this.atplugInfFolder = atplugInfFolder; - } - - @Override - public void execute(Task task) { - String serviceComponents = PlugGenerateTask.atplugComponents(atplugInfFolder); - Jar jarTask = (Jar) task; - if (serviceComponents == null) { - jarTask.getManifest().getAttributes().remove(PlugPlugin.SERVICE_COMPONENT); - } else { - jarTask.getManifest().getAttributes().put(PlugPlugin.SERVICE_COMPONENT, serviceComponents); - } - } - } -} diff --git a/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugPlugin.kt b/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugPlugin.kt new file mode 100644 index 0000000..c5c5ab6 --- /dev/null +++ b/atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugPlugin.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020-2023 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.atplug.tooling.gradle + +import java.io.File +import java.io.Serializable +import org.gradle.api.Action +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.UnknownTaskException +import org.gradle.api.artifacts.Configuration +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.SourceSet +import org.gradle.jvm.tasks.Jar + +/** + * `plugGenerate` task uses `@Plug` to generate files in `src/main/resources/ATPLUG-INF` as a + * dependency of `processResources`. + */ +class PlugPlugin : Plugin { + override fun apply(project: Project) { + // get the classes we're compiling + project.plugins.apply(JavaPlugin::class.java) + val javaExtension = project.extensions.getByType(JavaPluginExtension::class.java) + val main = javaExtension.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME) + val dep = project.dependencies.create("org.jetbrains.kotlin:kotlin-reflect:1.8.20") + val plugGenConfig = + project.configurations.create("plugGenerate") { plugGen: Configuration -> + plugGen.extendsFrom( + project.configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)) + plugGen.dependencies.add(dep) + } + + // jar --dependsOn--> plugGenerate + val generateTask = + project.tasks.register(GENERATE, PlugGenerateTask::class.java) { + task: PlugGenerateTask -> + task.setClassesFolders(main.output.classesDirs) + task.jarsToLinkAgainst.setFrom(plugGenConfig) + task.resourcesFolder = main.resources.sourceDirectories.singleFile + // dep on java + for (taskName in mutableListOf("compileJava", "compileKotlin")) { + try { + task.dependsOn(project.tasks.named(taskName)) + } catch (e: UnknownTaskException) { + // not a problem + } + } + } + project.tasks.named(JavaPlugin.JAR_TASK_NAME).configure { jarTaskUntyped: Task -> + val jarTask = jarTaskUntyped as Jar + val metadataTask = generateTask.get() + jarTask.inputs.dir(metadataTask.atplugInfFolder) + jarTask.doFirst( + "Set " + SERVICE_COMPONENT + " header", + SetServiceComponentHeader(metadataTask.atplugInfFolder)) + } + project.tasks.named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME).configure { t: Task -> + t.dependsOn(generateTask) + } + } + + internal class SetServiceComponentHeader(private val atplugInfFolder: File) : + Serializable, Action { + override fun execute(task: Task) { + val serviceComponents = PlugGenerateTask.atplugComponents(atplugInfFolder) + val jarTask = task as Jar + if (serviceComponents == null) { + jarTask.manifest.attributes.remove(SERVICE_COMPONENT) + } else { + jarTask.manifest.attributes[SERVICE_COMPONENT] = serviceComponents + } + } + } + + companion object { + const val GENERATE = "plugGenerate" + const val SERVICE_COMPONENT = "AtPlug-Component" + const val DOT_JSON = ".json" + const val ATPLUG_INF = "ATPLUG-INF/" + } +} diff --git a/atplug-plugin-gradle/src/test/java/com/diffplug/atplug/tooling/PlugGeneratorTest.kt b/atplug-plugin-gradle/src/test/java/com/diffplug/atplug/tooling/PlugGeneratorTest.kt index 8df8173..3c09cc7 100644 --- a/atplug-plugin-gradle/src/test/java/com/diffplug/atplug/tooling/PlugGeneratorTest.kt +++ b/atplug-plugin-gradle/src/test/java/com/diffplug/atplug/tooling/PlugGeneratorTest.kt @@ -23,7 +23,7 @@ class PlugGeneratorTest : ResourceHarness() { true, listOf( "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1", - "org.jetbrains.kotlin:kotlin-reflect:1.8.0")) + "org.jetbrains.kotlin:kotlin-reflect:1.8.20")) val atplug_runtime = mutableSetOf(findRuntimeJar()) atplug_runtime.addAll(transitives) return atplug_runtime diff --git a/atplug-runtime/src/main/java/com/diffplug/atplug/PlugInstanceMap.kt b/atplug-runtime/src/main/java/com/diffplug/atplug/PlugInstanceMap.kt index 7ccc181..7d4cd57 100644 --- a/atplug-runtime/src/main/java/com/diffplug/atplug/PlugInstanceMap.kt +++ b/atplug-runtime/src/main/java/com/diffplug/atplug/PlugInstanceMap.kt @@ -6,6 +6,8 @@ */ package com.diffplug.atplug +import kotlin.reflect.KClass + class PlugInstanceMap { internal val descriptorMap = mutableMapOf>() internal val instanceMap = mutableMapOf() @@ -15,9 +17,13 @@ class PlugInstanceMap { descriptors.add(descriptor) } - fun putInstance(clazz: Class, descriptor: PlugDescriptor, instance: T) { - putDescriptor(clazz.name, descriptor) - instanceMap[descriptor] = instance!! + fun putInstance(clazz: KClass, descriptor: PlugDescriptor, instance: T) { + putDescriptor(clazz.qualifiedName!!, descriptor) + instanceMap[descriptor] = instance + } + + fun putInstance(clazz: Class, descriptor: PlugDescriptor, instance: T) { + putInstance(clazz.kotlin, descriptor, instance) } fun instanceFor(plugDescriptor: PlugDescriptor) = instanceMap[plugDescriptor] diff --git a/atplug-runtime/src/main/java/com/diffplug/atplug/PlugRegistry.kt b/atplug-runtime/src/main/java/com/diffplug/atplug/PlugRegistry.kt index b9cfe49..5d59539 100644 --- a/atplug-runtime/src/main/java/com/diffplug/atplug/PlugRegistry.kt +++ b/atplug-runtime/src/main/java/com/diffplug/atplug/PlugRegistry.kt @@ -13,20 +13,31 @@ import java.net.URL import java.nio.charset.StandardCharsets import java.util.jar.Manifest import java.util.zip.ZipException +import kotlin.reflect.KClass interface PlugRegistry { - fun registerSocket(socketClass: Class, socketOwner: SocketOwner) + fun registerSocket(socketClass: KClass, socketOwner: SocketOwner) - fun instantiatePlug(socketClass: Class, plugDescriptor: PlugDescriptor): T + fun instantiatePlug(socketClass: KClass, plugDescriptor: PlugDescriptor): T + + fun registerSocket(socketClass: Class, socketOwner: SocketOwner) { + registerSocket(socketClass.kotlin, socketOwner) + } + + fun instantiatePlug(socketClass: Class, plugDescriptor: PlugDescriptor): T = + instantiatePlug(socketClass.kotlin, plugDescriptor) companion object { private val instance: Lazy = lazy { Eager() } - internal fun registerSocket(socketClass: Class, socketOwner: SocketOwner) { + internal fun registerSocket(socketClass: Class, socketOwner: SocketOwner) { instance.value.registerSocket(socketClass, socketOwner) } - internal fun instantiatePlug(socketClass: Class, plugDescriptor: PlugDescriptor): T { + internal fun instantiatePlug( + socketClass: Class, + plugDescriptor: PlugDescriptor + ): T { return instance.value.instantiatePlug(socketClass, plugDescriptor) } @@ -113,15 +124,18 @@ interface PlugRegistry { return PlugDescriptor.fromJson(serviceFileContent) } - override fun registerSocket(socketClass: Class, socketOwner: SocketOwner) { + override fun registerSocket(socketClass: KClass, socketOwner: SocketOwner) { synchronized(this) { - val prevOwner = owners.put(socketClass.name, socketOwner) + val prevOwner = owners.put(socketClass.qualifiedName!!, socketOwner) assert(prevOwner == null) { "Multiple owners registered for $socketClass" } - data.descriptorMap[socketClass.name]?.forEach(socketOwner::doRegister) + data.descriptorMap[socketClass.qualifiedName]?.forEach(socketOwner::doRegister) } } - override fun instantiatePlug(socketClass: Class, plugDescriptor: PlugDescriptor): T { + override fun instantiatePlug( + socketClass: KClass, + plugDescriptor: PlugDescriptor + ): T { val value = lastHarness?.instanceFor(plugDescriptor) ?: instantiate(Class.forName(plugDescriptor.implementation)) @@ -129,7 +143,7 @@ interface PlugRegistry { return value as T } - private fun instantiate(clazz: Class): T { + private fun instantiate(clazz: Class): T { var constructor: Constructor<*>? = null for (candidate in clazz.declaredConstructors) { if (candidate.parameterCount == 0) { diff --git a/atplug-runtime/src/main/java/com/diffplug/atplug/SocketOwner.kt b/atplug-runtime/src/main/java/com/diffplug/atplug/SocketOwner.kt index 050b669..2990e2d 100644 --- a/atplug-runtime/src/main/java/com/diffplug/atplug/SocketOwner.kt +++ b/atplug-runtime/src/main/java/com/diffplug/atplug/SocketOwner.kt @@ -14,11 +14,11 @@ import java.util.function.Consumer import java.util.function.Function import java.util.function.Predicate -abstract class SocketOwner(val socketClass: Class) { +abstract class SocketOwner(val socketClass: Class) { abstract fun metadata(plug: T): Map fun asDescriptor(plug: T) = - PlugDescriptor(plug!!::class.java.name, socketClass.name, metadata(plug)).toJson() + PlugDescriptor(plug::class.java.name, socketClass.name, metadata(plug)).toJson() /** * Instantiates the given plug. Already implemented by the default implementations [SingletonById] @@ -39,7 +39,7 @@ abstract class SocketOwner(val socketClass: Class) { protected abstract fun remove(plugDescriptor: PlugDescriptor) - abstract class EphemeralByDescriptor(socketClass: Class) : + abstract class EphemeralByDescriptor(socketClass: Class) : SocketOwner(socketClass) { private val descriptors = mutableMapOf() init { @@ -51,13 +51,15 @@ abstract class SocketOwner(val socketClass: Class) { protected abstract fun parse(plugDescriptor: PlugDescriptor): ParsedDescriptor - protected fun computeAgainstDescriptors(compute: Function, R>): R { + protected fun computeAgainstDescriptors( + compute: Function, R> + ): R { synchronized(this) { return compute.apply(descriptors.keys) } } - protected fun forEachDescriptor(forEach: Consumer) { + protected fun forEachDescriptor(forEach: Consumer) { synchronized(this) { descriptors.keys.forEach(forEach) } } @@ -85,8 +87,7 @@ abstract class SocketOwner(val socketClass: Class) { predicateInstance: Predicate ): T? { synchronized(this) { - return descriptors - .keys + return descriptors.keys .filter { predicateDescriptor.test(it) } .sortedWith(order) .map { instantiatePlug(descriptors[it]!!) } @@ -120,7 +121,7 @@ abstract class SocketOwner(val socketClass: Class) { open fun removeHook(plugDescriptor: PlugDescriptor) {} } - abstract class SingletonById(socketClass: Class) : SocketOwner(socketClass) { + abstract class SingletonById(socketClass: Class) : SocketOwner(socketClass) { private val descriptorById = mutableMapOf() private val singletonById = mutableMapOf() init { @@ -179,7 +180,7 @@ abstract class SocketOwner(val socketClass: Class) { companion object { const val KEY_ID = "id" - fun metadataGeneratorFor(socketClass: Class): Function { + fun metadataGeneratorFor(socketClass: Class): Function { var firstAttempt: Throwable? = null try { val socketField = socketClass.getDeclaredField("socket") @@ -204,7 +205,7 @@ abstract class SocketOwner(val socketClass: Class) { } } - private fun generatorForSocket(socket: SocketOwner): Function { + private fun generatorForSocket(socket: SocketOwner): Function { return Function { plug -> try { socket.asDescriptor(plug) @@ -212,14 +213,14 @@ abstract class SocketOwner(val socketClass: Class) { if (rootCause(e) is ClassNotFoundException) { throw RuntimeException( "Unable to generate metadata for " + - plug!!::class.java + + plug::class.java + ", missing transitive dependency " + rootCause(e).message, e) } else { throw RuntimeException( "Unable to generate metadata for " + - plug!!::class.java + + plug::class.java + ", make sure that its metadata methods return simple constants: " + e.message, e) diff --git a/atplug-test-harness/src/main/java/com/diffplug/atplug/PlugHarness.kt b/atplug-test-harness/src/main/java/com/diffplug/atplug/PlugHarness.kt index 5a3640e..033ab90 100644 --- a/atplug-test-harness/src/main/java/com/diffplug/atplug/PlugHarness.kt +++ b/atplug-test-harness/src/main/java/com/diffplug/atplug/PlugHarness.kt @@ -7,16 +7,19 @@ package com.diffplug.atplug import java.lang.AutoCloseable +import kotlin.reflect.KClass class PlugHarness { var map = PlugInstanceMap() - fun add(clazz: Class, instance: T): PlugHarness { - val descriptor = SocketOwner.metadataGeneratorFor(clazz).apply(instance) + fun add(clazz: KClass, instance: T): PlugHarness { + val descriptor = SocketOwner.metadataGeneratorFor(clazz.java).apply(instance) map.putInstance(clazz, PlugDescriptor.fromJson(descriptor), instance) return this } + fun add(clazz: Class, instance: T): PlugHarness = add(clazz.kotlin, instance) + fun start(): AutoCloseable { PlugRegistry.setHarness(map) return AutoCloseable { PlugRegistry.setHarness(null) } diff --git a/build.gradle b/build.gradle index b857a18..09723bc 100644 --- a/build.gradle +++ b/build.gradle @@ -11,10 +11,17 @@ allprojects { } apply from: 干.file('base/sonatype.gradle') -String VER_JUNIT_JUPITER = '5.9.1' +String VER_JUNIT_JUPITER = '5.9.2' subprojects { apply from: 干.file('base/java.gradle') apply from: 干.file('base/kotlin.gradle') +// kotlin { +// sourceSets.all { +// languageSettings { +// languageVersion = "2.0" +// } +// } +// } apply from: 干.file('spotless/java.gradle') apply plugin: 'java-library' @@ -28,22 +35,22 @@ subprojects { ext.maven_name = 'AtPlug runtime' apply plugin: 'kotlinx-serialization' dependencies { - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0" } } else if (it.name == 'atplug-test-harness') { ext.maven_name='AtPlug test harness' dependencies { compileOnly "org.junit.jupiter:junit-jupiter-api:5.0.0" - implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0" + implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.20" implementation project(':atplug-runtime') } } else if (it.name == 'atplug-plugin-gradle') { ext.maven_name='AtPlug metadata generation Gradle plugin' apply from: 干.file('base/gradle-plugin.gradle') dependencies { - implementation "org.ow2.asm:asm:9.4" - implementation 'com.diffplug.gradle:goomph:3.40.0' - testImplementation 'org.assertj:assertj-core:3.23.1' + implementation "org.ow2.asm:asm:9.5" + implementation 'com.diffplug.gradle:goomph:3.41.0' + testImplementation 'org.assertj:assertj-core:3.24.2' } tasks.named('test') { dependsOn ':atplug-runtime:jar', ':atplug-runtime:testClasses' diff --git a/settings.gradle b/settings.gradle index cbbfb70..fa3a91b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,25 +8,25 @@ plugins { // https://github.com/diffplug/blowdryer/blob/main/CHANGELOG.md id 'com.diffplug.blowdryerSetup' version '1.7.0' // https://github.com/diffplug/spotless/blob/main/plugin-gradle/CHANGES.md - id 'com.diffplug.spotless' version '6.14.0' apply false + id 'com.diffplug.spotless' version '6.16.0' apply false // https://github.com/diffplug/spotless-changelog/blob/main/CHANGELOG.md - id 'com.diffplug.spotless-changelog' version '2.4.1' apply false + id 'com.diffplug.spotless-changelog' version '3.0.1' apply false // https://plugins.gradle.org/plugin/com.gradle.plugin-publish id 'com.gradle.plugin-publish' version '1.1.0' apply false // https://github.com/equodev/equo-ide/blob/main/plugin-gradle/CHANGELOG.md - id 'dev.equo.ide' version '0.12.1' apply false + id 'dev.equo.ide' version '0.16.0' apply false // https://github.com/gradle-nexus/publish-plugin/releases - id 'io.github.gradle-nexus.publish-plugin' version '1.1.0' apply false + id 'io.github.gradle-nexus.publish-plugin' version '1.2.0' apply false // https://plugins.gradle.org/plugin/org.jetbrains.dokka - id 'org.jetbrains.dokka' version '1.7.20' apply false + id 'org.jetbrains.dokka' version '1.8.10' apply false // https://plugins.gradle.org/plugin/org.jetbrains.kotlin.jvm - id 'org.jetbrains.kotlin.jvm' version '1.8.0' apply false + id 'org.jetbrains.kotlin.jvm' version '1.8.20' apply false // https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.serialization - id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.0' apply false + id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.20' apply false } blowdryerSetup { - github 'diffplug/blowdryer-diffplug', 'tag', '7.0.0' + github 'diffplug/blowdryer-diffplug', 'tag', '7.0.2' //devLocal '../blowdryer-diffplug' setPluginsBlockTo { it.file 'plugin.versions'