4130 |
26 Sep 16 |
nicklas |
1 |
package net.sf.basedb.opengrid; |
4130 |
26 Sep 16 |
nicklas |
2 |
|
5996 |
26 Aug 20 |
nicklas |
3 |
import java.util.Arrays; |
5996 |
26 Aug 20 |
nicklas |
4 |
import java.util.Collection; |
4130 |
26 Sep 16 |
nicklas |
5 |
import java.util.HashMap; |
5996 |
26 Aug 20 |
nicklas |
6 |
import java.util.HashSet; |
4130 |
26 Sep 16 |
nicklas |
7 |
import java.util.Map; |
5996 |
26 Aug 20 |
nicklas |
8 |
import java.util.Set; |
4130 |
26 Sep 16 |
nicklas |
9 |
|
6627 |
07 Mar 22 |
nicklas |
10 |
import net.sf.basedb.opengrid.filetransfer.StringUploadSource; |
6627 |
07 Mar 22 |
nicklas |
11 |
import net.sf.basedb.opengrid.filetransfer.UploadSource; |
4130 |
26 Sep 16 |
nicklas |
12 |
|
6627 |
07 Mar 22 |
nicklas |
13 |
|
4130 |
26 Sep 16 |
nicklas |
14 |
/** |
4130 |
26 Sep 16 |
nicklas |
Utility class for generating shell scripts. |
4130 |
26 Sep 16 |
nicklas |
@author nicklas |
4130 |
26 Sep 16 |
nicklas |
@since 1.0 |
4130 |
26 Sep 16 |
nicklas |
18 |
*/ |
4130 |
26 Sep 16 |
nicklas |
19 |
public class ScriptBuilder |
4130 |
26 Sep 16 |
nicklas |
20 |
{ |
4130 |
26 Sep 16 |
nicklas |
21 |
private final StringBuilder script; |
4130 |
26 Sep 16 |
nicklas |
22 |
private final String logFolder; |
4130 |
26 Sep 16 |
nicklas |
23 |
|
4130 |
26 Sep 16 |
nicklas |
24 |
private int nextBgPid; |
4130 |
26 Sep 16 |
nicklas |
25 |
private Map<String, String> bgProcesses; |
4130 |
26 Sep 16 |
nicklas |
26 |
|
5996 |
26 Aug 20 |
nicklas |
27 |
private int indentLevel; |
5996 |
26 Aug 20 |
nicklas |
28 |
private String indentString; |
5996 |
26 Aug 20 |
nicklas |
29 |
private Set<String> autoIndent; |
5996 |
26 Aug 20 |
nicklas |
30 |
private Set<String> autoUnindent; |
4130 |
26 Sep 16 |
nicklas |
31 |
|
4130 |
26 Sep 16 |
nicklas |
32 |
/** |
4130 |
26 Sep 16 |
nicklas |
Create a new script builder using the working folder |
4130 |
26 Sep 16 |
nicklas |
for log and progress information. |
4130 |
26 Sep 16 |
nicklas |
35 |
*/ |
4130 |
26 Sep 16 |
nicklas |
36 |
public ScriptBuilder() |
4130 |
26 Sep 16 |
nicklas |
37 |
{ |
4130 |
26 Sep 16 |
nicklas |
38 |
this(null); |
4130 |
26 Sep 16 |
nicklas |
39 |
} |
4130 |
26 Sep 16 |
nicklas |
40 |
|
4130 |
26 Sep 16 |
nicklas |
41 |
/** |
4130 |
26 Sep 16 |
nicklas |
Create a new script builder for a generic script. |
4130 |
26 Sep 16 |
nicklas |
@param logFolder Path to a folder to use for outputting |
4229 |
10 Nov 16 |
nicklas |
log and progress information. If null, the folder set |
4270 |
16 Dec 16 |
nicklas |
by ${WD} is used (=working directory for jobs executed by |
4229 |
10 Nov 16 |
nicklas |
the Open Grid cluster). |
4130 |
26 Sep 16 |
nicklas |
47 |
*/ |
4130 |
26 Sep 16 |
nicklas |
48 |
public ScriptBuilder(String logFolder) |
4130 |
26 Sep 16 |
nicklas |
49 |
{ |
4130 |
26 Sep 16 |
nicklas |
50 |
this.script = new StringBuilder(); |
4270 |
16 Dec 16 |
nicklas |
51 |
this.logFolder = logFolder == null ? "${WD}" : logFolder; |
5996 |
26 Aug 20 |
nicklas |
52 |
this.indentLevel = 0; |
5996 |
26 Aug 20 |
nicklas |
53 |
this.indentString = ""; |
5996 |
26 Aug 20 |
nicklas |
54 |
this.autoIndent = new HashSet<String>(Arrays.asList("if", "elif", "else", "case", "while", "until", "for")); |
5996 |
26 Aug 20 |
nicklas |
55 |
this.autoUnindent = new HashSet<String>(Arrays.asList("elif", "else", "fi", "esac", "done")); |
4130 |
26 Sep 16 |
nicklas |
56 |
} |
4130 |
26 Sep 16 |
nicklas |
57 |
|
4130 |
26 Sep 16 |
nicklas |
58 |
/** |
6648 |
21 Mar 22 |
nicklas |
Join the other script with this script. |
6648 |
21 Mar 22 |
nicklas |
@since 1.5 |
6648 |
21 Mar 22 |
nicklas |
61 |
*/ |
6648 |
21 Mar 22 |
nicklas |
62 |
public void join(ScriptBuilder other) |
6648 |
21 Mar 22 |
nicklas |
63 |
{ |
6648 |
21 Mar 22 |
nicklas |
64 |
script.append(other.toString()); |
6648 |
21 Mar 22 |
nicklas |
65 |
} |
6648 |
21 Mar 22 |
nicklas |
66 |
|
6648 |
21 Mar 22 |
nicklas |
67 |
/** |
4130 |
26 Sep 16 |
nicklas |
Adds a comment to the script. |
4130 |
26 Sep 16 |
nicklas |
@param comment The comment (must not contain any new lines) |
4130 |
26 Sep 16 |
nicklas |
70 |
*/ |
4130 |
26 Sep 16 |
nicklas |
71 |
public void comment(String comment) |
4130 |
26 Sep 16 |
nicklas |
72 |
{ |
5996 |
26 Aug 20 |
nicklas |
73 |
script.append(indentString).append("# ").append(comment).append("\n"); |
4130 |
26 Sep 16 |
nicklas |
74 |
} |
4130 |
26 Sep 16 |
nicklas |
75 |
|
4130 |
26 Sep 16 |
nicklas |
76 |
/** |
4130 |
26 Sep 16 |
nicklas |
Adds an empty line. |
4130 |
26 Sep 16 |
nicklas |
78 |
*/ |
4130 |
26 Sep 16 |
nicklas |
79 |
public void newLine() |
4130 |
26 Sep 16 |
nicklas |
80 |
{ |
4130 |
26 Sep 16 |
nicklas |
81 |
script.append("\n"); |
4130 |
26 Sep 16 |
nicklas |
82 |
} |
4130 |
26 Sep 16 |
nicklas |
83 |
|
4130 |
26 Sep 16 |
nicklas |
84 |
/** |
4229 |
10 Nov 16 |
nicklas |
Append the current time (hour:minute:second) and comment into the 'time.log' file in the |
4130 |
26 Sep 16 |
nicklas |
log folder. Eg: '12:23:45 Demux complete'. |
4130 |
26 Sep 16 |
nicklas |
87 |
*/ |
4130 |
26 Sep 16 |
nicklas |
88 |
public void time(String comment) |
4130 |
26 Sep 16 |
nicklas |
89 |
{ |
5996 |
26 Aug 20 |
nicklas |
90 |
script.append(indentString).append("echo \"`date +%H:%M:%S` ").append(comment).append("\" >> ").append(logFolder).append("/time.log\n"); |
4130 |
26 Sep 16 |
nicklas |
91 |
} |
4130 |
26 Sep 16 |
nicklas |
92 |
|
4130 |
26 Sep 16 |
nicklas |
93 |
/** |
4130 |
26 Sep 16 |
nicklas |
Update the progress file. The existing file will be replaced. |
4130 |
26 Sep 16 |
nicklas |
@param progress The progress (in percent) |
4130 |
26 Sep 16 |
nicklas |
@param status The status message |
4130 |
26 Sep 16 |
nicklas |
97 |
*/ |
4130 |
26 Sep 16 |
nicklas |
98 |
public void progress(int progress, String status) |
4130 |
26 Sep 16 |
nicklas |
99 |
{ |
5996 |
26 Aug 20 |
nicklas |
100 |
script.append(indentString).append("echo ").append(progress).append(" \"").append(status).append("\" > ").append(logFolder).append("/progress\n"); |
4130 |
26 Sep 16 |
nicklas |
101 |
} |
4130 |
26 Sep 16 |
nicklas |
102 |
|
4130 |
26 Sep 16 |
nicklas |
103 |
/** |
6625 |
04 Mar 22 |
nicklas |
Add an exported variable declaration to the script: |
6625 |
04 Mar 22 |
nicklas |
export key="value". The value is surrounded with ". |
6625 |
04 Mar 22 |
nicklas |
Nothing is added if the value is null. |
6625 |
04 Mar 22 |
nicklas |
@since 1.5 |
6625 |
04 Mar 22 |
nicklas |
108 |
*/ |
6625 |
04 Mar 22 |
nicklas |
109 |
public void export(String key, String value) |
6625 |
04 Mar 22 |
nicklas |
110 |
{ |
6625 |
04 Mar 22 |
nicklas |
111 |
if (value != null) cmd("export "+key+"=\""+value+"\""); |
6625 |
04 Mar 22 |
nicklas |
112 |
} |
6625 |
04 Mar 22 |
nicklas |
113 |
|
6625 |
04 Mar 22 |
nicklas |
114 |
|
6625 |
04 Mar 22 |
nicklas |
115 |
/** |
6625 |
04 Mar 22 |
nicklas |
Add an variable declaration to the script: key="value". |
6625 |
04 Mar 22 |
nicklas |
The value is surrounded with ". Nothing is added if the value is null. |
6625 |
04 Mar 22 |
nicklas |
@since 1.5 |
6625 |
04 Mar 22 |
nicklas |
119 |
*/ |
6625 |
04 Mar 22 |
nicklas |
120 |
public void var(String key, String value) |
6625 |
04 Mar 22 |
nicklas |
121 |
{ |
6625 |
04 Mar 22 |
nicklas |
122 |
if (value != null) cmd(key+"=\""+value+"\""); |
6625 |
04 Mar 22 |
nicklas |
123 |
} |
6625 |
04 Mar 22 |
nicklas |
124 |
|
6625 |
04 Mar 22 |
nicklas |
125 |
|
6625 |
04 Mar 22 |
nicklas |
126 |
/** |
4130 |
26 Sep 16 |
nicklas |
Run a command. |
4130 |
26 Sep 16 |
nicklas |
128 |
*/ |
4130 |
26 Sep 16 |
nicklas |
129 |
public void cmd(String cmd) |
4130 |
26 Sep 16 |
nicklas |
130 |
{ |
6659 |
01 Apr 22 |
nicklas |
131 |
if (cmd == null) return; |
5996 |
26 Aug 20 |
nicklas |
132 |
checkIndent(cmd, autoUnindent, -1); |
5996 |
26 Aug 20 |
nicklas |
133 |
script.append(indentString).append(cmd).append("\n"); |
5996 |
26 Aug 20 |
nicklas |
134 |
checkIndent(cmd, autoIndent, 1); |
4130 |
26 Sep 16 |
nicklas |
135 |
} |
4130 |
26 Sep 16 |
nicklas |
136 |
|
4130 |
26 Sep 16 |
nicklas |
137 |
/** |
5996 |
26 Aug 20 |
nicklas |
If cmd starts with one of the given keys, apply delta to the |
5996 |
26 Aug 20 |
nicklas |
indentation level and create a new indent-string. |
5996 |
26 Aug 20 |
nicklas |
140 |
*/ |
5996 |
26 Aug 20 |
nicklas |
141 |
private void checkIndent(String cmd, Collection<String> keys, int delta) |
5996 |
26 Aug 20 |
nicklas |
142 |
{ |
5996 |
26 Aug 20 |
nicklas |
143 |
for (String k : keys) |
5996 |
26 Aug 20 |
nicklas |
144 |
{ |
5996 |
26 Aug 20 |
nicklas |
145 |
if (cmd.startsWith(k)) |
5996 |
26 Aug 20 |
nicklas |
146 |
{ |
5996 |
26 Aug 20 |
nicklas |
147 |
indentLevel = Math.max(0, indentLevel+delta); |
5996 |
26 Aug 20 |
nicklas |
148 |
indentString = " ".repeat(indentLevel*3); |
5996 |
26 Aug 20 |
nicklas |
149 |
return; |
5996 |
26 Aug 20 |
nicklas |
150 |
} |
5996 |
26 Aug 20 |
nicklas |
151 |
} |
5996 |
26 Aug 20 |
nicklas |
152 |
} |
5996 |
26 Aug 20 |
nicklas |
153 |
|
5996 |
26 Aug 20 |
nicklas |
154 |
/** |
4130 |
26 Sep 16 |
nicklas |
Echo the given text to stdout |
4130 |
26 Sep 16 |
nicklas |
156 |
*/ |
4130 |
26 Sep 16 |
nicklas |
157 |
public void echo(String text) |
4130 |
26 Sep 16 |
nicklas |
158 |
{ |
6659 |
01 Apr 22 |
nicklas |
159 |
if (text == null) return; |
4130 |
26 Sep 16 |
nicklas |
160 |
cmd("echo " + text); |
4130 |
26 Sep 16 |
nicklas |
161 |
} |
4130 |
26 Sep 16 |
nicklas |
162 |
|
4130 |
26 Sep 16 |
nicklas |
163 |
/** |
4130 |
26 Sep 16 |
nicklas |
Adds a command that is executed in the background. The return value is the |
4130 |
26 Sep 16 |
nicklas |
name of a variable containing the process id of the background task. Use this |
4130 |
26 Sep 16 |
nicklas |
in {@link #waitForProcess(String)} to add a wait statement that also catches the |
4130 |
26 Sep 16 |
nicklas |
exit code of the background task. |
4130 |
26 Sep 16 |
nicklas |
168 |
*/ |
4130 |
26 Sep 16 |
nicklas |
169 |
public String bkgr(String cmd) |
4130 |
26 Sep 16 |
nicklas |
170 |
{ |
4130 |
26 Sep 16 |
nicklas |
171 |
if (bgProcesses == null) bgProcesses = new HashMap<String, String>(); |
4130 |
26 Sep 16 |
nicklas |
172 |
String pid = "BG"+nextBgPid; |
4130 |
26 Sep 16 |
nicklas |
173 |
nextBgPid++; |
4130 |
26 Sep 16 |
nicklas |
174 |
bgProcesses.put(pid, cmd); |
5996 |
26 Aug 20 |
nicklas |
175 |
script.append(indentString).append(cmd).append(" &\n"); |
5996 |
26 Aug 20 |
nicklas |
176 |
script.append(indentString).append(pid).append("=$!\n"); |
4130 |
26 Sep 16 |
nicklas |
177 |
return pid; |
4130 |
26 Sep 16 |
nicklas |
178 |
} |
4130 |
26 Sep 16 |
nicklas |
179 |
|
4130 |
26 Sep 16 |
nicklas |
180 |
/** |
4130 |
26 Sep 16 |
nicklas |
Adds a batch of commands that are executed in the background. |
4130 |
26 Sep 16 |
nicklas |
Note that it is only the batch as a whole that is executed in |
4130 |
26 Sep 16 |
nicklas |
the background, the individual commands are executed in serial. |
4130 |
26 Sep 16 |
nicklas |
The return value is the name of a variable containing the process |
4130 |
26 Sep 16 |
nicklas |
id of the background task. Use this in {@link #waitForProcess(String)} to add |
4130 |
26 Sep 16 |
nicklas |
a wait statement that also catches the exit code of the background task. |
4130 |
26 Sep 16 |
nicklas |
187 |
*/ |
4130 |
26 Sep 16 |
nicklas |
188 |
public String bkgr(String... cmds) |
4130 |
26 Sep 16 |
nicklas |
189 |
{ |
4130 |
26 Sep 16 |
nicklas |
190 |
StringBuilder cmd = new StringBuilder(); |
4130 |
26 Sep 16 |
nicklas |
191 |
cmd.append("{ "); |
4130 |
26 Sep 16 |
nicklas |
192 |
for (String c : cmds) |
4130 |
26 Sep 16 |
nicklas |
193 |
{ |
4130 |
26 Sep 16 |
nicklas |
194 |
cmd.append(c).append("; "); |
4130 |
26 Sep 16 |
nicklas |
195 |
} |
4130 |
26 Sep 16 |
nicklas |
196 |
cmd.append(" }"); |
4130 |
26 Sep 16 |
nicklas |
197 |
return bkgr(cmd.toString()); |
4130 |
26 Sep 16 |
nicklas |
198 |
} |
4130 |
26 Sep 16 |
nicklas |
199 |
|
4130 |
26 Sep 16 |
nicklas |
200 |
/** |
4130 |
26 Sep 16 |
nicklas |
Wait for the given process to end. |
4130 |
26 Sep 16 |
nicklas |
@param pid The process id variable name returned by {@link #bkgr(String)}. |
4130 |
26 Sep 16 |
nicklas |
@throws IllegalArgumentException If the pid is unknown |
4130 |
26 Sep 16 |
nicklas |
204 |
*/ |
4130 |
26 Sep 16 |
nicklas |
205 |
public void waitForProcess(String pid) |
4130 |
26 Sep 16 |
nicklas |
206 |
{ |
4130 |
26 Sep 16 |
nicklas |
207 |
if (bgProcesses == null || !bgProcesses.containsKey(pid)) |
4130 |
26 Sep 16 |
nicklas |
208 |
{ |
4130 |
26 Sep 16 |
nicklas |
209 |
throw new IllegalArgumentException("Unknown background process id: " + pid); |
4130 |
26 Sep 16 |
nicklas |
210 |
} |
4130 |
26 Sep 16 |
nicklas |
211 |
bgProcesses.remove(pid); |
5996 |
26 Aug 20 |
nicklas |
212 |
script.append(indentString).append("wait $").append(pid).append("\n"); |
4130 |
26 Sep 16 |
nicklas |
213 |
} |
4130 |
26 Sep 16 |
nicklas |
214 |
|
4130 |
26 Sep 16 |
nicklas |
215 |
/** |
4130 |
26 Sep 16 |
nicklas |
Get the current length of the script. |
4130 |
26 Sep 16 |
nicklas |
217 |
*/ |
4130 |
26 Sep 16 |
nicklas |
218 |
public int length() |
4130 |
26 Sep 16 |
nicklas |
219 |
{ |
4130 |
26 Sep 16 |
nicklas |
220 |
return script.length(); |
4130 |
26 Sep 16 |
nicklas |
221 |
} |
4130 |
26 Sep 16 |
nicklas |
222 |
|
4130 |
26 Sep 16 |
nicklas |
223 |
/** |
4130 |
26 Sep 16 |
nicklas |
Get the generated script. |
4130 |
26 Sep 16 |
nicklas |
225 |
|
4130 |
26 Sep 16 |
nicklas |
@throws IllegalStateException If {@link #waitForProcess(String)} hasn't |
4130 |
26 Sep 16 |
nicklas |
been called for all commands started in the background ({@link #bkgr(String)} |
4130 |
26 Sep 16 |
nicklas |
or {@link #bkgr(String...)}) |
4130 |
26 Sep 16 |
nicklas |
229 |
*/ |
4130 |
26 Sep 16 |
nicklas |
230 |
@Override |
4130 |
26 Sep 16 |
nicklas |
231 |
public String toString() |
4130 |
26 Sep 16 |
nicklas |
232 |
{ |
4130 |
26 Sep 16 |
nicklas |
233 |
if (bgProcesses != null && bgProcesses.size() > 0) |
4130 |
26 Sep 16 |
nicklas |
234 |
{ |
4130 |
26 Sep 16 |
nicklas |
235 |
throw new IllegalStateException("The script start background tasks, but does not wait for them: " + bgProcesses); |
4130 |
26 Sep 16 |
nicklas |
236 |
} |
4130 |
26 Sep 16 |
nicklas |
237 |
return script.toString(); |
4130 |
26 Sep 16 |
nicklas |
238 |
} |
4130 |
26 Sep 16 |
nicklas |
239 |
|
6627 |
07 Mar 22 |
nicklas |
240 |
/** |
6627 |
07 Mar 22 |
nicklas |
Get an upload source representation for uploading the script |
6627 |
07 Mar 22 |
nicklas |
to a remote server. |
6627 |
07 Mar 22 |
nicklas |
@param name The name to give to the file on the remote system |
6627 |
07 Mar 22 |
nicklas |
@since 1.5 |
6627 |
07 Mar 22 |
nicklas |
245 |
*/ |
6627 |
07 Mar 22 |
nicklas |
246 |
public UploadSource toUploadSource(String name) |
6627 |
07 Mar 22 |
nicklas |
247 |
{ |
6627 |
07 Mar 22 |
nicklas |
248 |
return new StringUploadSource(name, toString()); |
6627 |
07 Mar 22 |
nicklas |
249 |
} |
4130 |
26 Sep 16 |
nicklas |
250 |
|
4130 |
26 Sep 16 |
nicklas |
251 |
} |