File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\matrix\MatrixProject.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2011, Sun Microsystems, Inc., Kohsuke Kawaguchi,
5 * Jorg Heymans, Red Hat, Inc., id:cactusman
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 */
25 package hudson.matrix;
26
27 import com.google.common.base.Function;
28 import com.google.common.collect.Iterables;
29 import com.google.common.collect.Lists;
30 import com.google.common.collect.Sets;
31 import hudson.CopyOnWrite;
32 import hudson.Extension;
33 import hudson.Util;
34 import hudson.XmlFile;
35 import hudson.matrix.MatrixBuild.MatrixBuildExecution;
36 import hudson.model.AbstractProject;
37 import hudson.model.Action;
38 import hudson.model.BuildableItemWithBuildWrappers;
39 import hudson.model.DependencyGraph;
40 import hudson.model.Descriptor;
41 import hudson.model.Descriptor.FormException;
42 import hudson.model.Node;
43 import hudson.slaves.WorkspaceList;
44 import jenkins.model.Jenkins;
45 import hudson.model.Item;
46 import hudson.model.ItemGroup;
47 import hudson.model.Items;
48 import hudson.model.JDK;
49 import hudson.model.Job;
50 import hudson.model.Label;
51 import hudson.model.Queue.FlyweightTask;
52 import hudson.model.Result;
53 import hudson.model.SCMedItem;
54 import hudson.model.Saveable;
55 import hudson.model.TopLevelItem;
56 import hudson.tasks.BuildStep;
57 import hudson.tasks.BuildWrapper;
58 import hudson.tasks.BuildWrappers;
59 import hudson.tasks.Builder;
60 import hudson.tasks.Publisher;
61 import hudson.triggers.Trigger;
62 import hudson.util.CopyOnWriteMap;
63 import hudson.util.DescribableList;
64 import hudson.util.FormValidation;
65 import hudson.util.FormValidation.Kind;
66 import jenkins.scm.SCMCheckoutStrategyDescriptor;
67 import net.sf.json.JSONObject;
68 import org.kohsuke.stapler.HttpResponse;
69 import org.kohsuke.stapler.StaplerRequest;
70 import org.kohsuke.stapler.StaplerResponse;
71 import org.kohsuke.stapler.TokenList;
72 import org.kohsuke.stapler.export.Exported;
73
74 import javax.annotation.Nullable;
75 import javax.servlet.ServletException;
76 import java.io.File;
77 import java.io.FileFilter;
78 import java.io.IOException;
79 import java.util.ArrayList;
80 import java.util.Collection;
81 import java.util.Collections;
82 import java.util.HashMap;
83 import java.util.HashSet;
84 import java.util.LinkedHashSet;
85 import java.util.List;
86 import java.util.Map;
87 import java.util.Map.Entry;
88 import java.util.Set;
89 import java.util.logging.Level;
90 import java.util.logging.Logger;
91
92 /**
93 * {@link Job} that allows you to run multiple different configurations
94 * from a single setting.
95 *
96 * @author Kohsuke Kawaguchi
97 */
98 public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> implements TopLevelItem, SCMedItem, ItemGroup<MatrixConfiguration>, Saveable, FlyweightTask, BuildableItemWithBuildWrappers {
99 /**
100 * Configuration axes.
101 */
102 private volatile AxisList axes = new AxisList();
103
104 /**
105 * The filter that is applied to combinations. It is a Groovy if condition.
106 * This can be null, which means "true".
107 *
108 * @see #getCombinationFilter()
109 */
110 private volatile String combinationFilter;
111
112 /**
113 * List of active {@link Builder}s configured for this project.
114 */
115 private DescribableList<Builder,Descriptor<Builder>> builders =
116 new DescribableList<Builder,Descriptor<Builder>>(this);
117
118 /**
119 * List of active {@link Publisher}s configured for this project.
120 */
121 private DescribableList<Publisher,Descriptor<Publisher>> publishers =
122 new DescribableList<Publisher,Descriptor<Publisher>>(this);
123
124 /**
125 * List of active {@link BuildWrapper}s configured for this project.
126 */
127 private DescribableList<BuildWrapper,Descriptor<BuildWrapper>> buildWrappers =
128 new DescribableList<BuildWrapper,Descriptor<BuildWrapper>>(this);
129
130 /**
131 * All {@link MatrixConfiguration}s, keyed by their {@link MatrixConfiguration#getName() names}.
132 */
133 private transient /*final*/ Map<Combination,MatrixConfiguration> configurations = new CopyOnWriteMap.Tree<Combination,MatrixConfiguration>();
134
135 /**
136 * @see #getActiveConfigurations()
137 */
138 @CopyOnWrite
139 private transient /*final*/ Set<MatrixConfiguration> activeConfigurations = new LinkedHashSet<MatrixConfiguration>();
140
141 /**
142 * @deprecated as of 1.456
143 * Moved to {@link DefaultMatrixExecutionStrategyImpl}
144 */
145 private transient Boolean runSequentially;
146
147 /**
148 * Filter to select a number of combinations to build first
149 * @deprecated as of 1.456
150 * Moved to {@link DefaultMatrixExecutionStrategyImpl}
151 */
152 private transient String touchStoneCombinationFilter;
153
154 /**
155 * Required result on the touchstone combinations, in order to
156 * continue with the rest
157 * @deprecated as of 1.456
158 * Moved to {@link DefaultMatrixExecutionStrategyImpl}
159 */
160 private transient Result touchStoneResultCondition;
161
162 /**
163 * @deprecated as of 1.456
164 * Moved to {@link DefaultMatrixExecutionStrategyImpl}
165 */
166 private transient MatrixConfigurationSorter sorter;
167
168 private MatrixExecutionStrategy executionStrategy;
169
170 /**
171 * Custom workspace location for {@link MatrixConfiguration}s.
172 *
173 * <p>
174 * (Historically, we used {@link AbstractProject#customWorkspace} + some unique suffix (see {@link MatrixConfiguration#useShortWorkspaceName})
175 * for custom workspace, but we now separated that so that the user has more control.
176 *
177 * <p>
178 * If null, the historical semantics is assumed.
179 *
180 * @since 1.466
181 */
182 private String childCustomWorkspace;
183
184 public MatrixProject(String name) {
185 this(Jenkins.getInstance(), name);
186 }
187
188 public MatrixProject(ItemGroup parent, String name) {
189 super(parent, name);
190 }
191
192 /**
193 * Gets the workspace location that {@link MatrixConfiguration} uses.
194 *
195 * @see MatrixRun.MatrixRunExecution#decideWorkspace(Node, WorkspaceList)
196 *
197 * @return never null
198 * even when {@link MatrixProject} uses no custom workspace, this method still
199 * returns something like "${PARENT_WORKSPACE}/${COMBINATION}" that controls
200 * how the workspace should be laid out.
201 *
202 * The return value can be absolute or relative. If relative, it is resolved
203 * against the working directory of the overarching {@link MatrixBuild}.
204 */
205 public String getChildCustomWorkspace() {
206 String ws = childCustomWorkspace;
207 if (ws==null) {
208 ws = MatrixConfiguration.useShortWorkspaceName?"${SHORT_COMBINATION}":"${COMBINATION}";
209 }
210 return ws;
211 }
212
213 /**
214 * Do we have an explicit child custom workspace setting (true)? Or just using the default value (false)?
215 */
216 public boolean hasChildCustomWorkspace() {
217 return childCustomWorkspace!=null;
218 }
219
220 public void setChildCustomWorkspace(String childCustomWorkspace) throws IOException {
221 this.childCustomWorkspace = Util.fixEmptyAndTrim(childCustomWorkspace);
222 save();
223 }
224
225 /**
226 * {@link MatrixProject} is relevant with all the labels its configurations are relevant.
227 */
228 @Override
229 public Set<Label> getRelevantLabels() {
230 Set<Label> r = new HashSet<Label>();
231 for (MatrixConfiguration c : getActiveConfigurations())
232 r.add(c.getAssignedLabel());
233 return r;
234 }
235
236 /**
237 * @return can be null (to indicate that the configurations should be left to their natural order.)
238 * @deprecated as of 1.456
239 * Use {@link DefaultMatrixExecutionStrategyImpl#getSorter()}.
240 * This method tries to emulate the previous behavior the best it can, but will return null
241 * if the current {@link MatrixExecutionStrategy} is not the default one.
242 */
243 public MatrixConfigurationSorter getSorter() {
244 MatrixExecutionStrategy e = executionStrategy;
245 if (e instanceof DefaultMatrixExecutionStrategyImpl) {
246 DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
247 return dm.getSorter();
248 }
249 return null;
250 }
251
252 /**
253 * @deprecated as of 1.456
254 * Use {@link DefaultMatrixExecutionStrategyImpl#setSorter(MatrixConfigurationSorter)}.
255 * This method tries to emulate the previous behavior the best it can, but will fall back
256 * to no-op if the current {@link MatrixExecutionStrategy} is not the default one.
257 */
258 public void setSorter(MatrixConfigurationSorter sorter) throws IOException {
259 MatrixExecutionStrategy e = executionStrategy;
260 if (e instanceof DefaultMatrixExecutionStrategyImpl) {
261 DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
262 dm.setSorter(sorter);
263 save();
264 }
265 }
266
267 public AxisList getAxes() {
268 return axes;
269 }
270
271 /**
272 * Reconfigures axes.
273 */
274 public void setAxes(AxisList axes) throws IOException {
275 this.axes = new AxisList(axes);
276 rebuildConfigurations(null);
277 save();
278 }
279
280 public MatrixExecutionStrategy getExecutionStrategy() {
281 return executionStrategy;
282 }
283
284 public void setExecutionStrategy(MatrixExecutionStrategy executionStrategy) throws IOException {
285 if (executionStrategy ==null) throw new IllegalArgumentException();
286 this.executionStrategy = executionStrategy;
287 save();
288 }
289
290 /**
291 * @deprecated as of 1.456
292 * Use {@link DefaultMatrixExecutionStrategyImpl#isRunSequentially()}.
293 * This method tries to emulate the previous behavior the best it can, but will return false
294 * if the current {@link MatrixExecutionStrategy} is not the default one.
295 */
296 public boolean isRunSequentially() {
297 MatrixExecutionStrategy e = executionStrategy;
298 if (e instanceof DefaultMatrixExecutionStrategyImpl) {
299 DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
300 return dm.isRunSequentially();
301 }
302 return false;
303 }
304
305 /**
306 * @deprecated as of 1.456
307 * Use {@link DefaultMatrixExecutionStrategyImpl#setRunSequentially(boolean)}.
308 * This method tries to emulate the previous behavior the best it can, but will fall back
309 * to no-op if the current {@link MatrixExecutionStrategy} is not the default one.
310 */
311 public void setRunSequentially(boolean runSequentially) throws IOException {
312 MatrixExecutionStrategy e = executionStrategy;
313 if (e instanceof DefaultMatrixExecutionStrategyImpl) {
314 DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
315 dm.setRunSequentially(runSequentially);
316 save();
317 }
318 }
319
320 /**
321 * Sets the combination filter.
322 *
323 * @param combinationFilter the combinationFilter to set
324 */
325 public void setCombinationFilter(String combinationFilter) throws IOException {
326 this.combinationFilter = combinationFilter;
327 rebuildConfigurations(null);
328 save();
329 }
330
331 /**
332 * Obtains the combination filter, used to trim down the size of the matrix.
333 *
334 * <p>
335 * By default, a {@link MatrixConfiguration} is created for every possible combination of axes exhaustively.
336 * But by specifying a Groovy expression as a combination filter, one can trim down the # of combinations built.
337 *
338 * <p>
339 * Namely, this expression is evaluated for each axis value combination, and only when it evaluates to true,
340 * a corresponding {@link MatrixConfiguration} will be created and built.
341 *
342 * @return can be null.
343 * @since 1.279
344 */
345 public String getCombinationFilter() {
346 return combinationFilter;
347 }
348
349 /**
350 * @return can be null (to indicate that the configurations should be left to their natural order.)
351 * @deprecated as of 1.456
352 * Use {@link DefaultMatrixExecutionStrategyImpl#getTouchStoneCombinationFilter()}.
353 * This method tries to emulate the previous behavior the best it can, but will return null
354 * if the current {@link MatrixExecutionStrategy} is not the default one.
355 */
356 public String getTouchStoneCombinationFilter() {
357 MatrixExecutionStrategy e = executionStrategy;
358 if (e instanceof DefaultMatrixExecutionStrategyImpl) {
359 DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
360 return dm.getTouchStoneCombinationFilter();
361 }
362 return null;
363 }
364
365 /**
366 * @deprecated as of 1.456
367 * Use {@link DefaultMatrixExecutionStrategyImpl#setTouchStoneCombinationFilter(String)}.
368 * This method tries to emulate the previous behavior the best it can, but will fall back
369 * to no-op if the current {@link MatrixExecutionStrategy} is not the default one.
370 */
371 public void setTouchStoneCombinationFilter(String touchStoneCombinationFilter) throws IOException {
372 MatrixExecutionStrategy e = executionStrategy;
373 if (e instanceof DefaultMatrixExecutionStrategyImpl) {
374 DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
375 dm.setTouchStoneCombinationFilter(touchStoneCombinationFilter);
376 save();
377 }
378 }
379
380 /**
381 * @return can be null (to indicate that the configurations should be left to their natural order.)
382 * @deprecated as of 1.456
383 * Use {@link DefaultMatrixExecutionStrategyImpl#getTouchStoneResultCondition()}.
384 * This method tries to emulate the previous behavior the best it can, but will return null
385 * if the current {@link MatrixExecutionStrategy} is not the default one.
386 */
387 public Result getTouchStoneResultCondition() {
388 MatrixExecutionStrategy e = executionStrategy;
389 if (e instanceof DefaultMatrixExecutionStrategyImpl) {
390 DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
391 return dm.getTouchStoneResultCondition();
392 }
393 return null;
394 }
395
396 /**
397 * @deprecated as of 1.456
398 * Use {@link DefaultMatrixExecutionStrategyImpl#setTouchStoneResultCondition(Result)}.
399 * This method tries to emulate the previous behavior the best it can, but will fall back
400 * to no-op if the current {@link MatrixExecutionStrategy} is not the default one.
401 */
402 public void setTouchStoneResultCondition(Result touchStoneResultCondition) throws IOException {
403 MatrixExecutionStrategy e = executionStrategy;
404 if (e instanceof DefaultMatrixExecutionStrategyImpl) {
405 DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
406 dm.setTouchStoneResultCondition(touchStoneResultCondition);
407 save();
408 }
409 }
410
411 @Override
412 protected List<Action> createTransientActions() {
413 List<Action> r = super.createTransientActions();
414
415 for (BuildStep step : builders)
416 r.addAll(step.getProjectActions(this));
417 for (BuildStep step : publishers)
418 r.addAll(step.getProjectActions(this));
419 for (BuildWrapper step : buildWrappers)
420 r.addAll(step.getProjectActions(this));
421 for (Trigger<?> trigger : triggers)
422 r.addAll(trigger.getProjectActions());
423
424 return r;
425 }
426
427 @Override
428 protected void updateTransientActions(){
429 super.updateTransientActions();
430 if(getActiveConfigurations() !=null){
431 // update all transient actions in configurations too.
432 for(MatrixConfiguration configuration: getActiveConfigurations()){
433 configuration.updateTransientActions();
434 }
435 }
436 }
437
438 /**
439 * Gets the subset of {@link AxisList} that are not system axes.
440 *
441 * @deprecated as of 1.373
442 * System vs user difference are generalized into extension point.
443 */
444 public List<Axis> getUserAxes() {
445 List<Axis> r = new ArrayList<Axis>();
446 for (Axis a : axes)
447 if(!a.isSystem())
448 r.add(a);
449 return r;
450 }
451
452 public Layouter<MatrixConfiguration> getLayouter() {
453 return new Layouter<MatrixConfiguration>(axes) {
454 protected MatrixConfiguration getT(Combination c) {
455 return getItem(c);
456 }
457 };
458 }
459
460 @Override
461 public void onCreatedFromScratch() {
462 executionStrategy = new DefaultMatrixExecutionStrategyImpl();
463 super.onCreatedFromScratch();
464 }
465
466 @Override
467 public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
468 super.onLoad(parent,name);
469 builders.setOwner(this);
470 publishers.setOwner(this);
471 buildWrappers.setOwner(this);
472
473 if (executionStrategy ==null)
474 executionStrategy = new DefaultMatrixExecutionStrategyImpl(runSequentially != null ? runSequentially : false, touchStoneCombinationFilter, touchStoneResultCondition, sorter);
475
476 rebuildConfigurations(null);
477 }
478
479 @Override
480 public void logRotate() throws IOException, InterruptedException {
481 super.logRotate();
482 // perform the log rotation of inactive configurations to make sure
483 // their logs get eventually discarded
484 for (MatrixConfiguration config : configurations.values()) {
485 if(!config.isActiveConfiguration())
486 config.logRotate();
487 }
488 }
489
490 /**
491 * Recursively search for configuration and put them to the map
492 *
493 * <p>
494 * The directory structure would be <tt>axis-a/b/axis-c/d/axis-e/f</tt> for
495 * combination [a=b,c=d,e=f]. Note that two combinations [a=b,c=d] and [a=b,c=d,e=f]
496 * can both co-exist (where one is an archived record and the other is live, for example)
497 * so search needs to be thorough.
498 *
499 * @param dir
500 * Directory to be searched.
501 * @param result
502 * Receives the loaded {@link MatrixConfiguration}s.
503 * @param combination
504 * Combination of key/values discovered so far while traversing the directories.
505 * Read-only.
506 */
507 private void loadConfigurations( File dir, CopyOnWriteMap.Tree<Combination,MatrixConfiguration> result, Map<String,String> combination ) {
508 File[] axisDirs = dir.listFiles(new FileFilter() {
509 public boolean accept(File child) {
510 return child.isDirectory() && child.getName().startsWith("axis-");
511 }
512 });
513 if(axisDirs==null) return;
514
515 for (File subdir : axisDirs) {
516 String axis = subdir.getName().substring(5); // axis name
517
518 File[] valuesDir = subdir.listFiles(new FileFilter() {
519 public boolean accept(File child) {
520 return child.isDirectory();
521 }
522 });
523 if(valuesDir==null) continue; // no values here
524
525 for (File v : valuesDir) {
526 Map<String,String> c = new HashMap<String, String>(combination);
527 c.put(axis,TokenList.decode(v.getName()));
528
529 try {
530 XmlFile config = Items.getConfigFile(v);
531 if(config.exists()) {
532 Combination comb = new Combination(c);
533 // if we already have this in memory, just use it.
534 // otherwise load it
535 MatrixConfiguration item=null;
536 if(this.configurations!=null)
537 item = this.configurations.get(comb);
538 if(item==null) {
539 item = (MatrixConfiguration) config.read();
540 item.setCombination(comb);
541 item.onLoad(this, v.getName());
542 }
543 result.put(item.getCombination(), item);
544 }
545 } catch (IOException e) {
546 LOGGER.log(Level.WARNING, "Failed to load matrix configuration "+v,e);
547 }
548 loadConfigurations(v,result,c);
549 }
550 }
551 }
552
553 /**
554 * Rebuilds the {@link #configurations} list and {@link #activeConfigurations}.
555 *
556 * @param context
557 * We rebuild configurations right before a build, to allow configurations to be adjusted for the build.
558 * (think of it as reconfiguring a project right before a build.) And when that happens, this value is the
559 * build in progress. Otherwise this value is null (for example, when Jenkins is booting up.)
560 */
561 /*package*/ Set<MatrixConfiguration> rebuildConfigurations(MatrixBuildExecution context) throws IOException {
562 {
563 // backward compatibility check to see if there's any data in the old structure
564 // if so, bring them to the newer structure.
565 File[] oldDirs = getConfigurationsDir().listFiles(new FileFilter() {
566 public boolean accept(File child) {
567 return child.isDirectory() && !child.getName().startsWith("axis-");
568 }
569 });
570 if(oldDirs!=null) {
571 // rename the old directory to the new one
572 for (File dir : oldDirs) {
573 try {
574 Combination c = Combination.fromString(dir.getName());
575 dir.renameTo(getRootDirFor(c));
576 } catch (IllegalArgumentException e) {
577 // it's not a configuration dir. Just ignore.
578 }
579 }
580 }
581 }
582
583 CopyOnWriteMap.Tree<Combination,MatrixConfiguration> configurations =
584 new CopyOnWriteMap.Tree<Combination,MatrixConfiguration>();
585 loadConfigurations(getConfigurationsDir(),configurations,Collections.<String,String>emptyMap());
586 this.configurations = configurations;
587
588 Iterable<Combination> activeCombinations;
589 if (context!=null) {
590 List<Set<String>> axesList = Lists.newArrayList();
591 for (Axis axis : axes)
592 axesList.add(Sets.newLinkedHashSet(axis.rebuild(context)));
593
594 activeCombinations = Iterables.transform(Sets.cartesianProduct(axesList), new Function<List<String>, Combination>() {
595 public Combination apply(@Nullable List<String> strings) {
596 assert strings != null;
597 return new Combination(axes, (String[]) strings.toArray(new String[0]));
598 }
599 });
600 } else {
601 activeCombinations = axes.list();
602 }
603
604 // find all active configurations
605 Set<MatrixConfiguration> active = new LinkedHashSet<MatrixConfiguration>();
606 for (Combination c : activeCombinations) {
607 if(c.evalGroovyExpression(axes,combinationFilter)) {
608 LOGGER.fine("Adding configuration: " + c);
609 MatrixConfiguration config = configurations.get(c);
610 if(config==null) {
611 config = new MatrixConfiguration(this,c);
612 config.save();
613 configurations.put(config.getCombination(), config);
614 }
615 active.add(config);
616 }
617 }
618 this.activeConfigurations = active;
619
620 return active;
621 }
622
623 private File getConfigurationsDir() {
624 return new File(getRootDir(),"configurations");
625 }
626
627 /**
628 * Gets all active configurations.
629 *
630 * <p>
631 * In contract, inactive configurations are those that are left for archival purpose
632 * and no longer built when a new {@link MatrixBuild} is executed.
633 *
634 * <p>
635 * During a build, {@link MatrixBuildExecution#getActiveConfigurations()} should be used
636 * to make sure that a build is using the consistent set of active configurations from
637 * the start to the end.
638 */
639 @Exported
640 public Collection<MatrixConfiguration> getActiveConfigurations() {
641 return activeConfigurations;
642 }
643
644 public Collection<MatrixConfiguration> getItems() {
645 return configurations.values();
646 }
647
648 @Override
649 public Collection<? extends Job> getAllJobs() {
650 Set<Job> jobs = new HashSet<Job>(getItems());
651 jobs.add(this);
652 return jobs;
653 }
654
655 public String getUrlChildPrefix() {
656 return ".";
657 }
658
659 public MatrixConfiguration getItem(String name) {
660 try {
661 return getItem(Combination.fromString(name));
662 } catch (IllegalArgumentException e) {
663 return null;
664 }
665 }
666
667 public MatrixConfiguration getItem(Combination c) {
668 return configurations.get(c);
669 }
670
671 public File getRootDirFor(MatrixConfiguration child) {
672 return getRootDirFor(child.getCombination());
673 }
674
675 public void onRenamed(MatrixConfiguration item, String oldName, String newName) throws IOException {
676 throw new UnsupportedOperationException();
677 }
678
679 public void onDeleted(MatrixConfiguration item) throws IOException {
680 // noop
681 }
682
683 public File getRootDirFor(Combination combination) {
684 File f = getConfigurationsDir();
685 for (Entry<String, String> e : combination.entrySet())
686 f = new File(f,"axis-"+e.getKey()+'/'+Util.rawEncode(e.getValue()));
687 f.getParentFile().mkdirs();
688 return f;
689 }
690
691 /**
692 * @see #getJDKs()
693 */
694 @Override @Deprecated
695 public JDK getJDK() {
696 return super.getJDK();
697 }
698
699 /**
700 * Gets the {@link JDK}s where the builds will be run.
701 * @return never null but can be empty
702 */
703 public Set<JDK> getJDKs() {
704 Axis a = axes.find("jdk");
705 if(a==null) return Collections.emptySet();
706 Set<JDK> r = new HashSet<JDK>();
707 for (String j : a) {
708 JDK jdk = Jenkins.getInstance().getJDK(j);
709 if(jdk!=null)
710 r.add(jdk);
711 }
712 return r;
713 }
714
715 /**
716 * Gets the {@link Label}s where the builds will be run.
717 * @return never null
718 */
719 public Set<Label> getLabels() {
720 Set<Label> r = new HashSet<Label>();
721 for (Combination c : axes.subList(LabelAxis.class).list())
722 r.add(Jenkins.getInstance().getLabel(Util.join(c.values(),"&&")));
723 return r;
724 }
725
726 public List<Builder> getBuilders() {
727 return builders.toList();
728 }
729
730 public DescribableList<Builder,Descriptor<Builder>> getBuildersList() {
731 return builders;
732 }
733
734 public Map<Descriptor<Publisher>,Publisher> getPublishers() {
735 return publishers.toMap();
736 }
737
738 public DescribableList<Publisher,Descriptor<Publisher>> getPublishersList() {
739 return publishers;
740 }
741
742 public DescribableList<BuildWrapper, Descriptor<BuildWrapper>> getBuildWrappersList() {
743 return buildWrappers;
744 }
745
746 public Map<Descriptor<BuildWrapper>,BuildWrapper> getBuildWrappers() {
747 return buildWrappers.toMap();
748 }
749
750 public Publisher getPublisher(Descriptor<Publisher> descriptor) {
751 for (Publisher p : publishers) {
752 if(p.getDescriptor()==descriptor)
753 return p;
754 }
755 return null;
756 }
757
758 protected Class<MatrixBuild> getBuildClass() {
759 return MatrixBuild.class;
760 }
761
762 public boolean isFingerprintConfigured() {
763 return false;
764 }
765
766 protected void buildDependencyGraph(DependencyGraph graph) {
767 publishers.buildDependencyGraph(this,graph);
768 builders.buildDependencyGraph(this,graph);
769 buildWrappers.buildDependencyGraph(this,graph);
770 }
771
772 public MatrixProject asProject() {
773 return this;
774 }
775
776 @Override
777 public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
778 try {
779 MatrixConfiguration item = getItem(token);
780 if(item!=null)
781 return item;
782 } catch (IllegalArgumentException _) {
783 // failed to parse the token as Combination. Must be something else
784 }
785 return super.getDynamic(token,req,rsp);
786 }
787
788 @Override
789 protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
790 super.submit(req, rsp);
791
792 JSONObject json = req.getSubmittedForm();
793
794 if(req.getParameter("hasCombinationFilter")!=null) {
795 this.combinationFilter = Util.nullify(req.getParameter("combinationFilter"));
796 } else {
797 this.combinationFilter = null;
798 }
799
800 if(req.hasParameter("customWorkspace")) {
801 setChildCustomWorkspace(Util.fixEmptyAndTrim(req.getParameter("childCustomWorkspace.directory")));
802 } else {
803 setChildCustomWorkspace(null);
804 }
805
806 List<MatrixExecutionStrategyDescriptor> esd = getDescriptor().getExecutionStrategyDescriptors();
807 if (esd.size()>1)
808 executionStrategy = req.bindJSON(MatrixExecutionStrategy.class,json.getJSONObject("executionStrategy"));
809 else
810 executionStrategy = req.bindJSON(esd.get(0).clazz,json.getJSONObject("executionStrategy"));
811
812 // parse system axes
813 DescribableList<Axis,AxisDescriptor> newAxes = new DescribableList<Axis,AxisDescriptor>(this);
814 newAxes.rebuildHetero(req, json, Axis.all(),"axis");
815 checkAxisNames(newAxes);
816 this.axes = new AxisList(newAxes.toList());
817
818 buildWrappers.rebuild(req, json, BuildWrappers.getFor(this));
819 builders.rebuildHetero(req, json, Builder.all(), "builder");
820 publishers.rebuildHetero(req, json, Publisher.all(), "publisher");
821
822 rebuildConfigurations(null);
823 }
824
825 /**
826 * Verifies that Axis names are valid and unique.
827 */
828 private void checkAxisNames(Iterable<Axis> newAxes) throws FormException {
829 HashSet<String> axisNames = new HashSet<String>();
830 for (Axis a : newAxes) {
831 FormValidation fv = a.getDescriptor().doCheckName(a.getName());
832 if (fv.kind!=Kind.OK)
833 throw new FormException(Messages.MatrixProject_DuplicateAxisName(),fv,"axis.name");
834
835 if (axisNames.contains(a.getName()))
836 throw new FormException(Messages.MatrixProject_DuplicateAxisName(),"axis.name");
837 axisNames.add(a.getName());
838 }
839 }
840
841 /**
842 * Also delete all the workspaces of the configuration, too.
843 */
844 @Override
845 public HttpResponse doDoWipeOutWorkspace() throws IOException, ServletException, InterruptedException {
846 HttpResponse rsp = super.doDoWipeOutWorkspace();
847 for (MatrixConfiguration c : configurations.values())
848 c.doDoWipeOutWorkspace();
849 return rsp;
850 }
851
852
853 public DescriptorImpl getDescriptor() {
854 return DESCRIPTOR;
855 }
856
857 @Extension
858 public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
859
860 public static final class DescriptorImpl extends AbstractProjectDescriptor {
861 public String getDisplayName() {
862 return Messages.MatrixProject_DisplayName();
863 }
864
865 public MatrixProject newInstance(ItemGroup parent, String name) {
866 return new MatrixProject(parent,name);
867 }
868
869 /**
870 * All {@link AxisDescriptor}s that contribute to the UI.
871 */
872 public List<AxisDescriptor> getAxisDescriptors() {
873 List<AxisDescriptor> r = new ArrayList<AxisDescriptor>();
874 for (AxisDescriptor d : Axis.all()) {
875 if (d.isInstantiable())
876 r.add(d);
877 }
878 return r;
879 }
880
881 /**
882 * @deprecated as of 1.456
883 * This was only exposed for Jelly.
884 */
885 public List<MatrixConfigurationSorterDescriptor> getSorterDescriptors() {
886 return MatrixConfigurationSorterDescriptor.all();
887 }
888
889 public List<MatrixExecutionStrategyDescriptor> getExecutionStrategyDescriptors() {
890 return MatrixExecutionStrategyDescriptor.all();
891 }
892
893 public List<SCMCheckoutStrategyDescriptor> getMatrixRunCheckoutStrategyDescriptors() {
894 return SCMCheckoutStrategyDescriptor.all();
895 }
896 }
897
898 private static final Logger LOGGER = Logger.getLogger(MatrixProject.class.getName());
899 }
900