Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial commit for commit path. A function to skip jobs if the commit… #103

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions CommitPath.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Commit Path

## Applicable

The commit path feature is applicable if:
- You have the SCM defined in your main MultiJob configuration.
- You want to exclude most path jobs from execution if commits touch only certain paths.

## Details
Activate the feature by clicking the checkbox _Use 'Commit Path' in phase jobs._ in section _Multijob specific configuration_

If checked, patterns defined in phase jobs of the MultiJob will be considered when determining which jobs to be executed.

This is only valid if ALL paths in a commit are covered by patterns.

The same path may be present in more than one job.

##Example:
We have a large number of jobs in our first phase. Job-One and Job-Two runs integration tests
and will be run on any change. But there is no need to run anything else if ONLY the integration
tests themselves are updated.
We configure the projects to use commit path:

Job-One: project1/src/integTest/java/.*
Job-Two: project2/src/integTest/java/.*

When a commit affects paths...

project1/src/integTest/java/com/acme/test/TestOne.java
project2/src/integTest/java/com/acme/test/TestSomethingElse.java

...then only the jobs Job-One and Job-Two will be executed.

If another change (project1/src/main/java/com/acme/product/Main.java) would be present
that is not covered in a phase job config, then ALL jobs will be executed for the whole change set.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
- Add context to your buildflow implementing parameter inheritance from the MultiJob to all its Phases and Jobs, Phases are sequential whilst jobs inside each Phase are parallel

## News
In version 1.24 there is support for skipping phase jobs based on paths touched in the commit.
You can read the details [here](CommitPath.md).

In version 1.17 we inject new variables to use them in conditions, powering this feature.
You can read the details (variable names, values, ...) [here](AboutNewVariables.md).

Expand Down
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,12 @@
<version>1.7.1</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,41 @@
package com.tikal.jenkins.plugins.multijob;

import hudson.EnvVars;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.jenkinsci.lib.envinject.EnvInjectLogger;
import org.jenkinsci.plugins.envinject.EnvInjectBuilder;
import org.jenkinsci.plugins.envinject.EnvInjectBuilderContributionAction;
import org.jenkinsci.plugins.envinject.service.EnvInjectActionSetter;
import org.jenkinsci.plugins.envinject.service.EnvInjectEnvVars;
import org.jenkinsci.plugins.envinject.service.EnvInjectVariableGetter;
import org.jenkinsci.plugins.tokenmacro.TokenMacro;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

import com.tikal.jenkins.plugins.multijob.MultiJobBuild.SubBuild;
import com.tikal.jenkins.plugins.multijob.PhaseJobsConfig.KillPhaseOnJobResultCondition;
import com.tikal.jenkins.plugins.multijob.counters.CounterHelper;
Expand Down Expand Up @@ -32,42 +67,6 @@
import hudson.tasks.Builder;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.jenkinsci.lib.envinject.EnvInjectLogger;
import org.jenkinsci.plugins.envinject.EnvInjectBuilder;
import org.jenkinsci.plugins.envinject.EnvInjectBuilderContributionAction;
import org.jenkinsci.plugins.envinject.service.EnvInjectActionSetter;
import org.jenkinsci.plugins.envinject.service.EnvInjectEnvVars;
import org.jenkinsci.plugins.envinject.service.EnvInjectVariableGetter;
import org.jenkinsci.plugins.tokenmacro.TokenMacro;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
public class MultiJobBuilder extends Builder implements DependecyDeclarer {
/**
* The name of the parameter in the build.getBuildVariables() to enable the job build, regardless
Expand Down Expand Up @@ -266,6 +265,33 @@ public boolean perform(final AbstractBuild<?, ? > build, final Launcher launcher
phaseSubJobs.put(new PhaseSubJob(job), phaseJobConfig);
}
}
// Commit path logic, enabling
boolean useCommitPath = false;
List<AbstractProject> commitPathJobs = new ArrayList<>();
if (thisProject.getUseCommitPath()) {
if (build.getChangeSets().isEmpty()) {
listener.getLogger().println("Commit path active but no changes detected");
} else {
List<String> affectedPaths = getAffectedPaths(multiJobBuild);
List<String> remainingPaths = new ArrayList<>(affectedPaths);
for (Map.Entry<PhaseSubJob, PhaseJobsConfig> entry : phaseSubJobs.entrySet()) {
AbstractProject subJob = entry.getKey().job;
PhaseJobsConfig phaseJobsConfig = entry.getValue();
for (String affectedPath : affectedPaths) {
if (jobPathIsMatching(phaseJobsConfig, affectedPath)) {
remainingPaths.remove(affectedPath); //ignore result, duplicates are ok
commitPathJobs.add(subJob);
}
}
}
if (remainingPaths.isEmpty()) {
useCommitPath = true;
listener.getLogger().println("Commit path is used for this build");
} else {
listener.getLogger().println("Commit path will not be used, unhandled paths found.");
}
}
}

List<SubTask> subTasks = new ArrayList<SubTask>();
int index = 0;
Expand All @@ -274,6 +300,15 @@ public boolean perform(final AbstractBuild<?, ? > build, final Launcher launcher

AbstractProject subJob = phaseSubJob.job;

// Commit path logic, per job
if (useCommitPath) {
if (!commitPathJobs.contains(subJob)) {
listener.getLogger().println(String.format("Commit path: Skipping %s. Project is not affected by the SCM changes in this build.", subJob.getName()));
phaseCounters.processSkipped();
continue;
}
}

// To be coherent with final results, we need to do this here.
PhaseJobsConfig phaseConfig = phaseSubJobs.get(phaseSubJob);
StatusJob jobStatus = getScmChange(subJob,phaseConfig,multiJobBuild ,listener,launcher );
Expand Down Expand Up @@ -445,6 +480,31 @@ else if(!jobStatus.isBuildable() && !phaseConfig.isApplyConditionOnlyIfNoSCMChan
return true;
}

private boolean jobPathIsMatching(PhaseJobsConfig config, String path) {
return path != null
&& !path.trim().isEmpty()
&& hasCommitPath(config)
&& getCommitPath(config).matcher(path).matches();
}

private List<String> getAffectedPaths(MultiJobBuild build) {
List<String> affectedPaths = new ArrayList<>();
for (ChangeLogSet<? extends Entry> changeSet : build.getChangeSets()) {
for (Entry entry : changeSet) {
affectedPaths.addAll(entry.getAffectedPaths());
}
}
return affectedPaths;
}

private boolean hasCommitPath(PhaseJobsConfig config) {
return config.getEnableCommitPath() && config.getCommitPathPattern() != null;
}

private Pattern getCommitPath(PhaseJobsConfig config) {
return config.getCommitPathPattern();
}

public final class SubJobWorker implements Callable {
final private MultiJobProject multiJobProject;
final private BuildListener listener;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,27 @@
package com.tikal.jenkins.plugins.multijob;

import java.util.List;
import java.io.IOException;
import javax.servlet.ServletException;

import hudson.model.AbstractBuild;
import hudson.model.Cause;
import jenkins.model.Jenkins;
import com.tikal.jenkins.plugins.multijob.views.MultiJobView;
import hudson.Extension;
import hudson.model.DependencyGraph;
import hudson.model.ItemGroup;
import hudson.model.TopLevelItem;
import hudson.model.Hudson;
import hudson.model.Project;
import hudson.model.TaskListener;
import hudson.model.AbstractProject;
import hudson.model.*;
import hudson.model.Descriptor.FormException;
import hudson.util.AlternativeUiTextProvider;
import hudson.scm.PollingResult;
import hudson.scm.PollingResult.*;

import com.tikal.jenkins.plugins.multijob.views.MultiJobView;

import hudson.util.AlternativeUiTextProvider;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import javax.servlet.ServletException;
import java.io.IOException;
import java.util.List;

public class MultiJobProject extends Project<MultiJobProject, MultiJobBuild>
implements TopLevelItem {

private volatile boolean pollSubjobs = false;
private volatile String resumeEnvVars = null;
private volatile boolean useCommitPath;

@SuppressWarnings("rawtypes")
private MultiJobProject(ItemGroup parent, String name) {
Expand Down Expand Up @@ -60,6 +49,14 @@ public DescriptorImpl getDescriptor() {
@Extension(ordinal = 1000)
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

public void setUseCommitPath(boolean useCommitPath) {
this.useCommitPath = useCommitPath;
}

public boolean getUseCommitPath() {
return useCommitPath;
}

public static final class DescriptorImpl extends AbstractProjectDescriptor {
public String getDisplayName() {
return "MultiJob Project";
Expand Down Expand Up @@ -147,6 +144,10 @@ protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOExceptio
if (json.has(k)) {
setPollSubjobs(json.getBoolean(k));
}
k = "useCommitPath";
if (json.has(k)) {
setUseCommitPath(json.getBoolean(k));
}
String resumeEnvVars = null;
k = "resumeEnvVars";
if (json.has(k)) {
Expand Down
Loading