diff --git a/typescript-generator-gradle-plugin/pom.xml b/typescript-generator-gradle-plugin/pom.xml
index 43742569d..67131d82b 100644
--- a/typescript-generator-gradle-plugin/pom.xml
+++ b/typescript-generator-gradle-plugin/pom.xml
@@ -17,6 +17,17 @@
+
+ dev.gradleplugins
+ gradle-test-kit
+ 8.2.1
+ test
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.14.2
+
org.gradle
gradle-core
@@ -58,11 +69,42 @@
groovy-all
2.4.21
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
+ com.fasterxml.jackson.module
+ jackson-module-scala_2.13
+ 2.14.2
+ test
+
cz.habarta.typescript-generator
typescript-generator-core
3.2-SNAPSHOT
+
+ com.fasterxml.jackson.module
+ jackson-module-kotlin
+ 2.14.2
+ test
+
+
+
+ commons-io
+ commons-io
+ 2.11.0
+
+
+ org.sonatype.sisu
+ sisu-inject-bean
+ 2.3.0
+ compile
+
+
diff --git a/typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/GenerateTask.java b/typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/GenerateTask.java
index 12b282619..6f69d4ef4 100644
--- a/typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/GenerateTask.java
+++ b/typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/GenerateTask.java
@@ -25,21 +25,24 @@
import cz.habarta.typescript.generator.TypeScriptOutputKind;
import cz.habarta.typescript.generator.util.Utils;
import java.io.File;
+import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
+import javax.inject.Inject;
import org.gradle.api.DefaultTask;
-import org.gradle.api.Task;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.ProjectLayout;
+import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.TaskAction;
+import org.jetbrains.annotations.NotNull;
-public class GenerateTask extends DefaultTask {
- public String outputFile;
+public abstract class GenerateTask extends DefaultTask {
+
public TypeScriptFileType outputFileType;
public TypeScriptOutputKind outputKind;
public String module;
@@ -130,6 +133,20 @@ public class GenerateTask extends DefaultTask {
public List jackson2Modules;
public Logger.Level loggingLevel;
+ public String projectName;
+
+ private final ProjectLayout projectLayout;
+
+ @Classpath
+ abstract ConfigurableFileCollection getClasspath();
+
+ public String outputFile;
+
+ @Inject
+ public GenerateTask(ProjectLayout projectLayout) {
+ this.projectLayout = projectLayout;
+ }
+
private Settings createSettings(URLClassLoader classLoader) {
final Settings settings = new Settings();
if (outputFileType != null) {
@@ -200,7 +217,7 @@ private Settings createSettings(URLClassLoader classLoader) {
settings.primitivePropertiesRequired = primitivePropertiesRequired;
settings.generateInfoJson = generateInfoJson;
settings.generateNpmPackageJson = generateNpmPackageJson;
- settings.npmName = npmName == null && generateNpmPackageJson ? getProject().getName() : npmName;
+ settings.npmName = npmName == null && generateNpmPackageJson ? projectName : npmName;
settings.npmVersion = npmVersion == null && generateNpmPackageJson ? settings.getDefaultNpmVersion() : npmVersion;
settings.npmTypescriptVersion = npmTypescriptVersion;
settings.npmBuildScript = npmBuildScript;
@@ -215,6 +232,7 @@ private Settings createSettings(URLClassLoader classLoader) {
return settings;
}
+
@TaskAction
public void generate() throws Exception {
if (outputKind == null) {
@@ -226,55 +244,54 @@ public void generate() throws Exception {
TypeScriptGenerator.setLogger(new Logger(loggingLevel));
TypeScriptGenerator.printVersion();
+ try (URLClassLoader classLoader = createClassloader()) {
+ final Settings settings = createSettings(classLoader);
+ final Input.Parameters parameters = parameters(classLoader, settings);
+ File finalOutputFile = calculateOutputFile(settings);
+ settings.validateFileName(finalOutputFile);
+ new TypeScriptGenerator(settings).generateTypeScript(Input.from(parameters), Output.to(finalOutputFile));
+ }
+ }
+ @NotNull
+ private URLClassLoader createClassloader() throws MalformedURLException {
// class loader
final Set urls = new LinkedHashSet<>();
- for (Task task : getProject().getTasks()) {
- if (task.getName().startsWith("compile") && !task.getName().startsWith("compileTest")) {
- for (File file : task.getOutputs().getFiles()) {
- urls.add(file.toURI().toURL());
- }
- }
+ for (File file : getClasspath()) {
+ urls.add(file.toURI().toURL());
}
- urls.addAll(getFilesFromConfiguration("compileClasspath"));
-
- try (URLClassLoader classLoader = Settings.createClassLoader(getProject().getName(), urls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader())) {
-
- final Settings settings = createSettings(classLoader);
-
- final Input.Parameters parameters = new Input.Parameters();
- parameters.classNames = classes;
- parameters.classNamePatterns = classPatterns;
- parameters.classesWithAnnotations = classesWithAnnotations;
- parameters.classesImplementingInterfaces = classesImplementingInterfaces;
- parameters.classesExtendingClasses = classesExtendingClasses;
- parameters.jaxrsApplicationClassName = classesFromJaxrsApplication;
- parameters.automaticJaxrsApplication = classesFromAutomaticJaxrsApplication;
- parameters.isClassNameExcluded = settings.getExcludeFilter();
- parameters.classLoader = classLoader;
- parameters.scanningAcceptedPackages = scanningAcceptedPackages;
- parameters.debug = loggingLevel == Logger.Level.Debug;
+ return Settings.createClassLoader(projectName, urls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader());
+ }
- final File output = outputFile != null
- ? getProject().file(outputFile)
- : new File(new File(getProject().getBuildDir(), "typescript-generator"), getProject().getName() + settings.getExtension());
- settings.validateFileName(output);
+ @NotNull
+ private File calculateOutputFile(Settings settings) {
+ return new File(outputFile != null ? outputFile : defaultOutputFile(settings));
+ }
- new TypeScriptGenerator(settings).generateTypeScript(Input.from(parameters), Output.to(output));
- }
+ @NotNull
+ private String defaultOutputFile(Settings settings) {
+ return projectLayout.getBuildDirectory().dir("typescript-generator").get().file(projectName + ext(settings.outputFileType)).getAsFile().getAbsolutePath();
}
- private List getFilesFromConfiguration(String configuration) {
- try {
- final List urls = new ArrayList<>();
- for (File file : getProject().getConfigurations().getAt(configuration).getFiles()) {
- urls.add(file.toURI().toURL());
- }
- return urls;
- } catch (Exception e) {
- TypeScriptGenerator.getLogger().warning(String.format("Cannot get file names from configuration '%s': %s", configuration, e.getMessage()));
- return Collections.emptyList();
- }
+ @NotNull
+ private Input.Parameters parameters(URLClassLoader classLoader, Settings settings) {
+ final Input.Parameters parameters = new Input.Parameters();
+ parameters.classNames = classes;
+ parameters.classNamePatterns = classPatterns;
+ parameters.classesWithAnnotations = classesWithAnnotations;
+ parameters.classesImplementingInterfaces = classesImplementingInterfaces;
+ parameters.classesExtendingClasses = classesExtendingClasses;
+ parameters.jaxrsApplicationClassName = classesFromJaxrsApplication;
+ parameters.automaticJaxrsApplication = classesFromAutomaticJaxrsApplication;
+ parameters.isClassNameExcluded = settings.getExcludeFilter();
+ parameters.classLoader = classLoader;
+ parameters.scanningAcceptedPackages = scanningAcceptedPackages;
+ parameters.debug = loggingLevel == Logger.Level.Debug;
+ return parameters;
}
+ private String ext(TypeScriptFileType outputFileType) {
+ return outputFileType.equals(TypeScriptFileType.implementationFile) ? ".ts" : ".d.ts";
+ }
}
+
diff --git a/typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/TypeScriptGeneratorPlugin.java b/typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/TypeScriptGeneratorPlugin.java
index d9e0eb8c4..23a4f43b2 100644
--- a/typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/TypeScriptGeneratorPlugin.java
+++ b/typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/TypeScriptGeneratorPlugin.java
@@ -1,7 +1,6 @@
package cz.habarta.typescript.generator.gradle;
-import java.util.Collections;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
@@ -11,14 +10,13 @@ public class TypeScriptGeneratorPlugin implements Plugin {
@Override
public void apply(Project project) {
- final Task generateTsTask = project.task(Collections.singletonMap(Task.TASK_TYPE, GenerateTask.class), "generateTypeScript");
-
+ GenerateTask generateTsTask = project.getTasks().create("generateTypeScript", GenerateTask.class);
+ generateTsTask.projectName = project.getName();
for (Task task : project.getTasks()) {
if (task.getName().startsWith("compile") && !task.getName().startsWith("compileTest")) {
generateTsTask.dependsOn(task.getName());
- generateTsTask.getInputs().files(task);
}
}
- }
+ }
}
diff --git a/typescript-generator-gradle-plugin/src/test/java/cz/habarta/typescript/generator/gradle/BuildLogicFunctionalTest.java b/typescript-generator-gradle-plugin/src/test/java/cz/habarta/typescript/generator/gradle/BuildLogicFunctionalTest.java
new file mode 100644
index 000000000..e5d0b4dbb
--- /dev/null
+++ b/typescript-generator-gradle-plugin/src/test/java/cz/habarta/typescript/generator/gradle/BuildLogicFunctionalTest.java
@@ -0,0 +1,107 @@
+package cz.habarta.typescript.generator.gradle;
+
+import com.google.common.io.Files;
+import static cz.habarta.typescript.generator.gradle.GradlePluginClasspathProvider.getClasspath;
+import java.io.BufferedWriter;
+import java.io.File;
+import static java.io.File.pathSeparator;
+import static java.io.File.separator;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import org.apache.commons.io.FileUtils;
+import org.gradle.testkit.runner.BuildResult;
+import org.gradle.testkit.runner.GradleRunner;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+public class BuildLogicFunctionalTest {
+
+ String sampleGradle = "../../typescript-generator/sample-gradle";
+ File sourceDir = new File(sampleGradle + "/src");
+ private File testKitDir = Files.createTempDir();
+
+ @TempDir
+ File testProjectDir;
+ private File buildFile;
+ private File classpathFile;
+
+ @BeforeEach
+ public void setup() {
+ buildFile = new File(testProjectDir, "build.gradle");
+ classpathFile = new File(buildGradleTemplate().getParent(), "plugin-under-test-metadata.properties");
+ }
+
+ @Test
+ public void shouldWorkWithConfigurationCache() throws IOException, NoSuchFieldException, IllegalAccessException {
+ try {
+ String classpath = "implementation-classpath=" + String.join(pathSeparator, getClasspath(testProjectDir));
+ System.out.println("Classpath: " + classpath);
+ writeFile(classpathFile, classpath);
+ FileUtils.copyToFile(buildGradleTemplateUrl().openStream(), buildFile);
+ FileUtils.copyDirectory(sourceDir, new File(testProjectDir, "src"));
+
+ assertTrue(runGradle("assemble").getOutput().contains("BUILD SUCCESSFUL"));
+ BuildResult generateTypeScript = runGradle("generateTypeScript");
+ assertTrue(generateTypeScript.getOutput().contains("BUILD SUCCESSFUL"));
+
+ String testFileName = testProjectDir.getName() + ".d.ts";
+ String testFilePath = testProjectDir + separator + "build" + separator + "typescript-generator" + separator + testFileName;
+ String schema = FileUtils.readFileToString(new File(testFilePath), StandardCharsets.UTF_8);
+ assertThat(schema, containsString("export interface Person {"));
+ assertThat(schema, containsString("export interface PersonGroovy {"));
+ assertThat(schema, containsString("export interface PersonKt {"));
+ assertThat(schema, containsString("export interface PersonScala {"));
+ } finally {
+ deleteGradleDir(testKitDir);
+ }
+ }
+
+ private static void deleteGradleDir(File testKitDir) {
+ try {
+ FileUtils.deleteDirectory(testKitDir);
+ }catch (IOException e)
+ {
+ //might happen on Windows but should be ignored
+ }
+ }
+
+ private BuildResult runGradle(String task) {
+ System.setProperty("org.gradle.testkit.dir", testKitDir.getAbsolutePath());
+ return GradleRunner.create()
+ .withProjectDir(testProjectDir)
+ .withGradleVersion("8.2.1")
+ .withPluginClasspath()
+ .withArguments(
+ "--stacktrace",
+ "--info",
+ "--configuration-cache",
+ task
+ )
+ .build();
+ }
+
+ @NotNull
+ private static File buildGradleTemplate() {
+ return new File(buildGradleTemplateUrl().getPath());
+ }
+
+ @Nullable
+ private static URL buildGradleTemplateUrl() {
+ return BuildLogicFunctionalTest.class.getResource("/build.gradle.template");
+ }
+
+ private void writeFile(File destination, String content) throws IOException {
+ try (BufferedWriter output = new BufferedWriter(new FileWriter(destination))) {
+ output.write(content);
+ }
+ }
+}
+
diff --git a/typescript-generator-gradle-plugin/src/test/java/cz/habarta/typescript/generator/gradle/GradlePluginClasspathProvider.java b/typescript-generator-gradle-plugin/src/test/java/cz/habarta/typescript/generator/gradle/GradlePluginClasspathProvider.java
new file mode 100644
index 000000000..d7fabf2bc
--- /dev/null
+++ b/typescript-generator-gradle-plugin/src/test/java/cz/habarta/typescript/generator/gradle/GradlePluginClasspathProvider.java
@@ -0,0 +1,102 @@
+package cz.habarta.typescript.generator.gradle;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.commons.io.FileUtils;
+import org.jetbrains.annotations.NotNull;
+import sun.misc.Unsafe;
+
+public class GradlePluginClasspathProvider {
+
+ public static List getClasspath(File projectDir) throws NoSuchFieldException, IllegalAccessException {
+ List list = GradlePluginClasspathProvider.getUrls(ClassLoader.getSystemClassLoader())
+ .stream().filter(file -> !gradleDependency(file))
+ .collect(Collectors.toList());
+ list.addAll(buildDirs(projectDir));
+ return list.stream().map(file -> path(file)).collect(Collectors.toList());
+ }
+
+ private static boolean gradleDependency(File file) {
+ return file.getAbsolutePath().contains(String.format("%sorg%sgradle%s", File.separator, File.separator, File.separator));
+ }
+
+ @NotNull
+ private static String path(File file) {
+ String path = file.getAbsolutePath();
+ return path.replace("\\", "\\\\");
+ }
+
+ public static List getUrls(ClassLoader classLoader) throws NoSuchFieldException, IllegalAccessException {
+ System.out.println(classLoader.getClass().getName());
+ if (classLoader instanceof URLClassLoader) {
+ return (Arrays.asList(((URLClassLoader) classLoader).getURLs())).stream().map(URL -> toFile(URL)).collect(Collectors.toList());
+ }
+
+ // jdk9
+ if (classLoader.getClass().getName().startsWith("jdk.internal.loader.ClassLoaders$")) {
+ Field field = Unsafe.class.getDeclaredField("theUnsafe");
+ field.setAccessible(true);
+ Unsafe unsafe = (Unsafe) field.get(null);
+
+ // jdk.internal.loader.ClassLoaders.AppClassLoader.ucp
+ Field ucpField = classLoader.getClass().getSuperclass().getDeclaredField("ucp");
+ long ucpFieldOffset = unsafe.objectFieldOffset(ucpField);
+ Object ucpObject = unsafe.getObject(classLoader, ucpFieldOffset);
+
+ // jdk.internal.loader.URLClassPath.path
+ Field pathField = ucpField.getType().getDeclaredField("path");
+ long pathFieldOffset = unsafe.objectFieldOffset(pathField);
+ List path = (ArrayList) unsafe.getObject(ucpObject, pathFieldOffset);
+
+ Field mapField = ucpField.getType().getDeclaredField("lmap");
+ long mapFieldOffset = unsafe.objectFieldOffset(mapField);
+ Map map = (Map) unsafe.getObject(ucpObject, mapFieldOffset);
+ List all = new ArrayList<>();
+ all.addAll(path.stream().map(URL -> toFile(URL)).collect(Collectors.toList()));
+ all.addAll(map.keySet().stream().map(url -> toFile(asUrl(url))).collect(Collectors.toSet()));
+ return all;
+ }
+
+ return null;
+ }
+
+ @NotNull
+ private static URL asUrl(String url) {
+ try {
+ return new URL(url);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NotNull
+ private static File toFile(URL url) {
+ try {
+ return new File(url.toURI());
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ @NotNull
+ private static List buildDirs(File sampleGradleDir) {
+ List buildDirs = new ArrayList<>();
+ buildDirs.add(FileUtils.getFile(sampleGradleDir, "build", "classes", "java", "main"));
+ buildDirs.add(FileUtils.getFile(sampleGradleDir, "build", "classes", "groovy", "main"));
+ buildDirs.add(FileUtils.getFile(sampleGradleDir, "build", "classes", "scala", "main"));
+ buildDirs.add(FileUtils.getFile(sampleGradleDir, "build", "classes", "kotlin", "main"));
+
+ return buildDirs;
+ }
+
+}
\ No newline at end of file
diff --git a/typescript-generator-gradle-plugin/src/test/resources/build.gradle.template b/typescript-generator-gradle-plugin/src/test/resources/build.gradle.template
new file mode 100644
index 000000000..6ad97c0a6
--- /dev/null
+++ b/typescript-generator-gradle-plugin/src/test/resources/build.gradle.template
@@ -0,0 +1,62 @@
+
+buildscript {
+ dependencies {
+ classpath 'com.fasterxml.jackson.module:jackson-module-scala_2.13:2.14.2'
+ }
+}
+
+plugins {
+ id 'java'
+ id "org.jetbrains.kotlin.jvm" version "1.9.0"
+ id 'scala'
+ id 'groovy'
+ id 'cz.habarta.typescript-generator'
+
+}
+
+version = '3.0'
+sourceCompatibility = 11
+targetCompatibility = 11
+
+repositories {
+ mavenCentral()
+}
+
+tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
+ kotlinOptions {
+ jvmTarget = '11'
+ }
+}
+
+dependencies {
+ implementation 'com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.14.2'
+ implementation 'org.codehaus.groovy:groovy-all:3.0.16'
+ implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.10'
+ implementation 'org.scala-lang:scala-library:2.13.10'
+ implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.14.2'
+}
+
+generateTypeScript {
+ classes = [
+ 'cz.habarta.typescript.generator.sample.Person',
+ 'cz.habarta.typescript.generator.sample.PersonGroovy',
+ 'cz.habarta.typescript.generator.sample.PersonKt',
+ 'cz.habarta.typescript.generator.sample.PersonScala',
+ ]
+ jsonLibrary = 'jackson2'
+ outputKind = 'module'
+ excludeClasses = [
+ 'groovy.lang.GroovyObject',
+ 'groovy.lang.MetaClass',
+ 'java.io.Serializable',
+ 'scala.Equals',
+ 'scala.Product',
+ 'scala.Serializable',
+ ]
+ jackson2Modules = [
+ 'com.fasterxml.jackson.module.scala.DefaultScalaModule',
+ 'com.fasterxml.jackson.module.kotlin.KotlinModule',
+ ]
+}
+
+build.dependsOn generateTypeScript