
hudson.maven.MavenModuleSet Maven / Gradle / Ivy
package hudson.maven;
import hudson.*;
import hudson.model.*;
import hudson.model.Descriptor.FormException;
import static hudson.model.ItemGroupMixIn.loadChildren;
import hudson.model.Queue;
import hudson.model.Queue.Task;
import hudson.search.CollectionSearchIndex;
import hudson.search.SearchIndexBuilder;
import hudson.tasks.Maven.MavenInstallation;
import hudson.tasks.*;
import hudson.util.CopyOnWriteMap;
import hudson.util.DescribableList;
import hudson.util.FormFieldValidator;
import hudson.util.Function1;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.util.*;
import net.sf.json.JSONObject;
/**
* Group of {@link MavenModule}s.
*
*
* This corresponds to the group of Maven POMs that constitute a single
* tree of projects. This group serves as the grouping of those related
* modules.
*
* @author Kohsuke Kawaguchi
*/
public final class MavenModuleSet extends AbstractMavenProject implements TopLevelItem, ItemGroup, SCMedItem, DescribableList.Owner {
/**
* All {@link MavenModule}s, keyed by their {@link MavenModule#getModuleName()} module name}s.
*/
transient /*final*/ Map modules = new CopyOnWriteMap.Tree();
/**
* Topologically sorted list of modules. This only includes live modules,
* since archived ones usually don't have consistent history.
*/
@CopyOnWrite
transient List sortedActiveModules;
/**
* Name of the top-level module. Null until the root module is determined.
*/
private ModuleName rootModule;
private String rootPOM;
private String goals;
/**
* Default goals specified in POM. Can be null.
*/
private String defaultGoals;
/**
* Identifies {@link MavenInstallation} to be used.
* Null to indicate 'default' maven.
*/
private String mavenName;
/**
* Equivalent of CLI MAVEN_OPTS. Can be null.
*/
private String mavenOpts;
/**
* If true, the build will be aggregator style, meaning
* all the modules are executed in a single Maven invocation, as in CLI.
* False otherwise, meaning each module is built separately and possibly in parallel.
*
* @since 1.133
*/
private boolean aggregatorStyleBuild = true;
/**
* If true, the build will use its own local Maven repository
* via "-Dmaven.repo.local=...".
*
* This would consume additional disk space, but provides isolation with other builds on the same machine,
* such as mixing SNAPSHOTS. Maven also doesn't try to coordinate the concurrent access to Maven repositories
* from multiple Maven process, so this helps there too.
*
* @since 1.223
*/
private boolean usePrivateRepository = false;
/**
* Reporters configured at {@link MavenModuleSet} level. Applies to all {@link MavenModule} builds.
*/
private DescribableList> reporters =
new DescribableList>(this);
/**
* List of active {@link Publisher}s configured for this project.
* @since 1.176
*/
private DescribableList> publishers =
new DescribableList>(this);
/**
* List of active ${link BuildWrapper}s configured for this project.
* @since 1.212
*/
private DescribableList> buildWrappers =
new DescribableList>(this);
public MavenModuleSet(String name) {
super(Hudson.getInstance(),name);
}
public String getUrlChildPrefix() {
// seemingly redundant "./" is used to make sure that ':' is not interpreted as the scheme identifier
return ".";
}
public Hudson getParent() {
return Hudson.getInstance();
}
public Collection getItems() {
return modules.values();
}
public Collection getModules() {
return getItems();
}
public MavenModule getItem(String name) {
return modules.get(ModuleName.fromString(name));
}
public MavenModule getModule(String name) {
return getItem(name);
}
protected void updateTransientActions() {
super.updateTransientActions();
// Fix for ISSUE-1149
for (MavenModule module: modules.values()) {
module.updateTransientActions();
}
if(publishers!=null) // this method can be loaded from within the onLoad method, where this might be null
for (BuildStep step : publishers) {
Action a = step.getProjectAction(this);
if(a!=null)
transientActions.add(a);
}
}
protected void addTransientActionsFromBuild(MavenModuleSetBuild build, Set added) {
if(build==null) return;
for (Action a : build.getActions())
if(a instanceof MavenAggregatedReport)
if(added.add(a.getClass()))
transientActions.add(((MavenAggregatedReport)a).getProjectAction(this));
List list = build.projectActionReporters;
if(list==null) return;
for (MavenReporter step : list) {
if(!added.add(step.getClass())) continue; // already added
Action a = step.getAggregatedProjectAction(this);
if(a!=null)
transientActions.add(a);
}
}
/**
* Called by {@link MavenModule#doDoDelete(StaplerRequest, StaplerResponse)}.
* Real deletion is done by the caller, and this method only adjusts the
* data structure the parent maintains.
*/
/*package*/ void onModuleDeleted(MavenModule module) {
modules.remove(module.getModuleName());
}
/**
* Returns true if there's any disabled module.
*/
public boolean hasDisabledModule() {
for (MavenModule m : modules.values()) {
if(m.isDisabled())
return true;
}
return false;
}
/**
* Possibly empty list of all disabled modules (if disabled==true)
* or all enabeld modules (if disabled==false)
*/
public List getDisabledModules(boolean disabled) {
if(!disabled && sortedActiveModules!=null)
return sortedActiveModules;
List r = new ArrayList();
for (MavenModule m : modules.values()) {
if(m.isDisabled()==disabled)
r.add(m);
}
return r;
}
public Indenter createIndenter() {
return new Indenter() {
protected int getNestLevel(MavenModule job) {
return job.nestLevel;
}
};
}
public boolean isAggregatorStyleBuild() {
return aggregatorStyleBuild;
}
public boolean usesPrivateRepository() {
return usePrivateRepository;
}
/**
* List of active {@link MavenReporter}s that should be applied to all module builds.
*/
public DescribableList> getReporters() {
return reporters;
}
/**
* List of active {@link Publisher}s. Can be empty but never null.
*/
public DescribableList> getPublishers() {
return publishers;
}
@Override
public DescribableList> getPublishersList() {
return publishers;
}
/**
* List of active {@link BuildWrapper}s. Can be empty but never null.
*/
public DescribableList> getBuildWrappers() {
return buildWrappers;
}
public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
if(ModuleName.isValid(token))
return getModule(token);
return super.getDynamic(token,req,rsp);
}
public File getRootDirFor(MavenModule child) {
return new File(getModulesDir(),child.getModuleName().toFileSystemName());
}
public Collection getAllJobs() {
Set jobs = new HashSet(getItems());
jobs.add(this);
return jobs;
}
/**
* Gets the workspace of this job.
*/
public FilePath getWorkspace() {
Node node = getLastBuiltOn();
if(node==null) node = Hudson.getInstance();
return node.getWorkspaceFor(this);
}
@Override
protected Class getBuildClass() {
return MavenModuleSetBuild.class;
}
@Override
protected SearchIndexBuilder makeSearchIndex() {
return super.makeSearchIndex()
.add(new CollectionSearchIndex() {// for computers
protected MavenModule get(String key) {
for (MavenModule m : modules.values()) {
if(m.getDisplayName().equals(key))
return m;
}
return null;
}
protected Collection all() {
return modules.values();
}
protected String getName(MavenModule o) {
return o.getName();
}
});
}
@Override
public boolean isFingerprintConfigured() {
return true;
}
public void onLoad(ItemGroup extends Item> parent, String name) throws IOException {
modules = Collections.emptyMap(); // needed during load
super.onLoad(parent, name);
modules = loadChildren(this, getModulesDir(),new Function1() {
public ModuleName call(MavenModule module) {
return module.getModuleName();
}
});
// update the transient nest level field.
MavenModule root = getRootModule();
if(root!=null && root.getChildren()!=null) {
List sortedList = new ArrayList();
Stack q = new Stack();
root.nestLevel = 0;
q.push(root);
while(!q.isEmpty()) {
MavenModule p = q.pop();
sortedList.add(p);
List children = p.getChildren();
if(children!=null) {
for (MavenModule m : children)
m.nestLevel = p.nestLevel+1;
for( int i=children.size()-1; i>=0; i--) // add them in the reverse order
q.push(children.get(i));
}
}
this.sortedActiveModules = sortedList;
} else {
this.sortedActiveModules = getDisabledModules(false);
}
if(reporters==null)
reporters = new DescribableList>(this);
reporters.setOwner(this);
if(publishers==null)
publishers = new DescribableList>(this);
publishers.setOwner(this);
if(buildWrappers==null)
buildWrappers = new DescribableList>(this);
buildWrappers.setOwner(this);
updateTransientActions();
}
private File getModulesDir() {
return new File(getRootDir(),"modules");
}
/**
* To make it easy to grasp relationship among modules
* and the module set, we'll align the build numbers of
* all the modules.
*
*
* This method is invoked from {@link Executor#run()},
* and because of the mutual exclusion among {@link MavenModuleSetBuild}
* and {@link MavenBuild}, we can safely touch all the modules.
*/
public synchronized int assignBuildNumber() throws IOException {
// determine the next value
updateNextBuildNumber();
return super.assignBuildNumber();
}
public void logRotate() throws IOException {
super.logRotate();
// perform the log rotation of modules
for (MavenModule m : modules.values())
m.logRotate();
}
/**
* The next build of {@link MavenModuleSet} must have
* the build number newer than any of the current module build.
*/
/*package*/ void updateNextBuildNumber() throws IOException {
int next = this.nextBuildNumber;
for (MavenModule m : modules.values())
next = Math.max(next,m.getNextBuildNumber());
if(this.nextBuildNumber!=next) {
this.nextBuildNumber=next;
this.saveNextBuildNumber();
}
}
protected void buildDependencyGraph(DependencyGraph graph) {
publishers.buildDependencyGraph(this,graph);
buildWrappers.buildDependencyGraph(this,graph);
}
public MavenModule getRootModule() {
if(rootModule==null) return null;
return modules.get(rootModule);
}
@Override
public MavenInstallation inferMavenInstallation() {
return getMaven();
}
@Override
protected Set getResourceActivities() {
final Set activities = new HashSet();
activities.addAll(super.getResourceActivities());
activities.addAll(Util.filter(publishers,ResourceActivity.class));
activities.addAll(Util.filter(buildWrappers,ResourceActivity.class));
return activities;
}
/**
* Gets the location of top-level pom.xml relative to the workspace root.
*/
public String getRootPOM() {
if(rootPOM==null) return "pom.xml";
return rootPOM;
}
public AbstractProject,?> asProject() {
return this;
}
/**
* Gets the list of goals to execute.
*/
public String getGoals() {
if(goals==null) {
if(defaultGoals!=null) return defaultGoals;
return "install";
}
return goals;
}
/**
* If the list of configured goals contain the "-P" option,
* return the configured profiles. Otherwise null.
*/
public String getProfiles() {
boolean switchFound=false;
for (String t : Util.tokenize(getGoals())) {
if(switchFound)
return t;
if(t.equals("-P"))
switchFound=true;
}
return null;
}
/**
* Possibly null, whitespace-separated (including TAB, NL, etc) VM options
* to be used to launch Maven process.
*/
public String getMavenOpts() {
return mavenOpts;
}
/**
* Gets the Maven to invoke.
* If null, we pick any random Maven installation.
*/
public MavenInstallation getMaven() {
for( MavenInstallation i : DESCRIPTOR.getMavenDescriptor().getInstallations() ) {
if(mavenName==null || i.getName().equals(mavenName))
return i;
}
return null;
}
/**
* Returns the {@link MavenModule}s that are in the queue.
*/
public List getQueueItems() {
List r = new ArrayList();
for( Queue.Item item : Hudson.getInstance().getQueue().getItems() ) {
Task t = item.task;
if((t instanceof MavenModule && ((MavenModule)t).getParent()==this) || t ==this)
r.add(item);
}
return r;
}
/**
* Gets the list of goals specified by the user,
* without taking inheritance and POM default goals
* into account.
*
*
* This is only used to present the UI screen, and in
* all the other cases {@link #getGoals()} should be used.
*/
public String getUserConfiguredGoals() {
return goals;
}
/*package*/ void reconfigure(PomInfo rootPom) throws IOException {
if(this.rootModule!=null && this.rootModule.equals(rootPom.name))
return; // no change
this.rootModule = rootPom.name;
this.defaultGoals = rootPom.defaultGoal;
save();
}
//
//
// Web methods
//
//
protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
super.submit(req,rsp);
rootPOM = Util.fixEmpty(req.getParameter("rootPOM").trim());
if(rootPOM!=null && rootPOM.equals("pom.xml")) rootPOM=null; // normalization
goals = Util.fixEmpty(req.getParameter("goals").trim());
mavenOpts = Util.fixEmpty(req.getParameter("mavenOpts").trim());
mavenName = req.getParameter("maven_version");
aggregatorStyleBuild = !req.hasParameter("maven.perModuleBuild");
usePrivateRepository = req.hasParameter("maven.usePrivateRepository");
JSONObject json = req.getSubmittedForm();
reporters.rebuild(req,json,MavenReporters.getConfigurableList(),"reporter");
publishers.rebuild(req,json,BuildStepDescriptor.filter(BuildStep.PUBLISHERS,this.getClass()),"publisher");
buildWrappers.rebuild(req,json,BuildWrappers.getFor(this),"wrapper");
updateTransientActions(); // to pick up transient actions from builder, publisher, etc.
}
/**
* Delete all disabled modules.
*/
public void doDoDeleteAllDisabledModules(StaplerRequest req, StaplerResponse rsp) throws IOException {
checkPermission(DELETE);
for( MavenModule m : getDisabledModules(true))
m.delete();
rsp.sendRedirect2(".");
}
/**
* Check the location of POM.
*/
public void doCheckRootPOM(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
new FormFieldValidator.WorkspaceFilePath(req,rsp,true,true) {
protected AbstractProject, ?> getProject() {
return MavenModuleSet.this;
}
protected FilePath getBaseDirectory(AbstractProject,?> p) {
return p.getModuleRoot();
}
}.process();
}
public TopLevelItemDescriptor getDescriptor() {
return DESCRIPTOR;
}
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
public static final class DescriptorImpl extends TopLevelItemDescriptor {
private DescriptorImpl() {
super(MavenModuleSet.class);
}
public String getDisplayName() {
return Messages.MavenModuleSet_DiplayName();
}
public MavenModuleSet newInstance(String name) {
return new MavenModuleSet(name);
}
public Maven.DescriptorImpl getMavenDescriptor() {
return Maven.DESCRIPTOR;
}
}
}