Fix: Rebuild common or native module with Delegate to gradle does nothing

Issues #KT-27295:fixed, #KT-27296:fixed
This commit is contained in:
Dmitriy Dolovov
2018-12-17 17:00:04 +07:00
parent 3b0cb2258a
commit af4c2b8030
5 changed files with 730 additions and 0 deletions
@@ -0,0 +1,362 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.gradle.execution;
import com.intellij.build.BuildViewManager;
import com.intellij.compiler.impl.CompilerUtil;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.openapi.compiler.ex.CompilerPathsEx;
import com.intellij.openapi.externalSystem.model.DataNode;
import com.intellij.openapi.externalSystem.model.ProjectKeys;
import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings;
import com.intellij.openapi.externalSystem.model.project.ModuleData;
import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode;
import com.intellij.openapi.externalSystem.task.TaskCallback;
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootModificationTracker;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.task.*;
import com.intellij.task.impl.JpsProjectTaskRunner;
import com.intellij.util.SmartList;
import com.intellij.util.SystemProperties;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FactoryMap;
import com.intellij.util.containers.MultiMap;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.config.KotlinFacetSettings;
import org.jetbrains.kotlin.idea.facet.KotlinFacet;
import org.jetbrains.kotlin.platform.IdePlatform;
import org.jetbrains.kotlin.platform.impl.CommonIdePlatformUtil;
import org.jetbrains.kotlin.platform.impl.NativeIdePlatformUtil;
import org.jetbrains.plugins.gradle.execution.build.CachedModuleDataFinder;
import org.jetbrains.plugins.gradle.execution.build.GradleProjectTaskRunner;
import org.jetbrains.plugins.gradle.service.project.GradleBuildSrcProjectsResolver;
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil;
import org.jetbrains.plugins.gradle.service.task.GradleTaskManager;
import org.jetbrains.plugins.gradle.settings.GradleSettings;
import org.jetbrains.plugins.gradle.settings.GradleSystemRunningSettings;
import org.jetbrains.plugins.gradle.util.GradleConstants;
import java.io.File;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration.PROGRESS_LISTENER_KEY;
import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.*;
import static com.intellij.openapi.util.text.StringUtil.*;
import static org.jetbrains.plugins.gradle.execution.GradleRunnerUtil.resolveProjectPath;
/**
* This is a modified copy of {@link GradleProjectTaskRunner} that allows building Kotlin Common and Kotlin Native modules
* in IDEA by delegating to Gradle builder ("Delegate IDE build/run actions to gradle"). See #KT-27295, #KT-27296.
*
* TODO: Refactor this class to remove duplicated logic when {@link GradleProjectTaskRunner} will allow extending it to
* collect custom Gradle tasks. See #IDEA-204372, #KT-28880.
*/
class KotlinMPPGradleProjectTaskRunner extends ProjectTaskRunner
{
@Language("Groovy")
private static final String FORCE_COMPILE_TASKS_INIT_SCRIPT_TEMPLATE = "projectsEvaluated { \n" +
" rootProject.findProject('%s')?.tasks?.withType(AbstractCompile) { \n" +
" outputs.upToDateWhen { false } \n" +
" } \n" +
"}\n";
@Override
public void run(@NotNull Project project,
@NotNull ProjectTaskContext context,
@Nullable ProjectTaskNotification callback,
@NotNull Collection<? extends ProjectTask> tasks) {
MultiMap<String, String> buildTasksMap = MultiMap.createLinkedSet();
MultiMap<String, String> cleanTasksMap = MultiMap.createLinkedSet();
MultiMap<String, String> initScripts = MultiMap.createLinkedSet();
Map<Class<? extends ProjectTask>, List<ProjectTask>> taskMap = JpsProjectTaskRunner.groupBy(tasks);
List<Module> modules = addModulesBuildTasks(taskMap.get(ModuleBuildTask.class), buildTasksMap, initScripts);
// TODO there should be 'gradle' way to build files instead of related modules entirely
List<Module> modulesOfFiles = addModulesBuildTasks(taskMap.get(ModuleFilesBuildTask.class), buildTasksMap, initScripts);
// TODO send a message if nothing to build
Set<String> rootPaths = buildTasksMap.keySet();
AtomicInteger successCounter = new AtomicInteger();
AtomicInteger errorCounter = new AtomicInteger();
TaskCallback taskCallback = callback == null ? null : new TaskCallback() {
@Override
public void onSuccess() {
handle(true);
}
@Override
public void onFailure() {
handle(false);
}
private void handle(boolean success) {
int successes = success ? successCounter.incrementAndGet() : successCounter.get();
int errors = success ? errorCounter.get() : errorCounter.incrementAndGet();
if (successes + errors == rootPaths.size()) {
if (!project.isDisposed()) {
// refresh on output roots is required in order for the order enumerator to see all roots via VFS
final List<Module> affectedModules = ContainerUtil.concat(modules, modulesOfFiles);
// have to refresh in case of errors too, because run configuration may be set to ignore errors
Collection<String> affectedRoots = ContainerUtil.newHashSet(
CompilerPathsEx.getOutputPaths(affectedModules.toArray(Module.EMPTY_ARRAY)));
if (!affectedRoots.isEmpty()) {
CompilerUtil.refreshOutputRoots(affectedRoots);
}
}
callback.finished(new ProjectTaskResult(false, errors, 0));
}
}
};
// TODO compiler options should be configurable
@Language("Groovy")
String compilerOptionsInitScript = "allprojects {\n" +
" tasks.withType(JavaCompile) {\n" +
" options.compilerArgs += [\"-Xlint:deprecation\"]\n" +
" }" +
"}\n";
String gradleVmOptions = GradleSettings.getInstance(project).getGradleVmOptions();
for (String rootProjectPath : rootPaths) {
Collection<String> buildTasks = buildTasksMap.get(rootProjectPath);
if (buildTasks.isEmpty()) continue;
Collection<String> cleanTasks = cleanTasksMap.get(rootProjectPath);
ExternalSystemTaskExecutionSettings settings = new ExternalSystemTaskExecutionSettings();
File projectFile = new File(rootProjectPath);
final String projectName;
if (projectFile.isFile()) {
projectName = projectFile.getParentFile().getName();
}
else {
projectName = projectFile.getName();
}
String executionName = "Build " + projectName;
settings.setExecutionName(executionName);
settings.setExternalProjectPath(rootProjectPath);
settings.setTaskNames(ContainerUtil.collect(ContainerUtil.concat(cleanTasks, buildTasks).iterator()));
//settings.setScriptParameters(scriptParameters);
settings.setVmOptions(gradleVmOptions);
settings.setExternalSystemIdString(GradleConstants.SYSTEM_ID.getId());
UserDataHolderBase userData = new UserDataHolderBase();
userData.putUserData(PROGRESS_LISTENER_KEY, BuildViewManager.class);
Collection<String> scripts = initScripts.getModifiable(rootProjectPath);
scripts.add(compilerOptionsInitScript);
userData.putUserData(GradleTaskManager.INIT_SCRIPT_KEY, join(scripts, SystemProperties.getLineSeparator()));
userData.putUserData(GradleTaskManager.INIT_SCRIPT_PREFIX_KEY, executionName);
ExternalSystemUtil.runTask(settings, DefaultRunExecutor.EXECUTOR_ID, project, GradleConstants.SYSTEM_ID,
taskCallback, ProgressExecutionMode.IN_BACKGROUND_ASYNC, false, userData);
}
}
@Override
public boolean canRun(@NotNull ProjectTask projectTask) {
if (!GradleSystemRunningSettings.getInstance().isUseGradleAwareMake()) return false;
if (projectTask instanceof ModuleBuildTask) {
final ModuleBuildTask moduleBuildTask = (ModuleBuildTask) projectTask;
final Module module = moduleBuildTask.getModule();
if (!isExternalSystemAwareModule(GradleConstants.SYSTEM_ID, module)) return false;
// ---------------------------------------- //
// TODO BEGIN: Extract custom Kotlin logic. //
// ---------------------------------------- //
if (isProjectWithNativeSourceOrCommonProductionSourceModules(module.getProject())) return true;
// ---------------------------------------- //
// TODO END: Extract custom Kotlin logic. //
// ---------------------------------------- //
}
return false;
}
private static List<Module> addModulesBuildTasks(@Nullable Collection<? extends ProjectTask> projectTasks,
@NotNull MultiMap<String, String> buildTasksMap,
@NotNull MultiMap<String, String> initScripts) {
if (ContainerUtil.isEmpty(projectTasks)) return Collections.emptyList();
List<Module> affectedModules = new SmartList<>();
Map<Module, String> rootPathsMap = FactoryMap.create(module -> notNullize(resolveProjectPath(module)));
final CachedModuleDataFinder moduleDataFinder = new CachedModuleDataFinder();
for (ProjectTask projectTask : projectTasks) {
if (!(projectTask instanceof ModuleBuildTask)) continue;
ModuleBuildTask moduleBuildTask = (ModuleBuildTask)projectTask;
Module module = moduleBuildTask.getModule();
affectedModules.add(module);
final String rootProjectPath = rootPathsMap.get(module);
if (isEmpty(rootProjectPath)) continue;
final String projectId = getExternalProjectId(module);
if (projectId == null) continue;
final String externalProjectPath = getExternalProjectPath(module);
if (externalProjectPath == null || endsWith(externalProjectPath, "buildSrc")) continue;
final DataNode<? extends ModuleData> moduleDataNode = moduleDataFinder.findMainModuleData(module);
if (moduleDataNode == null) continue;
// all buildSrc runtime projects will be built by gradle implicitly
if (Boolean.parseBoolean(moduleDataNode.getData().getProperty(GradleBuildSrcProjectsResolver.BUILD_SRC_MODULE_PROPERTY))) {
continue;
}
String gradlePath = GradleProjectResolverUtil.getGradlePath(module);
if (gradlePath == null) continue;
String taskPrefix = endsWithChar(gradlePath, ':') ? gradlePath : (gradlePath + ':');
List<String> gradleTasks = ContainerUtil.mapNotNull(
findAll(moduleDataNode, ProjectKeys.TASK), node ->
node.getData().isInherited() ? null : trimStart(node.getData().getName(), taskPrefix));
Collection<String> projectInitScripts = initScripts.getModifiable(rootProjectPath);
Collection<String> buildRootTasks = buildTasksMap.getModifiable(rootProjectPath);
final String moduleType = getExternalModuleType(module);
if (!moduleBuildTask.isIncrementalBuild()) {
projectInitScripts.add(String.format(FORCE_COMPILE_TASKS_INIT_SCRIPT_TEMPLATE, gradlePath));
}
String assembleTask = "assemble";
if (GradleConstants.GRADLE_SOURCE_SET_MODULE_TYPE_KEY.equals(moduleType)) {
String sourceSetName = GradleProjectResolverUtil.getSourceSetName(module);
String gradleTask = isEmpty(sourceSetName) || "main".equals(sourceSetName) ? "classes" : sourceSetName + "Classes";
if (gradleTasks.contains(gradleTask)) {
buildRootTasks.add(taskPrefix + gradleTask);
}
else if ("main".equals(sourceSetName) || "test".equals(sourceSetName)) {
buildRootTasks.add(taskPrefix + assembleTask);
}
// ---------------------------------------- //
// TODO BEGIN: Extract custom Kotlin logic. //
// ---------------------------------------- //
else if (isNativeSourceModule(module)) {
// Add tasks for Kotlin/Native.
buildRootTasks.addAll(addPrefix(findNativeGradleBuildTasks(gradleTasks, sourceSetName), taskPrefix));
}
else if (isCommonProductionSourceModule(module)) {
// Add tasks for compiling metadata.
buildRootTasks.addAll(addPrefix(findMetadataBuildTasks(gradleTasks, sourceSetName), taskPrefix));
}
// ---------------------------------------- //
// TODO END: Extract custom Kotlin logic. //
// ---------------------------------------- //
}
else {
if (gradleTasks.contains("classes")) {
buildRootTasks.add(taskPrefix + "classes");
buildRootTasks.add(taskPrefix + "testClasses");
}
else if (gradleTasks.contains(assembleTask)) {
buildRootTasks.add(taskPrefix + assembleTask);
}
}
}
return affectedModules;
}
// ---------------------------------------- //
// TODO BEGIN: Extract custom Kotlin logic. //
// ---------------------------------------- //
private static boolean isProjectWithNativeSourceOrCommonProductionSourceModules(Project project) {
return CachedValuesManager.getManager(project).getCachedValue(
project,
() -> new CachedValueProvider.Result<>(
Arrays.stream(ModuleManager.getInstance(project).getModules()).anyMatch(
module -> isNativeSourceModule(module) || isCommonProductionSourceModule(module)
),
ProjectRootModificationTracker.getInstance(project)
));
}
private static boolean isNativeSourceModule(Module module) {
final KotlinFacet kotlinFacet = KotlinFacet.Companion.get(module);
if (kotlinFacet == null) return false;
final IdePlatform platform = kotlinFacet.getConfiguration().getSettings().getPlatform();
if (platform == null) return false;
return NativeIdePlatformUtil.isKotlinNative(platform);
}
private static boolean isCommonProductionSourceModule(Module module) {
final KotlinFacet kotlinFacet = KotlinFacet.Companion.get(module);
if (kotlinFacet == null) return false;
final KotlinFacetSettings facetSettings = kotlinFacet.getConfiguration().getSettings();
if (facetSettings.isTestModule()) return false;
final IdePlatform platform = facetSettings.getPlatform();
if (platform == null) return false;
return CommonIdePlatformUtil.isCommon(platform);
}
private static Collection<String> findNativeGradleBuildTasks(Collection<String> gradleTasks, String sourceSetName) {
// First, attempt to find Kotlin/Native convention Gradle task that unites all outputType-specific build tasks.
final String conventionGradleTask = sourceSetName + "Binaries";
if (gradleTasks.contains(conventionGradleTask)) {
return Collections.singletonList(conventionGradleTask);
}
// If convention task not found, then attempt to find all appropriate build tasks for the given source set.
final Collection<String> linkPrefixes;
final String targetName;
if (sourceSetName.endsWith("Main")) {
targetName = StringUtil.substringBeforeLast(sourceSetName,"Main");
linkPrefixes = ContainerUtil.newArrayList("link", "linkMain");
}
else if (sourceSetName.endsWith("Test")) {
targetName = StringUtil.substringBeforeLast(sourceSetName,"Test");
linkPrefixes = Collections.singletonList("linkTest");
}
else {
targetName = sourceSetName;
linkPrefixes = Collections.singletonList("link");
}
return linkPrefixes.stream()
// get base task name (without disambiguation classifier)
.map(linkPrefix -> linkPrefix + capitalize(targetName))
// find all Gradle tasks that start with base task name
.flatMap(nativeTaskName -> gradleTasks.stream().filter(taskName -> taskName.startsWith(nativeTaskName)))
.collect(Collectors.toList());
}
private static Collection<String> findMetadataBuildTasks(Collection<String> gradleTasks, String sourceSetName) {
if ("commonMain".equals(sourceSetName)) {
final String metadataTaskName = "metadataMainClasses";
if (gradleTasks.contains(metadataTaskName)) {
return Collections.singletonList(metadataTaskName);
}
}
return Collections.emptyList();
}
private static Collection<String> addPrefix(Collection<String> tasks, String taskPrefix) {
return tasks.stream().map(task -> taskPrefix + task).collect(Collectors.toList());
}
// ---------------------------------------- //
// TODO END: Extract custom Kotlin logic. //
// ---------------------------------------- //
}
@@ -0,0 +1,361 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.gradle.execution;
import com.intellij.build.BuildViewManager;
import com.intellij.compiler.impl.CompilerUtil;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.openapi.compiler.ex.CompilerPathsEx;
import com.intellij.openapi.externalSystem.model.DataNode;
import com.intellij.openapi.externalSystem.model.ProjectKeys;
import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings;
import com.intellij.openapi.externalSystem.model.project.ModuleData;
import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode;
import com.intellij.openapi.externalSystem.task.TaskCallback;
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootModificationTracker;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.task.*;
import com.intellij.task.impl.JpsProjectTaskRunner;
import com.intellij.util.SmartList;
import com.intellij.util.SystemProperties;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FactoryMap;
import com.intellij.util.containers.MultiMap;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.config.KotlinFacetSettings;
import org.jetbrains.kotlin.idea.facet.KotlinFacet;
import org.jetbrains.kotlin.platform.IdePlatform;
import org.jetbrains.kotlin.platform.impl.CommonIdePlatformUtil;
import org.jetbrains.kotlin.platform.impl.NativeIdePlatformUtil;
import org.jetbrains.plugins.gradle.execution.build.CachedModuleDataFinder;
import org.jetbrains.plugins.gradle.execution.build.GradleProjectTaskRunner;
import org.jetbrains.plugins.gradle.service.project.GradleBuildSrcProjectsResolver;
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil;
import org.jetbrains.plugins.gradle.service.task.GradleTaskManager;
import org.jetbrains.plugins.gradle.settings.GradleSettings;
import org.jetbrains.plugins.gradle.settings.GradleSystemRunningSettings;
import org.jetbrains.plugins.gradle.util.GradleConstants;
import java.io.File;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration.PROGRESS_LISTENER_KEY;
import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.*;
import static com.intellij.openapi.util.text.StringUtil.*;
import static org.jetbrains.plugins.gradle.execution.GradleRunnerUtil.resolveProjectPath;
/**
* This is a modified copy of {@link GradleProjectTaskRunner} that allows building Kotlin Common and Kotlin Native modules
* in IDEA by delegating to Gradle builder ("Delegate IDE build/run actions to gradle"). See #KT-27295, #KT-27296.
*
* TODO: Refactor this class to remove duplicated logic when {@link GradleProjectTaskRunner} will allow extending it to
* collect custom Gradle tasks. See #IDEA-204372, #KT-28880.
*/
class KotlinMPPGradleProjectTaskRunner extends ProjectTaskRunner
{
@Language("Groovy")
private static final String FORCE_COMPILE_TASKS_INIT_SCRIPT_TEMPLATE = "projectsEvaluated { \n" +
" rootProject.findProject('%s')?.tasks?.withType(AbstractCompile) { \n" +
" outputs.upToDateWhen { false } \n" +
" } \n" +
"}\n";
@Override
public void run(@NotNull Project project,
@NotNull ProjectTaskContext context,
@Nullable ProjectTaskNotification callback,
@NotNull Collection<? extends ProjectTask> tasks) {
MultiMap<String, String> buildTasksMap = MultiMap.createLinkedSet();
MultiMap<String, String> cleanTasksMap = MultiMap.createLinkedSet();
MultiMap<String, String> initScripts = MultiMap.createLinkedSet();
Map<Class<? extends ProjectTask>, List<ProjectTask>> taskMap = JpsProjectTaskRunner.groupBy(tasks);
List<Module> modules = addModulesBuildTasks(taskMap.get(ModuleBuildTask.class), buildTasksMap, initScripts);
// TODO there should be 'gradle' way to build files instead of related modules entirely
List<Module> modulesOfFiles = addModulesBuildTasks(taskMap.get(ModuleFilesBuildTask.class), buildTasksMap, initScripts);
// TODO send a message if nothing to build
Set<String> rootPaths = buildTasksMap.keySet();
AtomicInteger successCounter = new AtomicInteger();
AtomicInteger errorCounter = new AtomicInteger();
TaskCallback taskCallback = callback == null ? null : new TaskCallback() {
@Override
public void onSuccess() {
handle(true);
}
@Override
public void onFailure() {
handle(false);
}
private void handle(boolean success) {
int successes = success ? successCounter.incrementAndGet() : successCounter.get();
int errors = success ? errorCounter.get() : errorCounter.incrementAndGet();
if (successes + errors == rootPaths.size()) {
if (!project.isDisposed()) {
// refresh on output roots is required in order for the order enumerator to see all roots via VFS
final List<Module> affectedModules = ContainerUtil.concat(modules, modulesOfFiles);
// have to refresh in case of errors too, because run configuration may be set to ignore errors
Collection<String> affectedRoots = ContainerUtil.newHashSet(
CompilerPathsEx.getOutputPaths(affectedModules.toArray(Module.EMPTY_ARRAY)));
if (!affectedRoots.isEmpty()) {
CompilerUtil.refreshOutputRoots(affectedRoots);
}
}
callback.finished(new ProjectTaskResult(false, errors, 0));
}
}
};
// TODO compiler options should be configurable
@Language("Groovy")
String compilerOptionsInitScript = "allprojects {\n" +
" tasks.withType(JavaCompile) {\n" +
" options.compilerArgs += [\"-Xlint:deprecation\"]\n" +
" }" +
"}\n";
String gradleVmOptions = GradleSettings.getInstance(project).getGradleVmOptions();
for (String rootProjectPath : rootPaths) {
Collection<String> buildTasks = buildTasksMap.get(rootProjectPath);
if (buildTasks.isEmpty()) continue;
Collection<String> cleanTasks = cleanTasksMap.get(rootProjectPath);
ExternalSystemTaskExecutionSettings settings = new ExternalSystemTaskExecutionSettings();
File projectFile = new File(rootProjectPath);
final String projectName;
if (projectFile.isFile()) {
projectName = projectFile.getParentFile().getName();
}
else {
projectName = projectFile.getName();
}
String executionName = "Build " + projectName;
settings.setExecutionName(executionName);
settings.setExternalProjectPath(rootProjectPath);
settings.setTaskNames(ContainerUtil.collect(ContainerUtil.concat(cleanTasks, buildTasks).iterator()));
//settings.setScriptParameters(scriptParameters);
settings.setVmOptions(gradleVmOptions);
settings.setExternalSystemIdString(GradleConstants.SYSTEM_ID.getId());
UserDataHolderBase userData = new UserDataHolderBase();
userData.putUserData(PROGRESS_LISTENER_KEY, BuildViewManager.class);
Collection<String> scripts = initScripts.getModifiable(rootProjectPath);
scripts.add(compilerOptionsInitScript);
userData.putUserData(GradleTaskManager.INIT_SCRIPT_KEY, join(scripts, SystemProperties.getLineSeparator()));
ExternalSystemUtil.runTask(settings, DefaultRunExecutor.EXECUTOR_ID, project, GradleConstants.SYSTEM_ID,
taskCallback, ProgressExecutionMode.IN_BACKGROUND_ASYNC, false, userData);
}
}
@Override
public boolean canRun(@NotNull ProjectTask projectTask) {
if (!GradleSystemRunningSettings.getInstance().isUseGradleAwareMake()) return false;
if (projectTask instanceof ModuleBuildTask) {
final ModuleBuildTask moduleBuildTask = (ModuleBuildTask) projectTask;
final Module module = moduleBuildTask.getModule();
if (!isExternalSystemAwareModule(GradleConstants.SYSTEM_ID, module)) return false;
// ---------------------------------------- //
// TODO BEGIN: Extract custom Kotlin logic. //
// ---------------------------------------- //
if (isProjectWithNativeSourceOrCommonProductionSourceModules(module.getProject())) return true;
// ---------------------------------------- //
// TODO END: Extract custom Kotlin logic. //
// ---------------------------------------- //
}
return false;
}
private static List<Module> addModulesBuildTasks(@Nullable Collection<? extends ProjectTask> projectTasks,
@NotNull MultiMap<String, String> buildTasksMap,
@NotNull MultiMap<String, String> initScripts) {
if (ContainerUtil.isEmpty(projectTasks)) return Collections.emptyList();
List<Module> affectedModules = new SmartList<>();
Map<Module, String> rootPathsMap = FactoryMap.create(module -> notNullize(resolveProjectPath(module)));
final CachedModuleDataFinder moduleDataFinder = new CachedModuleDataFinder();
for (ProjectTask projectTask : projectTasks) {
if (!(projectTask instanceof ModuleBuildTask)) continue;
ModuleBuildTask moduleBuildTask = (ModuleBuildTask)projectTask;
Module module = moduleBuildTask.getModule();
affectedModules.add(module);
final String rootProjectPath = rootPathsMap.get(module);
if (isEmpty(rootProjectPath)) continue;
final String projectId = getExternalProjectId(module);
if (projectId == null) continue;
final String externalProjectPath = getExternalProjectPath(module);
if (externalProjectPath == null || endsWith(externalProjectPath, "buildSrc")) continue;
final DataNode<? extends ModuleData> moduleDataNode = moduleDataFinder.findMainModuleData(module);
if (moduleDataNode == null) continue;
// all buildSrc runtime projects will be built by gradle implicitly
if (Boolean.parseBoolean(moduleDataNode.getData().getProperty(GradleBuildSrcProjectsResolver.BUILD_SRC_MODULE_PROPERTY))) {
continue;
}
String gradlePath = GradleProjectResolverUtil.getGradlePath(module);
if (gradlePath == null) continue;
String taskPrefix = endsWithChar(gradlePath, ':') ? gradlePath : (gradlePath + ':');
List<String> gradleTasks = ContainerUtil.mapNotNull(
findAll(moduleDataNode, ProjectKeys.TASK), node ->
node.getData().isInherited() ? null : trimStart(node.getData().getName(), taskPrefix));
Collection<String> projectInitScripts = initScripts.getModifiable(rootProjectPath);
Collection<String> buildRootTasks = buildTasksMap.getModifiable(rootProjectPath);
final String moduleType = getExternalModuleType(module);
if (!moduleBuildTask.isIncrementalBuild()) {
projectInitScripts.add(String.format(FORCE_COMPILE_TASKS_INIT_SCRIPT_TEMPLATE, gradlePath));
}
String assembleTask = "assemble";
if (GradleConstants.GRADLE_SOURCE_SET_MODULE_TYPE_KEY.equals(moduleType)) {
String sourceSetName = GradleProjectResolverUtil.getSourceSetName(module);
String gradleTask = isEmpty(sourceSetName) || "main".equals(sourceSetName) ? "classes" : sourceSetName + "Classes";
if (gradleTasks.contains(gradleTask)) {
buildRootTasks.add(taskPrefix + gradleTask);
}
else if ("main".equals(sourceSetName) || "test".equals(sourceSetName)) {
buildRootTasks.add(taskPrefix + assembleTask);
}
// ---------------------------------------- //
// TODO BEGIN: Extract custom Kotlin logic. //
// ---------------------------------------- //
else if (isNativeSourceModule(module)) {
// Add tasks for Kotlin/Native.
buildRootTasks.addAll(addPrefix(findNativeGradleBuildTasks(gradleTasks, sourceSetName), taskPrefix));
}
else if (isCommonProductionSourceModule(module)) {
// Add tasks for compiling metadata.
buildRootTasks.addAll(addPrefix(findMetadataBuildTasks(gradleTasks, sourceSetName), taskPrefix));
}
// ---------------------------------------- //
// TODO END: Extract custom Kotlin logic. //
// ---------------------------------------- //
}
else {
if (gradleTasks.contains("classes")) {
buildRootTasks.add(taskPrefix + "classes");
buildRootTasks.add(taskPrefix + "testClasses");
}
else if (gradleTasks.contains(assembleTask)) {
buildRootTasks.add(taskPrefix + assembleTask);
}
}
}
return affectedModules;
}
// ---------------------------------------- //
// TODO BEGIN: Extract custom Kotlin logic. //
// ---------------------------------------- //
private static boolean isProjectWithNativeSourceOrCommonProductionSourceModules(Project project) {
return CachedValuesManager.getManager(project).getCachedValue(
project,
() -> new CachedValueProvider.Result<>(
Arrays.stream(ModuleManager.getInstance(project).getModules()).anyMatch(
module -> isNativeSourceModule(module) || isCommonProductionSourceModule(module)
),
ProjectRootModificationTracker.getInstance(project)
));
}
private static boolean isNativeSourceModule(Module module) {
final KotlinFacet kotlinFacet = KotlinFacet.Companion.get(module);
if (kotlinFacet == null) return false;
final IdePlatform platform = kotlinFacet.getConfiguration().getSettings().getPlatform();
if (platform == null) return false;
return NativeIdePlatformUtil.isKotlinNative(platform);
}
private static boolean isCommonProductionSourceModule(Module module) {
final KotlinFacet kotlinFacet = KotlinFacet.Companion.get(module);
if (kotlinFacet == null) return false;
final KotlinFacetSettings facetSettings = kotlinFacet.getConfiguration().getSettings();
if (facetSettings.isTestModule()) return false;
final IdePlatform platform = facetSettings.getPlatform();
if (platform == null) return false;
return CommonIdePlatformUtil.isCommon(platform);
}
private static Collection<String> findNativeGradleBuildTasks(Collection<String> gradleTasks, String sourceSetName) {
// First, attempt to find Kotlin/Native convention Gradle task that unites all outputType-specific build tasks.
final String conventionGradleTask = sourceSetName + "Binaries";
if (gradleTasks.contains(conventionGradleTask)) {
return Collections.singletonList(conventionGradleTask);
}
// If convention task not found, then attempt to find all appropriate build tasks for the given source set.
final Collection<String> linkPrefixes;
final String targetName;
if (sourceSetName.endsWith("Main")) {
targetName = StringUtil.substringBeforeLast(sourceSetName,"Main");
linkPrefixes = ContainerUtil.newArrayList("link", "linkMain");
}
else if (sourceSetName.endsWith("Test")) {
targetName = StringUtil.substringBeforeLast(sourceSetName,"Test");
linkPrefixes = Collections.singletonList("linkTest");
}
else {
targetName = sourceSetName;
linkPrefixes = Collections.singletonList("link");
}
return linkPrefixes.stream()
// get base task name (without disambiguation classifier)
.map(linkPrefix -> linkPrefix + capitalize(targetName))
// find all Gradle tasks that start with base task name
.flatMap(nativeTaskName -> gradleTasks.stream().filter(taskName -> taskName.startsWith(nativeTaskName)))
.collect(Collectors.toList());
}
private static Collection<String> findMetadataBuildTasks(Collection<String> gradleTasks, String sourceSetName) {
if ("commonMain".equals(sourceSetName)) {
final String metadataTaskName = "metadataMainClasses";
if (gradleTasks.contains(metadataTaskName)) {
return Collections.singletonList(metadataTaskName);
}
}
return Collections.emptyList();
}
private static Collection<String> addPrefix(Collection<String> tasks, String taskPrefix) {
return tasks.stream().map(task -> taskPrefix + task).collect(Collectors.toList());
}
// ---------------------------------------- //
// TODO END: Extract custom Kotlin logic. //
// ---------------------------------------- //
}
@@ -3,6 +3,7 @@
* that can be found in the license/LICENSE.txt file.
*/
@file:JvmName("NativeIdePlatformUtil")
package org.jetbrains.kotlin.platform.impl
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
@@ -37,3 +38,8 @@ object NativeIdePlatformKind : IdePlatformKind<NativeIdePlatformKind>() {
// These are fake compiler arguments for Kotlin/Native - only for usage within IDEA plugin:
class FakeK2NativeCompilerArguments : CommonCompilerArguments()
val IdePlatformKind<*>?.isKotlinNative
get() = this is NativeIdePlatformKind
val IdePlatform<*, *>?.isKotlinNative
get() = this is NativeIdePlatformKind.Platform
+1
View File
@@ -17,6 +17,7 @@
</extensions>
<extensions defaultExtensionNs="com.intellij">
<projectTaskRunner implementation="org.jetbrains.kotlin.idea.gradle.execution.KotlinMPPGradleProjectTaskRunner" order="first, before gradle"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinSourceSetDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleProjectSettingsDataService"/>
</extensions>