Skip to content
This repository was archived by the owner on Mar 27, 2025. It is now read-only.

Commit 0b31a13

Browse files
authored
Merge pull request #364 from mathworks/mpm_tools
Support for installing MATLAB using MPM through Jenkins tools
2 parents 2640d54 + eb93324 commit 0b31a13

17 files changed

+526
-35
lines changed

azure-pipelines.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ trigger:
1919
- master
2020

2121
steps:
22+
- task: JavaToolInstaller@0
23+
inputs:
24+
versionSpec: '11'
25+
jdkArchitectureOption: 'x64'
26+
jdkSourceOption: 'PreInstalled'
27+
2228
- task: Maven@4
2329
inputs:
2430
mavenPomFile: 'pom.xml'

src/main/java/com/mathworks/ci/MatlabInstallation.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public MatlabInstallation forNode(@Nonnull Node node, TaskListener log) throws I
5858
@Override
5959
public void buildEnvVars(EnvVars env) {
6060
String pathToExecutable = getHome() + "/bin";
61+
env.put ("PATH+matlab_batch", getHome ());
6162
env.put("PATH+matlabroot", pathToExecutable);
6263
}
6364

src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,21 +188,43 @@ public void setUp(Context context, Run<?, ?> build, FilePath workspace, Launcher
188188
// Set Environment variable
189189
setEnv(initialEnvironment);
190190

191-
FilePath matlabExecutablePath = new FilePath(launcher.getChannel(),
192-
getNodeSpecificMatlab(Computer.currentComputer(), listener) + getNodeSpecificExecutable(launcher));
193191

192+
String nodeSpecificMatlab = getNodeSpecificMatlab(Computer.currentComputer(), listener) + getNodeSpecificExecutable(launcher);
193+
FilePath matlabExecutablePath = new FilePath(launcher.getChannel(), nodeSpecificMatlab);
194+
listener.getLogger().println("**The path which is trying to uppend is **" + matlabExecutablePath.getRemote ());
194195
if (!matlabExecutablePath.exists()) {
195196
throw new MatlabNotFoundError(Message.getValue("matlab.not.found.error"));
196197
}
198+
// Add matlab-batch executable in path
199+
FilePath batchExecutable = getNthParentFilePath(matlabExecutablePath, 3);
200+
if(batchExecutable != null && batchExecutable.exists ()){
201+
context.env("PATH+matlab_batch", batchExecutable.getRemote ());
202+
}
203+
197204
// Add "matlabroot" without bin as env variable which will be available across the build.
198-
context.env("matlabroot", getNodeSpecificMatlab(Computer.currentComputer(), listener));
205+
context.env("matlabroot", nodeSpecificMatlab);
199206
// Add matlab bin to path to invoke MATLAB directly on command line.
200-
context.env("PATH+matlabroot", matlabExecutablePath.getParent().getRemote());
201-
// Specify which MATLAB was added to path.
207+
context.env("PATH+matlabroot", matlabExecutablePath.getParent().getRemote());;
202208
listener.getLogger().println("\n" + String.format(Message.getValue("matlab.added.to.path.from"), matlabExecutablePath.getParent().getRemote()) + "\n");
203209
}
204210

205211
private String getNodeSpecificExecutable(Launcher launcher) {
206212
return (launcher.isUnix()) ? "/bin/matlab" : "\\bin\\matlab.exe";
207213
}
214+
215+
public static FilePath getNthParentFilePath (FilePath path, int levels) {
216+
if (path == null || levels < 0) {
217+
return null;
218+
}
219+
220+
FilePath currentPath = path;
221+
for (int i = 0; i < levels; i++) {
222+
if (currentPath == null) {
223+
return null;
224+
}
225+
currentPath = currentPath.getParent ();
226+
}
227+
return currentPath;
228+
}
229+
208230
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.mathworks.ci.tools;
2+
3+
/**
4+
* Copyright 2024, The MathWorks, Inc.
5+
*
6+
*/
7+
8+
import java.io.IOException;
9+
10+
// Extend IOException so we can throw and stop the build if installation fails
11+
12+
public class InstallationFailedException extends IOException {
13+
14+
InstallationFailedException (String message) {
15+
super (message);
16+
}
17+
}
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
package com.mathworks.ci.tools;
2+
/**
3+
* Copyright 2024, The MathWorks, Inc.
4+
*
5+
*/
6+
7+
8+
import com.mathworks.ci.MatlabInstallation;
9+
import com.mathworks.ci.Message;
10+
import com.mathworks.ci.utilities.GetSystemProperties;
11+
12+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
13+
14+
import hudson.Extension;
15+
import hudson.FilePath;
16+
import hudson.Launcher;
17+
import hudson.Launcher.ProcStarter;
18+
19+
import hudson.model.Node;
20+
import hudson.model.TaskListener;
21+
import hudson.tools.ToolInstaller;
22+
import hudson.tools.ToolInstallation;
23+
import hudson.tools.ToolInstallerDescriptor;
24+
25+
import hudson.util.ArgumentListBuilder;
26+
import hudson.util.FormValidation;
27+
28+
import java.io.BufferedReader;
29+
import java.io.IOException;
30+
import java.io.InputStreamReader;
31+
import java.net.URL;
32+
33+
import java.nio.charset.StandardCharsets;
34+
35+
import java.util.Arrays;
36+
import java.util.HashSet;
37+
import java.util.Locale;
38+
39+
import java.util.Set;
40+
import jenkins.model.Jenkins;
41+
import org.apache.commons.io.IOUtils;
42+
43+
import org.kohsuke.stapler.DataBoundConstructor;
44+
import org.kohsuke.stapler.DataBoundSetter;
45+
import org.kohsuke.stapler.QueryParameter;
46+
import org.kohsuke.stapler.verb.POST;
47+
48+
public class MatlabInstaller extends ToolInstaller {
49+
50+
private String release;
51+
private String products;
52+
private static String DEFAULT_PRODUCT = "MATLAB";
53+
54+
@DataBoundConstructor
55+
public MatlabInstaller(String id) {
56+
super(id);
57+
}
58+
59+
public String getRelease() {
60+
return this.release;
61+
}
62+
63+
@DataBoundSetter
64+
public void setVersion(String release) {
65+
this.release = release;
66+
}
67+
68+
public String getProducts() {
69+
return this.products;
70+
}
71+
72+
@DataBoundSetter
73+
public void setProducts(String products) {
74+
this.products = products;
75+
}
76+
77+
@Override
78+
public FilePath performInstallation(ToolInstallation tool, Node node, TaskListener log)
79+
throws IOException, InterruptedException {
80+
FilePath destination = preferredLocation(tool, node);
81+
String[] systemProperties = getSystemProperties(node);
82+
FilePath matlabRootPath;
83+
if(systemProperties[0].toLowerCase().contains("os x")) {
84+
matlabRootPath= new FilePath(destination, this.getRelease()+".app");
85+
} else {
86+
matlabRootPath = new FilePath(destination, this.getRelease());
87+
}
88+
String platform = getPlatform(systemProperties[0], systemProperties[1]);
89+
getFreshCopyOfExecutables(platform, destination);
90+
91+
makeDir(matlabRootPath);
92+
int result = installUsingMpm(node, this.getRelease (), matlabRootPath, this.getProducts (), log);
93+
if (result != 0) {
94+
throw new InstallationFailedException("Unable to install MATLAB using mpm.");
95+
}
96+
return matlabRootPath;
97+
}
98+
99+
private int installUsingMpm(Node node, String release, FilePath destination, String products, TaskListener log)
100+
throws IOException, InterruptedException {
101+
102+
Launcher matlabInstaller = node.createLauncher(log);
103+
ProcStarter installerProc = matlabInstaller.launch ();
104+
105+
ArgumentListBuilder args = new ArgumentListBuilder();
106+
args.add(destination.getParent().getRemote() + getNodeSpecificMPMExecutor(node));
107+
args.add("install");
108+
appendReleaseToArguments(release,args, log);
109+
args.add("--destination=" + destination.getRemote());
110+
addMatlabProductsToArgs(args, products);
111+
installerProc.pwd(destination).cmds(args).stdout(log);
112+
int result;
113+
try {
114+
result = installerProc.join();
115+
} catch (Exception e) {
116+
log.getLogger().println("MATLAB installation failed " + e.getMessage());
117+
throw new InstallationFailedException(e.getMessage ());
118+
}
119+
return result;
120+
}
121+
122+
123+
private void makeDir(FilePath path) throws IOException, InterruptedException {
124+
if(!path.exists()){
125+
path.mkdirs();
126+
path.chmod(0777);
127+
}
128+
}
129+
130+
private void appendReleaseToArguments(String release, ArgumentListBuilder args, TaskListener log) {
131+
String trimmedRelease = release.trim();
132+
String actualRelease = trimmedRelease;
133+
134+
if (trimmedRelease.equalsIgnoreCase("latest") || trimmedRelease.equalsIgnoreCase(
135+
"latest-including-prerelease")) {
136+
String releaseInfoUrl =
137+
Message.getValue("matlab.release.info.url") + trimmedRelease;
138+
String releaseVersion = null;
139+
try {
140+
releaseVersion = IOUtils.toString(new URL(releaseInfoUrl),
141+
StandardCharsets.UTF_8).trim();
142+
} catch (IOException e) {
143+
log.getLogger().println("Failed to fetch release version: " + e.getMessage());
144+
}
145+
146+
if (releaseVersion != null && releaseVersion.contains("prerelease")) {
147+
actualRelease = releaseVersion.replace("prerelease", "");
148+
args.add ("--release-status=Prerelease");
149+
} else {
150+
actualRelease = releaseVersion;
151+
}
152+
}
153+
args.add("--release=" + actualRelease);
154+
}
155+
156+
private void getFreshCopyOfExecutables(String platform, FilePath expectedPath)
157+
throws IOException, InterruptedException {
158+
FilePath matlabBatchPath = new FilePath(expectedPath, "matlab-batch");
159+
FilePath mpmPath = new FilePath(expectedPath, "mpm");
160+
161+
URL mpmUrl;
162+
URL matlabBatchUrl;
163+
164+
switch (platform) {
165+
case "glnxa64":
166+
mpmUrl = new URL(Message.getValue("tools.matlab.mpm.installer.linux"));
167+
matlabBatchUrl = new URL(Message.getValue("tools.matlab.batch.executable.linux"));
168+
break;
169+
case "maci64":
170+
mpmUrl = new URL(Message.getValue("tools.matlab.mpm.installer.maci64"));
171+
matlabBatchUrl = new URL(Message.getValue("tools.matlab.batch.executable.maci64"));
172+
break;
173+
case "maca64":
174+
mpmUrl = new URL(Message.getValue("tools.matlab.mpm.installer.maca64"));
175+
matlabBatchUrl = new URL(Message.getValue("tools.matlab.batch.executable.maca64"));
176+
break;
177+
default:
178+
throw new InstallationFailedException("Unsupported OS");
179+
}
180+
181+
mpmPath.copyFrom(mpmUrl.openStream());
182+
mpmPath.chmod(0777);
183+
matlabBatchPath.copyFrom(matlabBatchUrl.openStream());
184+
matlabBatchPath.chmod(0777);
185+
}
186+
187+
@SuppressFBWarnings(value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"},
188+
justification =
189+
"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE: Its false positive scenario for sport bug which is fixed in later versions "
190+
+ "https://github.com/spotbugs/spotbugs/issues/1843")
191+
private String getNodeSpecificMPMExecutor(Node node) {
192+
if (!node.toComputer().isUnix()) {
193+
return "\\mpm.exe";
194+
}
195+
return "/mpm";
196+
}
197+
198+
private void addMatlabProductsToArgs(ArgumentListBuilder args, String products)
199+
throws IOException, InterruptedException {
200+
args.add("--products");
201+
if (products.isEmpty()) {
202+
args.add(DEFAULT_PRODUCT);
203+
204+
} else {
205+
if (!products.contains(DEFAULT_PRODUCT)) {
206+
args.add(DEFAULT_PRODUCT);
207+
}
208+
String[] productList = products.split(" ");
209+
for (String prod : productList) {
210+
args.add(prod);
211+
}
212+
}
213+
}
214+
215+
public String getPlatform(String os, String architecture) throws InstallationFailedException {
216+
String value = os.toLowerCase(Locale.ENGLISH);
217+
if (value.contains("linux")) {
218+
return "glnxa64";
219+
} else if (value.contains("os x")) {
220+
if (architecture.equalsIgnoreCase("aarch64") || architecture.equalsIgnoreCase (
221+
"arm64")) {
222+
return "maca64";
223+
} else {
224+
return "maci64";
225+
}
226+
} else {
227+
throw new InstallationFailedException("Unsupported OS");
228+
}
229+
}
230+
231+
@SuppressFBWarnings(value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"},
232+
justification =
233+
"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE: Its false positive scenario for sport bug which is fixed in later versions "
234+
+ "https://github.com/spotbugs/spotbugs/issues/1843")
235+
private String[] getSystemProperties(Node node) throws IOException, InterruptedException {
236+
String[] properties = node.getChannel()
237+
.call (new GetSystemProperties("os.name", "os.arch", "os.version"));
238+
return properties;
239+
}
240+
241+
@Extension
242+
public static final class DescriptorImpl extends ToolInstallerDescriptor<MatlabInstaller> {
243+
244+
public String getDisplayName() {
245+
return Message.getValue("matlab.tools.auto.install.display.name");
246+
}
247+
248+
@Override
249+
public boolean isApplicable(Class<? extends ToolInstallation> toolType) {
250+
return toolType == MatlabInstallation.class;
251+
}
252+
253+
@POST
254+
public FormValidation doCheckRelease(@QueryParameter String value) {
255+
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
256+
if (value.isEmpty()) {
257+
return FormValidation.error(Message.getValue("tools.matlab.empty.release.error"));
258+
}
259+
return FormValidation.ok();
260+
}
261+
}
262+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.mathworks.ci.utilities;
2+
/**
3+
* Copyright 2024, The MathWorks, Inc.
4+
*
5+
*/
6+
7+
8+
import jenkins.security.MasterToSlaveCallable;
9+
10+
public class GetSystemProperties extends MasterToSlaveCallable<String[], InterruptedException> {
11+
12+
private static final long serialVersionUID = 1L;
13+
14+
private final String[] properties;
15+
16+
public GetSystemProperties (String... properties) {
17+
this.properties = properties;
18+
}
19+
20+
public String[] call () {
21+
String[] values = new String[properties.length];
22+
for (int i = 0; i < properties.length; i++) {
23+
values[i] = System.getProperty (properties[i]);
24+
}
25+
return values;
26+
}
27+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?jelly escape-by-default='true'?>
2+
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
3+
<f:entry title="Release " field="release">
4+
<f:textbox />
5+
</f:entry>
6+
<f:entry title="Products" field="products">
7+
<f:textbox />
8+
</f:entry>
9+
</j:jelly>

0 commit comments

Comments
 (0)