Skip to content
Permalink
Browse files

Merge pull request #69 from ikedam/feature/JENKINS-18938_BuildNumberV…

…ariable

[JENKINS-18938] Added a field to specify the suffix for the variable to store the build number
  • Loading branch information...
ikedam committed Sep 6, 2015
2 parents e68ed44 + 250f8a5 commit ad631aec628fc881bb9becf4586a58952fc5f59c
@@ -48,6 +48,7 @@
import hudson.tasks.Builder;
import hudson.util.DescribableList;
import hudson.util.FormValidation;
import hudson.util.VariableResolver;
import hudson.util.XStream2;

import java.io.IOException;
@@ -75,6 +76,7 @@
import org.kohsuke.stapler.StaplerRequest;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
* Build step to copy artifacts from another project.
@@ -96,6 +98,7 @@
@Deprecated private transient Boolean stable;
private Boolean flatten, optional;
private boolean doNotFingerprintArtifacts;
private String resultVariableSuffix;

@Deprecated
public CopyArtifact(String projectName, String parameters, BuildSelector selector, String filter, String target,
@@ -154,6 +157,7 @@ public CopyArtifact(String projectName) {
setFlatten(false);
setOptional(false);
setFingerprintArtifacts(false);
setResultVariableSuffix(null);
}

@DataBoundSetter
@@ -196,6 +200,16 @@ public void setFingerprintArtifacts(boolean fingerprintArtifacts) {
this.doNotFingerprintArtifacts = !fingerprintArtifacts;
}

/**
* Set the suffix for variables to store copying results.
*
* @param resultVariableSuffix
*/
@DataBoundSetter
public void setResultVariableSuffix(String resultVariableSuffix) {
this.resultVariableSuffix = Util.fixEmptyAndTrim(resultVariableSuffix);
}

// Upgrade data from old format
public static class ConverterImpl extends XStream2.PassthruConverter<CopyArtifact> {
public ConverterImpl(XStream2 xstream) { super(xstream); }
@@ -302,6 +316,13 @@ public boolean isOptional() {
return optional != null && optional;
}

/**
* @return the suffix for variables to store copying results.
*/
public String getResultVariableSuffix() {
return resultVariableSuffix;
}

private boolean upgradeIfNecessary(AbstractProject<?,?> job) throws IOException {
if (isUpgradeNeeded()) {
Jenkins jenkins = Jenkins.getInstance();
@@ -386,7 +407,7 @@ public void perform(@Nonnull Run<?, ?> build, @Nonnull FilePath workspace, @Nonn
envData = new EnvAction();
build.addAction(envData);
}
envData.add(getItemGroup(build), expandedProject, src.getNumber());
envData.add(build, src, expandedProject, getResultVariableSuffix());
if (target.length() > 0) targetDir = new FilePath(targetDir, env.expand(target));
expandedFilter = env.expand(filter);
if (expandedFilter.trim().length() == 0) expandedFilter = "**";
@@ -464,7 +485,7 @@ private boolean canReadFrom(Job<?, ?> job, Run<?, ?> build) {
return b;
}

private Job<?, ?> getRootProject(Job<?, ?> job) {
private static Job<?, ?> getRootProject(Job<?, ?> job) {
if (job instanceof AbstractProject) {
return ((AbstractProject<?,?>)job).getRootProject();
} else {
@@ -473,7 +494,7 @@ private boolean canReadFrom(Job<?, ?> job, Run<?, ?> build) {
}

// retrieve the "folder" (jenkins root if no folder used) for this build
private ItemGroup getItemGroup(Run<?, ?> build) {
private static ItemGroup getItemGroup(Run<?, ?> build) {
return getRootProject(build.getParent()).getParent();
}

@@ -508,6 +529,38 @@ private boolean perform(Run src, Run<?,?> dst, String expandedFilter, String exp
}
}

/**
* Tests whether specified variable name is valid.
* Package scope for testing purpose.
*
* @param variableName
* @return
*/
static boolean isValidVariableName(final String variableName) {
if(StringUtils.isBlank(variableName)) {
return false;
}

// The pattern for variables are defined in hudson.Util.VARIABLE.
// It's not exposed unfortunately and tests the variable
// by actually expanding that.
final String expected = "GOOD";
String expanded = Util.replaceMacro(
String.format("${%s}", variableName),
new VariableResolver<String>() {
@Override
public String resolve(String name) {
if(variableName.equals(name)) {
return expected;
}
return null;
}
}
);

return expected.equals(expanded);
}

@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {

@@ -546,6 +599,20 @@ else if (value.indexOf('$') >= 0)
return result;
}

public FormValidation doCheckResultVariableSuffix(@QueryParameter String value) {
value = Util.fixEmptyAndTrim(value);
if (value == null) {
// optional field.
return FormValidation.ok();
}

if (!isValidVariableName(value)) {
return FormValidation.error(Messages.CopyArtifact_InvalidVariableName());
}

return FormValidation.ok();
}

public boolean isApplicable(Class<? extends AbstractProject> clazz) {
return true;
}
@@ -616,9 +683,10 @@ public void onRenamed(Item item, String oldName, String newName) {
// Decided not to record this data in build.xml, so marked transient:
private transient Map<String,String> data = new HashMap<String,String>();

private void add(ItemGroup ctx, String projectName, int buildNumber) {
if (data==null) return;
Item item = getProject(ctx, projectName);
@Nullable
private String calculateDefaultSuffix(@Nonnull Run<?,?> build, @Nonnull Run<?,?> src, @Nonnull String projectName) {
ItemGroup<?> ctx = getItemGroup(build);
Job<?,?> item = src.getParent();
// Use full name if configured with absolute path
// and relative otherwise
projectName = projectName.startsWith("/") ? item.getFullName() : item.getRelativeNameFrom(ctx);
@@ -633,44 +701,30 @@ private void add(ItemGroup ctx, String projectName, int buildNumber) {
ctx.getFullName(),
}
);
return;
}
data.put("COPYARTIFACT_BUILD_NUMBER_"
+ projectName.toUpperCase().replaceAll("[^A-Z]+", "_"), // Only use letters and _
Integer.toString(buildNumber));
}

/**
* Retrieve root Job identified by this projectPath. For legacy reason, projectPath uses '/' as separator for
* job name and parameters or matrix axe, so can't just use {@link Jenkins#getItemByFullName(String)}.
* As a workaround, we split the path into parts and retrieve the item(group)s up to a Job.
*/
private Job getProject(ItemGroup ctx, String projectPath) {
String[] parts = projectPath.split("/");
if (projectPath.startsWith("/") || ctx == null) ctx = Jenkins.getInstance();
if (ctx == null) {
LOGGER.log(Level.SEVERE, "Jenkins instance is no longer available.");
return null;
}
for (int i =0; i<parts.length; i++) {
String part = parts[i];
if (part.length() == 0) continue;
if (part.equals("..")) {
ctx = ((Item) ctx).getParent();
continue;
}
Item item = ctx.getItem(part);
if (item == null && i == 0) {
// not a relative job name, fall back to "classic" interpretation to consider absolute
Jenkins jenkins = Jenkins.getInstance();
if (jenkins != null) {
item = jenkins.getItem(part);
}

return projectName.toUpperCase().replaceAll("[^A-Z]+", "_"); // Only use letters and _
}

private void add(
@Nonnull Run<?,?> build,
@Nonnull Run<?,?> src,
@Nonnull String projectName,
@Nullable String resultVariableSuffix
) {
if (data==null) return;

if (!isValidVariableName(resultVariableSuffix)) {
resultVariableSuffix = calculateDefaultSuffix(build, src, projectName);
if (resultVariableSuffix == null) {
return;
}
if (item instanceof Job) return (Job) item;
ctx = (ItemGroup) item;
}
return null;
data.put(
String.format("COPYARTIFACT_BUILD_NUMBER_%s", resultVariableSuffix),
Integer.toString(src.getNumber())
);
}

public void buildEnvVars(AbstractBuild<?,?> build, EnvVars env) {
@@ -49,4 +49,9 @@ THE SOFTWARE.
<f:checkbox field="fingerprintArtifacts" default="true"/>
<label class="attach-previous">${%Fingerprint Artifacts}</label>
</f:entry>
<f:advanced>
<f:entry title="${%Result variable suffix}" field="resultVariableSuffix">
<f:textbox/>
</f:entry>
</f:advanced>
</j:jelly>
@@ -0,0 +1,25 @@
<div>
The build number of the selected build will be recorded
into the variable named <tt>COPYARTIFACT_BUILD_NUMBER_(SUFFIX)</tt>
for later build steps to reference.
You can specify that suffix part for that variable here.
<p>
If not specified, the source project name will be used instead
(in all uppercase, and sequences of characters other than A-Z
replaced by a single underscore).
<p><strong>Example</strong>:
<table>
<tr>
<th>Source project name</th>
<th>Suffix to be used</th>
</tr>
<tr>
<td>Project-ABC</td>
<td>PROJECT_ABC</td>
</tr>
<tr>
<td>tool1-release1.2</td>
<td>TOOL_RELEASE_</td>
</tr>
</table>
</div>
@@ -0,0 +1,23 @@
<div>
コピー元のビルドのビルド番号を <tt>COPYARTIFACT_BUILD_NUMBER_(SUFFIX)</tt>
という変数に保存し、以降のビルド処理で参照できるようにします。
その際に使用する SUFFIX の部分をここで指定します。
<p>
指定しない場合、コピー元のプロジェクト名が使用されます。
プロジェクト名はすべて大文字、A-Z 以外の文字がすべてアンダースコアに変換されます。
<p><strong>例</strong>:
<table>
<tr>
<th>Source project name</th>
<th>Suffix to be used</th>
</tr>
<tr>
<td>Project-ABC</td>
<td>PROJECT_ABC</td>
</tr>
<tr>
<td>tool1-release1.2</td>
<td>TOOL_RELEASE_</td>
</tr>
</table>
</div>
@@ -3,17 +3,7 @@
or stable build, or latest "keep forever" build. Other plugins may provide
additional selections. <br/>
The build number of the selected build will be recorded in the environment
for later build steps to reference. The name of the environment variable
is <tt>COPYARTIFACT_BUILD_NUMBER_</tt> with the source project name appended
(in all uppercase, and sequences of characters other than A-Z replaced by a
single underscore).
<p><strong>Example</strong>:
<ul>
<li>
The build number of the build for the source project <code>Project-ABC</code> is
available in <code>COPYARTIFACT_BUILD_NUMBER_PROJECT_ABC</code></li>
<li>
The build number of the build for the source project <code>tool1-release1.2</code> is
available in <code>COPYARTIFACT_BUILD_NUMBER_TOOL_RELEASE_</code></li>
</ul>
for later build steps to reference.
For details, see the help of "Result variable suffix"
in "Advanced" section.
</div>
@@ -10,6 +10,7 @@ see help for project name in job configuration.
CopyArtifact.MissingSrcArtifacts=Unable to access upstream artifacts area {0}. Does source project archive artifacts?
CopyArtifact.MissingSrcWorkspace=Unable to access upstream workspace for artifact copy. Slave node offline?
CopyArtifact.ParameterizedName=Value references a build parameter, so it cannot be validated.
CopyArtifact.InvalidVariableName=Contains letters not applicable for variable names.
PermalinkBuildSelector.DisplayName=Specified by permalink
LastCompletedBuildSelector.DisplayName=Last completed build (ignoring build status)
StatusBuildSelector.DisplayName=Latest successful build

0 comments on commit ad631ae

Please sign in to comment.
You can’t perform that action at this time.