org.gradle.plugins.ide.idea.model.IdeaModule Maven / Gradle / Ivy
Show all versions of gradle-api Show documentation
/*
* Copyright 2011 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.model;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import groovy.lang.Closure;
import org.gradle.api.Action;
import org.gradle.api.Incubating;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.language.scala.ScalaPlatform;
import org.gradle.plugins.ide.idea.model.internal.IdeaDependenciesProvider;
import org.gradle.plugins.ide.internal.resolver.UnresolvedDependenciesLogger;
import java.io.File;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import static org.gradle.util.ConfigureUtil.configure;
/**
* Enables fine-tuning module details (*.iml file) of the IDEA plugin.
*
* Example of use with a blend of most possible properties.
* Typically you don't have to configure this model directly because Gradle configures it for you.
*
*
* apply plugin: 'java'
* apply plugin: 'idea'
*
* //for the sake of this example, let's introduce a 'performanceTestCompile' configuration
* configurations {
* performanceTestCompile
* performanceTestCompile.extendsFrom(testCompile)
* }
*
* dependencies {
* //performanceTestCompile "some.interesting:dependency:1.0"
* }
*
* idea {
*
* //if you want parts of paths in resulting files (*.iml, etc.) to be replaced by variables (Files)
* pathVariables GRADLE_HOME: file('~/cool-software/gradle')
*
* module {
* //if for some reason you want to add an extra sourceDirs
* sourceDirs += file('some-extra-source-folder')
*
* //and some extra test source dirs
* testSourceDirs += file('some-extra-test-dir')
*
* //and hint to mark some of existing source dirs as generated sources
* generatedSourceDirs += file('some-extra-source-folder')
*
* //and some extra dirs that should be excluded by IDEA
* excludeDirs += file('some-extra-exclude-dir')
*
* //if you don't like the name Gradle has chosen
* name = 'some-better-name'
*
* //if you prefer different output folders
* inheritOutputDirs = false
* outputDir = file('muchBetterOutputDir')
* testOutputDir = file('muchBetterTestOutputDir')
*
* //if you prefer different SDK than the one inherited from IDEA project
* jdkName = '1.6'
*
* //put our custom test dependencies onto IDEA's TEST scope
* scopes.TEST.plus += [ configurations.performanceTestCompile ]
*
* //if 'content root' (as IDEA calls it) of the module is different
* contentRoot = file('my-module-content-root')
*
* //if you love browsing Javadoc
* downloadJavadoc = true
*
* //and hate reading sources :)
* downloadSources = false
* }
* }
*
*
* For tackling edge cases, users can perform advanced configuration on the resulting XML file.
* It is also possible to affect the way the IDEA plugin merges the existing configuration
* via beforeMerged and whenMerged closures.
*
* beforeMerged and whenMerged closures receive a {@link Module} parameter
*
* Examples of advanced configuration:
*
*
* apply plugin: 'java'
* apply plugin: 'idea'
*
* idea {
* module {
* iml {
* //if you like to keep *.iml in a secret folder
* generateTo = file('secret-modules-folder')
*
* //if you want to mess with the resulting XML in whatever way you fancy
* withXml {
* def node = it.asNode()
* node.appendNode('iLoveGradle', 'true')
* node.appendNode('butAlso', 'I find increasing pleasure tinkering with output *.iml contents. Yeah!!!')
* }
*
* //closure executed after *.iml content is loaded from existing file
* //but before gradle build information is merged
* beforeMerged { module ->
* //if you want skip merging exclude dirs
* module.excludeFolders.clear()
* }
*
* //closure executed after *.iml content is loaded from existing file
* //and after gradle build information is merged
* whenMerged { module ->
* //you can tinker with {@link Module}
* }
* }
* }
* }
*
*
*/
public class IdeaModule {
private String name;
private Set sourceDirs;
private Set generatedSourceDirs = Sets.newLinkedHashSet();
private Map>> scopes = Maps.newLinkedHashMap();
private boolean downloadSources = true;
private boolean downloadJavadoc;
private File contentRoot;
private Set testSourceDirs;
private Set excludeDirs;
private Boolean inheritOutputDirs;
private File outputDir;
private File testOutputDir;
private Map pathVariables = Maps.newLinkedHashMap();
private String jdkName;
private IdeaLanguageLevel languageLevel;
private JavaVersion targetBytecodeVersion;
private ScalaPlatform scalaPlatform;
private final IdeaModuleIml iml;
private final Project project;
private PathFactory pathFactory;
private boolean offline;
private Map> singleEntryLibraries;
public IdeaModule(Project project, IdeaModuleIml iml) {
this.project = project;
this.iml = iml;
}
/**
* Configures module name, that is the name of the *.iml file.
*
* It's optional because the task should configure it correctly for you.
* By default it will try to use the project.name or prefix it with a part of a project.path to make
* sure the module name is unique in the scope of a multi-module build.
* The 'uniqueness' of a module name is required for correct import into IDEA and the task will make sure the name
* is unique.
*
* since 1.0-milestone-2
*
* If your project has problems with unique names it is recommended to always run gradle idea from the
* root, i.e. for all subprojects.
* If you run the generation of the IDEA module only for a single subproject then you may have different results
* because the unique names are calculated based on IDEA modules that are involved in the specific build run.
*
* If you update the module names then make sure you run gradle idea from the root, e.g. for all
* subprojects, including generation of IDEA project.
* The reason is that there may be subprojects that depend on the subproject with amended module name.
* So you want them to be generated as well because the module dependencies need to refer to the amended project
* name.
* Basically, for non-trivial projects it is recommended to always run gradle idea from the root.
*
* For example see docs for {@link IdeaModule}
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* The directories containing the production sources.
*
* For example see docs for {@link IdeaModule}
*/
public Set getSourceDirs() {
return sourceDirs;
}
public void setSourceDirs(Set sourceDirs) {
this.sourceDirs = sourceDirs;
}
/**
* The directories containing the generated sources (both production and test sources).
*
* For example see docs for {@link IdeaModule}
*/
@Incubating
public Set getGeneratedSourceDirs() {
return generatedSourceDirs;
}
@Incubating
public void setGeneratedSourceDirs(Set generatedSourceDirs) {
this.generatedSourceDirs = generatedSourceDirs;
}
/**
* The keys of this map are the IDEA scopes. Each key points to another map that has two keys, plus and minus.
* The values of those keys are collections of {@link org.gradle.api.artifacts.Configuration} objects. The files of the
* plus configurations are added minus the files from the minus configurations. See example below...
*
* Example how to use scopes property to enable 'performanceTestCompile' dependencies in the output *.iml file:
*
* apply plugin: 'java'
* apply plugin: 'idea'
*
* configurations {
* performanceTestCompile
* performanceTestCompile.extendsFrom(testCompile)
* }
*
* dependencies {
* //performanceTestCompile "some.interesting:dependency:1.0"
* }
*
* idea {
* module {
* scopes.TEST.plus += [ configurations.performanceTestCompile ]
* }
* }
*
*/
public Map>> getScopes() {
return scopes;
}
public void setScopes(Map>> scopes) {
this.scopes = scopes;
}
/**
* Whether to download and add sources associated with the dependency jars. For example see docs for {@link IdeaModule}
*/
public boolean isDownloadSources() {
return downloadSources;
}
public void setDownloadSources(boolean downloadSources) {
this.downloadSources = downloadSources;
}
/**
* Whether to download and add javadoc associated with the dependency jars.
For example see docs for {@link IdeaModule}
*/
public boolean isDownloadJavadoc() {
return downloadJavadoc;
}
public void setDownloadJavadoc(boolean downloadJavadoc) {
this.downloadJavadoc = downloadJavadoc;
}
/**
* The content root directory of the module.
For example see docs for {@link IdeaModule}
*/
public File getContentRoot() {
return contentRoot;
}
public void setContentRoot(File contentRoot) {
this.contentRoot = contentRoot;
}
/**
* The directories containing the test sources.
For example see docs for {@link IdeaModule}
*/
public Set getTestSourceDirs() {
return testSourceDirs;
}
public void setTestSourceDirs(Set testSourceDirs) {
this.testSourceDirs = testSourceDirs;
}
/**
* {@link org.gradle.api.dsl.ConventionProperty} for the directories to be excluded. For example see docs for {@link IdeaModule}
*/
public Set getExcludeDirs() {
return excludeDirs;
}
public void setExcludeDirs(Set excludeDirs) {
this.excludeDirs = excludeDirs;
}
/**
* If true, output directories for this module will be located below the output directory for the project;
* otherwise, they will be set to the directories specified by {@link #getOutputDir()} and {@link #getTestOutputDir()}.
*
* For example see docs for {@link IdeaModule}
*/
public Boolean getInheritOutputDirs() {
return inheritOutputDirs;
}
public void setInheritOutputDirs(Boolean inheritOutputDirs) {
this.inheritOutputDirs = inheritOutputDirs;
}
/**
* The output directory for production classes.
* If {@code null}, no entry will be created.
*
* For example see docs for {@link IdeaModule}
*/
public File getOutputDir() {
return outputDir;
}
public void setOutputDir(File outputDir) {
this.outputDir = outputDir;
}
/**
* The output directory for test classes.
* If {@code null}, no entry will be created.
*
* For example see docs for {@link IdeaModule}
*/
public File getTestOutputDir() {
return testOutputDir;
}
public void setTestOutputDir(File testOutputDir) {
this.testOutputDir = testOutputDir;
}
/**
* The variables to be used for replacing absolute paths in the iml entries.
* For example, you might add a {@code GRADLE_USER_HOME} variable to point to the Gradle user home dir.
*
* For example see docs for {@link IdeaModule}
*/
public Map getPathVariables() {
return pathVariables;
}
public void setPathVariables(Map pathVariables) {
this.pathVariables = pathVariables;
}
/**
* The JDK to use for this module.
* If {@code null}, the value of the existing or default ipr XML (inherited) is used.
* If it is set to inherited
, the project SDK is used.
* Otherwise the SDK for the corresponding value of java version is used for this module.
*
* For example see docs for {@link IdeaModule}
*/
public String getJdkName() {
return jdkName;
}
public void setJdkName(String jdkName) {
this.jdkName = jdkName;
}
/**
* The module specific language Level to use for this module.
* When {@code null}, the module will inherit the language level from the idea project.
*
* The Idea module language level is based on the {@code sourceCompatibility} settings for the associated Gradle project.
*/
@Incubating
public IdeaLanguageLevel getLanguageLevel() {
return languageLevel;
}
@Incubating
public void setLanguageLevel(IdeaLanguageLevel languageLevel) {
this.languageLevel = languageLevel;
}
/**
* The module specific bytecode version to use for this module.
* When {@code null}, the module will inherit the bytecode version from the idea project.
*
* The Idea module bytecode version is based on the {@code targetCompatibility} settings for the associated Gradle project.
*/
@Incubating
public JavaVersion getTargetBytecodeVersion() {
return targetBytecodeVersion;
}
@Incubating
public void setTargetBytecodeVersion(JavaVersion targetBytecodeVersion) {
this.targetBytecodeVersion = targetBytecodeVersion;
}
/**
* The Scala version used by this module.
*/
@Incubating
public ScalaPlatform getScalaPlatform() {
return scalaPlatform;
}
@Incubating
public void setScalaPlatform(ScalaPlatform scalaPlatform) {
this.scalaPlatform = scalaPlatform;
}
/**
* See {@link #iml(Action)}
*/
public IdeaModuleIml getIml() {
return iml;
}
/**
* An owner of this IDEA module.
*
* If IdeaModule requires some information from gradle this field should not be used for this purpose.
* IdeaModule instances should be configured with all necessary information by the plugin or user.
*/
public Project getProject() {
return project;
}
public PathFactory getPathFactory() {
return pathFactory;
}
public void setPathFactory(PathFactory pathFactory) {
this.pathFactory = pathFactory;
}
/**
* If true then external artifacts (e.g. those found in repositories) will not be included in the resulting classpath (only project and local file dependencies will be included).
*/
public boolean isOffline() {
return offline;
}
public void setOffline(boolean offline) {
this.offline = offline;
}
public Map> getSingleEntryLibraries() {
return singleEntryLibraries;
}
public void setSingleEntryLibraries(Map> singleEntryLibraries) {
this.singleEntryLibraries = singleEntryLibraries;
}
/**
* Enables advanced configuration like tinkering with the output XML or affecting the way existing *.iml content is merged with gradle build information.
*
* For example see docs for {@link IdeaModule}.
*/
public void iml(Closure closure) {
configure(closure, getIml());
}
/**
* Enables advanced configuration like tinkering with the output XML or affecting the way existing *.iml content is merged with gradle build information.
*
* For example see docs for {@link IdeaModule}.
*
* @since 3.5
*/
public void iml(Action super IdeaModuleIml> action) {
action.execute(getIml());
}
/**
* Configures output *.iml file.
* It's optional because the task should configure it correctly for you (including making sure it is unique in the multi-module build).
* If you really need to change the output file name (or the module name) it is much easier to do it via the moduleName property!
*
* Please refer to documentation on moduleName property.
* In IntelliJ IDEA the module name is the same as the name of the *.iml file.
*/
public File getOutputFile() {
return new File(iml.getGenerateTo(), getName() + ".iml");
}
public void setOutputFile(File newOutputFile) {
setName(newOutputFile.getName().replaceFirst("\\.iml$", ""));
iml.setGenerateTo(newOutputFile.getParentFile());
}
/**
* Resolves and returns the module's dependencies.
*
* @return dependencies
*/
public Set resolveDependencies() {
ProjectInternal projectInternal = (ProjectInternal) project;
IdeaDependenciesProvider ideaDependenciesProvider = new IdeaDependenciesProvider(projectInternal.getServices());
new UnresolvedDependenciesLogger().log(ideaDependenciesProvider.getUnresolvedDependencies(this));
return ideaDependenciesProvider.provide(this);
}
public void mergeXmlModule(Module xmlModule) {
iml.getBeforeMerged().execute(xmlModule);
Path contentRoot = getPathFactory().path(getContentRoot());
Set sourceFolders = pathsOf(existing(getSourceDirs()));
Set generatedSourceFolders = pathsOf(existing(getGeneratedSourceDirs()));
Set testSourceFolders = pathsOf(existing(getTestSourceDirs()));
Set excludeFolders = pathsOf(getExcludeDirs());
Path outputDir = getOutputDir() != null ? getPathFactory().path(getOutputDir()) : null;
Path testOutputDir = getTestOutputDir() != null ? getPathFactory().path(getTestOutputDir()) : null;
Set dependencies = resolveDependencies();
String level = getLanguageLevel() != null ? getLanguageLevel().getLevel() : null;
xmlModule.configure(
contentRoot,
sourceFolders, testSourceFolders, generatedSourceFolders, excludeFolders,
getInheritOutputDirs(), outputDir, testOutputDir,
dependencies,
getJdkName(), level
);
iml.getWhenMerged().execute(xmlModule);
}
private Set existing(Set files) {
return Sets.filter(files, new Predicate() {
@Override
public boolean apply(File file) {
return file.exists();
}
});
}
private Set pathsOf(Set files) {
return Sets.newLinkedHashSet(Iterables.transform(files, new Function() {
@Override
public Path apply(File file) {
return getPathFactory().path(file);
}
}));
}
}