/* * Copyright 2016 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.eclipse.model; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import groovy.lang.Closure; import groovy.lang.DelegatesTo; import org.gradle.api.Action; import org.gradle.api.Incubating; import org.gradle.api.InvalidUserDataException; import org.gradle.api.internal.ClosureBackedAction; import org.gradle.plugins.ide.api.XmlFileContentMerger; import org.gradle.plugins.ide.eclipse.model.internal.DefaultResourceFilter; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import static org.gradle.util.ConfigureUtil.configure; /** * Enables fine-tuning project details (.project file) of the Eclipse plugin * <p> * Example of use with a blend of all possible properties. * Bear in mind that usually you don't have configure eclipse project directly because Gradle configures it for free! * * <pre autoTested=''> * apply plugin: 'java' * apply plugin: 'eclipse' * * eclipse { * project { * //if you don't like the name Gradle has chosen * name = 'someBetterName' * * //if you want to specify the Eclipse project's comment * comment = 'Very interesting top secret project' * * //if you want to append some extra referenced projects in a declarative fashion: * referencedProjects 'someProject', 'someOtherProject' * //if you want to assign referenced projects * referencedProjects = ['someProject'] as Set * * //if you want to append some extra natures in a declarative fashion: * natures 'some.extra.eclipse.nature', 'some.another.interesting.nature' * //if you want to assign natures in a groovy fashion: * natures = ['some.extra.eclipse.nature', 'some.another.interesting.nature'] * * //if you want to append some extra build command: * buildCommand 'buildThisLovelyProject' * //if you want to append a build command with parameters: * buildCommand 'buildItWithTheArguments', argumentOne: "I'm first", argumentTwo: "I'm second" * * //if you want to create an extra link in the eclipse project, * //by location uri: * linkedResource name: 'someLinkByLocationUri', type: 'someLinkType', locationUri: 'file://someUri' * //by location: * linkedResource name: 'someLinkByLocation', type: 'someLinkType', location: '/some/location' * * //if you don't want any node_modules folder to appear in Eclipse, you can filter it out: * resourceFilter { * appliesTo = 'FOLDERS' * type = 'EXCLUDE_ALL' * matcher { * id = 'org.eclipse.ui.ide.multiFilter' * arguments = '1.0-name-matches-false-false-node_modules' * } * } * } * } * </pre> * * For tackling edge cases users can perform advanced configuration on resulting XML file. * It is also possible to affect the way eclipse plugin merges the existing configuration * via beforeMerged and whenMerged closures. * <p> * beforeMerged and whenMerged closures receive {@link Project} object * <p> * Examples of advanced configuration: * * <pre autoTested=''> * apply plugin: 'java' * apply plugin: 'eclipse' * * eclipse { * project { * * file { * //if you want to mess with the resulting XML in whatever way you fancy * withXml { * def node = it.asNode() * node.appendNode('xml', 'is what I love') * } * * //closure executed after .project content is loaded from existing file * //but before gradle build information is merged * beforeMerged { project -> * //if you want skip merging natures... (a very abstract example) * project.natures.clear() * } * * //closure executed after .project content is loaded from existing file * //and after gradle build information is merged * whenMerged { project -> * //you can tinker with the {@link Project} here * } * } * } * } * </pre> */ public class EclipseProject { public static final ImmutableSet<String> VALID_LINKED_RESOURCE_ARGS = ImmutableSet.of("name", "type", "location", "locationUri"); private String name; private String comment; private Set<String> referencedProjects = Sets.newLinkedHashSet(); private List<String> natures = Lists.newArrayList(); private List<BuildCommand> buildCommands = Lists.newArrayList(); private Set<Link> linkedResources = Sets.newLinkedHashSet(); private Set<ResourceFilter> resourceFilters = Sets.newLinkedHashSet(); private final XmlFileContentMerger file; public EclipseProject(XmlFileContentMerger file) { this.file = file; } public String getName() { return name; } /** * Configures eclipse project name. It is <b>optional</b> because the task should configure it correctly for you. * By default it will try to use the <b>project.name</b> or prefix it with a part of a <b>project.path</b> * to make sure the moduleName is unique in the scope of a multi-module build. * The 'uniqueness' of a module name is required for correct import * into Eclipse and the task will make sure the name is unique. * <p> * The logic that makes sure project names are unique is available <b>since</b> 1.0-milestone-2 * <p> * If your project has problems with unique names it is recommended to always run gradle eclipse from the root, e.g. for all subprojects, including generation of .classpath. * If you run the generation of the eclipse project only for a single subproject then you may have different results * because the unique names are calculated based on eclipse projects that are involved in the specific build run. * <p> * If you update the project names then make sure you run gradle eclipse from the root, e.g. for all subprojects. * The reason is that there may be subprojects that depend on the subproject with amended eclipse project name. * So you want them to be generated as well because the project dependencies in .classpath need to refer to the amended project name. * Basically, for non-trivial projects it is recommended to always run gradle eclipse from the root. * <p> * For example see docs for {@link EclipseProject} */ public void setName(String name) { this.name = name; } public String getComment() { return comment; } /** * A comment used for the eclipse project. By default it will be configured to <b>project.description</b> * <p> * For example see docs for {@link EclipseProject} */ public void setComment(String comment) { this.comment = comment; } public Set<String> getReferencedProjects() { return referencedProjects; } /** * The referenced projects of this Eclipse project (*not*: java build path project references). * <p> * Referencing projects does not mean adding a build path dependencies between them! * If you need to configure a build path dependency use Gradle's dependencies section or * eclipse.classpath.whenMerged { classpath -> ... to manipulate the classpath entries * <p> * For example see docs for {@link EclipseProject} */ public void setReferencedProjects(Set<String> referencedProjects) { this.referencedProjects = referencedProjects; } /** * The referenced projects of this Eclipse project (*not*: java build path project references). <p> Referencing projects does not mean adding a build path dependencies between them! If you need to * configure a build path dependency use Gradle's dependencies section or eclipse.classpath.whenMerged { classpath -> ... to manipulate the classpath entries * * @param referencedProjects The name of the project references. */ public void referencedProjects(String... referencedProjects) { assert referencedProjects != null; this.referencedProjects.addAll(Arrays.asList(referencedProjects)); } public List<String> getNatures() { return natures; } /** * The natures to be added to this Eclipse project. * <p> * For example see docs for {@link EclipseProject} */ public void setNatures(List<String> natures) { this.natures = natures; } /** * Appends natures entries to the eclipse project. <p> For example see docs for {@link EclipseProject} * * @param natures the nature names */ public void natures(String... natures) { assert natures != null; this.natures.addAll(Arrays.asList(natures)); } public List<BuildCommand> getBuildCommands() { return buildCommands; } /** * The build commands to be added to this Eclipse project. * <p> * For example see docs for {@link EclipseProject} */ public void setBuildCommands(List<BuildCommand> buildCommands) { this.buildCommands = buildCommands; } /** * Adds a build command with arguments to the eclipse project. <p> For example see docs for {@link EclipseProject} * * @param args A map with arguments, where the key is the name of the argument and the value the value. * @param buildCommand The name of the build command. * @see #buildCommand(String) */ public void buildCommand(Map<String, String> args, String buildCommand) { assert buildCommand != null; buildCommands.add(new BuildCommand(buildCommand, args)); } /** * Adds a build command to the eclipse project. <p> For example see docs for {@link EclipseProject} * * @param buildCommand The name of the build command * @see #buildCommand(Map, String) */ public void buildCommand(String buildCommand) { assert buildCommand != null; buildCommands.add(new BuildCommand(buildCommand)); } public Set<Link> getLinkedResources() { return linkedResources; } /** * The linked resources to be added to this Eclipse project. * <p> * For example see docs for {@link EclipseProject} */ public void setLinkedResources(Set<Link> linkedResources) { this.linkedResources = linkedResources; } /** * Adds a resource link (aka 'source link') to the eclipse project. <p> For example see docs for {@link EclipseProject} * * @param args A maps with the args for the link. Legal keys for the map are name, type, location and locationUri. */ public void linkedResource(Map<String, String> args) { Set<String> illegalArgs = Sets.difference(args.keySet(), VALID_LINKED_RESOURCE_ARGS); if (!illegalArgs.isEmpty()) { throw new InvalidUserDataException("You provided illegal argument for a link: " + illegalArgs + ". Valid link args are: " + VALID_LINKED_RESOURCE_ARGS); } linkedResources.add(new Link(args.get("name"), args.get("type"), args.get("location"), args.get("locationUri"))); } /** * The resource filters of the eclipse project. * @since 3.5 */ @Incubating public Set<ResourceFilter> getResourceFilters() { return resourceFilters; } /** * Adds a resource filter to the eclipse project. * <p> * For examples, see docs for {@link ResourceFilter} * * @param configureClosure The closure to use to configure the resource filter. * @since 3.5 */ @Incubating public ResourceFilter resourceFilter(@DelegatesTo(value=ResourceFilter.class, strategy = Closure.DELEGATE_FIRST) Closure configureClosure) { return resourceFilter(new ClosureBackedAction<ResourceFilter>(configureClosure)); } /** * Adds a resource filter to the eclipse project. * <p> * For examples, see docs for {@link ResourceFilter} * * @param configureAction The action to use to configure the resource filter. * @since 3.5 */ @Incubating public ResourceFilter resourceFilter(Action<? super ResourceFilter> configureAction) { ResourceFilter f = new DefaultResourceFilter(); configureAction.execute(f); resourceFilters.add(f); return f; } /** * Enables advanced configuration like tinkering with the output XML or affecting the way existing .project content is merged with gradle build information <p> The object passed to whenMerged{} * and beforeMerged{} closures is of type {@link Project} <p> * * For example see docs for {@link EclipseProject} */ public void file(Closure closure) { configure(closure, file); } /** * Enables advanced configuration like tinkering with the output XML or affecting the way existing .project content is merged with gradle build information. * * For example see docs for {@link EclipseProject} * * @since 3.5 */ public void file(Action<? super XmlFileContentMerger> action) { action.execute(file); } /** * See {@link #file(Action)} */ public final XmlFileContentMerger getFile() { return file; } @SuppressWarnings("unchecked") public void mergeXmlProject(Project xmlProject) { Project decoratedProject = new NonRenamableProject(xmlProject); file.getBeforeMerged().execute(decoratedProject); xmlProject.configure(this); file.getWhenMerged().execute(decoratedProject); } }