├── .gitignore ├── Jenkinsfile ├── src ├── main │ ├── resources │ │ └── org │ │ │ └── jenkinsci │ │ │ └── plugins │ │ │ └── golang │ │ │ ├── Messages.properties │ │ │ ├── GolangBuildWrapper │ │ │ ├── help.html │ │ │ └── config.groovy │ │ │ └── GolangInstaller │ │ │ └── config.groovy │ └── java │ │ └── org │ │ └── jenkinsci │ │ └── plugins │ │ └── golang │ │ ├── GolangInstallation.java │ │ ├── GolangBuildWrapper.java │ │ └── GolangInstaller.java └── test │ └── java │ └── org │ └── jenkinsci │ └── plugins │ └── golang │ └── GolangInstallerTest.java ├── CHANGELOG.md ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | target 3 | work 4 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | // Build on ci.jenkins.io; see https://github.com/jenkins-infra/pipeline-library 2 | buildPlugin() 3 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/golang/Messages.properties: -------------------------------------------------------------------------------- 1 | SetUpGoTools=Set up Go programming language tools 2 | InstallFromWebsite=Install from go.dev 3 | InstallingGoOnNode=Unpacking Go from {0} to {1} on {2} 4 | UnrecognisedReleaseId=Go can not be installed, as ''{0}'' is not a known version 5 | CouldNotInstallGo=Could not install Go because {0} 6 | NoInstallerForOs=there is no {0} installer for {1} ({2})... 7 | UnsupportedOs={0} is not a supported OS 8 | UnsupportedCpuArch={0} is not a supported CPU type 9 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/golang/GolangBuildWrapper/help.html: -------------------------------------------------------------------------------- 1 |
2 | Ensures that the selected version of the Go programming language tools are 3 | installed, and that the GOROOT environment variable points to 4 | that installation during a build. 5 |

6 | In addition, the Go tools (under $GOROOT/bin) will be added at 7 | the start of the PATH environment variable during builds. 8 |

9 | If no Go installations have been defined in the Jenkins system config, then 10 | none of the above steps will take place. 11 |

12 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/golang/GolangBuildWrapper/config.groovy: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.golang; 2 | 3 | def f = namespace(lib.FormTagLib) 4 | 5 | def installationsDefined = descriptor.installations.length != 0 6 | def title = installationsDefined ? _("Go version") : 7 | _("No setup will be done, as no Go installations have been defined in the Jenkins system config") 8 | 9 | f.entry(title: title) { 10 | if (installationsDefined) { 11 | select(class:"setting-input", name:".goVersion") { 12 | descriptor.installations.each { 13 | f.option(selected: it.name == instance?.goInstallation?.name, value: it.name, it.name) 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/golang/GolangInstaller/config.groovy: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.golang 2 | 3 | def f = namespace(lib.FormTagLib) 4 | 5 | def releases = descriptor.installableReleases 6 | if (releases == null || releases.isEmpty()) { 7 | f.block { 8 | text(_('Go version information has not been downloaded. ' + 9 | 'To do so, press "Check now" in the Plugin Manager, or restart Jenkins.')) 10 | } 11 | } else { 12 | f.entry(title: _("Version"), field: "id") { 13 | select(name: ".id") { 14 | releases.each { release -> 15 | f.option(value: release.id, selected: release.id == instance?.id, release.name) 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version history 2 | 3 | ## Version 1.4 4 | January 27, 2021 5 | 6 | - Added support for installing Go on machines with 32-bit ARM CPU architecture 7 | 8 | ## Version 1.3 9 | December 20, 2020 10 | 11 | - Added support for installing Go on machines with 64-bit ARM (AArch64) CPU architecture ([#3](https://github.com/jenkinsci/golang-plugin/pull/3); thanks to [Sergei](https://github.com/serges147)) 12 | - Increased required Jenkins version to 2.190.3 13 | 14 | ## Version 1.2 15 | February 25, 2017 16 | 17 | - Stopped breaking tool configuration page if the version metadata had not been fetched (see [JENKINS-27499](https://issues.jenkins-ci.org/browse/JENKINS-27499)) 18 | - Added `go` symbol, for use with the Pipeline `tool` step (see [JENKINS-34345](https://issues.jenkins-ci.org/browse/JENKINS-34345)) 19 | - Increased required Jenkins version to 1.642.3 20 | 21 | ## Version 1.1 22 | June 21, 2014 23 | 24 | - Worked around bug causing newer versions of Go to not install (see [JENKINS-23509](https://issues.jenkins-ci.org/browse/JENKINS-23509)) 25 | - Fixed bug where wrong package for OS X could be installed (see [JENKINS-23505](https://issues.jenkins-ci.org/browse/JENKINS-23505)) 26 | - Ensured "Install automatically" is checked by default when adding a Go installation 27 | 28 | ## Version 1.0 29 | June 18, 2014 30 | 31 | - Initial release 32 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/golang/GolangInstallation.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.golang; 2 | 3 | import hudson.EnvVars; 4 | import hudson.Extension; 5 | import hudson.model.EnvironmentSpecific; 6 | import hudson.model.Node; 7 | import hudson.model.TaskListener; 8 | import hudson.slaves.NodeSpecific; 9 | import hudson.tools.ToolDescriptor; 10 | import hudson.tools.ToolInstallation; 11 | import hudson.tools.ToolInstaller; 12 | import hudson.tools.ToolProperty; 13 | import jenkins.model.Jenkins; 14 | import org.jenkinsci.Symbol; 15 | import org.kohsuke.stapler.DataBoundConstructor; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.util.Collections; 20 | import java.util.List; 21 | 22 | public class GolangInstallation extends ToolInstallation implements EnvironmentSpecific, 23 | NodeSpecific { 24 | 25 | @DataBoundConstructor 26 | public GolangInstallation(String name, String home, List> properties) { 27 | super(name, home, properties); 28 | } 29 | 30 | @Override 31 | public void buildEnvVars(EnvVars env) { 32 | String root = getHome(); 33 | if (root != null) { 34 | env.put("GOROOT", root); 35 | env.put("PATH+GOROOT_BIN", new File(root, "bin").toString()); 36 | } 37 | } 38 | 39 | public GolangInstallation forEnvironment(EnvVars environment) { 40 | return new GolangInstallation(getName(), environment.expand(getHome()), getProperties().toList()); 41 | } 42 | 43 | public GolangInstallation forNode(Node node, TaskListener log) throws IOException, InterruptedException { 44 | return new GolangInstallation(getName(), translateFor(node, log), getProperties().toList()); 45 | } 46 | 47 | @Extension 48 | @Symbol("go") 49 | public static class DescriptorImpl extends ToolDescriptor { 50 | 51 | @Override 52 | public String getDisplayName() { 53 | return "Go"; 54 | } 55 | 56 | @Override 57 | public List getDefaultInstallers() { 58 | return Collections.singletonList(new GolangInstaller(null)); 59 | } 60 | 61 | @Override 62 | public GolangInstallation[] getInstallations() { 63 | return Jenkins.getActiveInstance() 64 | .getDescriptorByType(GolangBuildWrapper.DescriptorImpl.class) 65 | .getInstallations(); 66 | } 67 | 68 | @Override 69 | public void setInstallations(GolangInstallation... installations) { 70 | Jenkins.getActiveInstance() 71 | .getDescriptorByType(GolangBuildWrapper.DescriptorImpl.class) 72 | .setInstallations(installations); 73 | } 74 | 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/golang/GolangBuildWrapper.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.golang; 2 | 3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 4 | import hudson.CopyOnWrite; 5 | import hudson.EnvVars; 6 | import hudson.Extension; 7 | import hudson.Launcher; 8 | import hudson.model.AbstractBuild; 9 | import hudson.model.AbstractProject; 10 | import hudson.model.BuildListener; 11 | import hudson.model.Computer; 12 | import hudson.tasks.BuildWrapper; 13 | import hudson.tasks.BuildWrapperDescriptor; 14 | import org.kohsuke.stapler.DataBoundConstructor; 15 | 16 | import java.io.IOException; 17 | import java.util.Map; 18 | 19 | public class GolangBuildWrapper extends BuildWrapper { 20 | 21 | private final String goVersion; 22 | 23 | @DataBoundConstructor 24 | public GolangBuildWrapper(String goVersion) { 25 | this.goVersion = goVersion; 26 | } 27 | 28 | @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") 29 | public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { 30 | GolangInstallation installation = getGoInstallation(); 31 | if (installation != null) { 32 | EnvVars env = build.getEnvironment(listener); 33 | env.overrideAll(build.getBuildVariables()); 34 | 35 | // Get the Go version for this node, installing it if necessary 36 | installation = installation.forNode(Computer.currentComputer().getNode(), listener).forEnvironment(env); 37 | } 38 | 39 | // Apply the GOROOT and go binaries to PATH 40 | final GolangInstallation install = installation; 41 | return new Environment() { 42 | @Override 43 | public void buildEnvVars(Map env) { 44 | if (install != null) { 45 | EnvVars envVars = new EnvVars(); 46 | install.buildEnvVars(envVars); 47 | env.putAll(envVars); 48 | } 49 | } 50 | }; 51 | } 52 | 53 | private GolangInstallation getGoInstallation() { 54 | for (GolangInstallation i : ((DescriptorImpl) getDescriptor()).getInstallations()) { 55 | if (i.getName().equals(goVersion)) { 56 | return i; 57 | } 58 | } 59 | return null; 60 | } 61 | 62 | @Extension 63 | public static class DescriptorImpl extends BuildWrapperDescriptor { 64 | 65 | @CopyOnWrite 66 | private volatile GolangInstallation[] installations = new GolangInstallation[0]; 67 | 68 | public DescriptorImpl() { 69 | load(); 70 | } 71 | 72 | @Override 73 | public String getDisplayName() { 74 | return Messages.SetUpGoTools(); 75 | } 76 | 77 | @Override 78 | public boolean isApplicable(AbstractProject item) { 79 | return true; 80 | } 81 | 82 | @SuppressFBWarnings("EI_EXPOSE_REP") 83 | public GolangInstallation[] getInstallations() { 84 | return installations; 85 | } 86 | 87 | public void setInstallations(GolangInstallation... installations) { 88 | this.installations = installations; 89 | save(); 90 | } 91 | 92 | } 93 | 94 | } 95 | 96 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | org.jenkins-ci.plugins 6 | plugin 7 | 4.14 8 | 9 | 10 | org.jenkins-ci.plugins 11 | golang 12 | 1.5-SNAPSHOT 13 | hpi 14 | 15 | 16 | 2.190.3 17 | 2.190.x 18 | 16 19 | 8 20 | 21 | 22 | Go Plugin 23 | https://github.com/jenkinsci/golang-plugin 24 | Automatically installs and sets up the Go programming language (golang) tools for a build. 25 | 26 | 27 | orrc 28 | Christopher Orr 29 | chris@orr.me.uk 30 | 31 | 32 | 33 | 34 | 35 | MIT License 36 | http://opensource.org/licenses/MIT 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.jenkins-ci.plugins 44 | structs 45 | 1.20 46 | 47 | 48 | 49 | 50 | org.jenkins-ci.plugins.workflow 51 | workflow-aggregator 52 | 2.6 53 | test 54 | 55 | 56 | 57 | 58 | org.jenkins-ci.plugins 59 | mock-slave 60 | 1.14 61 | test 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | io.jenkins.tools.bom 70 | bom-${jenkins.bom.artifactId} 71 | ${jenkins.bom.version} 72 | import 73 | pom 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | repo.jenkins-ci.org 82 | https://repo.jenkins-ci.org/public/ 83 | 84 | 85 | 86 | 87 | 88 | repo.jenkins-ci.org 89 | https://repo.jenkins-ci.org/public/ 90 | 91 | 92 | 93 | 94 | scm:git:git://github.com/jenkinsci/golang-plugin.git 95 | scm:git:git@github.com:jenkinsci/golang-plugin.git 96 | https://github.com/jenkinsci/golang-plugin 97 | HEAD 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go programming language plugin for Jenkins 2 | 3 | [![Jenkins plugin](https://img.shields.io/jenkins/plugin/v/golang.svg)](https://plugins.jenkins.io/golang) 4 | [![Jenkins plugin installs](https://img.shields.io/jenkins/plugin/i/golang?color=blue)](https://plugins.jenkins.io/golang) 5 | [![Build status](https://ci.jenkins.io/job/Plugins/job/golang-plugin/job/master/badge/icon)](https://ci.jenkins.io/job/Plugins/job/golang-plugin/job/master/) 6 | 7 | Automatically installs and sets up the [Go programming language](http://golang.org/) (golang) tools on a Jenkins agent during a build. 8 | 9 | ## Functionality 10 | During a build, this plugin can: 11 | - Install a particular version of Go on the agent that the build is running on 12 | - The correct package for the machine's operating system and CPU architecture will be automatically downloaded and installed, if not already present 13 | - Export the `GOROOT` environment variable, pointing to the installed Go tools 14 | - Add the path `$GOROOT/bin` as a prefix of the `PATH`, so that the tools are available during the build 15 | 16 | ## Usage 17 | Once this plugin is installed, you must first configure which Go version(s) you need for your Jenkins jobs, and then configure any jobs that need Go as appropriate. 18 | 19 | ### Global configuration 20 | 1. In the Global Tool Configuration (Manage Jenkins → Global Tool Configuration), find the "Go" section, click "Go Installations…" and "Add Go". 21 | 2. Enter a name, e.g. "1.19" — the name itself has no significance, but it's what you'll need to provide for a Pipeline, or will be displayed to users during Freestyle job configuration 22 | 3. Either select "Install automatically" and pick the desired Go version from the drop-down list, or specify another installation method 23 | 24 | ### Per-job configuration 25 | #### Declarative Pipeline 26 | You can use the [`tools` directive](https://www.jenkins.io/doc/book/pipeline/syntax/#tools) within any `pipeline` or `stage`. For example: 27 | 28 | ```groovy 29 | pipeline { 30 | // Run on an agent where we want to use Go 31 | agent any 32 | 33 | // Ensure the desired Go version is installed for all stages, 34 | // using the name defined in the Global Tool Configuration 35 | tools { go '1.19' } 36 | 37 | stages { 38 | stage('Build') { 39 | steps { 40 | // Output will be something like "go version go1.19 darwin/arm64" 41 | sh 'go version' 42 | } 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | #### Scripted Pipeline 49 | You will need to grab the installation directory from the [`tool` step](https://www.jenkins.io/doc/pipeline/steps/workflow-basic-steps/#tool-use-a-tool-from-a-predefined-tool-installation), and ensure that the correct `GOROOT` and `PATH` are set. For example: 50 | 51 | ```groovy 52 | // Run on an agent where we want to use Go 53 | node { 54 | // Ensure the desired Go version is installed on this agent, 55 | // using the name defined in the Global Tool Configuration 56 | def root = tool type: 'go', name: '1.19' 57 | 58 | // Export environment variables to pointing the Go installation; 59 | // the `PATH+X` syntax prepends an item to the existing `PATH`: 60 | // https://jenkins.io/doc/pipeline/steps/workflow-basic-steps/#withenv-set-environment-variables 61 | withEnv(["GOROOT=${root}", "PATH+GO=${root}/bin"]) { 62 | // Output will be something like "go version go1.19 darwin/arm64" 63 | sh 'go version' 64 | } 65 | } 66 | ``` 67 | 68 | #### Freestyle 69 | 1. In a job's configuration, find the "Build environment" section 70 | 2. Select the "Set up Go programming language tools" checkbox 71 | 3. Select the name of a Go installation from the drop-down 72 | 73 | ## Changelog 74 | See [CHANGELOG.md](https://github.com/jenkinsci/golang-plugin/blob/master/CHANGELOG.md). 75 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/golang/GolangInstaller.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.golang; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 5 | import hudson.Extension; 6 | import hudson.FilePath; 7 | import hudson.model.DownloadService; 8 | import hudson.model.Node; 9 | import hudson.model.TaskListener; 10 | import hudson.tools.DownloadFromUrlInstaller; 11 | import hudson.tools.ToolInstallation; 12 | import hudson.tools.ToolInstallerDescriptor; 13 | import hudson.util.VersionNumber; 14 | import jenkins.security.MasterToSlaveCallable; 15 | import net.sf.json.JSONObject; 16 | import org.kohsuke.stapler.DataBoundConstructor; 17 | 18 | import java.io.IOException; 19 | import java.net.URL; 20 | import java.util.Arrays; 21 | import java.util.Collections; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Locale; 25 | import java.util.Map; 26 | 27 | /** Installs the Go programming language tools by downloading the archive for the detected OS/architecture combo. */ 28 | public class GolangInstaller extends DownloadFromUrlInstaller { 29 | 30 | @DataBoundConstructor 31 | public GolangInstaller(String id) { 32 | super(id); 33 | } 34 | 35 | // This is essentially the parent implementation, but we override it so we can pass Node into getInstallable() 36 | public FilePath performInstallation(ToolInstallation tool, Node node, TaskListener log) throws IOException, 37 | InterruptedException { 38 | FilePath expectedPath = preferredLocation(tool, node); 39 | 40 | Installable installable; 41 | try { 42 | installable = getInstallable(node); 43 | } catch (InstallationFailedException e) { 44 | throw new InstallationFailedException(Messages.CouldNotInstallGo(e.getMessage())); 45 | } 46 | 47 | if (installable == null) { 48 | log.getLogger().println(Messages.UnrecognisedReleaseId(id)); 49 | return expectedPath; 50 | } 51 | 52 | if (isUpToDate(expectedPath, installable)) { 53 | return expectedPath; 54 | } 55 | 56 | String message = Messages.InstallingGoOnNode(installable.url, expectedPath, node.getDisplayName()); 57 | if (expectedPath.installIfNecessaryFrom(new URL(installable.url), log, message)) { 58 | expectedPath.child(".timestamp").delete(); // we don't use the timestamp 59 | FilePath base = findPullUpDirectory(expectedPath); 60 | if (base != null && base != expectedPath) 61 | base.moveAllChildrenTo(expectedPath); 62 | // leave a record for the next up-to-date check 63 | expectedPath.child(".installedFrom").write(installable.url, "UTF-8"); 64 | } 65 | 66 | return expectedPath; 67 | } 68 | 69 | @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") 70 | private Installable getInstallable(Node node) throws IOException, InterruptedException { 71 | // Get the Go release that we want to install 72 | GolangRelease release = getConfiguredRelease(); 73 | if (release == null) { 74 | return null; 75 | } 76 | 77 | // Gather properties for the node to install on 78 | String[] properties = node.getChannel().call(new GetSystemProperties("os.name", "os.arch", "os.version")); 79 | 80 | // Get the best matching install candidate for this node 81 | return getInstallCandidate(release, properties[0], properties[1], properties[2]); 82 | } 83 | 84 | @VisibleForTesting 85 | static GolangInstallable getInstallCandidate(GolangRelease release, String osName, String osArch, String osVersion) 86 | throws InstallationFailedException { 87 | String platform = getPlatform(osName); 88 | String architecture = getArchitecture(osArch); 89 | 90 | // Sort and search for an appropriate variant 91 | List variants = Arrays.asList(release.variants); 92 | Collections.sort(variants); 93 | for (GolangInstallable i : variants) { 94 | if (i.os.equals(platform) && i.arch.equals(architecture)) { 95 | if (i.osxversion == null) { 96 | return i; 97 | } 98 | if (new VersionNumber(osVersion).compareTo(new VersionNumber(i.osxversion)) >= 0) { 99 | return i; 100 | } 101 | } 102 | } 103 | 104 | String osWithVersion = osVersion == null ? osName : String.format("%s %s", osName, osVersion); 105 | throw new InstallationFailedException(Messages.NoInstallerForOs(release.name, osWithVersion, osArch)); 106 | } 107 | 108 | private GolangRelease getConfiguredRelease() { 109 | List releases = ((DescriptorImpl) getDescriptor()).getInstallableReleases(); 110 | if (releases == null) { 111 | return null; 112 | } 113 | 114 | for (GolangRelease r : releases) { 115 | if (r.id.equals(id)) { 116 | return r; 117 | } 118 | } 119 | return null; 120 | } 121 | 122 | @Extension 123 | public static final class DescriptorImpl extends ToolInstallerDescriptor { 124 | public String getDisplayName() { 125 | return Messages.InstallFromWebsite(); 126 | } 127 | 128 | // Used by config.groovy to show a human-readable list of releases 129 | @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") 130 | public List getInstallableReleases() { 131 | return GolangReleaseList.all().get(GolangReleaseList.class).toList(); 132 | } 133 | 134 | @Override 135 | public boolean isApplicable(Class toolType) { 136 | return toolType == GolangInstallation.class; 137 | } 138 | } 139 | 140 | @Extension 141 | public static final class GolangReleaseList extends DownloadService.Downloadable { 142 | 143 | // Public for JSON deserialisation 144 | public List releases; 145 | 146 | public GolangReleaseList() { 147 | super(GolangInstaller.class); 148 | } 149 | 150 | /** @return A list of available Go releases, obtained by parsing the JSON received from the update centre. */ 151 | public List toList() { 152 | JSONObject root; 153 | try { 154 | root = getData(); 155 | if (root == null) { 156 | // JSON file has not yet been downloaded by Jenkins 157 | return null; 158 | } 159 | } catch (IOException e) { 160 | // JSON parsing exception occurred 161 | return null; 162 | } 163 | 164 | Map classMap = new HashMap(); 165 | classMap.put("releases", GolangRelease.class); 166 | classMap.put("variants", GolangInstallable.class); 167 | return ((GolangReleaseList) JSONObject.toBean(root, GolangReleaseList.class, classMap)).releases; 168 | } 169 | } 170 | 171 | // Needs to be public for JSON deserialisation 172 | public static class GolangRelease { 173 | public String id; 174 | public String name; 175 | public GolangInstallable[] variants; 176 | } 177 | 178 | // Needs to be public for JSON deserialisation 179 | public static class GolangInstallable extends Installable implements Comparable { 180 | public String os; 181 | public String osxversion; 182 | public String arch; 183 | 184 | @SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUALS") 185 | public int compareTo(GolangInstallable o) { 186 | // Sort by OS X version, descending 187 | if (osxversion != null && o.osxversion != null) { 188 | return new VersionNumber(o.osxversion).compareTo(new VersionNumber(osxversion)); 189 | } 190 | // Otherwise we don't really care; sort by OS name 191 | return os.compareTo(o.os); 192 | } 193 | 194 | @Override 195 | public String toString() { 196 | return String.format("GolangInstallable[os=%s, arch=%s, version=%s]", os, arch, osxversion); 197 | } 198 | } 199 | 200 | /** @return The OS value used in a Go archive filename, for the given {@code os.name} value. */ 201 | private static String getPlatform(String os) throws InstallationFailedException { 202 | String value = os.toLowerCase(Locale.ENGLISH); 203 | if (value.contains("freebsd")) { 204 | return "freebsd"; 205 | } 206 | if (value.contains("linux")) { 207 | return "linux"; 208 | } 209 | if (value.contains("os x")) { 210 | return "darwin"; 211 | } 212 | if (value.contains("windows")) { 213 | return "windows"; 214 | } 215 | throw new InstallationFailedException(Messages.UnsupportedOs(os)); 216 | } 217 | 218 | /** @return The CPU architecture value used in a Go archive filename, for the given {@code os.arch} value. */ 219 | private static String getArchitecture(String arch) throws InstallationFailedException { 220 | String value = arch.toLowerCase(Locale.ENGLISH); 221 | if (value.contains("amd64") || value.contains("86_64")) { 222 | return "amd64"; 223 | } 224 | // There seems to be no canonical documentation on what the `os.arch` values are for different ARM CPU types, 225 | // and presumably there's no real guarantee of consistency between JVM vendors anyway. 226 | // It _seems_ to be `aarch64` and `arm` for 64- and 32-bit, but let's also have some reasonable fallbacks… 227 | if (value.contains("aarch64") || value.contains("arm64")) { 228 | return "arm64"; 229 | } 230 | if (value.contains("aarch32") || value.contains("arm")) { 231 | return "armv6l"; 232 | } 233 | if (value.contains("86")) { 234 | return "386"; 235 | } 236 | if (value.contains("s390x")) { 237 | return "s390x"; 238 | } 239 | throw new InstallationFailedException(Messages.UnsupportedCpuArch(arch)); 240 | } 241 | 242 | /** Returns the values of the given Java system properties. */ 243 | private static class GetSystemProperties extends MasterToSlaveCallable { 244 | private static final long serialVersionUID = 1L; 245 | 246 | private final String[] properties; 247 | 248 | GetSystemProperties(String... properties) { 249 | this.properties = properties; 250 | } 251 | 252 | public String[] call() { 253 | String[] values = new String[properties.length]; 254 | for (int i = 0; i < properties.length; i++) { 255 | values[i] = System.getProperty(properties[i]); 256 | } 257 | return values; 258 | } 259 | } 260 | 261 | // Extend IOException so we can throw and stop the build if installation fails 262 | static class InstallationFailedException extends IOException { 263 | InstallationFailedException(String message) { 264 | super(message); 265 | } 266 | } 267 | 268 | } 269 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/golang/GolangInstallerTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.golang; 2 | 3 | import org.junit.Test; 4 | 5 | import javax.annotation.Nullable; 6 | 7 | import static org.jenkinsci.plugins.golang.GolangInstaller.GolangInstallable; 8 | import static org.jenkinsci.plugins.golang.GolangInstaller.GolangRelease; 9 | import static org.jenkinsci.plugins.golang.GolangInstaller.InstallationFailedException; 10 | import static org.junit.Assert.assertEquals; 11 | 12 | public class GolangInstallerTest { 13 | 14 | // These are definitions of available Go packages, as would be listed in the Jenkins installer JSON 15 | private static final GolangInstallable FREEBSD_32 = createPackage("freebsd", "386"); 16 | private static final GolangInstallable FREEBSD_64 = createPackage("freebsd", "amd64"); 17 | private static final GolangInstallable LINUX_32 = createPackage("linux", "386"); 18 | private static final GolangInstallable LINUX_64 = createPackage("linux", "amd64"); 19 | private static final GolangInstallable LINUX_ARM32 = createPackage("linux", "armv6l"); 20 | private static final GolangInstallable LINUX_ARM64 = createPackage("linux", "arm64"); 21 | private static final GolangInstallable OS_X_10_6_32 = createPackage("darwin", "386", "10.6"); 22 | private static final GolangInstallable OS_X_10_6_64 = createPackage("darwin", "amd64", "10.6"); 23 | private static final GolangInstallable OS_X_10_8_32 = createPackage("darwin", "386", "10.8"); 24 | private static final GolangInstallable OS_X_10_8_64 = createPackage("darwin", "amd64", "10.8"); 25 | // As of Go 1.5, there is only a single OS X build distributed, with no OS X version set 26 | private static final GolangInstallable OS_X_GO_1_5 = createPackage("darwin", "amd64"); 27 | 28 | @Test(expected = InstallationFailedException.class) 29 | public void testUnsupportedOs() throws InstallationFailedException { 30 | // Given we have configured a release we want to install 31 | GolangRelease release = createReleaseInfo(); 32 | 33 | // When we try to get the install package for an OS which is not supported 34 | GolangInstaller.getInstallCandidate(release, "Android", "armv7a", null); 35 | 36 | // Then an exception should be thrown 37 | } 38 | 39 | @Test 40 | public void testFreeBsdInstallation() throws InstallationFailedException { 41 | // Given we have configured a release we want to install 42 | GolangRelease release = createReleaseInfo(); 43 | 44 | // When we try to get the install package for FreeBSD 45 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "FreeBSD", "amd64", "10.2-RELEASE"); 46 | 47 | // Then we should get the correct FreeBSD package 48 | assertEquals("Got unexpected package", FREEBSD_64, pkg); 49 | } 50 | 51 | @Test 52 | public void testFreeBsd32BitInstallation() throws InstallationFailedException { 53 | // Given we have configured a release we want to install 54 | GolangRelease release = createReleaseInfo(); 55 | 56 | // When we try to get the install package for FreeBSD 57 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "FreeBSD", "i386", "10.2-RELEASE"); 58 | 59 | // Then we should get the correct FreeBSD package 60 | assertEquals("Got unexpected package", FREEBSD_32, pkg); 61 | } 62 | 63 | @Test(expected = InstallationFailedException.class) 64 | public void testUnsupportedFreeBsd32BitInstallation() throws InstallationFailedException { 65 | // Given we have configured a Go 1.5 release we want to install 66 | GolangRelease release = createReleaseInfo(OS_X_GO_1_5, FREEBSD_64, LINUX_32, LINUX_64, LINUX_ARM64); 67 | 68 | // When we try to get the install package for 32-bit FreeBSD 69 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "FreeBSD", "i386", "10.2-RELEASE"); 70 | 71 | // Then an exception should be thrown, as there is no 32-bit package 72 | } 73 | 74 | @Test 75 | public void testLinuxArm32BitInstallation() throws InstallationFailedException { 76 | // Given we have configured a release we want to install 77 | GolangRelease release = createReleaseInfo(); 78 | 79 | // When we try to get the install package for Linux ARM 32-bit (aka `arm`) 80 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "linux", "arm", "5.4.0"); 81 | 82 | // Then we should get the correct Linux ARM 32-bit package 83 | assertEquals("Got unexpected package", LINUX_ARM32, pkg); 84 | } 85 | 86 | @Test 87 | public void testLinuxArm32BitInstallationAlias() throws InstallationFailedException { 88 | // Given we have configured a release we want to install 89 | GolangRelease release = createReleaseInfo(); 90 | 91 | // When we try to get the install package for Linux ARM 32-bit, with the CPU value `aarch32` 92 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "linux", "aarch32", "5.4.0"); 93 | 94 | // Then we should get the correct Linux ARM 32-bit package 95 | assertEquals("Got unexpected package", LINUX_ARM32, pkg); 96 | } 97 | 98 | @Test 99 | public void testLinuxAarch64BitInstallation() throws InstallationFailedException { 100 | // Given we have configured a release we want to install 101 | GolangRelease release = createReleaseInfo(); 102 | 103 | // When we try to get the install package for Linux ARM 64-bit (aka `aarch64`) 104 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "linux", "aarch64", "5.4.0"); 105 | 106 | // Then we should get the correct Linux ARM 64-bit package 107 | assertEquals("Got unexpected package", LINUX_ARM64, pkg); 108 | } 109 | 110 | @Test 111 | public void testLinuxArm64BitInstallationAlias() throws InstallationFailedException { 112 | // Given we have configured a release we want to install 113 | GolangRelease release = createReleaseInfo(); 114 | 115 | // When we try to get the install package for Linux ARM 64-bit, with the CPU value `arm64` 116 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "linux", "arm64", "5.4.0"); 117 | 118 | // Then we should get the correct Linux ARM 64-bit package 119 | assertEquals("Got unexpected package", LINUX_ARM64, pkg); 120 | } 121 | 122 | @Test 123 | public void testLatestGo15PackageForOsXVersionReturned() throws InstallationFailedException { 124 | // Given we have configured a Go 1.5 release we want to install 125 | GolangRelease release = createReleaseInfo(LINUX_32, LINUX_64, LINUX_ARM64, OS_X_GO_1_5); 126 | 127 | // When we try to get the install package for an OS X version 128 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "Mac OS X", "x86_64", "10.11.12"); 129 | 130 | // Then we should get the sole OS X package 131 | assertEquals("Got unexpected package", OS_X_GO_1_5, pkg); 132 | } 133 | 134 | @Test 135 | public void testLatestPackageForOsXVersionReturned() throws InstallationFailedException { 136 | // Given we have configured a release we want to install 137 | GolangRelease release = createReleaseInfo(); 138 | 139 | // When we try to get the install package for a much newer OS X version 140 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "Mac OS X", "x86_64", "10.11.12"); 141 | 142 | // Then we should get the newest package which is older than our version 143 | assertEquals("Got unexpected package", OS_X_10_8_64, pkg); 144 | } 145 | 146 | @Test 147 | public void testLatestPackageFor32BitOsXVersionReturned() throws InstallationFailedException { 148 | // Given we have configured a release we want to install 149 | GolangRelease release = createReleaseInfo(); 150 | 151 | // When we try to get the install package for a much newer 32-bit OS X version 152 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "Mac OS X", "i386", "10.11.12"); 153 | 154 | // Then we should get the newest package which is older than our version 155 | assertEquals("Got unexpected package", OS_X_10_8_32, pkg); 156 | } 157 | 158 | @Test 159 | public void testEarlierPackageForOsXVersionReturned() throws InstallationFailedException { 160 | // Given we have configured a release we want to install 161 | GolangRelease release = createReleaseInfo(); 162 | 163 | // When we try to get the install package for an older, but supported OS X version 164 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "Mac OS X", "x86_64", "10.7"); 165 | 166 | // Then we should get the newest package which is older than our version 167 | assertEquals("Got unexpected package", OS_X_10_6_64, pkg); 168 | } 169 | 170 | @Test 171 | public void testEarlierPackageFor32BitOsXVersionReturned() throws InstallationFailedException { 172 | // Given we have configured a release we want to install 173 | GolangRelease release = createReleaseInfo(); 174 | 175 | // When we try to get the install package for an older, but supported 32-bit OS X version 176 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "Mac OS X", "i386", "10.7"); 177 | 178 | // Then we should get the newest 32-bit package which is older than our version 179 | assertEquals("Got unexpected package", OS_X_10_6_32, pkg); 180 | } 181 | 182 | @Test 183 | public void testExactMatchPackageForOsXVersionReturned() throws InstallationFailedException { 184 | // Given we have configured a release we want to install 185 | GolangRelease release = createReleaseInfo(); 186 | 187 | // When we try to get a install package which has an exact match on OS X version 188 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "Mac OS X", "x86_64", "10.6"); 189 | 190 | // Then we should get the package which matches the given version exactly 191 | assertEquals("Got unexpected package", OS_X_10_6_64, pkg); 192 | } 193 | 194 | @Test(expected = InstallationFailedException.class) 195 | public void testUnsupportedOsXVersion() throws InstallationFailedException { 196 | // Given we have configured a release we want to install 197 | GolangRelease release = createReleaseInfo(); 198 | 199 | // When we try to get a install package which has an exact match on OS X version 200 | GolangInstallable pkg = GolangInstaller.getInstallCandidate(release, "Mac OS X", "x86_64", "10.5"); 201 | 202 | // Then an exception should be thrown 203 | } 204 | 205 | private static GolangRelease createReleaseInfo() { 206 | return createReleaseInfo(FREEBSD_32, FREEBSD_64, LINUX_32, LINUX_64, LINUX_ARM32, LINUX_ARM64, 207 | OS_X_10_6_32, OS_X_10_6_64, OS_X_10_8_32, OS_X_10_8_64); 208 | } 209 | 210 | private static GolangRelease createReleaseInfo(GolangInstallable... releases) { 211 | GolangRelease release = new GolangRelease(); 212 | release.variants = releases; 213 | return release; 214 | } 215 | 216 | /** 217 | * @param os The OS value used in a Go archive filename. 218 | * @param arch The CPU architecture value used in a Go archive filename. 219 | */ 220 | private static GolangInstallable createPackage(String os, String arch) { 221 | return createPackage(os, arch, null); 222 | } 223 | 224 | /** 225 | * @param os The OS value used in a Go archive filename. 226 | * @param arch The CPU architecture value used in a Go archive filename. 227 | * @param osxVersion Mac OS X version name. 228 | */ 229 | private static GolangInstallable createPackage(String os, String arch, @Nullable String osxVersion) { 230 | GolangInstallable pkg = new GolangInstallable(); 231 | pkg.os = os; 232 | pkg.arch = arch; 233 | pkg.osxversion = osxVersion; 234 | return pkg; 235 | } 236 | 237 | } 238 | --------------------------------------------------------------------------------