com.demonwav.mcdev.buildsystem.gradle.GradleBuildSystem.java Source code

Java tutorial

Introduction

Here is the source code for com.demonwav.mcdev.buildsystem.gradle.GradleBuildSystem.java

Source

/*
 * Minecraft Dev for IntelliJ
 *
 * https://minecraftdev.org
 *
 * Copyright (c) 2016 minecraft-dev
 *
 * MIT License
 */

package com.demonwav.mcdev.buildsystem.gradle;

import com.demonwav.mcdev.buildsystem.BuildDependency;
import com.demonwav.mcdev.buildsystem.BuildRepository;
import com.demonwav.mcdev.buildsystem.BuildSystem;
import com.demonwav.mcdev.creator.MinecraftProjectCreator;
import com.demonwav.mcdev.platform.AbstractTemplate;
import com.demonwav.mcdev.platform.PlatformType;
import com.demonwav.mcdev.platform.ProjectConfiguration;
import com.demonwav.mcdev.platform.forge.ForgeProjectConfiguration;
import com.demonwav.mcdev.platform.forge.ForgeTemplate;
import com.demonwav.mcdev.platform.hybrid.SpongeForgeProjectConfiguration;
import com.demonwav.mcdev.platform.liteloader.LiteLoaderProjectConfiguration;
import com.demonwav.mcdev.platform.liteloader.LiteLoaderTemplate;
import com.demonwav.mcdev.platform.sponge.SpongeTemplate;
import com.demonwav.mcdev.util.Util;

import com.intellij.codeInsight.actions.ReformatCodeProcessor;
import com.intellij.execution.RunManager;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.application.ApplicationConfiguration;
import com.intellij.execution.application.ApplicationConfigurationType;
import com.intellij.execution.impl.RunManagerImpl;
import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl;
import com.intellij.externalSystem.JavaProjectData;
import com.intellij.ide.actions.ImportModuleAction;
import com.intellij.ide.util.newProjectWizard.AddModuleWizard;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.externalSystem.model.DataNode;
import com.intellij.openapi.externalSystem.model.ExternalProjectInfo;
import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType;
import com.intellij.openapi.externalSystem.model.project.LibraryData;
import com.intellij.openapi.externalSystem.model.project.ProjectData;
import com.intellij.openapi.externalSystem.service.execution.ExternalSystemJdkUtil;
import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration;
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManager;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.intellij.psi.PsiManager;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.gradle.tooling.BuildLauncher;
import org.gradle.tooling.GradleConnector;
import org.gradle.tooling.ProgressListener;
import org.gradle.tooling.ProjectConnection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.plugins.gradle.model.ExternalProject;
import org.jetbrains.plugins.gradle.model.ExternalSourceSet;
import org.jetbrains.plugins.gradle.service.execution.GradleExternalTaskConfigurationType;
import org.jetbrains.plugins.gradle.service.project.data.ExternalProjectDataCache;
import org.jetbrains.plugins.gradle.service.project.wizard.GradleProjectImportBuilder;
import org.jetbrains.plugins.gradle.service.project.wizard.GradleProjectImportProvider;
import org.jetbrains.plugins.gradle.util.GradleConstants;
import org.jetbrains.plugins.groovy.GroovyLanguage;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrApplicationStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCommandArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiElementFactoryImpl;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class GradleBuildSystem extends BuildSystem {

    private final static Logger logger = Logger.getLogger(GradleBuildSystem.class);

    @Nullable
    private VirtualFile buildGradle;

    @Nullable
    public VirtualFile getBuildGradle() {
        return buildGradle;
    }

    @Override
    public void create(@NotNull Project project, @NotNull ProjectConfiguration configuration,
            @NotNull ProgressIndicator indicator) {
        rootDirectory.refresh(false, true);
        createDirectories();

        if (configuration.type == PlatformType.FORGE || configuration instanceof SpongeForgeProjectConfiguration) {
            if (!(configuration instanceof ForgeProjectConfiguration)) {
                return;
            }

            ForgeProjectConfiguration settings = (ForgeProjectConfiguration) configuration;
            Util.runWriteTask(() -> {
                try {
                    final VirtualFile gradleProp = rootDirectory.findOrCreateChildData(this, "gradle.properties");

                    buildGradle = rootDirectory.findOrCreateChildData(this, "build.gradle");

                    ForgeTemplate.applyBuildGradleTemplate(project, buildGradle, gradleProp, groupId, artifactId,
                            settings.forgeVersion, settings.mcpVersion, version,
                            configuration instanceof SpongeForgeProjectConfiguration);

                    if (configuration instanceof SpongeForgeProjectConfiguration) {
                        PsiFile buildGradlePsi = PsiManager.getInstance(project).findFile(buildGradle);
                        if (buildGradlePsi != null) {
                            addBuildGradleDependencies(project, buildGradlePsi, false);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

            setupWrapper(project, indicator);
            setupDecompWorkspace(project, indicator);
        } else if (configuration.type == PlatformType.LITELOADER) {
            if (!(configuration instanceof LiteLoaderProjectConfiguration)) {
                return;
            }

            LiteLoaderProjectConfiguration settings = (LiteLoaderProjectConfiguration) configuration;
            Util.runWriteTask(() -> {
                try {
                    final VirtualFile gradleProp = rootDirectory.findOrCreateChildData(this, "gradle.properties");

                    buildGradle = rootDirectory.findOrCreateChildData(this, "build.gradle");

                    LiteLoaderTemplate.applyBuildGradleTemplate(project, buildGradle, gradleProp, groupId,
                            artifactId, settings.pluginVersion, settings.mcVersion, settings.mcpVersion);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

            setupWrapper(project, indicator);
            setupDecompWorkspace(project, indicator);
        } else {
            Util.runWriteTask(() -> {
                try {
                    final VirtualFile gradleProp = rootDirectory.findOrCreateChildData(this, "gradle.properties");

                    String buildGradleText;
                    if (configuration.type == PlatformType.SPONGE) {
                        buildGradleText = SpongeTemplate.applyBuildGradleTemplate(project, gradleProp, groupId,
                                artifactId, version, buildVersion);
                    } else {
                        buildGradleText = AbstractTemplate.applyBuildGradleTemplate(project, gradleProp, groupId,
                                version, buildVersion);
                    }

                    if (buildGradleText == null) {
                        return;
                    }

                    addBuildGradleDependencies(project, buildGradleText);

                    VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

            setupWrapper(project, indicator);
        }

        // The file needs to be saved, if not Gradle will see the file without the dependencies and won't import correctly
        if (buildGradle == null) {
            return;
        }

        saveFile(buildGradle);
    }

    private void setupWrapper(@NotNull Project project, @NotNull ProgressIndicator indicator) {
        // Setup gradle wrapper
        // We'll write the properties file to ensure it sets up with the right version
        Util.runWriteTask(() -> {
            try {
                String wrapperDirPath = rootDirectory.createChildDirectory(this, "gradle")
                        .createChildDirectory(this, "wrapper").getPath();
                FileUtils.writeLines(new File(wrapperDirPath, "gradle-wrapper.properties"),
                        Collections.singletonList(
                                "distributionUrl=https\\://services.gradle.org/distributions/gradle-2.14.1-bin.zip"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        // Use gradle tooling to run the wrapper task
        GradleConnector connector = GradleConnector.newConnector();
        connector.forProjectDirectory(new File(rootDirectory.getPath()));
        ProjectConnection connection = connector.connect();
        BuildLauncher launcher = connection.newBuild();
        try {
            Pair<String, Sdk> sdkPair = ExternalSystemJdkUtil.getAvailableJdk(project);
            if (sdkPair != null && sdkPair.getSecond() != null && sdkPair.getSecond().getHomePath() != null
                    && !ExternalSystemJdkUtil.USE_INTERNAL_JAVA.equals(sdkPair.getFirst())) {

                launcher.setJavaHome(new File(sdkPair.getSecond().getHomePath()));
            }

            launcher.forTasks("wrapper")
                    .addProgressListener(
                            (ProgressListener) progressEvent -> indicator.setText(progressEvent.getDescription()))
                    .run();
        } finally {
            connection.close();
        }
    }

    private void createRepositoriesOrDependencies(@NotNull Project project, @NotNull GroovyFile file,
            @NotNull String name, @NotNull List<String> expressions) {

        // Get the block so we can start working with it
        GrClosableBlock block = getClosableBlockByName(file, name);

        if (block == null) {
            return;
        }

        // Create a super expression with all the expressions tied together
        String expressionText = expressions.stream().collect(Collectors.joining("\n"));

        // We can't create each expression and add them to the file...that won't work. Groovy requires a new line
        // from one method call expression to another, and there's no way to put whitespace in Psi because Psi is
        // stupid. So instead we make the whole thing as one big clump and insert it into the block
        GroovyFile fakeFile = GroovyPsiElementFactoryImpl.getInstance(project).createGroovyFile(expressionText,
                false, null);
        PsiElement last = block.getChildren()[block.getChildren().length - 1];
        block.addBefore(fakeFile, last);
    }

    @Nullable
    private GrClosableBlock getClosableBlockByName(@NotNull PsiElement element, @NotNull String name) {
        List<GrClosableBlock> blocks = getClosableBlocksByName(element, name);
        if (blocks.isEmpty()) {
            return null;
        } else {
            return blocks.get(0);
        }
    }

    @NotNull
    private List<GrClosableBlock> getClosableBlocksByName(@NotNull PsiElement element, @NotNull String name) {
        return Arrays.stream(element.getChildren()).filter(c -> {
            // We want to find the child which has a GrReferenceExpression with the right name
            return Arrays.stream(c.getChildren())
                    .filter(g -> g instanceof GrReferenceExpression && g.getText().equals(name)).findAny()
                    .isPresent();
        }).map(c -> {
            // We want to find the grandchild which is a GrCloseableBlock, this is the
            // basis for the method block
            return Arrays.stream(c.getChildren()).filter(g -> g instanceof GrClosableBlock)
                    // cast to closable block so generics can handle this conversion
                    .map(g -> (GrClosableBlock) g).findFirst();
        }).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
    }

    @Override
    public void finishSetup(@NotNull Module rootModule, @NotNull Collection<ProjectConfiguration> configurations,
            @NotNull ProgressIndicator indicator) {
        Project project = rootModule.getProject();

        // Tell Gradle to import this project
        final ProjectDataManager projectDataManager = ServiceManager.getService(ProjectDataManager.class);
        GradleProjectImportBuilder gradleProjectImportBuilder = new GradleProjectImportBuilder(projectDataManager);
        final GradleProjectImportProvider gradleProjectImportProvider = new GradleProjectImportProvider(
                gradleProjectImportBuilder);
        if (buildGradle != null) {
            indicator.setText("Running Gradle Setup");
            ApplicationManager.getApplication().invokeLater(() -> {
                AddModuleWizard wizard = new AddModuleWizard(project, buildGradle.getPath(),
                        gradleProjectImportProvider);
                if (wizard.showAndGet()) {
                    ImportModuleAction.createFromWizard(project, wizard);
                }

                // Set up the run config
                // Get the gradle external task type, this is what set's it as a gradle task
                GradleExternalTaskConfigurationType gradleType = GradleExternalTaskConfigurationType.getInstance();

                // Set the Forge client and server run configs
                if (configurations.stream().anyMatch(
                        c -> c.type == PlatformType.FORGE || c instanceof SpongeForgeProjectConfiguration)) {
                    Module mainModule;
                    if (configurations.size() == 1) {
                        mainModule = ModuleManager.getInstance(project)
                                .findModuleByName(rootModule.getName() + "_main");
                    } else {
                        mainModule = ModuleManager.getInstance(project)
                                .findModuleByName(rootModule.getName() + "-forge_main");
                    }
                    Module forgeModule = ModuleManager.getInstance(project)
                            .findModuleByName(rootModule.getName() + "-forge");

                    // Client run config
                    ApplicationConfiguration runClientConfiguration = new ApplicationConfiguration(
                            (forgeModule != null ? forgeModule : rootModule).getName() + " run client", project,
                            ApplicationConfigurationType.getInstance());
                    File runningDir = new File(project.getBasePath(), "run");
                    if (!runningDir.exists()) {
                        //noinspection ResultOfMethodCallIgnored
                        runningDir.mkdir();
                    }
                    runClientConfiguration.setWorkingDirectory(project.getBasePath() + File.separator + "run");
                    runClientConfiguration.setMainClassName("GradleStart");
                    if (configurations.size() == 1) {
                        runClientConfiguration.setModule(mainModule != null ? mainModule : rootModule);
                    } else {
                        runClientConfiguration.setModule(
                                mainModule != null ? mainModule : forgeModule != null ? forgeModule : rootModule);
                    }
                    RunnerAndConfigurationSettings clientSettings = new RunnerAndConfigurationSettingsImpl(
                            RunManagerImpl.getInstanceImpl(project), runClientConfiguration, false);
                    clientSettings.setActivateToolWindowBeforeRun(true);
                    clientSettings.setSingleton(true);
                    RunManager.getInstance(project).addConfiguration(clientSettings, false);
                    RunManager.getInstance(project).setSelectedConfiguration(clientSettings);

                    // Server run config
                    ApplicationConfiguration runServerConfiguration = new ApplicationConfiguration(
                            rootModule.getName() + " run server", project,
                            ApplicationConfigurationType.getInstance());
                    runServerConfiguration.setMainClassName("GradleStartServer");
                    runServerConfiguration.setProgramParameters("nogui");
                    runServerConfiguration.setWorkingDirectory(project.getBasePath() + File.separator + "run");
                    if (configurations.size() == 1) {
                        runServerConfiguration.setModule(mainModule != null ? mainModule : rootModule);
                    } else {
                        runServerConfiguration.setModule(
                                mainModule != null ? mainModule : forgeModule != null ? forgeModule : rootModule);
                    }
                    RunnerAndConfigurationSettings serverSettings = new RunnerAndConfigurationSettingsImpl(
                            RunManagerImpl.getInstanceImpl(project), runServerConfiguration, false);
                    serverSettings.setActivateToolWindowBeforeRun(true);
                    serverSettings.setSingleton(true);
                    RunManager.getInstance(project).addConfiguration(serverSettings, false);
                }

                // Create a gradle external system run config
                ExternalSystemRunConfiguration runConfiguration = new ExternalSystemRunConfiguration(
                        GradleConstants.SYSTEM_ID, project, gradleType.getConfigurationFactories()[0],
                        rootModule.getName() + " build");
                // Set relevant gradle values
                runConfiguration.getSettings().setExternalProjectPath(rootDirectory.getPath());
                runConfiguration.getSettings().setExecutionName(rootModule.getName() + " build");
                runConfiguration.getSettings().setTaskNames(Collections.singletonList("build"));
                // Create a RunAndConfigurationSettings object, which defines general settings for the run configuration
                RunnerAndConfigurationSettings settings = new RunnerAndConfigurationSettingsImpl(
                        RunManagerImpl.getInstanceImpl(project), runConfiguration, false);
                // Open the tool window and set it as a singleton run types
                settings.setActivateToolWindowBeforeRun(true);
                settings.setSingleton(true);

                // Apply the run config and select it
                RunManager.getInstance(project).addConfiguration(settings, false);
            }, ModalityState.NON_MODAL);
        }
    }

    @NotNull
    @Override
    public Promise<BuildSystem> reImport(@NotNull Module module) {
        synchronized (this) {
            if (synchronize()) {
                return importPromise;
            }
        }

        // We must be on the event dispatch thread to run a backgroundable task
        ApplicationManager.getApplication().invokeLater(() -> ProgressManager.getInstance()
                .run(new Task.Backgroundable(module.getProject(), "Importing Gradle Module", false) {
                    @Override
                    public void run(@NotNull ProgressIndicator indicator) {
                        // We will need to request read access, which we can do from async
                        ApplicationManager.getApplication().runReadAction(() -> {
                            Project project = module.getProject();

                            // root directory is the first content root
                            ModuleRootManager manager = ModuleRootManager.getInstance(module);
                            if (manager.getContentRoots().length == 0) {
                                // TODO handle import failed
                                logger.error("GradleBuildSystem import FAILED: no content roots found");
                                importPromise.setResult(GradleBuildSystem.this);
                                return;
                            }

                            rootDirectory = manager.getContentRoots()[0];
                            buildGradle = rootDirectory.findChild("build.gradle");

                            if (rootDirectory.getCanonicalPath() == null || buildGradle == null) {
                                logger.error(
                                        "GradleBuildSystem import FAILED: Root Directory or Build Gradle paths null");
                                logger.error("rootDirectory: " + rootDirectory);
                                logger.error("buildGradle: " + buildGradle);
                                importPromise.setResult(GradleBuildSystem.this);
                                return;
                            }

                            sourceDirectories = new ArrayList<>();
                            resourceDirectories = new ArrayList<>();
                            testSourcesDirectories = new ArrayList<>();
                            testResourceDirectories = new ArrayList<>();

                            ExternalProjectDataCache externalProjectDataCache = ExternalProjectDataCache
                                    .getInstance(project);

                            String name = module.getName();
                            // I can't find a way to read module group children, group path only goes up
                            // So I guess check each module to see if it's a child....
                            Collection<Module> children = Arrays
                                    .stream(ModuleManager.getInstance(project).getModules()).filter(m -> {
                                        String[] paths = ModuleManager.getInstance(project).getModuleGroupPath(m);
                                        if (paths != null && paths.length > 0) {
                                            if (name.equals(paths[paths.length - 1])) {
                                                return true;
                                            }
                                        }
                                        return false;
                                    }).collect(Collectors.toList());

                            if (project.getBasePath() == null) {
                                logger.error("GradleBuildSystem import FAILED: Project base path null");
                                importPromise.setResult(GradleBuildSystem.this);
                                return;
                            }

                            // We need to check the parent too if it's a single module project
                            ExternalProject externalRootProject = externalProjectDataCache.getRootExternalProject(
                                    GradleConstants.SYSTEM_ID, new File(rootDirectory.getCanonicalPath()));
                            if (externalRootProject != null) {
                                for (Module child : children) {
                                    Map<String, ExternalSourceSet> externalSourceSets = externalProjectDataCache
                                            .findExternalProject(externalRootProject, child);

                                    for (ExternalSourceSet sourceSet : externalSourceSets.values()) {
                                        setupDirs(sourceDirectories, sourceSet, ExternalSystemSourceType.SOURCE);
                                        setupDirs(resourceDirectories, sourceSet,
                                                ExternalSystemSourceType.RESOURCE);
                                        setupDirs(testSourcesDirectories, sourceSet, ExternalSystemSourceType.TEST);
                                        setupDirs(testResourceDirectories, sourceSet,
                                                ExternalSystemSourceType.TEST_RESOURCE);
                                    }
                                }

                                groupId = externalRootProject.getGroup();
                                artifactId = externalRootProject.getName();
                                version = externalRootProject.getVersion();

                                pluginName = externalRootProject.getName();

                                // We need to get the project info from gradle
                                ExternalProjectInfo info = ProjectDataManager.getInstance().getExternalProjectData(
                                        project, GradleConstants.SYSTEM_ID, rootDirectory.getCanonicalPath());
                                if (info == null) {
                                    logger.error("GradleBuildSystem import FAILED: External project info null");
                                    importPromise.setResult(GradleBuildSystem.this);
                                    return;
                                }

                                DataNode<ProjectData> node = info.getExternalProjectStructure();

                                if (node == null) {
                                    logger.error("GradleBuildSystem import FAILED: Project data node null");
                                    importPromise.setResult(GradleBuildSystem.this);
                                    return;
                                }

                                dependencies = new ArrayList<>();
                                node.getChildren().forEach(child -> {
                                    if (child.getData() instanceof LibraryData) {
                                        LibraryData data = (LibraryData) child.getData();
                                        String[] parts = data.getExternalName().split(":");
                                        if (parts.length < 3) {
                                            logger.warn("LibraryData held incompatible data: "
                                                    + data.getExternalName());
                                            return;
                                        }
                                        String groupId = parts[0];
                                        String artifactId = parts[1];
                                        String version = parts[2];
                                        // I should probably remove scope, as I'm not even using it here...
                                        dependencies
                                                .add(new BuildDependency(groupId, artifactId, version, "provided"));
                                    } else if (child.getData() instanceof JavaProjectData) {
                                        // kashike guilt tripped me into this
                                        JavaProjectData data = (JavaProjectData) child.getData();
                                        String languageLevelName = data.getLanguageLevel().name();
                                        int index = languageLevelName.lastIndexOf('_') - 1;
                                        if (index != -1) {
                                            buildVersion = languageLevelName
                                                    .substring(index, languageLevelName.length()).replace("_", ".");
                                        }
                                    }
                                });
                            }

                            GroovyFile groovyFile = (GroovyFile) PsiManager.getInstance(project)
                                    .findFile(buildGradle);
                            if (groovyFile != null) {
                                // get repositories
                                repositories = new ArrayList<>();
                                // We need to climb the tree to get to the repositories

                                GrClosableBlock block = getClosableBlockByName(groovyFile, "repositories");
                                if (block != null) {
                                    addRepositories(block);
                                }
                            }
                        });
                        importPromise.setResult(GradleBuildSystem.this);
                    }
                }));
        return importPromise;
    }

    @NotNull
    public Map<GradleBuildSystem, ProjectConfiguration> createMultiModuleProject(@NotNull Project project,
            @NotNull Map<PlatformType, ProjectConfiguration> configurations, @NotNull ProgressIndicator indicator) {

        final Map<GradleBuildSystem, ProjectConfiguration> map = new HashMap<>();

        setupWrapper(project, indicator);

        rootDirectory.refresh(false, true);

        // Create the includes string for settings.gradle
        // First, we add the common module that all multi-module projects will have
        String tempIncludes = "'" + pluginName.toLowerCase() + "-common', ";
        // We use an iterator because we need to know when there won't be a next entry
        Iterator<ProjectConfiguration> configurationIterator = configurations.values().iterator();
        while (configurationIterator.hasNext()) {
            ProjectConfiguration configuration = configurationIterator.next();
            tempIncludes += "'" + pluginName.toLowerCase() + "-" + configuration.type.name().toLowerCase() + "'";
            // Only add the ending comma after the entry when there is another entry to add
            if (configurationIterator.hasNext()) {
                tempIncludes += ", ";
            }
        }

        String includes = tempIncludes;
        Util.runWriteTask(() -> {
            try {
                // Write the parent files to disk so the children modules can import correctly
                buildGradle = rootDirectory.createChildData(this, "build.gradle");
                final VirtualFile gradleProp = rootDirectory.findOrCreateChildData(this, "gradle.properties");
                final VirtualFile settingsGradle = rootDirectory.createChildData(this, "settings.gradle");

                AbstractTemplate.applyMultiModuleBuildGradleTemplate(project, buildGradle, gradleProp, groupId,
                        version, buildVersion);

                AbstractTemplate.applySettingsGradleTemplate(project, settingsGradle, artifactId.toLowerCase(),
                        includes);

                // Common will be empty, it's for the developer to fill in with common classes
                VirtualFile common = rootDirectory.createChildDirectory(this, artifactId.toLowerCase() + "-common");
                createDirectories(common);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        for (ProjectConfiguration configuration : configurations.values()) {
            // We associate each configuration with the given build system, which we add to the map at the end of this method
            GradleBuildSystem gradleBuildSystem = new GradleBuildSystem();
            Util.runWriteTask(() -> {
                try {
                    // Add settings for the new build system before it creates the module
                    gradleBuildSystem.rootDirectory = rootDirectory.createChildDirectory(this,
                            artifactId.toLowerCase() + "-" + configuration.type.name().toLowerCase());

                    gradleBuildSystem.artifactId = artifactId;
                    gradleBuildSystem.groupId = groupId;
                    gradleBuildSystem.version = version;

                    gradleBuildSystem.dependencies = new ArrayList<>();
                    gradleBuildSystem.repositories = new ArrayList<>();

                    gradleBuildSystem.pluginName = pluginName;
                    gradleBuildSystem.buildVersion = buildVersion;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

            // it knows which dependencies are needed for each configuration
            MinecraftProjectCreator.addDependencies(configuration, gradleBuildSystem);

            // For each build system we initialize it, but not the same as a normal create. We need to know the common
            // project name, as we automatically add it as a dependency too
            gradleBuildSystem.createSubModule(project, configuration, artifactId.toLowerCase() + "-common",
                    indicator);
            map.put(gradleBuildSystem, configuration);
        }

        return map;
    }

    private void createSubModule(@NotNull Project project, @NotNull ProjectConfiguration configuration,
            @NotNull String commonProjectName, @NotNull ProgressIndicator indicator) {
        rootDirectory.refresh(false, true);
        createDirectories();

        // This is mostly the same as a normal create, but we use different files and don't setup the wrapper
        if (configuration.type == PlatformType.FORGE || configuration instanceof SpongeForgeProjectConfiguration) {
            if (!(configuration instanceof ForgeProjectConfiguration)) {
                return;
            }

            ForgeProjectConfiguration settings = (ForgeProjectConfiguration) configuration;
            Util.runWriteTask(() -> {
                final VirtualFile gradleProp;
                try {
                    gradleProp = rootDirectory.findOrCreateChildData(this, "gradle.properties");
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }

                try {
                    buildGradle = rootDirectory.findOrCreateChildData(this, "build.gradle");

                    ForgeTemplate.applySubmoduleBuildGradleTemplate(project, buildGradle, gradleProp, artifactId,
                            settings.forgeVersion, settings.mcpVersion, commonProjectName,
                            configuration instanceof SpongeForgeProjectConfiguration);

                    // We're only going to write the dependencies if it's a sponge forge project
                    if (configuration instanceof SpongeForgeProjectConfiguration) {
                        PsiFile buildGradlePsi = PsiManager.getInstance(project).findFile(buildGradle);
                        if (buildGradlePsi != null) {
                            addBuildGradleDependencies(project, buildGradlePsi, false);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

            setupDecompWorkspace(project, indicator);
        } else if (configuration.type == PlatformType.LITELOADER) {
            if (!(configuration instanceof LiteLoaderProjectConfiguration)) {
                return;
            }

            LiteLoaderProjectConfiguration settings = (LiteLoaderProjectConfiguration) configuration;
            Util.runWriteTask(() -> {
                final VirtualFile gradleProp;
                try {
                    gradleProp = rootDirectory.findOrCreateChildData(this, "gradle.properties");
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }

                try {
                    buildGradle = rootDirectory.findOrCreateChildData(this, "build.gradle");

                    LiteLoaderTemplate.applySubmoduleBuildGradleTemplate(project, buildGradle, gradleProp,
                            settings.pluginVersion, settings.mcVersion, settings.mcpVersion, commonProjectName);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

            setupDecompWorkspace(project, indicator);
        } else {
            Util.runWriteTask(() -> {
                String buildGradleText;
                if (configuration.type == PlatformType.SPONGE) {
                    buildGradleText = SpongeTemplate.applySubmoduleBuildGradleTemplate(project, commonProjectName);
                } else {
                    buildGradleText = AbstractTemplate.applySubmoduleBuildGradleTemplate(project,
                            commonProjectName);
                }

                if (buildGradleText == null) {
                    return;
                }

                addBuildGradleDependencies(project, buildGradleText);
            });
        }

        // The file needs to be saved, if not Gradle will see the file without the dependencies and won't import correctly
        if (buildGradle == null) {
            return;
        }

        saveFile(buildGradle);
    }

    private void addBuildGradleDependencies(@NotNull Project project, @NotNull PsiFile file,
            boolean addToDirectory) {
        // Write the repository and dependency data to the psi file
        new WriteCommandAction.Simple(project, file) {
            @Override
            protected void run() throws Throwable {
                final VirtualFile buildGradle = rootDirectory.findOrCreateChildData(this, "build.gradle");

                file.setName("build.gradle");
                final GroovyFile groovyFile = (GroovyFile) file;

                // Add repositories
                createRepositoriesOrDependencies(project, groovyFile, "repositories",
                        repositories.stream()
                                .map(r -> String.format("maven {name = '%s'\nurl = '%s'\n}", r.getId(), r.getUrl()))
                                .collect(Collectors.toList()));

                // Add dependencies
                createRepositoriesOrDependencies(project, groovyFile, "dependencies", dependencies.stream().map(
                        d -> String.format("compile '%s:%s:%s'", d.getGroupId(), d.getArtifactId(), d.getVersion()))
                        .collect(Collectors.toList()));

                new ReformatCodeProcessor(file, false).run();
                if (addToDirectory) {
                    PsiDirectory rootDirectoryPsi = PsiManager.getInstance(project).findDirectory(rootDirectory);
                    if (rootDirectoryPsi != null) {
                        buildGradle.delete(this);

                        rootDirectoryPsi.add(file);
                    }
                }

                GradleBuildSystem.this.buildGradle = rootDirectory.findChild("build.gradle");
                if (GradleBuildSystem.this.buildGradle == null) {
                    return;
                }

                // Reformat the code to match their code style
                PsiFile newBuildGradlePsi = PsiManager.getInstance(project)
                        .findFile(GradleBuildSystem.this.buildGradle);
                if (newBuildGradlePsi != null) {
                    new ReformatCodeProcessor(newBuildGradlePsi, false).run();
                }
            }
        }.execute();
    }

    private void addBuildGradleDependencies(@NotNull Project project, @NotNull String text) {
        // Create the PSI file from the text, but don't write it until we are finished with it
        PsiFile buildGradlePsi = PsiFileFactory.getInstance(project).createFileFromText(GroovyLanguage.INSTANCE,
                text);

        addBuildGradleDependencies(project, buildGradlePsi, true);
    }

    private void setupDecompWorkspace(@NotNull Project project, @NotNull ProgressIndicator indicator) {
        // We need to setup decomp workspace first
        // We'll use gradle tooling to run it
        GradleConnector connector = GradleConnector.newConnector();
        connector.forProjectDirectory(new File(rootDirectory.getPath()));
        ProjectConnection connection = connector.connect();
        BuildLauncher launcher = connection.newBuild();

        try {
            Pair<String, Sdk> sdkPair = ExternalSystemJdkUtil.getAvailableJdk(project);
            if (sdkPair != null && sdkPair.getSecond() != null && sdkPair.getSecond().getHomePath() != null
                    && !ExternalSystemJdkUtil.USE_INTERNAL_JAVA.equals(sdkPair.getFirst())) {
                launcher.setJavaHome(new File(sdkPair.getSecond().getHomePath()));
            }

            launcher.forTasks("setupDecompWorkspace").setJvmArguments("-Xmx2G")
                    .addProgressListener(
                            (ProgressListener) progressEvent -> indicator.setText(progressEvent.getDescription()))
                    .run();
        } finally {
            connection.close();
        }
    }

    private void addRepositories(@NotNull GrClosableBlock block) {
        List<GrClosableBlock> mavenBlocks = getClosableBlocksByName(block, "maven");
        if (mavenBlocks.isEmpty()) {
            return;
        }

        mavenBlocks.forEach(mavenBlock -> {
            BuildRepository repository = new BuildRepository();
            for (PsiElement child : mavenBlock.getChildren()) {
                if (child instanceof GrApplicationStatement) {
                    handleApplicationStatement((GrApplicationStatement) child, repository);
                } else if (child instanceof GrAssignmentExpression) {
                    handleAssignmentExpression((GrAssignmentExpression) child, repository);
                }
            }
            repositories.add(repository);
        });
    }

    private void handleApplicationStatement(@NotNull GrApplicationStatement statement,
            @NotNull BuildRepository repository) {
        GrCommandArgumentList list = statement.getArgumentList();
        if (list.getChildren().length > 0) {
            if (statement.getInvokedExpression().getText().equals("name")) {
                repository.setId(list.getChildren()[0].getText().replaceAll("'", ""));
            } else if (statement.getInvokedExpression().getText().equals("url")) {
                repository.setUrl(list.getChildren()[0].getText().replaceAll("'", ""));
            }
        }
    }

    private void handleAssignmentExpression(@NotNull GrAssignmentExpression expression,
            @NotNull BuildRepository repository) {
        if (expression.getLValue().getText().equals("name")) {
            if (expression.getRValue() != null) {
                repository.setId(expression.getRValue().getText().replaceAll("'", ""));
            }
        } else if (expression.getLValue().getText().equals("url")) {
            if (expression.getRValue() != null) {
                repository.setUrl(expression.getRValue().getText().replaceAll("'", ""));
            }
        }
    }

    private void setupDirs(@NotNull List<VirtualFile> directories, @NotNull ExternalSourceSet set,
            @NotNull ExternalSystemSourceType type) {
        if (set.getSources().get(type) != null) {
            set.getSources().get(type).getSrcDirs().forEach(dir -> {
                if (dir.exists()) {
                    directories.add(LocalFileSystem.getInstance().findFileByIoFile(dir));
                }
            });
        }
    }

    private void saveFile(@Nullable VirtualFile file) {
        if (file == null) {
            return;
        }

        Util.runWriteTask(() -> {
            Document document = FileDocumentManager.getInstance().getDocument(file);
            if (document == null) {
                return;
            }

            FileDocumentManager.getInstance().saveDocument(document);
        });
    }
}