Pass a suspension to 'require'.
[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) -> 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 Periodic jobs}
116
117     If you want to have a rule that runs when some outside event
118     happens you have three choices: Manually run the script (this is
119     basically what [make] forces you to do).  Have some sort of hook
120     that runs the script (eg. a git hook).  Or use a periodic job to
121     poll for an event or change.
122
123     Periodic jobs run regularly to poll for an outside event or
124     change.  If a script has periodic jobs, then it runs continuously
125     (or until you kill it).
126
127     An example of a script that checks for new git commits and when
128     it sees one it will ensure it passes the tests:
129
130     {v
131     let repo = Sys.getenv "HOME" // "repo"
132
133     let goal git_commit_tested commit =
134     let key = sprintf "repo-tested-%s" commit in
135     target (memory_exists key);
136
137     sh "
138     git clone %s test
139     cd test
140     ./configure
141     make
142     make check
143     ";
144
145         (* Record that this commit was tested successfully. *)
146         memory_set key "1"
147
148     every 30 minutes (fun () ->
149         let commit = shout "cd %s && git rev-parse HEAD" repo in
150         (* Require that this commit has been tested. *)
151         require (git_commit_tested commit)
152     )
153     }
154
155     Some notes about the above example: Firstly only the current HEAD
156     commit is required to be tested.  This is because older commits
157     are irrelevant and because if they failed the test before there is
158     not point retesting them (commits are immutable).  Secondly we use
159     the Memory to remember that we have successfully tested a commit.
160     This is what stops the program from repeatedly testing the same
161     commit. *)
162
163 (* This is what lets you write '30 minutes' etc: *)
164 type period_t = Seconds | Days | Months | Years
165 val seconds : int * period_t
166 val sec : int * period_t
167 val secs : int * period_t
168 val second : int * period_t
169 val minutes : int * period_t
170 val min : int * period_t
171 val mins : int * period_t
172 val minute : int * period_t
173 val hours : int * period_t
174 val hour : int * period_t
175 val days : int * period_t
176 val day : int * period_t
177 val weeks : int * period_t
178 val week : int * period_t
179 val months : int * period_t
180 val month : int * period_t
181 val years : int * period_t
182 val year : int * period_t
183
184 val every : ?name:string -> int -> int * period_t -> (unit -> unit) -> unit
185   (** [every N (seconds|minutes|hours|days|weeks|months|years) f]
186       runs the function [f] periodically.
187
188       The optional [~name] parameter can be used to name the job
189       (for debugging). *)
190
191 (** {2 File and URL testing}
192
193     Various functions to test the existence of files, URLs.
194 *)
195
196 val file_exists : string -> bool
197   (** Return true if the named file exists.
198
199       This function also exists as a goal.  Writing:
200       {v require (file_exists "somefile");}
201       will die unless ["somefile"] exists. *)
202
203 val directory_exists : string -> bool
204   (** Return true if the named directory exists.
205
206       There is also a goal version of this function. *)
207
208 val file_newer_than : string -> string -> bool
209   (** [file_newer_than file_a file_b] returns true if [file_a] is
210       newer than [file_b].  Note that if [file_a] does not exist, it
211       returns false.  If [file_b] does not exist, it is an error.
212
213       There is also a goal version of this function. *)
214
215 val more_recent : string list -> string list -> bool
216   (** [more_recent objects sources] expresses the [make] relationship:
217
218       {v object(s) ...: source(s) ...}
219
220       in a convenient way:
221
222       {v
223       let goal built objects sources =
224         target (more_recent objects sources);
225         ... code to rebuild ...
226       }
227
228       It is roughly equivalent to checking that all the object files
229       exist and are newer than all of the source files.
230
231       Note that both parameters are lists (since in [make] you can
232       have a list of source files and a list of object files).  If you
233       don't want a list, pass a single-element list containing the
234       single the object/source file.
235
236       There is also a goal version of this function. *)
237
238 val url_exists : string -> bool
239   (** The URL is tested to see if it exists.
240
241       There is also a goal version of this function. *)
242
243 val file_contains_string : string -> string -> bool
244   (** [file_contains_string filename str] checks if the named file
245       contains the given substring [str].
246
247       There is also a goal version of this function. *)
248
249 val url_contains_string : string -> string -> bool
250   (** [url_contains_string url str] downloads the URL and checks
251       whether the content contains the given substring [str].
252
253       There is also a goal version of this function. *)
254
255 val (//) : string -> string -> string
256   (** Concatenate two paths. *)
257
258 val quote : string -> string
259   (** Quote the string to make it safe to pass directly to the shell. *)
260
261 (** {2 Shell}
262
263     Call out to the Unix shell.  [/bin/sh] is used unless you set
264     {!shell} to some other value.  Note that the environment variable
265     [SHELL] is {i not} used.
266
267     {!sh}, {!shout}, {!shlines} work like [printf].  ie. You can
268     substitute variables using [%s], [%d] and so on.  For example:
269
270     {v
271       sh "rsync foo-%s.tar.gz example.com:/html/" version
272     }
273
274     Each invocation of {!sh} (etc) is a single shell (this is slightly
275     different from how [make] works).  For example:
276
277     {v
278       sh "
279         package=foo-%s
280         tarball=$package.tar.gz
281         cp $HOME/$tarball .
282         tar zxf $tarball
283         cd $package
284         ./configure
285         make
286      " version
287     }
288
289     The shell error mode is set such that if any single command
290     returns an error then the {!sh} function as a whole exits with
291     an error.  Write:
292     {v command ||: }
293     to ignore the result of a command.
294
295     Each shell runs in a new temporary directory.  The temporary
296     directory and all its contents is deleted after the shell exits.
297     If you want to save any data, [cd] somewhere.  The environment
298     variable [$builddir] is exported to the script.  This is the
299     current directory when the goaljobs program was started.
300
301     For example you could start the command sequence with
302     [cd $HOME/data/] or [cd $builddir].
303 *)
304
305 val sh : ('a, unit, string, unit) format4 -> 'a
306   (** Run the command(s). *)
307
308 val shout : ('a, unit, string, string) format4 -> 'a
309   (** Run the command(s).
310
311       Anything printed on stdout is returned as a string.
312       The trailing [\n] character, if any, is not returned. *)
313
314 val shlines : ('a, unit, string, string list) format4 -> 'a
315   (** Run the command(s).
316
317       Any lines printed to stdout is returned as a list of strings.
318       Trailing [\n] characters are not returned. *)
319
320 val shell : string ref
321   (** Set this variable to override the default shell ([/bin/sh]). *)
322
323 (** {2 String functions}
324
325     Most string functions are provided by the OCaml standard
326     library (see the module [String]).  For convenience some
327     extra functions are provided here. *)
328
329 (*
330 val replace_substring : string -> string -> string -> string
331   (** [replace_substring patt repl string] replaces all occurrences
332       of [patt] with [repl] in [string]. *)
333 *)
334
335 val change_file_extension : string -> string -> string
336   (** [change_file_extension ext filename] changes the file extension
337       of [filename] to [.ext].  For example
338       [change_file_extension "o" "main.c"] returns ["main.o"].
339       If the original filename has no extension, this function
340       adds the extension. *)
341
342 (*
343 val filter_file_extension : string -> string list -> string
344   (** [filter_file_extension ext filenames] returns only those
345       filenames in the list which have the given file extension.
346       For example [filter_file_extension "o" ["foo.c"; "bar.o"]]
347       would return [["bar.o"]] (a single element list). *)
348 *)
349
350 (** {2 Memory (persistent key/value storage)
351
352     "The Memory" is key/value storage which persists across goaljobs
353     sessions.  It is stored in the file [$HOME/.goaljobs-memory]
354     (which is a binary file, but you can delete it if you want).
355
356     The Memory is locked during accesses, so it is safe to read
357     or write it from multiple parallel goaljobs sessions.
358
359     Keys and values are strings.  The keys should be globally
360     unique, so it is suggested you use some application-specific
361     prefix.  eg: "myapp-key"
362
363     A common pattern is:
364
365     {v
366     let goal tested version =
367         let key = "myapp-tested-" ^ version in
368         target (memory_exists key);
369
370         ... some work to test version ...
371
372         memory_set key "1"
373     }
374
375     Note in that example the value ["1"] is arbitrary.  You just
376     want to store {i any} value so that a later call to {!memory_exists}
377     will succeed.
378 *)
379
380 val memory_exists : string -> bool
381   (** [memory_exists key] checks that the named [key] exists in
382       the Memory.  It doesn't matter what value it has.
383
384       This is also available as a goal, so you can write
385       [requires (memory_exists key)] *)
386
387 val memory_set : string -> string -> unit
388   (** Set [key] to [value] in the Memory. *)
389
390 val memory_get : string -> string option
391   (** Return the current value of [key] in the Memory.  Returns [None]
392       if the key has never been set or was deleted. *)
393
394 val memory_delete : string -> unit
395   (** Delete the [key].  If the key doesn't exist, has no effect. *)
396
397 (** {2 Publishing goals}
398
399     To "publish" a goal means it's available on the command line
400     for users to use directly.
401
402     Goals that have zero arguments are {b automatically published}.
403     So for example:
404
405     {v
406     let goal clean () = sh "rm *~"
407     }
408
409     can be used on the command line:
410
411     {v ./script clean }
412
413     The special goal called [all] (if it exists) is run implicitly
414     unless the user specifies another goal.  Unlike [make], there is
415     nothing special about the first rule in the file.
416
417     You can also publish goals, especially ones which take a non-zero
418     number of parameters, by calling {!publish}.
419 *)
420
421 val publish : string -> (string list -> unit) -> unit
422   (** Publish the named goal.
423
424       Use this function as in this example:
425
426       {v
427       let goal compiled program sources =
428         ... stuff for building the program from sources ...
429
430       let () = publish "compiled" (
431         fun args ->
432           let program = List.hd args in
433           let sources = List.tl args in
434           require (compiled program sources)
435       )
436       }
437
438       This could be used as follows:
439
440       {v ./script compiled program main.c utils.c }
441
442       You will notice you have to write a bit of OCaml code to
443       map the string arguments from the command line on to the
444       goal arguments.  In the example it means taking the first
445       string argument as the program name, and the rest of the
446       string arguments as the source filenames.  This is also
447       the place to perform string to int conversion, checks, and
448       so on (remember that OCaml is strongly typed). *)
449
450 (**/**)
451
452 (* Goal versions of some common functions.  You are using these
453  * versions when you write something like:
454  *   require (file_exists "foo");
455  * They work the same way as the regular function, except they die
456  * if the predicate returns false.
457  *)
458 val goal_file_exists : string -> unit
459 val goal_directory_exists : string -> unit
460 val goal_file_newer_than : string -> string -> unit
461 val goal_more_recent : string list -> string list -> unit
462 val goal_url_exists : string -> unit
463 val goal_file_contains_string : string -> string -> unit
464 val goal_url_contains_string : string -> string -> unit
465 val goal_memory_exists : string -> unit
466
467 (* A single call to this function is added by the 'goaljobs' script.
468  * It is responsible for parsing the command line and so on.
469  *)
470 val init : unit -> unit
471
472 (* Export this so the macros can catch these exceptions. *)
473 type goal_result_t = Goal_OK | Goal_failed of string
474 exception Goal_result of goal_result_t