/* * Copyright 2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.gradle.plugins.ide.idea; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.gradle.api.Action; import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.PublishArtifact; import org.gradle.api.artifacts.component.ProjectComponentIdentifier; import org.gradle.api.file.FileCollection; import org.gradle.api.internal.ConventionMapping; import org.gradle.api.internal.IConventionAware; import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectLocalComponentProvider; import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact; import org.gradle.api.internal.project.ProjectInternal; import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.plugins.WarPlugin; import org.gradle.api.plugins.scala.ScalaBasePlugin; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.internal.component.local.model.LocalComponentArtifactMetadata; import org.gradle.internal.component.local.model.PublishArtifactLocalArtifactMetadata; import org.gradle.internal.reflect.Instantiator; import org.gradle.language.scala.plugins.ScalaLanguagePlugin; import org.gradle.plugins.ide.api.XmlFileContentMerger; import org.gradle.plugins.ide.idea.internal.IdeaScalaConfigurer; import org.gradle.plugins.ide.internal.configurer.UniqueProjectNameProvider; import org.gradle.plugins.ide.idea.model.IdeaLanguageLevel; import org.gradle.plugins.ide.idea.model.IdeaModel; import org.gradle.plugins.ide.idea.model.IdeaModule; import org.gradle.plugins.ide.idea.model.IdeaModuleIml; import org.gradle.plugins.ide.idea.model.IdeaProject; import org.gradle.plugins.ide.idea.model.IdeaWorkspace; import org.gradle.plugins.ide.idea.model.PathFactory; import org.gradle.plugins.ide.idea.model.internal.GeneratedIdeaScope; import org.gradle.plugins.ide.idea.model.internal.IdeaDependenciesProvider; import org.gradle.plugins.ide.internal.IdePlugin; import org.gradle.util.SingleMessageLogger; import javax.inject.Inject; import java.io.File; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import static org.gradle.internal.component.local.model.DefaultProjectComponentIdentifier.newProjectId; /** * Adds a GenerateIdeaModule task. When applied to a root project, also adds a GenerateIdeaProject task. For projects that have the Java plugin applied, the tasks receive additional Java-specific * configuration. */ public class IdeaPlugin extends IdePlugin { private static final Predicate<Project> HAS_IDEA_AND_JAVA_PLUGINS = new Predicate<Project>() { @Override public boolean apply(Project project) { return project.getPlugins().hasPlugin(IdeaPlugin.class) && project.getPlugins().hasPlugin(JavaBasePlugin.class); } }; public static final Function<Project, JavaVersion> SOURCE_COMPATIBILITY = new Function<Project, JavaVersion>() { @Override public JavaVersion apply(Project p) { return p.getConvention().getPlugin(JavaPluginConvention.class).getSourceCompatibility(); } }; public static final Function<Project, JavaVersion> TARGET_COMPATIBILITY = new Function<Project, JavaVersion>() { @Override public JavaVersion apply(Project p) { return p.getConvention().getPlugin(JavaPluginConvention.class).getTargetCompatibility(); } }; private final Instantiator instantiator; private IdeaModel ideaModel; private List<Project> allJavaProjects; private final UniqueProjectNameProvider uniqueProjectNameProvider; @Inject public IdeaPlugin(Instantiator instantiator, UniqueProjectNameProvider uniqueProjectNameProvider) { this.instantiator = instantiator; this.uniqueProjectNameProvider = uniqueProjectNameProvider; } public IdeaModel getModel() { return ideaModel; } @Override protected String getLifecycleTaskName() { return "idea"; } @Override protected void onApply(Project project) { getLifecycleTask().setDescription("Generates IDEA project files (IML, IPR, IWS)"); getCleanTask().setDescription("Cleans IDEA project files (IML, IPR)"); ideaModel = project.getExtensions().create("idea", IdeaModel.class); configureIdeaWorkspace(project); configureIdeaProject(project); configureIdeaModule(project); configureForJavaPlugin(project); configureForWarPlugin(project); configureForScalaPlugin(); registerImlArtifact(project); } // No one should be calling this. @Deprecated public void performPostEvaluationActions() { SingleMessageLogger.nagUserOfDiscontinuedMethod("performPostEvaluationActions"); } private void registerImlArtifact(Project project) { ProjectLocalComponentProvider projectComponentProvider = ((ProjectInternal) project).getServices().get(ProjectLocalComponentProvider.class); ProjectComponentIdentifier projectId = newProjectId(project); projectComponentProvider.registerAdditionalArtifact(projectId, createImlArtifact(projectId, project)); } private static LocalComponentArtifactMetadata createImlArtifact(ProjectComponentIdentifier projectId, Project project) { IdeaModule module = project.getExtensions().getByType(IdeaModel.class).getModule(); Task byName = project.getTasks().getByName("ideaModule"); PublishArtifact publishArtifact = new ImlArtifact(module, byName); return new PublishArtifactLocalArtifactMetadata(projectId, publishArtifact); } private void configureIdeaWorkspace(final Project project) { if (isRoot(project)) { GenerateIdeaWorkspace task = project.getTasks().create("ideaWorkspace", GenerateIdeaWorkspace.class); task.setDescription("Generates an IDEA workspace file (IWS)"); IdeaWorkspace workspace = new IdeaWorkspace(); workspace.setIws(new XmlFileContentMerger(task.getXmlTransformer())); task.setWorkspace(workspace); ideaModel.setWorkspace(task.getWorkspace()); task.setOutputFile(new File(project.getProjectDir(), project.getName() + ".iws")); addWorker(task, false); } } private void configureIdeaProject(final Project project) { if (isRoot(project)) { final GenerateIdeaProject task = project.getTasks().create("ideaProject", GenerateIdeaProject.class); task.setDescription("Generates IDEA project file (IPR)"); XmlFileContentMerger ipr = new XmlFileContentMerger(task.getXmlTransformer()); IdeaProject ideaProject = instantiator.newInstance(IdeaProject.class, project, ipr); task.setIdeaProject(ideaProject); ideaModel.setProject(ideaProject); ideaProject.setOutputFile(new File(project.getProjectDir(), project.getName() + ".ipr")); ConventionMapping conventionMapping = ((IConventionAware) ideaProject).getConventionMapping(); conventionMapping.map("jdkName", new Callable<String>() { @Override public String call() throws Exception { return JavaVersion.current().toString(); } }); conventionMapping.map("languageLevel", new Callable<IdeaLanguageLevel>() { @Override public IdeaLanguageLevel call() throws Exception { JavaVersion maxSourceCompatibility = getMaxJavaModuleCompatibilityVersionFor(SOURCE_COMPATIBILITY); return new IdeaLanguageLevel(maxSourceCompatibility); } }); conventionMapping.map("targetBytecodeVersion", new Callable<JavaVersion>() { @Override public JavaVersion call() throws Exception { return getMaxJavaModuleCompatibilityVersionFor(TARGET_COMPATIBILITY); } }); ideaProject.setWildcards(Sets.newHashSet("!?*.class", "!?*.scala", "!?*.groovy", "!?*.java")); conventionMapping.map("modules", new Callable<List<IdeaModule>>() { @Override public List<IdeaModule> call() throws Exception { return Lists.newArrayList(Iterables.transform(Sets.filter(project.getRootProject().getAllprojects(), new Predicate<Project>() { @Override public boolean apply(Project p) { return p.getPlugins().hasPlugin(IdeaPlugin.class); } }), new Function<Project, IdeaModule>() { @Override public IdeaModule apply(Project p) { return ideaModelFor(p).getModule(); } })); } }); conventionMapping.map("pathFactory", new Callable<PathFactory>() { @Override public PathFactory call() throws Exception { return new PathFactory().addPathVariable("PROJECT_DIR", task.getOutputFile().getParentFile()); } }); addWorker(task); } } private static IdeaModel ideaModelFor(Project project) { return project.getExtensions().getByType(IdeaModel.class); } private JavaVersion getMaxJavaModuleCompatibilityVersionFor(Function<Project, JavaVersion> toJavaVersion) { List<Project> allJavaProjects = getAllJavaProjects(); if (allJavaProjects.isEmpty()) { return JavaVersion.VERSION_1_6; } else { return Collections.max(Lists.transform(allJavaProjects, toJavaVersion)); } } private List<Project> getAllJavaProjects() { if (allJavaProjects != null) { // cache result because it is pretty expensive to compute return allJavaProjects; } allJavaProjects = Lists.newArrayList(Iterables.filter(project.getRootProject().getAllprojects(), HAS_IDEA_AND_JAVA_PLUGINS)); return allJavaProjects; } private void configureIdeaModule(final Project project) { final GenerateIdeaModule task = project.getTasks().create("ideaModule", GenerateIdeaModule.class); task.setDescription("Generates IDEA module files (IML)"); IdeaModuleIml iml = new IdeaModuleIml(task.getXmlTransformer(), project.getProjectDir()); final IdeaModule module = instantiator.newInstance(IdeaModule.class, project, iml); task.setModule(module); ideaModel.setModule(module); final String defaultModuleName = uniqueProjectNameProvider.getUniqueName(project); module.setName(defaultModuleName); ConventionMapping conventionMapping = ((IConventionAware) module).getConventionMapping(); conventionMapping.map("sourceDirs", new Callable<Set<File>>() { @Override public Set<File> call() throws Exception { return Sets.newHashSet(); } }); conventionMapping.map("contentRoot", new Callable<File>() { @Override public File call() throws Exception { return project.getProjectDir(); } }); conventionMapping.map("testSourceDirs", new Callable<Set<File>>() { @Override public Set<File> call() throws Exception { return Sets.newHashSet(); } }); conventionMapping.map("excludeDirs", new Callable<Set<File>>() { @Override public Set<File> call() throws Exception { return Sets.newHashSet(project.getBuildDir(), project.file(".gradle")); } }); conventionMapping.map("pathFactory", new Callable<PathFactory>() { @Override public PathFactory call() throws Exception { final PathFactory factory = new PathFactory(); factory.addPathVariable("MODULE_DIR", task.getOutputFile().getParentFile()); for (Map.Entry<String, File> entry : module.getPathVariables().entrySet()) { factory.addPathVariable(entry.getKey(), entry.getValue()); } return factory; } }); addWorker(task); } private void configureForJavaPlugin(final Project project) { project.getPlugins().withType(JavaPlugin.class, new Action<JavaPlugin>() { @Override public void execute(JavaPlugin javaPlugin) { configureIdeaModuleForJava(project); } }); } private void configureForWarPlugin(final Project project) { project.getPlugins().withType(WarPlugin.class, new Action<WarPlugin>() { @Override public void execute(WarPlugin warPlugin) { configureIdeaModuleForWar(project); } }); } private void configureIdeaModuleForJava(final Project project) { project.getTasks().withType(GenerateIdeaModule.class, new Action<GenerateIdeaModule>() { @Override public void execute(GenerateIdeaModule ideaModule) { // Defaults setupScopes(ideaModule); // Convention ConventionMapping convention = ((IConventionAware) ideaModule.getModule()).getConventionMapping(); convention.map("sourceDirs", new Callable<Set<File>>() { @Override public Set<File> call() throws Exception { SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets(); return sourceSets.getByName("main").getAllSource().getSrcDirs(); } }); convention.map("testSourceDirs", new Callable<Set<File>>() { @Override public Set<File> call() throws Exception { SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets(); return sourceSets.getByName("test").getAllSource().getSrcDirs(); } }); convention.map("singleEntryLibraries", new Callable<Map<String, FileCollection>>() { @Override public Map<String, FileCollection> call() throws Exception { SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets(); LinkedHashMap<String, FileCollection> map = new LinkedHashMap<String, FileCollection>(2); map.put("RUNTIME", sourceSets.getByName("main").getOutput().getDirs()); map.put("TEST", sourceSets.getByName("test").getOutput().getDirs()); return map; } }); convention.map("targetBytecodeVersion", new Callable<JavaVersion>() { @Override public JavaVersion call() throws Exception { JavaVersion moduleTargetBytecodeLevel = project.getConvention().getPlugin(JavaPluginConvention.class).getTargetCompatibility(); return includeModuleBytecodeLevelOverride(project.getRootProject(), moduleTargetBytecodeLevel) ? moduleTargetBytecodeLevel : null; } }); convention.map("languageLevel", new Callable<IdeaLanguageLevel>() { @Override public IdeaLanguageLevel call() throws Exception { IdeaLanguageLevel moduleLanguageLevel = new IdeaLanguageLevel(project.getConvention().getPlugin(JavaPluginConvention.class).getSourceCompatibility()); return includeModuleLanguageLevelOverride(project.getRootProject(), moduleLanguageLevel) ? moduleLanguageLevel : null; } }); // Dependencies ideaModule.dependsOn(new Callable<FileCollection>() { @Override public FileCollection call() throws Exception { SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets(); return sourceSets.getByName("main").getOutput().getDirs().plus(sourceSets.getByName("test").getOutput().getDirs()); } }); } }); } private void setupScopes(GenerateIdeaModule ideaModule) { Map<String, Map<String, Collection<Configuration>>> scopes = Maps.newLinkedHashMap(); for (GeneratedIdeaScope scope : GeneratedIdeaScope.values()) { Map<String, Collection<Configuration>> plusMinus = Maps.newLinkedHashMap(); plusMinus.put(IdeaDependenciesProvider.SCOPE_PLUS, Lists.<Configuration>newArrayList()); plusMinus.put(IdeaDependenciesProvider.SCOPE_MINUS, Lists.<Configuration>newArrayList()); scopes.put(scope.name(), plusMinus); } Project project = ideaModule.getProject(); ConfigurationContainer configurations = project.getConfigurations(); Collection<Configuration> provided = scopes.get(GeneratedIdeaScope.PROVIDED.name()).get(IdeaDependenciesProvider.SCOPE_PLUS); provided.add(configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)); Collection<Configuration> runtime = scopes.get(GeneratedIdeaScope.RUNTIME.name()).get(IdeaDependenciesProvider.SCOPE_PLUS); runtime.add(configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); Collection<Configuration> test = scopes.get(GeneratedIdeaScope.TEST.name()).get(IdeaDependenciesProvider.SCOPE_PLUS); test.add(configurations.getByName(JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME)); test.add(configurations.getByName(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME)); ideaModule.getModule().setScopes(scopes); } private void configureIdeaModuleForWar(final Project project) { project.getTasks().withType(GenerateIdeaModule.class, new Action<GenerateIdeaModule>() { @Override public void execute(GenerateIdeaModule ideaModule) { ConfigurationContainer configurations = project.getConfigurations(); Configuration providedRuntime = configurations.getByName(WarPlugin.PROVIDED_RUNTIME_CONFIGURATION_NAME); Collection<Configuration> providedPlus = ideaModule.getModule().getScopes().get(GeneratedIdeaScope.PROVIDED.name()).get(IdeaDependenciesProvider.SCOPE_PLUS); providedPlus.add(providedRuntime); Collection<Configuration> runtimeMinus = ideaModule.getModule().getScopes().get(GeneratedIdeaScope.RUNTIME.name()).get(IdeaDependenciesProvider.SCOPE_MINUS); runtimeMinus.add(providedRuntime); Collection<Configuration> testMinus = ideaModule.getModule().getScopes().get(GeneratedIdeaScope.TEST.name()).get(IdeaDependenciesProvider.SCOPE_MINUS); testMinus.add(providedRuntime); } }); } private static boolean includeModuleBytecodeLevelOverride(Project rootProject, JavaVersion moduleTargetBytecodeLevel) { if (!rootProject.getPlugins().hasPlugin(IdeaPlugin.class)) { return true; } IdeaProject ideaProject = ideaModelFor(rootProject).getProject(); return !moduleTargetBytecodeLevel.equals(ideaProject.getTargetBytecodeVersion()); } private static boolean includeModuleLanguageLevelOverride(Project rootProject, IdeaLanguageLevel moduleLanguageLevel) { if (!rootProject.getPlugins().hasPlugin(IdeaPlugin.class)) { return true; } IdeaProject ideaProject = ideaModelFor(rootProject).getProject(); return !moduleLanguageLevel.equals(ideaProject.getLanguageLevel()); } private void configureForScalaPlugin() { project.getPlugins().withType(ScalaBasePlugin.class, new Action<ScalaBasePlugin>() { @Override public void execute(ScalaBasePlugin scalaBasePlugin) { ideaModuleDependsOnRoot(); } }); project.getPlugins().withType(ScalaLanguagePlugin.class, new Action<ScalaLanguagePlugin>() { @Override public void execute(ScalaLanguagePlugin scalaLanguagePlugin) { ideaModuleDependsOnRoot(); } }); if (isRoot(project)) { new IdeaScalaConfigurer(project).configure(); } } private void ideaModuleDependsOnRoot() { // see IdeaScalaConfigurer which requires the ipr to be generated first project.getTasks().findByName("ideaModule").dependsOn(project.getRootProject().getTasks().findByName("ideaProject")); } private static boolean isRoot(Project project) { return project.getParent() == null; } private static class ImlArtifact extends DefaultPublishArtifact { private final IdeaModule module; private final File projectDir; public ImlArtifact(IdeaModule module, Object... tasks) { super(null, "iml", "iml", null, null, null, tasks); this.module = module; this.projectDir = module.getProject().getProjectDir(); } @Override public String getName() { return module.getName(); } @Override public File getFile() { return new File(projectDir, getName() + ".iml"); } } }