Run sh/shout/shlines in a temporary directory.
[goaljobs.git] / goaljobs.mli
1 (* goaljobs
2  * Copyright (C) 2013 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *)
18
19 (** {1 Goaljobs library of useful helper functions.} *)
20
21 (** {2 Targets and requires}
22
23     These are used to write goals.
24
25     Normally you write a goal with one or more [target]s and
26     zero or more [require]s, as the examples below should make
27     clear.
28
29     In the first example, there are two targets: that [o_file] (object)
30     exists, and that it is newer than [c_file] (source).  The rule
31     meets that target by running the C compiler ([cc]) which, if it
32     succeeds, will ensure that the object file exists and is newer
33     than the source file.
34
35     {v
36     let goal compiled c_file =
37         let o_file = change_file_extension "o" c_file in
38         target (more_recent [o_file] [c_file]);
39
40         sh "cd $builddir && cc -c %s -o %s" c_file o_file
41     }
42
43     In the second example, the rule requires that several files
44     have been compiled ([require (compiled ...)]
45     before it can link the final program:
46
47     {v
48     let goal built program source =
49         target (more_recent [program] [source]);
50
51         require (compiled source);
52
53         let object = change_file_extension "o" source in
54         sh "cd $builddir && cc %s -o %s" object program
55     }
56
57 *)
58
59 val target : bool -> unit
60   (** [target] {i condition} defines the target condition that {b will}
61       be met once the current rule has run.
62
63       Goaljobs is much more flexible than [make].  In [make] only a
64       single type of target is possible.  The following are roughly
65       equivalent:
66
67       {v
68       foo.o: foo.c
69         ...
70
71       let goal compiled () =
72         target (more_recent ["foo.o"] ["foo.c"]);
73         requires (file_exists "foo.c");
74         ...
75       }
76
77       Targets in goaljobs can be any arbitrary expression, and you
78       can have any number of different targets.
79
80       Almost every rule should have one or more targets, which should
81       accurately state the outcome once the rule has been run.
82
83       If you have more than one [target]s then it's as if they have
84       been ORed together ({b not} ANDed which you might expect).
85       You can make this explicit by using a single target and [&&]
86       or [||] between the expressions.  See also {!target_all}
87       and {!target_exists}.
88
89       Normally you put the target(s) early on in the rule, before any
90       running code and before any [require]s.  This is not a
91       hard-and-fast rule and it is not enforced, but doing it will
92       ensure the rule runs most efficiently since if the target is met
93       already then the rest of the rule doesn't run. *)
94
95 val target_all : bool list -> unit
96   (** [target_all [t1; t2; ...]] is the same as writing
97       [target (t1 && t2 && ...)] *)
98
99 val target_exists : bool list -> unit
100   (** [target_exists [t1; t2; ...]] is the same as writing
101       [target (t1 || t2 || ...)] *)
102
103 val require : unit -> unit
104   (** [require] {!goal} defines the requirements of this rule, that
105       is, other goals that have to be met before this rule is able to run.
106
107       In terms of [make], [require]s are roughly equivalent to the
108       right hand side after the [:], but in goaljobs the requirements
109       can be much richer than simply "that file must exist".
110
111       Some very simple rules don't need any [require]s.  Unlike with [make],
112       the requirements of a rule can be placed anywhere within the
113       rule, as long as you put them before they are needed. *)
114
115 (** {2 File and URL testing}
116
117     Various functions to test the existence of files, URLs.
118 *)
119
120 val file_exists : string -> bool
121   (** Return true if the named file exists.
122
123       This function also exists as a goal.  Writing:
124       {v require (file_exists "somefile");}
125       will die unless ["somefile"] exists. *)
126
127 val file_newer_than : string -> string -> bool
128   (** [file_newer_than file_a file_b] returns true if [file_a] is
129       newer than [file_b].  Note that if [file_a] does not exist, it
130       returns false.  If [file_b] does not exist, it is an error.
131
132       There is also a goal version of this function. *)
133
134 val more_recent : string list -> string list -> bool
135   (** [more_recent objects sources] expresses the [make] relationship:
136
137       {v object(s) ...: source(s) ...}
138
139       in a convenient way:
140
141       {v
142       let goal built objects sources =
143         target (more_recent objects sources);
144         ... code to rebuild ...
145       }
146
147       It is roughly equivalent to checking that all the object files
148       exist and are newer than all of the source files.
149
150       Note that both parameters are lists (since in [make] you can
151       have a list of source files and a list of object files).  If you
152       don't want a list, pass a single-element list containing the
153       single the object/source file.
154
155       There is also a goal version of this function. *)
156
157 val url_exists : string -> bool
158   (** The URL is tested to see if it exists.
159
160       There is also a goal version of this function. *)
161
162 val file_contains_string : string -> string -> bool
163   (** [file_contains_string filename str] checks if the named file
164       contains the given substring [str].
165
166       There is also a goal version of this function. *)
167
168 val url_contains_string : string -> string -> bool
169   (** [url_contains_string url str] downloads the URL and checks
170       whether the content contains the given substring [str].
171
172       There is also a goal version of this function. *)
173
174 val (//) : string -> string -> string
175   (** Concatenate two paths. *)
176
177 val quote : string -> string
178   (** Quote the string to make it safe to pass directly to the shell. *)
179
180 (** {2 Shell}
181
182     Call out to the Unix shell.  [/bin/sh] is used unless you set
183     {!shell} to some other value.  Note that the environment variable
184     [SHELL] is {i not} used.
185
186     {!sh}, {!shout}, {!shlines} work like [printf].  ie. You can
187     substitute variables using [%s], [%d] and so on.  For example:
188
189     {v
190       sh "rsync foo-%s.tar.gz example.com:/html/" version
191     }
192
193     Each invocation of {!sh} (etc) is a single shell (this is slightly
194     different from how [make] works).  For example:
195
196     {v
197       sh "
198         package=foo-%s
199         tarball=$package.tar.gz
200         cp $HOME/$tarball .
201         tar zxf $tarball
202         cd $package
203         ./configure
204         make
205      " version
206     }
207
208     The shell error mode is set such that if any single command
209     returns an error then the {!sh} function as a whole exits with
210     an error.  Write:
211     {v command ||: }
212     to ignore the result of a command.
213
214     Each shell runs in a new temporary directory.  The temporary
215     directory and all its contents is deleted after the shell exits.
216     If you want to save any data, [cd] somewhere.  The environment
217     variable [$builddir] is exported to the script.  This is the
218     current directory when the goaljobs program was started.
219
220     For example you could start the command sequence with
221     [cd $HOME/data/] or [cd $builddir].
222 *)
223
224 val sh : ('a, unit, string, unit) format4 -> 'a
225   (** Run the command(s). *)
226
227 val shout : ('a, unit, string, string) format4 -> 'a
228   (** Run the command(s).
229
230       Anything printed on stdout is returned as a string.
231       The trailing [\n] character, if any, is not returned. *)
232
233 val shlines : ('a, unit, string, string list) format4 -> 'a
234   (** Run the command(s).
235
236       Any lines printed to stdout is returned as a list of strings.
237       Trailing [\n] characters are not returned. *)
238
239 val shell : string ref
240   (** Set this variable to override the default shell ([/bin/sh]). *)
241
242 (** {2 String functions}
243
244     Most string functions are provided by the OCaml standard
245     library (see the module [String]).  For convenience some
246     extra functions are provided here. *)
247
248 (*
249 val replace_substring : string -> string -> string -> string
250   (** [replace_substring patt repl string] replaces all occurrences
251       of [patt] with [repl] in [string]. *)
252 *)
253
254 val change_file_extension : string -> string -> string
255   (** [change_file_extension ext filename] changes the file extension
256       of [filename] to [.ext].  For example
257       [change_file_extension "o" "main.c"] returns ["main.o"].
258       If the original filename has no extension, this function
259       adds the extension. *)
260
261 (*
262 val filter_file_extension : string -> string list -> string
263   (** [filter_file_extension ext filenames] returns only those
264       filenames in the list which have the given file extension.
265       For example [filter_file_extension "o" ["foo.c"; "bar.o"]]
266       would return [["bar.o"]] (a single element list). *)
267 *)
268
269 (** {2 Memory (persistent key/value storage)
270
271     "The Memory" is key/value storage which persists across goaljobs
272     sessions.  It is stored in the file [$HOME/.goaljobs-memory]
273     (which is a binary file, but you can delete it if you want).
274
275     The Memory is locked during accesses, so it is safe to read
276     or write it from multiple parallel goaljobs sessions.
277
278     Keys and values are strings.  The keys should be globally
279     unique, so it is suggested you use some application-specific
280     prefix.  eg: "myapp-key"
281
282     A common pattern is:
283
284     {v
285     let goal tested version =
286         let key = "myapp-tested-" ^ version in
287         target (memory_exists key);
288
289         ... some work to test version ...
290
291         memory_set key "1"
292     }
293
294     Note in that example the value ["1"] is arbitrary.  You just
295     want to store {i any} value so that a later call to {!memory_exists}
296     will succeed.
297 *)
298
299 val memory_exists : string -> bool
300   (** [memory_exists key] checks that the named [key] exists in
301       the Memory.  It doesn't matter what value it has.
302
303       This is also available as a goal, so you can write
304       [requires (memory_exists key)] *)
305
306 val memory_set : string -> string -> unit
307   (** Set [key] to [value] in the Memory. *)
308
309 val memory_get : string -> string option
310   (** Return the current value of [key] in the Memory.  Returns [None]
311       if the key has never been set or was deleted. *)
312
313 val memory_delete : string -> unit
314   (** Delete the [key].  If the key doesn't exist, has no effect. *)
315
316 (** {2 Publishing goals}
317
318     To "publish" a goal means it's available on the command line
319     for users to use directly.
320
321     Goals that have zero arguments are {b automatically published}.
322     So for example:
323
324     {v
325     let goal clean () = sh "rm *~"
326     }
327
328     can be used on the command line:
329
330     {v ./script clean }
331
332     The special goal called [all] (if it exists) is run implicitly
333     unless the user specifies another goal.  Unlike [make], there is
334     nothing special about the first rule in the file.
335
336     You can also publish goals, especially ones which take a non-zero
337     number of parameters, by calling {!publish}.
338 *)
339
340 val publish : string -> (string list -> unit) -> unit
341   (** Publish the named goal.
342
343       Use this function as in this example:
344
345       {v
346       let goal compiled program sources =
347         ... stuff for building the program from sources ...
348
349       let () = publish "compiled" (
350         fun args ->
351           let program = List.hd args in
352           let sources = List.tl args in
353           require (compiled program sources)
354       )
355       }
356
357       This could be used as follows:
358
359       {v ./script compiled program main.c utils.c }
360
361       You will notice you have to write a bit of OCaml code to
362       map the string arguments from the command line on to the
363       goal arguments.  In the example it means taking the first
364       string argument as the program name, and the rest of the
365       string arguments as the source filenames.  This is also
366       the place to perform string to int conversion, checks, and
367       so on (remember that OCaml is strongly typed). *)
368
369 (**/**)
370
371 (* Goal versions of some common functions.  You are using these
372  * versions when you write something like:
373  *   require (file_exists "foo");
374  * They work the same way as the regular function, except they die
375  * if the predicate returns false.
376  *)
377 val goal_file_exists : string -> unit
378 val goal_file_newer_than : string -> string -> unit
379 val goal_more_recent : string list -> string list -> unit
380 val goal_url_exists : string -> unit
381 val goal_file_contains_string : string -> string -> unit
382 val goal_url_contains_string : string -> string -> unit
383 val goal_memory_exists : string -> unit
384
385 (* A single call to this function is added by the 'goaljobs' script.
386  * It is responsible for parsing the command line and so on.
387  *)
388 val init : unit -> unit
389
390 (* Export this so the macros can catch these exceptions. *)
391 type goal_result_t = Goal_OK | Goal_failed of string
392 exception Goal_result of goal_result_t