├── .README ├── Screenshot_build_env.png ├── Screenshot_build_name.png └── Screenshot_build_step.png ├── .github ├── CODEOWNERS ├── dependabot.yml ├── release-drafter.yml └── workflows │ └── jenkins-security-scan.yml ├── .gitignore ├── .mvn ├── extensions.xml └── maven.config ├── Jenkinsfile ├── LICENSE ├── README.md ├── appveyor.yml ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── jenkinsci │ │ └── plugins │ │ ├── EnvironmentVarSetter.java │ │ ├── buildnamesetter │ │ ├── BuildNameSetter.java │ │ └── Executor.java │ │ ├── buildnameupdater │ │ └── BuildNameUpdater.java │ │ └── pipeline │ │ ├── BuildDescriptionStep.java │ │ └── BuildNameStep.java └── resources │ ├── index.jelly │ └── org │ └── jenkinsci │ └── plugins │ ├── buildnamesetter │ └── BuildNameSetter │ │ ├── config.jelly │ │ ├── help-template.jelly │ │ └── help.html │ ├── buildnameupdater │ └── BuildNameUpdater │ │ ├── config.jelly │ │ ├── help-buildName.jelly │ │ ├── help-macroFirst.jelly │ │ ├── help-macroTemplate.jelly │ │ └── help.html │ └── pipeline │ ├── BuildDescriptionStep │ ├── config.jelly │ └── help.html │ └── BuildNameStep │ ├── config.jelly │ └── help.html └── test └── java └── org └── jenkinsci └── plugins └── buildnamesetter └── BuildNameSetterTest.java /.README/Screenshot_build_env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/build-name-setter-plugin/4ab200bdc2b01fbff2c1cb19ca77122efdf0c296/.README/Screenshot_build_env.png -------------------------------------------------------------------------------- /.README/Screenshot_build_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/build-name-setter-plugin/4ab200bdc2b01fbff2c1cb19ca77122efdf0c296/.README/Screenshot_build_name.png -------------------------------------------------------------------------------- /.README/Screenshot_build_step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/build-name-setter-plugin/4ab200bdc2b01fbff2c1cb19ca77122efdf0c296/.README/Screenshot_build_step.png -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/build-name-setter-plugin-developers 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc 2 | _extends: .github 3 | name-template: build-name-setter-$NEXT_PATCH_VERSION 4 | tag-template: build-name-setter-$NEXT_PATCH_VERSION 5 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [ opened, synchronize, reopened ] 9 | workflow_dispatch: 10 | 11 | permissions: 12 | security-events: write 13 | contents: read 14 | actions: read 15 | 16 | jobs: 17 | security-scan: 18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 19 | with: 20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | !.gitignore 3 | *.iml 4 | *.ipr 5 | *.iws 6 | .DS_Store 7 | .idea 8 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.8 6 | 7 | 8 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | // `buildPlugin` step provided by: https://github.com/jenkins-infra/pipeline-library 2 | 3 | buildPlugin( 4 | useContainerAgent: true, 5 | failFast: false, 6 | configurations: [ 7 | [platform: 'linux', jdk: 21], 8 | [platform: 'windows', jdk: 17], 9 | ]) 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://ci.jenkins.io/job/Plugins/job/build-name-setter-plugin/job/master/badge/icon)](https://ci.jenkins.io/job/Plugins/job/build-name-setter-plugin/job/master/) 2 | [![Build status](https://ci.appveyor.com/api/projects/status/niut5mwbxdnht3pt/branch/master?svg=true)](https://ci.appveyor.com/project/damianszczepanik/build-name-setter-plugin/branch/master) 3 | 4 | [![Popularity](https://img.shields.io/jenkins/plugin/i/build-name-setter.svg)](https://plugins.jenkins.io/build-name-setter) 5 | 6 | # Build name setter plugin for Jenkins 7 | 8 | This plugin sets the display name of a build to something other than #1, #2, #3, ... so that you can use an identifier 9 | that makes more sense in your context. When you install this plugin, your job configuration page gets additional setting 10 | that lets you specify a build name for each new build. 11 | 12 | This plugin can be used in two ways: 13 | 14 | * Set build name at the begining and at the end of the build (both by default, it also can be ajusted) 15 | 16 | ![alt tag](./.README/Screenshot_build_env.png) 17 | 18 | * Set build name between two build steps (as a separate build step) 19 | 20 | ![alt tag](./.README/Screenshot_build_step.png) 21 | 22 | As the result you can obtain something like this: 23 | 24 | ![alt tag](./.README/Screenshot_build_name.png) 25 | 26 | # Pipeline 27 | This is how the plugin can be used via pipeline approach. Name and the description can be changed like any other steps. 28 | Mind that there are a few conventions which can be used to modify name or description: 29 | ```groovy 30 | pipeline { 31 | agent any 32 | stages { 33 | stage("Initialization") { 34 | steps { 35 | // use name of the patchset as the build name 36 | buildName "${GERRIT_CHANGE_SUBJECT}" 37 | buildDescription "Executed @ ${NODE_NAME}" 38 | } 39 | } 40 | } 41 | post { 42 | failure { 43 | // in case of failure, we'd like to have simple 'git blame' on build history :) 44 | currentBuild.displayName = 'This build needs help!!!' 45 | buildDescription("Committer: ${GERRIT_PATCHSET_UPLOADER_NAME}") 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | # Scripting 52 | The power of this plugin is based on [Macro Token](https://wiki.jenkins.io/display/JENKINS/Token+Macro+Plugin) so take a look what features you can use. 53 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: Visual Studio 2022 2 | 3 | version: '{build}' 4 | 5 | environment: 6 | JAVA_HOME: C:\Program Files\Java\jdk17 7 | 8 | branches: 9 | only: 10 | - master 11 | 12 | init: 13 | - git config --global core.autocrlf true 14 | 15 | build_script: 16 | - mvn clean package --batch-mode -DskipTest 17 | test_script: 18 | - mvn clean verify --batch-mode 19 | 20 | cache: 21 | - C:\maven\ 22 | - C:\Users\appveyor\.m2 23 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.jenkins-ci.plugins 6 | plugin 7 | 5.17 8 | 9 | 10 | 11 | build-name-setter 12 | ${revision}${changelist} 13 | hpi 14 | 15 | 16 | 17 | 18 | io.jenkins.tools.bom 19 | bom-${jenkins.baseline}.x 20 | 4770.v9a_2b_7a_9d8b_7f 21 | pom 22 | import 23 | 24 | 25 | 26 | 27 | Build Name and Description Setter 28 | Changes default build name and description. 29 | https://github.com/jenkinsci/build-name-setter-plugin 30 | 31 | 32 | 2.5.1 33 | -SNAPSHOT 34 | jenkinsci/build-name-setter-plugin 35 | 36 | 2.479 37 | ${jenkins.baseline}.3 38 | 39 | Max 40 | Low 41 | 42 | 43 | 44 | scm:git:https://github.com/${gitHubRepo}.git 45 | scm:git:git@github.com:${gitHubRepo}.git 46 | https://github.com/${gitHubRepo} 47 | ${scmTag} 48 | 49 | 50 | 51 | Jenkins 52 | https://ci.jenkins.io/job/Plugins/job/build-name-setter-plugin/ 53 | 54 | 55 | 56 | 57 | MIT 58 | https://opensource.org/licenses/MIT 59 | 60 | 61 | 62 | 63 | 64 | damianszczepanik 65 | Damian Szczepanik 66 | damianszczepanik@github 67 | 68 | 69 | kohsuke 70 | Kohsuke Kawaguchi 71 | kkawaguchi@cloudbees.com 72 | 73 | 74 | Le0 75 | Lev Mishin 76 | leomichine@gmail.com 77 | 78 | 79 | 80 | 81 | 82 | repo.jenkins-ci.org 83 | https://repo.jenkins-ci.org/public/ 84 | 85 | 86 | 87 | 88 | 89 | repo.jenkins-ci.org 90 | https://repo.jenkins-ci.org/public/ 91 | 92 | 93 | 94 | 95 | 96 | org.jenkins-ci.plugins 97 | token-macro 98 | 99 | 100 | org.jenkins-ci.plugins 101 | matrix-project 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/EnvironmentVarSetter.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins; 2 | 3 | import java.io.PrintStream; 4 | import java.util.Map; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | import java.util.logging.Level; 7 | import java.util.logging.Logger; 8 | 9 | import hudson.EnvVars; 10 | import hudson.model.AbstractBuild; 11 | import hudson.model.EnvironmentContributingAction; 12 | import edu.umd.cs.findbugs.annotations.CheckForNull; 13 | import org.apache.commons.lang.StringUtils; 14 | 15 | /** 16 | * Created by Leo on 4/20/2016. 17 | * Helper to work with environment variables. 18 | */ 19 | public class EnvironmentVarSetter implements EnvironmentContributingAction { 20 | 21 | @CheckForNull 22 | private transient PrintStream log; 23 | private final Map envVars = new ConcurrentHashMap(); 24 | 25 | private static final Logger LOGGER = Logger.getLogger(EnvironmentVarSetter.class.getName()); 26 | 27 | public static final String buildDisplayNameVar = "BUILD_DISPLAY_NAME"; 28 | 29 | public EnvironmentVarSetter(@CheckForNull String key, @CheckForNull String value, @CheckForNull PrintStream logger) { 30 | log = logger; 31 | envVars.put(key, value); 32 | } 33 | 34 | public static void setVar(AbstractBuild build, String key, String value, PrintStream logger) { 35 | EnvironmentVarSetter action = build.getAction(EnvironmentVarSetter.class); 36 | if (action == null) { 37 | action = new EnvironmentVarSetter(key, value, logger); 38 | build.addAction(action); 39 | } else { 40 | action.setVar(key, value); 41 | } 42 | } 43 | 44 | public void setVar(@CheckForNull String key, @CheckForNull String value) { 45 | if (StringUtils.isBlank(key)) { 46 | throw new IllegalArgumentException("key shouldn't be null or empty."); 47 | } 48 | if (StringUtils.isBlank(value)) { 49 | throw new IllegalArgumentException("value shouldn't be null or empty."); 50 | } 51 | 52 | if (envVars.containsKey(key)) { 53 | if (!envVars.get(key).equals(value)) { 54 | log("Variable with name '%s' already exists, current value: '%s', new value: '%s'", 55 | key, envVars.get(key), value); 56 | } 57 | } else { 58 | log("Create new variable %s=%s", key, value); 59 | } 60 | 61 | envVars.put(key, value); 62 | } 63 | 64 | public String getVar(String key) { 65 | if (envVars.containsKey(key)) { 66 | log("Get var: %s=%s", key, envVars.get(key)); 67 | return envVars.get(key); 68 | } else { 69 | log("Var '%s' doesn't exist", key); 70 | return ""; 71 | } 72 | } 73 | 74 | private void log(String format, Object... args) { 75 | if (log == null && !LOGGER.isLoggable(Level.FINE)) { // not loggable 76 | return; 77 | } 78 | 79 | String message = format.formatted(args); 80 | LOGGER.fine(message); 81 | if (log != null) { 82 | log.println(message); 83 | } 84 | } 85 | 86 | @Override 87 | public void buildEnvVars(AbstractBuild abstractBuild, EnvVars envVars) { 88 | envVars.putAll(this.envVars); 89 | } 90 | 91 | @Override 92 | public String getIconFileName() { 93 | return null; 94 | } 95 | 96 | @Override 97 | public String getDisplayName() { 98 | return null; 99 | } 100 | 101 | @Override 102 | public String getUrlName() { 103 | return null; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/buildnamesetter/BuildNameSetter.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.buildnamesetter; 2 | 3 | import static org.apache.commons.lang.BooleanUtils.toBooleanDefaultIfNull; 4 | 5 | import java.io.IOException; 6 | 7 | import hudson.Extension; 8 | import hudson.Launcher; 9 | import hudson.matrix.MatrixAggregatable; 10 | import hudson.matrix.MatrixAggregator; 11 | import hudson.matrix.MatrixBuild; 12 | import hudson.model.AbstractBuild; 13 | import hudson.model.AbstractProject; 14 | import hudson.model.BuildListener; 15 | import hudson.tasks.BuildWrapper; 16 | import hudson.tasks.BuildWrapperDescriptor; 17 | import org.kohsuke.stapler.DataBoundConstructor; 18 | import org.kohsuke.stapler.DataBoundSetter; 19 | 20 | /** 21 | * Sets the build name at two configurable points during the build. 22 | *

23 | * Once early on in the build, and another time later on. 24 | * 25 | * @author Kohsuke Kawaguchi 26 | */ 27 | public class BuildNameSetter extends BuildWrapper implements MatrixAggregatable { 28 | 29 | private String template; 30 | private String descriptionTemplate; 31 | private Boolean runAtStart = true; 32 | private Boolean runAtEnd = true; 33 | 34 | @DataBoundConstructor 35 | public BuildNameSetter(String template, Boolean runAtStart, Boolean runAtEnd) { 36 | // attribute is named differently than parameter that must be backwards compatible 37 | this.template = template; 38 | this.runAtStart = toBooleanDefaultIfNull(runAtStart, true); 39 | this.runAtEnd = toBooleanDefaultIfNull(runAtEnd, true); 40 | } 41 | 42 | @DataBoundSetter 43 | public void setDescriptionTemplate(String descriptionTemplate) { 44 | this.descriptionTemplate = descriptionTemplate; 45 | } 46 | 47 | public String getDescriptionTemplate() { 48 | return descriptionTemplate; 49 | } 50 | 51 | @DataBoundSetter 52 | public void setTemplate(String template) { 53 | this.template = template; 54 | } 55 | 56 | public String getTemplate() { 57 | return template; 58 | } 59 | 60 | public Boolean getRunAtStart() { 61 | return runAtStart; 62 | } 63 | 64 | public Boolean getRunAtEnd() { 65 | return runAtEnd; 66 | } 67 | 68 | protected Object readResolve() { 69 | if (runAtStart == null) { 70 | runAtStart = true; 71 | } 72 | if (runAtEnd == null) { 73 | runAtEnd = true; 74 | } 75 | return this; 76 | } 77 | 78 | @Override 79 | public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) { 80 | 81 | Executor executor = new Executor(build, listener); 82 | 83 | if (runAtStart) { 84 | executor.setName(template); 85 | executor.setDescription(descriptionTemplate); 86 | } 87 | 88 | return new Environment() { 89 | @Override 90 | public boolean tearDown(AbstractBuild build, BuildListener listener) { 91 | if (runAtEnd) { 92 | executor.setName(template); 93 | executor.setDescription(descriptionTemplate); 94 | } 95 | return true; 96 | } 97 | }; 98 | } 99 | 100 | 101 | // support for matrix project 102 | public MatrixAggregator createAggregator(MatrixBuild build, Launcher launcher, BuildListener listener) { 103 | 104 | Executor executor = new Executor(build, listener); 105 | 106 | return new MatrixAggregator(build, launcher, listener) { 107 | @Override 108 | public boolean startBuild() throws InterruptedException, IOException { 109 | executor.setName(template); 110 | executor.setDescription(descriptionTemplate); 111 | 112 | return super.startBuild(); 113 | } 114 | 115 | @Override 116 | public boolean endBuild() throws InterruptedException, IOException { 117 | executor.setName(template); 118 | executor.setDescription(descriptionTemplate); 119 | 120 | return super.endBuild(); 121 | } 122 | }; 123 | } 124 | 125 | @Extension 126 | public static class DescriptorImpl extends BuildWrapperDescriptor { 127 | @Override 128 | public boolean isApplicable(AbstractProject item) { 129 | return true; 130 | } 131 | 132 | @Override 133 | public String getDisplayName() { 134 | return "Set Build Name"; 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/buildnamesetter/Executor.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.buildnamesetter; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import hudson.FilePath; 7 | import hudson.model.AbstractBuild; 8 | import hudson.model.Run; 9 | import hudson.model.TaskListener; 10 | import org.apache.commons.lang.StringUtils; 11 | import org.jenkinsci.plugins.EnvironmentVarSetter; 12 | import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException; 13 | import org.jenkinsci.plugins.tokenmacro.TokenMacro; 14 | 15 | /** 16 | * @author Damian Szczepanik (damianszczepanik@github) 17 | */ 18 | public class Executor { 19 | 20 | private final Run run; 21 | private final TaskListener listener; 22 | 23 | public Executor(Run run, TaskListener listener) { 24 | this.run = run; 25 | this.listener = listener; 26 | } 27 | 28 | public void setName(String nameTemplate) { 29 | try { 30 | String name = evaluateMacro(nameTemplate); 31 | listener.getLogger().println("New run name is '" + name + "'"); 32 | run.setDisplayName(name); 33 | setVariable(nameTemplate); 34 | } catch (IOException e) { 35 | listener.error(e.getMessage()); 36 | } catch (MacroEvaluationException e) { 37 | // should be marked as failure but then many configuration 38 | // that work with older version of the plugin will fail 39 | listener.getLogger().println("Failed to evaluate name macro:" + e.toString()); 40 | } 41 | } 42 | 43 | public void setDescription(String descriptionTemplate) { 44 | // skip when the description is not provided (because plugin was updated but configuration not) 45 | if (StringUtils.isEmpty(descriptionTemplate)) { 46 | return; 47 | } 48 | 49 | try { 50 | String description = evaluateMacro(descriptionTemplate); 51 | listener.getLogger().println("New run description is '" + description + "'"); 52 | run.setDescription(description); 53 | } catch (IOException e) { 54 | listener.error(e.getMessage()); 55 | } catch (MacroEvaluationException e) { 56 | // should be marked as failure but then many configuration 57 | // that work with older version of the plugin will fail 58 | listener.getLogger().println("Failed to evaluate description macro:" + e.toString()); 59 | } 60 | } 61 | 62 | public void setVariable(String nameTemplate) throws MacroEvaluationException { 63 | if (run instanceof AbstractBuild abstractBuild) { 64 | EnvironmentVarSetter.setVar(abstractBuild, EnvironmentVarSetter.buildDisplayNameVar, 65 | evaluateMacro(nameTemplate), listener.getLogger()); 66 | } 67 | } 68 | 69 | public String evaluateMacro(String template) throws MacroEvaluationException { 70 | try { 71 | File workspace = run.getRootDir(); 72 | return TokenMacro.expandAll(run, new FilePath(workspace), listener, template); 73 | } catch (InterruptedException | IOException e) { 74 | throw new IllegalArgumentException(e); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/buildnameupdater/BuildNameUpdater.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.buildnameupdater; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.logging.Level; 9 | import java.util.logging.Logger; 10 | 11 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 12 | import hudson.Extension; 13 | import hudson.FilePath; 14 | import hudson.Launcher; 15 | import hudson.model.AbstractBuild; 16 | import hudson.model.AbstractProject; 17 | import hudson.model.BuildListener; 18 | import hudson.remoting.VirtualChannel; 19 | import hudson.tasks.BuildStepDescriptor; 20 | import hudson.tasks.Builder; 21 | import hudson.util.FormValidation; 22 | import jenkins.MasterToSlaveFileCallable; 23 | import org.apache.commons.lang.StringUtils; 24 | import org.jenkinsci.plugins.buildnamesetter.Executor; 25 | import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException; 26 | import org.jenkinsci.plugins.tokenmacro.TokenMacro; 27 | import org.kohsuke.stapler.DataBoundConstructor; 28 | import org.kohsuke.stapler.QueryParameter; 29 | 30 | /** 31 | * This plugin replace the build name with the first line from a file on a slave. 32 | * 33 | * @author Lev Mishin 34 | */ 35 | public class BuildNameUpdater extends Builder { 36 | 37 | private final String buildName; 38 | private final String macroTemplate; 39 | private final boolean fromFile; 40 | private final boolean fromMacro; 41 | private final boolean macroFirst; 42 | 43 | private static final Logger LOGGER = Logger.getLogger(BuildNameUpdater.class.getName()); 44 | 45 | // Fields in config.jelly must match the parameter names in the "DataBoundConstructor" 46 | @DataBoundConstructor 47 | public BuildNameUpdater(boolean fromFile, String buildName, boolean fromMacro, String macroTemplate, boolean macroFirst) { 48 | this.buildName = buildName; 49 | this.macroTemplate = macroTemplate; 50 | this.fromFile = fromFile; 51 | this.fromMacro = fromMacro; 52 | this.macroFirst = macroFirst; 53 | } 54 | 55 | @SuppressWarnings("unused") 56 | public boolean getFromFile() { 57 | return fromFile; 58 | } 59 | 60 | @SuppressWarnings("unused") 61 | public boolean getMacroFirst() { 62 | return macroFirst; 63 | } 64 | 65 | @SuppressWarnings("unused") 66 | public boolean getFromMacro() { 67 | return fromMacro; 68 | } 69 | 70 | @SuppressWarnings("unused") 71 | public String getBuildName() { 72 | return buildName; 73 | } 74 | 75 | @SuppressWarnings("unused") 76 | public String getMacroTemplate() { 77 | return macroTemplate; 78 | } 79 | 80 | @Override 81 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) { 82 | String buildNameToSet = ""; 83 | 84 | if (fromFile) { 85 | buildNameToSet = readFromFile(build, listener, buildName); 86 | } 87 | 88 | if (fromMacro) { 89 | String evaluatedMacro = getFromMacro(build, listener, macroTemplate); 90 | 91 | listener.getLogger().println("Evaluated macro: '" + evaluatedMacro + "'"); 92 | 93 | buildNameToSet = macroFirst ? evaluatedMacro + buildNameToSet : buildNameToSet + evaluatedMacro; 94 | } 95 | 96 | if (StringUtils.isNotBlank(buildNameToSet)) { 97 | Executor executor = new Executor(build, listener); 98 | executor.setName(buildNameToSet); 99 | } 100 | 101 | return true; 102 | } 103 | 104 | private String getFromMacro(AbstractBuild build, BuildListener listener, String macro) { 105 | String result = null; 106 | try { 107 | result = TokenMacro.expandAll(build, listener, macro); 108 | } catch (MacroEvaluationException e) { 109 | listener.getLogger().println("Failed to evaluate macro '" + macro + "'"); 110 | LOGGER.log(Level.WARNING, "Failed to evaluate macro '" + macro + "': ", e); 111 | } catch (IOException e) { 112 | LOGGER.log(Level.WARNING, "Exception was thrown during macro evaluation: ", e); 113 | } catch (InterruptedException e) { 114 | LOGGER.log(Level.WARNING, "Macro evaluation was interrupted: ", e); 115 | listener.getLogger().println("Macro evaluating failed with:"); 116 | } 117 | LOGGER.log(Level.INFO, "Macro evaluated: '" + result + "'"); 118 | return result; 119 | } 120 | 121 | private String readFromFile(AbstractBuild build, BuildListener listener, String filePath) { 122 | String version = ""; 123 | 124 | if (StringUtils.isBlank(filePath)) { 125 | listener.getLogger().println("File path is empty."); 126 | return ""; 127 | } 128 | 129 | FilePath workspace = build.getWorkspace(); 130 | if (workspace == null) { 131 | listener.getLogger().println("Workspace is empty."); 132 | return ""; 133 | } 134 | FilePath fp = new FilePath(workspace, filePath); 135 | 136 | listener.getLogger().println("Getting version from file: " + fp); 137 | 138 | try { 139 | version = fp.act(new FileCallable()); 140 | } catch (IOException e) { 141 | LOGGER.log(Level.WARNING, "Failed to read file: ", e); 142 | } catch (InterruptedException e) { 143 | LOGGER.log(Level.WARNING, "Getting name from file was interrupted: ", e); 144 | } 145 | 146 | listener.getLogger().println("Loaded version is " + version); 147 | return StringUtils.defaultString(version); 148 | } 149 | 150 | @Override 151 | public DescriptorImpl getDescriptor() { 152 | return (DescriptorImpl) super.getDescriptor(); 153 | } 154 | 155 | private static class FileCallable extends MasterToSlaveFileCallable { 156 | private static final long serialVersionUID = 1L; 157 | 158 | @Override 159 | public String invoke(File file, VirtualChannel channel) throws IOException { 160 | if (file.getAbsoluteFile().exists()) { 161 | LOGGER.log(Level.INFO, "File is found, reading..."); 162 | try (BufferedReader br = new BufferedReader( 163 | new FileReader(file.getAbsoluteFile(), StandardCharsets.UTF_8))) { 164 | return br.readLine(); 165 | } 166 | } else { 167 | LOGGER.log(Level.WARNING, "File was not found."); 168 | return ""; 169 | } 170 | } 171 | } 172 | 173 | @Extension // This indicates to Jenkins that this is an implementation of an extension point. 174 | public static final class DescriptorImpl extends BuildStepDescriptor { 175 | public DescriptorImpl() { 176 | load(); 177 | } 178 | 179 | @SuppressWarnings("unused") 180 | public FormValidation doCheckName(@QueryParameter String value) { 181 | if (value.isEmpty()) 182 | return FormValidation.error("Please set a file path"); 183 | return FormValidation.ok(); 184 | } 185 | 186 | public boolean isApplicable(Class jobType) { 187 | return true; 188 | } 189 | 190 | public String getDisplayName() { 191 | return "Update build name"; 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/pipeline/BuildDescriptionStep.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.pipeline; 2 | 3 | import hudson.Extension; 4 | import hudson.FilePath; 5 | import hudson.Launcher; 6 | import hudson.model.AbstractProject; 7 | import hudson.model.Run; 8 | import hudson.model.TaskListener; 9 | import hudson.tasks.BuildStepDescriptor; 10 | import hudson.tasks.Builder; 11 | import jenkins.tasks.SimpleBuildStep; 12 | import org.jenkinsci.Symbol; 13 | import org.jenkinsci.plugins.buildnamesetter.Executor; 14 | import org.kohsuke.stapler.DataBoundConstructor; 15 | 16 | /** 17 | * @author Damian Szczepanik (damianszczepanik@github) 18 | */ 19 | public class BuildDescriptionStep extends Builder implements SimpleBuildStep { 20 | 21 | private final String descriptionTemplate; 22 | 23 | @DataBoundConstructor 24 | public BuildDescriptionStep(String descriptionTemplate) { 25 | this.descriptionTemplate = descriptionTemplate; 26 | } 27 | 28 | public String getDescriptionTemplate() { 29 | return descriptionTemplate; 30 | } 31 | 32 | @Override 33 | public void perform(Run run, FilePath workspace, Launcher launcher, TaskListener listener) { 34 | Executor executor = new Executor(run, listener); 35 | executor.setDescription(descriptionTemplate); 36 | } 37 | 38 | @Symbol("buildDescription") 39 | @Extension 40 | public static class DescriptorImpl extends BuildStepDescriptor { 41 | 42 | @Override 43 | public String getDisplayName() { 44 | return "Changes build description"; 45 | } 46 | 47 | @Override 48 | public boolean isApplicable(Class t) { 49 | return true; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/pipeline/BuildNameStep.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.pipeline; 2 | 3 | import hudson.Extension; 4 | import hudson.FilePath; 5 | import hudson.Launcher; 6 | import hudson.model.AbstractProject; 7 | import hudson.model.Run; 8 | import hudson.model.TaskListener; 9 | import hudson.tasks.BuildStepDescriptor; 10 | import hudson.tasks.Builder; 11 | import jenkins.tasks.SimpleBuildStep; 12 | import org.jenkinsci.Symbol; 13 | import org.jenkinsci.plugins.buildnamesetter.Executor; 14 | import org.kohsuke.stapler.DataBoundConstructor; 15 | 16 | /** 17 | * @author Damian Szczepanik (damianszczepanik@github) 18 | */ 19 | public class BuildNameStep extends Builder implements SimpleBuildStep { 20 | 21 | private final String nameTemplate; 22 | 23 | @DataBoundConstructor 24 | public BuildNameStep(String nameTemplate) { 25 | this.nameTemplate = nameTemplate; 26 | } 27 | 28 | public String getNameTemplate() { 29 | return nameTemplate; 30 | } 31 | 32 | @Override 33 | public void perform(Run run, FilePath workspace, Launcher launcher, TaskListener listener) { 34 | Executor executor = new Executor(run, listener); 35 | executor.setName(nameTemplate); 36 | } 37 | 38 | @Symbol("buildName") 39 | @Extension 40 | public static class DescriptorImpl extends BuildStepDescriptor { 41 | 42 | @Override 43 | public String getDisplayName() { 44 | return "Changes build name"; 45 | } 46 | 47 | @Override 48 | public boolean isApplicable(Class t) { 49 | return true; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |

3 | This plug-in sets the display name and description of a build to something other than #1, #2, #3, ... 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/buildnamesetter/BuildNameSetter/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/buildnamesetter/BuildNameSetter/help-template.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | Normally, builds are named by their sequential numbers, but you can change that here by 4 | setting what name new build gets. This field can contain the following macros: 5 | 6 | 7 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/buildnamesetter/BuildNameSetter/help.html: -------------------------------------------------------------------------------- 1 |
2 | Normally, builds are named by their sequential numbers, but you can change name and description to something 3 | more meaningful to you in the context of this job. 4 | 5 |
6 | The update actually happens twice during the build; once right after the check out, and once before the build is 7 | completed. So depending on the macro you use, you might not see the complete value until your build completes. 8 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/buildnameupdater/BuildNameUpdater/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/buildnameupdater/BuildNameUpdater/help-buildName.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | This field could contain relative path to the file 4 | with build name from the build workspace. 5 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/buildnameupdater/BuildNameUpdater/help-macroFirst.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | Normally build name consists of [file content]+[macro], 4 |
5 | this option reverses the order i.e. [macro]+[file content]. 6 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/buildnameupdater/BuildNameUpdater/help-macroTemplate.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | This field can contain the following macros: 4 |
5 | 6 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/buildnameupdater/BuildNameUpdater/help.html: -------------------------------------------------------------------------------- 1 |
2 | This plugin updates build name and description during the build process. Values to set could be stored in a file 3 | in the build workspace or in an environment variable. 4 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/pipeline/BuildDescriptionStep/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/pipeline/BuildDescriptionStep/help.html: -------------------------------------------------------------------------------- 1 |
2 | Normally, build description is empty, but it can be changed by 3 | setting what name new build gets. This field can contain 4 | macros. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/pipeline/BuildNameStep/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/pipeline/BuildNameStep/help.html: -------------------------------------------------------------------------------- 1 |
2 | Normally, builds are named by their sequential numbers, but you can change that here by 3 | setting what name new build gets. This field can contain 4 | macros. 5 |
6 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/buildnamesetter/BuildNameSetterTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.buildnamesetter; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | 6 | import hudson.EnvVars; 7 | import hudson.model.FreeStyleBuild; 8 | import hudson.model.FreeStyleProject; 9 | import hudson.model.Result; 10 | import hudson.model.TaskListener; 11 | import org.jenkinsci.plugins.EnvironmentVarSetter; 12 | import org.junit.jupiter.api.Test; 13 | import org.jvnet.hudson.test.Issue; 14 | import org.jvnet.hudson.test.JenkinsRule; 15 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 16 | 17 | @WithJenkins 18 | class BuildNameSetterTest { 19 | 20 | @Test 21 | void shouldExpand_BUILD_NUMBER_macro(JenkinsRule jenkins) throws Exception { 22 | FreeStyleProject fooProj = jenkins.createFreeStyleProject("foo"); 23 | fooProj.getBuildWrappersList().add(getDefaultSetter("a_#${BUILD_NUMBER}")); 24 | 25 | FreeStyleBuild fooBuild = fooProj.scheduleBuild2(0).get(); 26 | assertDisplayName(fooBuild, "a_#1"); 27 | } 28 | 29 | @Test 30 | void shouldExpand_JOB_NAME_full_env_macro(JenkinsRule jenkins) throws Exception { 31 | FreeStyleProject barProj = jenkins.createFreeStyleProject("bar"); 32 | barProj.getBuildWrappersList().add(getDefaultSetter("b_${ENV,var=\"JOB_NAME\"}")); 33 | 34 | FreeStyleBuild barBuild = barProj.scheduleBuild2(0).get(); 35 | assertDisplayName(barBuild, "b_bar"); 36 | } 37 | 38 | @Issue("13347") 39 | @Test 40 | void shouldExpand_JOB_NAME_macro(JenkinsRule jenkins) throws Exception { 41 | FreeStyleProject barProj = jenkins.createFreeStyleProject("bar"); 42 | barProj.getBuildWrappersList().add(getDefaultSetter("c_${JOB_NAME}")); 43 | 44 | FreeStyleBuild barBuild = barProj.scheduleBuild2(0).get(); 45 | assertDisplayName(barBuild, "c_bar"); 46 | } 47 | 48 | @Issue("13347") 49 | @Test 50 | void shouldExpand_JOB_NAME_macro_twice(JenkinsRule jenkins) throws Exception { 51 | FreeStyleProject barProj = jenkins.createFreeStyleProject("bar"); 52 | barProj.getBuildWrappersList().add(getDefaultSetter("c_${JOB_NAME}_d_${JOB_NAME}")); 53 | 54 | FreeStyleBuild barBuild = barProj.scheduleBuild2(0).get(); 55 | assertDisplayName(barBuild, "c_bar_d_bar"); 56 | } 57 | 58 | @Issue("13347") 59 | @Test 60 | void shouldExpand_NODE_NAME_macro_and_JOB_NAME_full_env_macro(JenkinsRule jenkins) throws Exception { 61 | FreeStyleProject fooProj = jenkins.createFreeStyleProject("foo"); 62 | fooProj.getBuildWrappersList().add(getDefaultSetter("d_${NODE_NAME}_${ENV,var=\"JOB_NAME\"}")); 63 | 64 | FreeStyleBuild fooBuild = fooProj.scheduleBuild2(0).get(); 65 | assertDisplayName(fooBuild, "d_built-in_foo"); 66 | } 67 | 68 | @Issue("34181") 69 | @Test 70 | void shouldUse_default_config_values_if_null(JenkinsRule jenkins) throws Exception { 71 | FreeStyleProject fooProj = jenkins.createFreeStyleProject("foo"); 72 | fooProj.getBuildWrappersList().add(new BuildNameSetter("${ENV,var=\"JOB_NAME\"}", null, null)); 73 | 74 | FreeStyleBuild fooBuild = fooProj.scheduleBuild2(0).get(); 75 | assertDisplayName(fooBuild, "foo"); 76 | } 77 | 78 | private static void assertDisplayName(FreeStyleBuild build, String expectedName) { 79 | assertEquals(Result.SUCCESS, build.getResult()); 80 | assertEquals(expectedName, build.getDisplayName()); 81 | EnvironmentVarSetter action = build.getAction(EnvironmentVarSetter.class); 82 | assertEquals(expectedName, action.getVar(EnvironmentVarSetter.buildDisplayNameVar)); 83 | EnvVars envVars = assertDoesNotThrow( 84 | () -> build.getEnvironment(TaskListener.NULL), 85 | "Exception was thrown during getting build environment"); 86 | assertEquals(expectedName, envVars.get(EnvironmentVarSetter.buildDisplayNameVar)); 87 | } 88 | 89 | private static BuildNameSetter getDefaultSetter(String template) { 90 | return new BuildNameSetter(template, true, true); 91 | } 92 | } 93 | --------------------------------------------------------------------------------