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

Commit c577b12

Browse files
Run build (#231)
initial Run MATLAB Build implementation
1 parent 1f653b9 commit c577b12

File tree

14 files changed

+1032
-1
lines changed

14 files changed

+1032
-1
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Jenkins development server folders
2+
target
3+
work
4+
5+
# Binaries and downloaded resources
6+
src/main/resources/matlab-script-generator.zip
7+
src/main/resources/run_matlab_command.*
8+
src/main/resources/license.txt
9+
10+
**/.DS_Store
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.mathworks.ci;
2+
3+
/**
4+
* Copyright 2022 The MathWorks, Inc.
5+
*
6+
*/
7+
8+
import java.io.IOException;
9+
import org.jenkinsci.plugins.workflow.steps.StepContext;
10+
import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution;
11+
import hudson.EnvVars;
12+
import hudson.FilePath;
13+
import hudson.Launcher;
14+
import hudson.Launcher.ProcStarter;
15+
import hudson.model.Result;
16+
import hudson.model.TaskListener;
17+
18+
public class MatlabBuildStepExecution extends SynchronousNonBlockingStepExecution<Void> implements MatlabBuild {
19+
20+
private static final long serialVersionUID = 4771831219402275744L;
21+
22+
private String tasks;
23+
24+
public MatlabBuildStepExecution(StepContext context, String tasks) {
25+
super(context);
26+
this.tasks = tasks;
27+
}
28+
29+
private String getTasks() {
30+
return this.tasks;
31+
}
32+
33+
@Override
34+
public Void run() throws Exception {
35+
final Launcher launcher = getContext().get(Launcher.class);
36+
final FilePath workspace = getContext().get(FilePath.class);
37+
final TaskListener listener = getContext().get(TaskListener.class);
38+
final EnvVars env = getContext().get(EnvVars.class);
39+
40+
//Make sure the Workspace exists before run
41+
42+
workspace.mkdirs();
43+
44+
int exitCode = execMatlabCommand(workspace, launcher, listener, env);
45+
46+
if(exitCode != 0){
47+
// throw an exception if return code is non-zero
48+
stop(new MatlabExecutionException(exitCode));
49+
}
50+
51+
getContext().setResult(Result.SUCCESS);
52+
return null;
53+
}
54+
55+
@Override
56+
public void stop(Throwable cause) throws Exception {
57+
getContext().onFailure(cause);
58+
}
59+
60+
private int execMatlabCommand(FilePath workspace, Launcher launcher,
61+
TaskListener listener, EnvVars envVars) throws IOException, InterruptedException {
62+
final String uniqueTmpFldrName = getUniqueNameForRunnerFile();
63+
final String uniqueBuildFile =
64+
"build_" + getUniqueNameForRunnerFile().replaceAll("-", "_");
65+
final FilePath uniqueTmpFolderPath =
66+
getFilePathForUniqueFolder(launcher, uniqueTmpFldrName, workspace);
67+
68+
// Create MATLAB script
69+
createMatlabScriptByName(uniqueTmpFolderPath, uniqueBuildFile, workspace, listener);
70+
ProcStarter matlabLauncher;
71+
72+
try {
73+
matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, listener, envVars,
74+
"cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"'); "+ uniqueBuildFile, uniqueTmpFldrName);
75+
listener.getLogger()
76+
.println("#################### Starting command output ####################");
77+
return matlabLauncher.pwd(workspace).join();
78+
79+
} finally {
80+
// Cleanup the tmp directory
81+
if (uniqueTmpFolderPath.exists()) {
82+
uniqueTmpFolderPath.deleteRecursive();
83+
}
84+
}
85+
}
86+
87+
private void createMatlabScriptByName(FilePath uniqeTmpFolderPath, String uniqueScriptName, FilePath workspace, TaskListener listener) throws IOException, InterruptedException {
88+
89+
// Create a new command runner script in the temp folder.
90+
final FilePath matlabBuildFile =
91+
new FilePath(uniqeTmpFolderPath, uniqueScriptName + ".m");
92+
final String tasks = getContext().get(EnvVars.class).expand(getTasks());
93+
String cmd = "buildtool";
94+
95+
if (!tasks.trim().isEmpty()) {
96+
cmd += " " + tasks;
97+
}
98+
final String matlabBuildFileContent =
99+
"cd '" + workspace.getRemote().replaceAll("'", "''") + "';\n" + cmd;
100+
101+
// Display the commands on console output for users reference
102+
listener.getLogger()
103+
.println("Generating MATLAB script with content:\n" + cmd + "\n");
104+
105+
matlabBuildFile.write(matlabBuildFileContent, "UTF-8");
106+
}
107+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package com.mathworks.ci;
2+
3+
/**
4+
* Copyright 2022 The MathWorks, Inc.
5+
*
6+
*/
7+
8+
import java.io.IOException;
9+
import javax.annotation.Nonnull;
10+
import org.kohsuke.stapler.DataBoundConstructor;
11+
import org.kohsuke.stapler.DataBoundSetter;
12+
import org.kohsuke.stapler.StaplerRequest;
13+
import hudson.EnvVars;
14+
import hudson.Extension;
15+
import hudson.FilePath;
16+
import hudson.Launcher;
17+
import hudson.Launcher.ProcStarter;
18+
import hudson.model.AbstractProject;
19+
import hudson.model.Result;
20+
import hudson.model.Run;
21+
import hudson.model.TaskListener;
22+
import hudson.model.Computer;
23+
import hudson.tasks.BuildStepDescriptor;
24+
import hudson.tasks.Builder;
25+
import jenkins.tasks.SimpleBuildStep;
26+
import net.sf.json.JSONObject;
27+
28+
public class RunMatlabBuildBuilder extends Builder implements SimpleBuildStep, MatlabBuild {
29+
private int buildResult;
30+
private String tasks;
31+
32+
@DataBoundConstructor
33+
public RunMatlabBuildBuilder() {}
34+
35+
// Getter and Setters to access local members
36+
@DataBoundSetter
37+
public void setTasks(String tasks) {
38+
this.tasks = tasks;
39+
}
40+
41+
public String getTasks() {
42+
return this.tasks;
43+
}
44+
45+
@Extension
46+
public static class RunMatlabBuildDescriptor extends BuildStepDescriptor<Builder> {
47+
48+
// Overridden Method used to show the text under build dropdown
49+
@Override
50+
public String getDisplayName() {
51+
return Message.getValue("Builder.build.builder.display.name");
52+
}
53+
54+
@Override
55+
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
56+
save();
57+
return super.configure(req, formData);
58+
}
59+
60+
/*
61+
* This is to identify which project type in jenkins this should be applicable.(non-Javadoc)
62+
*
63+
* @see hudson.tasks.BuildStepDescriptor#isApplicable(java.lang.Class)
64+
*
65+
* if it returns true then this build step will be applicable for all project type.
66+
*/
67+
@Override
68+
public boolean isApplicable(
69+
@SuppressWarnings("rawtypes") Class<? extends AbstractProject> jobtype) {
70+
return true;
71+
}
72+
}
73+
74+
@Override
75+
public void perform(@Nonnull Run<?, ?> build, @Nonnull FilePath workspace,
76+
@Nonnull Launcher launcher, @Nonnull TaskListener listener)
77+
throws InterruptedException, IOException {
78+
79+
// Get the environment variable specific to the this build
80+
final EnvVars env = build.getEnvironment(listener);
81+
82+
// Invoke MATLAB build and transfer output to standard
83+
// Output Console
84+
85+
86+
buildResult = execMatlabCommand(workspace, launcher, listener, env);
87+
88+
if (buildResult != 0) {
89+
build.setResult(Result.FAILURE);
90+
}
91+
}
92+
93+
private int execMatlabCommand(FilePath workspace, Launcher launcher,
94+
TaskListener listener, EnvVars envVars) throws IOException, InterruptedException {
95+
96+
/*
97+
* Handle the case for using MATLAB Axis for multi conf projects by adding appropriate
98+
* matlabroot to env PATH
99+
* */
100+
Utilities.addMatlabToEnvPathFrmAxis(Computer.currentComputer(), listener, envVars);
101+
102+
final String uniqueTmpFldrName = getUniqueNameForRunnerFile();
103+
final String uniqueBuildFile =
104+
"build_" + getUniqueNameForRunnerFile().replaceAll("-", "_");
105+
final FilePath uniqeTmpFolderPath =
106+
getFilePathForUniqueFolder(launcher, uniqueTmpFldrName, workspace);
107+
108+
// Create MATLAB script
109+
createMatlabScriptByName(uniqeTmpFolderPath, uniqueBuildFile, workspace, listener, envVars);
110+
ProcStarter matlabLauncher;
111+
112+
try {
113+
matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, listener, envVars,
114+
"cd('"+ uniqeTmpFolderPath.getRemote().replaceAll("'", "''") +"');"+ uniqueBuildFile, uniqueTmpFldrName);
115+
116+
listener.getLogger()
117+
.println("#################### Starting command output ####################");
118+
return matlabLauncher.pwd(workspace).join();
119+
120+
} catch (Exception e) {
121+
listener.getLogger().println(e.getMessage());
122+
return 1;
123+
} finally {
124+
// Cleanup the tmp directory
125+
if (uniqeTmpFolderPath.exists()) {
126+
uniqeTmpFolderPath.deleteRecursive();
127+
}
128+
}
129+
}
130+
131+
private void createMatlabScriptByName(FilePath uniqeTmpFolderPath, String uniqueScriptName, FilePath workspace, TaskListener listener, EnvVars envVars) throws IOException, InterruptedException {
132+
133+
// Create a new command runner script in the temp folder.
134+
final FilePath matlabCommandFile =
135+
new FilePath(uniqeTmpFolderPath, uniqueScriptName + ".m");
136+
final String tasks = envVars.expand(getTasks());
137+
String cmd = "buildtool";
138+
139+
if (!tasks.trim().isEmpty()) {
140+
cmd += " " + tasks;
141+
}
142+
143+
final String matlabCommandFileContent =
144+
"cd '" + workspace.getRemote().replaceAll("'", "''") + "';\n" + cmd;
145+
146+
// Display the commands on console output for users reference
147+
listener.getLogger()
148+
.println("Generating MATLAB script with content:\n" + cmd + "\n");
149+
150+
matlabCommandFile.write(matlabCommandFileContent, "UTF-8");
151+
}
152+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.mathworks.ci;
2+
3+
/**
4+
* Copyright 2022 The MathWorks, Inc.
5+
*
6+
*/
7+
8+
import java.util.Set;
9+
import org.jenkinsci.plugins.workflow.steps.Step;
10+
import org.jenkinsci.plugins.workflow.steps.StepContext;
11+
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
12+
import org.jenkinsci.plugins.workflow.steps.StepExecution;
13+
import org.kohsuke.stapler.DataBoundConstructor;
14+
import org.kohsuke.stapler.DataBoundSetter;
15+
import com.google.common.collect.ImmutableSet;
16+
import hudson.EnvVars;
17+
import hudson.Extension;
18+
import hudson.FilePath;
19+
import hudson.Launcher;
20+
import hudson.model.Run;
21+
import hudson.model.TaskListener;
22+
import hudson.Util;
23+
24+
public class RunMatlabBuildStep extends Step {
25+
26+
private String tasks;
27+
28+
@DataBoundConstructor
29+
public RunMatlabBuildStep(String tasks) {
30+
this.tasks = tasks;
31+
}
32+
33+
public String getTasks() {
34+
return Util.fixNull(tasks);
35+
}
36+
37+
@Override
38+
public StepExecution start(StepContext context) throws Exception {
39+
return new MatlabBuildStepExecution(context, getTasks());
40+
}
41+
42+
@Extension
43+
public static class CommandStepDescriptor extends StepDescriptor {
44+
45+
@Override
46+
public Set<? extends Class<?>> getRequiredContext() {
47+
return ImmutableSet.of(TaskListener.class, FilePath.class, Launcher.class,
48+
EnvVars.class, Run.class);
49+
}
50+
51+
@Override
52+
public String getFunctionName() {
53+
return Message.getValue("matlab.build.build.step.name");
54+
}
55+
56+
@Override
57+
public String getDisplayName() {
58+
return Message.getValue("matlab.build.step.display.name");
59+
}
60+
}
61+
}
62+
63+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
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+
4+
<f:entry title="Tasks" field="tasks">
5+
<f:textbox/>
6+
</f:entry>
7+
8+
</j:jelly>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<div>
2+
Insert a space-separated list of tasks to run in the <b>Tasks</b> box. If not specified, the action runs the default tasks in buildfile.m as well as all the tasks on which they depend.<br>
3+
<b>Example: </b>test<br>
4+
<b>Example: </b>compile test
5+
<br>&nbsp;</br>
6+
<b>Note:</b>
7+
<ul>
8+
<li>MATLAB exits with exit code 0 if the tasks run without error. Otherwise, MATLAB terminates with a nonzero exit code, which causes the step to fail.</li>
9+
<li>To use the Run MATLAB Build step, you need MATLAB R2022b or a later release.</li>
10+
</ul>
11+
</div>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
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+
4+
<f:entry field="tasks">
5+
<f:textbox/>
6+
</f:entry>
7+
8+
</j:jelly>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<div>
2+
Insert a space-separated list of tasks to run in the <b>Tasks</b> box. If not specified, the action runs the default tasks in buildfile.m as well as all the tasks on which they depend.<br>
3+
<b>Example: </b>test<br>
4+
<b>Example: </b>compile test
5+
<br>&nbsp;</br>
6+
<b>Note:</b>
7+
<ul>
8+
<li>MATLAB exits with exit code 0 if the tasks run without error. Otherwise, MATLAB terminates with a nonzero exit code, which causes the step to fail.</li>
9+
<li>To use the Run MATLAB Build step, you need MATLAB R2022b or a later release.</li>
10+
</ul>
11+
</div>

0 commit comments

Comments
 (0)