vojtechhabarta/typescript-generator

Gradle toolchain setup not respected

uwolfer opened this issue · 4 comments

In my project, I have a Gradle toolchain setup like this:

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
        vendor = JvmVendorSpec.ADOPTOPENJDK
    }
}

JAVA_HOME points to a JRE 15 (not 17 as in the toolchain setup).

That will lead in the following error:
com/example/MyClassTO has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 59.0

Once I change JAVA_HOME to point to a JRE 17, things work as expected. To me, this looks like this plugin does not respect the toolchain setup.

typescript-generator-gradle-plugin supports Gradle from version 4 while toolchains feature was added to Gradle 6.7. So I would not expect support for it anytime soon.

But maybe there could be workaround (apart from running build on newer Java version) to write a task in build.gradle that would get correct toolchain version and use it for running typescript-generator. Would anybody be able to do that?

@vojtechhabarta I can see that a new major version of the plugin is on its way. What will the minimum Gradle version supported be? If it's at least 6.7, could it be an option to add default support for the Java toolkit there? (https://docs.gradle.org/current/userguide/toolchains.html#sec:plugins)

I have created our own task to add support for Toolchain.
We were blocked by some code had to be build with a never version of the jdk than gradle was running on.

I chose to use a WorkerExecutor which can be used to run work on another java version.

@vojtechhabarta if you choose to go with a gradle 6.7 + going forward i'm happy to make a pr.

The code could be a lot simpler when the native classes can be changed. Here is what we have done:

Task

import cz.habarta.typescript.generator.ClassMapping;
import cz.habarta.typescript.generator.DateMapping;
import cz.habarta.typescript.generator.EnumMapping;
import cz.habarta.typescript.generator.IdentifierCasing;
import cz.habarta.typescript.generator.JsonLibrary;
import cz.habarta.typescript.generator.Logger;
import cz.habarta.typescript.generator.MapMapping;
import cz.habarta.typescript.generator.NullabilityDefinition;
import cz.habarta.typescript.generator.OptionalProperties;
import cz.habarta.typescript.generator.OptionalPropertiesDeclaration;
import cz.habarta.typescript.generator.RestNamespacing;
import cz.habarta.typescript.generator.Settings;
import cz.habarta.typescript.generator.StringQuotes;
import cz.habarta.typescript.generator.TypeScriptFileType;
import cz.habarta.typescript.generator.TypeScriptOutputKind;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.OutputFile;
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;

import javax.inject.Inject;


public abstract class GenerateTypescriptTask extends DefaultTask {

    @OutputFile
    public RegularFileProperty getOutputFile() {
        return outputFile;
    }

    public final RegularFileProperty outputFile;

    public final Property<TypeScriptFileType> outputFileType;
    public TypeScriptOutputKind outputKind;
    public String module;
    public String namespace;
    public boolean mapPackagesToNamespaces;
    public String umdNamespace;
    public List<ModuleDependencySerializable> moduleDependencies = new ArrayList<>();
    public List<String> classes;
    public List<String> classPatterns;
    public List<String> classesWithAnnotations;
    public List<String> classesImplementingInterfaces;
    public List<String> classesExtendingClasses;
    public String classesFromJaxrsApplication;
    public boolean classesFromAutomaticJaxrsApplication;
    public List<String> scanningAcceptedPackages;
    public List<String> excludeClasses;
    public List<String> excludeClassPatterns;
    public List<String> includePropertyAnnotations;
    public List<String> excludePropertyAnnotations;
    public JsonLibrary jsonLibrary;
    public final Jackson2ConfigurationSerializable jackson2Configuration = new Jackson2ConfigurationSerializable();
//    public final GsonConfiguration gsonConfiguration = new GsonConfigurationSerializable();
//    public final JsonbConfiguration jsonbConfiguration = new JsonbConfigurationSerializable();
    public List<String> additionalDataLibraries;
    public OptionalProperties optionalProperties;
    public OptionalPropertiesDeclaration optionalPropertiesDeclaration;
    public NullabilityDefinition nullabilityDefinition;
    public boolean declarePropertiesAsReadOnly;
    public String removeTypeNamePrefix;
    public String removeTypeNameSuffix;
    public String addTypeNamePrefix;
    public String addTypeNameSuffix;
    public List<String> customTypeNaming;
    public String customTypeNamingFunction;
    public List<String> referencedFiles;
    public List<String> importDeclarations;
    public List<String> customTypeMappings;
    public List<String> customTypeAliases;
    public DateMapping mapDate;
    public MapMapping mapMap;
    public EnumMapping mapEnum;
    public IdentifierCasing enumMemberCasing;
    public boolean nonConstEnums;
    public List<String> nonConstEnumAnnotations;
    public ClassMapping mapClasses;
    public List<String> mapClassesAsClassesPatterns;
    public boolean generateConstructors;
    public List<String> disableTaggedUnionAnnotations;
    public boolean disableTaggedUnions;
    public boolean generateReadonlyAndWriteonlyJSDocTags;
    public boolean ignoreSwaggerAnnotations;
    public boolean generateJaxrsApplicationInterface;
    public boolean generateJaxrsApplicationClient;
    public boolean generateSpringApplicationInterface;
    public boolean generateSpringApplicationClient;
    public boolean scanSpringApplication;
    public RestNamespacing restNamespacing;
    public String restNamespacingAnnotation;
    public String restResponseType;
    public String restOptionsType;
    public String customTypeProcessor;
    public boolean sortDeclarations;
    public boolean sortTypeDeclarations;
    public boolean noFileComment;
    public boolean noTslintDisable;
    public boolean noEslintDisable;
    public boolean tsNoCheck;
    public List<File> javadocXmlFiles;
    public List<String> extensionClasses;
    public List<String> extensions;
    public List<Settings.ConfiguredExtension> extensionsWithConfiguration;
    public List<String> optionalAnnotations;
    public List<String> requiredAnnotations;
    public List<String> nullableAnnotations;
    public boolean primitivePropertiesRequired;
    public boolean generateInfoJson;
    public boolean generateNpmPackageJson;
    public String npmName;
    public String npmVersion;
    public String npmTypescriptVersion;
    public String npmBuildScript;
    public List<String> npmDependencies;
    public List<String> npmDevDependencies;
    public List<String> npmPeerDependencies;
    public StringQuotes stringQuotes;
    public String indentString;
    public boolean jackson2ModuleDiscovery;
    public List<String> jackson2Modules;
    public Logger.Level loggingLevel;

    @Inject
    public GenerateTypescriptTask() {
        // Access the default toolchain
        JavaToolchainSpec toolchain = getProject().getExtensions().getByType(JavaPluginExtension.class).getToolchain();

        // acquire a provider that returns the launcher for the toolchain
        JavaToolchainService service = getProject().getExtensions().getByType(JavaToolchainService.class);
        Provider<JavaLauncher> defaultLauncher = service.launcherFor(toolchain);

        // use it as our default for the property
        getLauncher().convention(defaultLauncher);

        outputFileType = getProject().getObjects().property(TypeScriptFileType.class);
        outputFile = getProject().getObjects().fileProperty();
        Provider<String> filename = outputFileType.map(fileType -> getProject().getName() + (fileType == TypeScriptFileType.implementationFile ? ".ts" : ".d.ts"));
        outputFile.convention(getProject().getLayout().getBuildDirectory().file(filename));
    }

    @org.gradle.api.tasks.Classpath
    public abstract ConfigurableFileCollection getClasses();

    @org.gradle.api.tasks.Classpath
    public abstract ConfigurableFileCollection getClasspath();

    private InputSettings createSettings() {
        InputSettings settings = new InputSettings();
        if (outputFileType.getOrNull() != null) {
            settings.outputFileType = outputFileType.get();
        }
        settings.outputKind = outputKind;
        settings.module = module;
        settings.namespace = namespace;
        settings.mapPackagesToNamespaces = mapPackagesToNamespaces;
        settings.umdNamespace = umdNamespace;
        settings.moduleDependencies = moduleDependencies;
        settings.excludeClasses = excludeClasses;
        settings.excludeClassPatterns = excludeClassPatterns;
        settings.jsonLibrary = jsonLibrary;
        settings.jackson2Configuration = jackson2Configuration;
//        settings.gsonConfiguration = gsonConfiguration;
//        settings.jsonbConfiguration = jsonbConfiguration;
        settings.additionalDataLibraries = additionalDataLibraries;
        settings.optionalProperties = optionalProperties;
        settings.optionalPropertiesDeclaration = optionalPropertiesDeclaration;
        settings.nullabilityDefinition = nullabilityDefinition;
        settings.declarePropertiesAsReadOnly = declarePropertiesAsReadOnly;
        settings.removeTypeNamePrefix = removeTypeNamePrefix;
        settings.removeTypeNameSuffix = removeTypeNameSuffix;
        settings.addTypeNamePrefix = addTypeNamePrefix;
        settings.addTypeNameSuffix = addTypeNameSuffix;
        settings.customTypeNaming = Settings.convertToMap(customTypeNaming, "customTypeNaming");
        settings.customTypeNamingFunction = customTypeNamingFunction;
        settings.referencedFiles = referencedFiles;
        settings.importDeclarations = importDeclarations;
        settings.customTypeMappings = Settings.convertToMap(customTypeMappings, "customTypeMapping");
        settings.customTypeAliases = Settings.convertToMap(customTypeAliases, "customTypeAlias");
        settings.mapDate = mapDate;
        settings.mapMap = mapMap;
        settings.mapEnum = mapEnum;
        settings.enumMemberCasing = enumMemberCasing;
        settings.nonConstEnums = nonConstEnums;
        settings.nonConstEnumAnnotations = nonConstEnumAnnotations;
        settings.mapClasses = mapClasses;
        settings.mapClassesAsClassesPatterns = mapClassesAsClassesPatterns;
        settings.generateConstructors = generateConstructors;
        settings.disableTaggedUnionAnnotations = disableTaggedUnionAnnotations;
        settings.disableTaggedUnions = disableTaggedUnions;
        settings.generateReadonlyAndWriteonlyJSDocTags = generateReadonlyAndWriteonlyJSDocTags;
        settings.ignoreSwaggerAnnotations = ignoreSwaggerAnnotations;
        settings.generateJaxrsApplicationInterface = generateJaxrsApplicationInterface;
        settings.generateJaxrsApplicationClient = generateJaxrsApplicationClient;
        settings.generateSpringApplicationInterface = generateSpringApplicationInterface;
        settings.generateSpringApplicationClient = generateSpringApplicationClient;
        settings.scanSpringApplication = scanSpringApplication;
        settings.restNamespacing = restNamespacing;
        settings.restNamespacingAnnotation = restNamespacingAnnotation;
        settings.restResponseType = restResponseType;
        settings.restOptionsType = restOptionsType;
        settings.customTypeProcessor = customTypeProcessor;
        settings.sortDeclarations = sortDeclarations;
        settings.sortTypeDeclarations = sortTypeDeclarations;
        settings.noFileComment = noFileComment;
        settings.noTslintDisable = noTslintDisable;
        settings.noEslintDisable = noEslintDisable;
        settings.tsNoCheck = tsNoCheck;
        settings.javadocXmlFiles = javadocXmlFiles;
        settings.extensions = extensions;
        settings.extensionClasses = extensionClasses;
        settings.extensionsWithConfiguration = extensionsWithConfiguration;
        settings.includePropertyAnnotations = includePropertyAnnotations;
        settings.excludePropertyAnnotations = excludePropertyAnnotations;
        settings.optionalAnnotations = optionalAnnotations;
        settings.requiredAnnotations = requiredAnnotations;
        settings.nullableAnnotations = nullableAnnotations;
        settings.primitivePropertiesRequired = primitivePropertiesRequired;
        settings.generateInfoJson = generateInfoJson;
        settings.generateNpmPackageJson = generateNpmPackageJson;
        settings.npmName = npmName == null && generateNpmPackageJson ? getProject().getName() : npmName;
        settings.npmVersion = npmVersion == null && generateNpmPackageJson ? "1.0.0" : npmVersion;
        settings.npmTypescriptVersion = npmTypescriptVersion;
        settings.npmBuildScript = npmBuildScript;
        settings.npmPackageDependencies = Settings.convertToMap(npmDependencies, "npmDependencies");
        settings.npmDevDependencies = Settings.convertToMap(npmDevDependencies, "npmDevDependencies");
        settings.npmPeerDependencies = Settings.convertToMap(npmPeerDependencies, "npmPeerDependencies");
        settings.stringQuotes = stringQuotes;
        settings.indentString = indentString;
        settings.jackson2ModuleDiscovery = jackson2ModuleDiscovery;
        settings.jackson2Modules = jackson2Modules;
        settings.loggingLevel = loggingLevel;
        return settings;
    }

    @Inject
    abstract public WorkerExecutor getWorkerExecutor();

    @Nested
    public abstract Property<JavaLauncher> getLauncher();

    @TaskAction
    public void generate() {
        if (outputKind == null) {
            throw new RuntimeException("Please specify 'outputKind' property.");
        }
        if (jsonLibrary == null) {
            throw new RuntimeException("Please specify 'jsonLibrary' property.");
        }

        // class loader
        WorkQueue workQueue = getWorkerExecutor().processIsolation(processWorkerSpec -> {
            processWorkerSpec.getClasspath().from(getClasspath());

            processWorkerSpec.forkOptions( forkOptions -> {
                forkOptions.setExecutable(getLauncher().get().getExecutablePath().getAsFile());
            });
        });

        final InputSettings settings = createSettings();
        InputParameters inputParameters = new InputParameters();
        inputParameters.classNames = classes;
        inputParameters.classNamePatterns = classPatterns;
        inputParameters.classesWithAnnotations = classesWithAnnotations;
        inputParameters.classesImplementingInterfaces = classesImplementingInterfaces;
        inputParameters.classesExtendingClasses = classesExtendingClasses;
        inputParameters.jaxrsApplicationClassName = classesFromJaxrsApplication;
        inputParameters.automaticJaxrsApplication = classesFromAutomaticJaxrsApplication;
        inputParameters.scanningAcceptedPackages = scanningAcceptedPackages;
        inputParameters.debug = loggingLevel == Logger.Level.Debug;

        final Settings settingsTemp = new Settings();
        settingsTemp.outputFileType = outputFileType.get();
        settingsTemp.validateFileName(outputFile.getAsFile().get());

        workQueue.submit(GenerateTypescriptWorker.class, configs -> {
            configs.getName().set(getProject().getName());
            configs.getInputParameters().set(inputParameters);
            configs.getOutput().set(outputFile);
            configs.getSettings().set(settings);
            configs.getClasspath().from(this.getClasspath());
        });
        workQueue.await();
    }
}

// workParameters for worker api

import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.workers.WorkParameters;

public interface TypescriptGeneratorWorkParameters extends WorkParameters {
    Property<String> getName();
    Property<InputParameters> getInputParameters();
    Property<InputSettings> getSettings();
    RegularFileProperty getOutput();
    ConfigurableFileCollection getClasspath();
}

// worker class
import cz.habarta.typescript.generator.*;
import cz.habarta.typescript.generator.util.Utils;
import org.gradle.workers.WorkAction;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedHashSet;
import java.util.Set;

public abstract class GenerateTypescriptWorker implements WorkAction<TypescriptGeneratorWorkParameters> {

    private Settings createSettings(URLClassLoader classLoader, InputSettings inputSettings) {
        final Settings settings = new Settings();
        if (inputSettings.outputFileType != null) {
            settings.outputFileType = inputSettings.outputFileType;
        }
        settings.outputKind = inputSettings.outputKind;
        settings.module = inputSettings.module;
        settings.namespace = inputSettings.namespace;
        settings.mapPackagesToNamespaces = inputSettings.mapPackagesToNamespaces;
        settings.umdNamespace = inputSettings.umdNamespace;

        if(inputSettings.moduleDependencies != null) {
            for (ModuleDependencySerializable moduleDependency : inputSettings.moduleDependencies) {
                settings.moduleDependencies.add(moduleDependency.toModuleDependency());
            }
        }

        settings.setExcludeFilter(inputSettings.excludeClasses, inputSettings.excludeClassPatterns);
        settings.jsonLibrary = inputSettings.jsonLibrary;
        settings.setJackson2Configuration(classLoader, inputSettings.jackson2Configuration.toConfiguration());
        settings.gsonConfiguration = inputSettings.gsonConfiguration;
        settings.jsonbConfiguration = inputSettings.jsonbConfiguration;
        settings.additionalDataLibraries = inputSettings.additionalDataLibraries;
        settings.optionalProperties = inputSettings.optionalProperties;
        settings.optionalPropertiesDeclaration = inputSettings.optionalPropertiesDeclaration;
        settings.nullabilityDefinition = inputSettings.nullabilityDefinition;
        settings.declarePropertiesAsReadOnly = inputSettings.declarePropertiesAsReadOnly;
        settings.removeTypeNamePrefix = inputSettings.removeTypeNamePrefix;
        settings.removeTypeNameSuffix = inputSettings.removeTypeNameSuffix;
        settings.addTypeNamePrefix = inputSettings.addTypeNamePrefix;
        settings.addTypeNameSuffix = inputSettings.addTypeNameSuffix;
        settings.customTypeNaming = inputSettings.customTypeNaming;
        settings.customTypeNamingFunction = inputSettings.customTypeNamingFunction;
        settings.referencedFiles = inputSettings.referencedFiles;
        settings.importDeclarations = inputSettings.importDeclarations;
        settings.customTypeMappings = inputSettings.customTypeMappings;
        settings.customTypeAliases = inputSettings.customTypeAliases;
        settings.mapDate = inputSettings.mapDate;
        settings.mapMap = inputSettings.mapMap;
        settings.mapEnum = inputSettings.mapEnum;
        settings.enumMemberCasing = inputSettings.enumMemberCasing;
        settings.nonConstEnums = inputSettings.nonConstEnums;
        settings.loadNonConstEnumAnnotations(classLoader, inputSettings.nonConstEnumAnnotations);
        settings.mapClasses = inputSettings.mapClasses;
        settings.mapClassesAsClassesPatterns = inputSettings.mapClassesAsClassesPatterns;
        settings.generateConstructors = inputSettings.generateConstructors;
        settings.loadDisableTaggedUnionAnnotations(classLoader, inputSettings.disableTaggedUnionAnnotations);
        settings.disableTaggedUnions = inputSettings.disableTaggedUnions;
        settings.generateReadonlyAndWriteonlyJSDocTags = inputSettings.generateReadonlyAndWriteonlyJSDocTags;
        settings.ignoreSwaggerAnnotations = inputSettings.ignoreSwaggerAnnotations;
        settings.generateJaxrsApplicationInterface = inputSettings.generateJaxrsApplicationInterface;
        settings.generateJaxrsApplicationClient = inputSettings.generateJaxrsApplicationClient;
        settings.generateSpringApplicationInterface = inputSettings.generateSpringApplicationInterface;
        settings.generateSpringApplicationClient = inputSettings.generateSpringApplicationClient;
        settings.scanSpringApplication = inputSettings.scanSpringApplication;
        settings.restNamespacing = inputSettings.restNamespacing;
        settings.setRestNamespacingAnnotation(classLoader, inputSettings.restNamespacingAnnotation);
        settings.restResponseType = inputSettings.restResponseType;
        settings.setRestOptionsType(inputSettings.restOptionsType);
        settings.loadCustomTypeProcessor(classLoader, inputSettings.customTypeProcessor);
        settings.sortDeclarations = inputSettings.sortDeclarations;
        settings.sortTypeDeclarations = inputSettings.sortTypeDeclarations;
        settings.noFileComment = inputSettings.noFileComment;
        settings.noTslintDisable = inputSettings.noTslintDisable;
        settings.noEslintDisable = inputSettings.noEslintDisable;
        settings.tsNoCheck = inputSettings.tsNoCheck;
        settings.javadocXmlFiles = inputSettings.javadocXmlFiles;
        settings.loadExtensions(classLoader, Utils.concat(inputSettings.extensionClasses, inputSettings.extensions), inputSettings.extensionsWithConfiguration);
        settings.loadIncludePropertyAnnotations(classLoader, inputSettings.includePropertyAnnotations);
        settings.loadExcludePropertyAnnotations(classLoader, inputSettings.excludePropertyAnnotations);
        settings.loadOptionalAnnotations(classLoader, inputSettings.optionalAnnotations);
        settings.loadRequiredAnnotations(classLoader, inputSettings.requiredAnnotations);
        settings.loadNullableAnnotations(classLoader, inputSettings.nullableAnnotations);
        settings.primitivePropertiesRequired = inputSettings.primitivePropertiesRequired;
        settings.generateInfoJson = inputSettings.generateInfoJson;
        settings.generateNpmPackageJson = inputSettings.generateNpmPackageJson;
        settings.npmName = inputSettings.npmName == null && inputSettings.generateNpmPackageJson ? getParameters().getName().get() : inputSettings.npmName;
        settings.npmVersion = inputSettings.npmVersion == null && inputSettings.generateNpmPackageJson ? settings.getDefaultNpmVersion() : inputSettings.npmVersion;
        settings.npmTypescriptVersion = inputSettings.npmTypescriptVersion;
        settings.npmBuildScript = inputSettings.npmBuildScript;
        settings.npmPackageDependencies = inputSettings.npmPackageDependencies;
        settings.npmDevDependencies = inputSettings.npmDevDependencies;
        settings.npmPeerDependencies = inputSettings.npmPeerDependencies;
        settings.setStringQuotes(inputSettings.stringQuotes);
        settings.setIndentString(inputSettings.indentString);
        settings.jackson2ModuleDiscovery = inputSettings.jackson2ModuleDiscovery;
        settings.loadJackson2Modules(classLoader, inputSettings.jackson2Modules);
        settings.classLoader = classLoader;
        return settings;
    }

    @Override
    public void execute() {
        TypeScriptGenerator.setLogger(new Logger(getParameters().getSettings().get().loggingLevel));
        TypeScriptGenerator.printVersion();

        final Set<URL> urls = new LinkedHashSet<>();

        for (File file : getParameters().getClasspath().getFiles()) {
            try {
                urls.add(file.toURI().toURL());
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            }
        }
        URLClassLoader classLoader = Settings.createClassLoader(getParameters().getName().get(), urls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader());
        Settings settings = createSettings(classLoader, getParameters().getSettings().get());
        InputParameters inputParameters = getParameters().getInputParameters().get();
        final Input.Parameters parameters = new Input.Parameters();
        parameters.classNames = inputParameters.classNames;
        parameters.classNamePatterns = inputParameters.classNamePatterns;
        parameters.classesWithAnnotations = inputParameters.classesWithAnnotations;
        parameters.classesImplementingInterfaces = inputParameters.classesImplementingInterfaces;
        parameters.classesExtendingClasses = inputParameters.classesExtendingClasses;
        parameters.jaxrsApplicationClassName = inputParameters.jaxrsApplicationClassName;
        parameters.automaticJaxrsApplication = inputParameters.automaticJaxrsApplication;
        parameters.classLoader = classLoader;
        parameters.scanningAcceptedPackages = inputParameters.scanningAcceptedPackages;
        parameters.debug = inputParameters.debug;
        new TypeScriptGenerator(settings)
            .generateTypeScript(Input.from(parameters), Output.to(getParameters().getOutput().get().getAsFile()));
    }
}