Initial revision.
[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 (file_exists o_file);
39         target (file_newer_than o_file c_file);
40
41         sh "cc -c %s -o %s" c_file o_file
42     }
43
44     In the second example, the rule requires that several files
45     have been compiled ([require (compiled ...)]
46     before it can link the final program:
47
48     {v
49     let goal built program sources =
50         target (file_exists program);
51         target (file_newer_than program sources);
52
53         List.iter (fun s -> require (compiled s)) sources;
54
55         let objects = List.map (change_file_extension "o") sources in
56         sh "cc %s -o %s" (String.concat " " objects) program
57     }
58
59 *)
60
61 val target : bool -> unit
62   (** [target] {i predicate} defines the target condition that will
63       be met once the current rule has run.
64
65       Goaljobs is much more flexible than [make].  In [make] only a
66       single type of target is possible.  The following are roughly
67       equivalent:
68
69       {v
70       foo.o: foo.c
71         ...
72
73       let goal compiled () =
74         target (file_exists "foo.o");
75         target (file_newer_than "foo.o" "foo.c");
76         ...
77       }
78
79       Targets in goaljobs can be any arbitrary expression, and you
80       can have any number of different targets.
81
82       Almost every rule should have one or more targets, which should
83       accurately state the outcome once the rule has been run
84
85       Normally you put the target(s) early on in the rule, before any
86       running code and before any [require]s.  This is not a
87       hard-and-fast rule and it is not enforced, but doing it will
88       ensure the rule runs most efficiently since if the target is met
89       already then the rest of the rule doesn't run. *)
90
91 val require : unit -> unit
92   (** [require] {!goal} defines the requirements of this rule, that
93       is, other goals that have to be met before this rule is able to run.
94
95       In terms of [make], [require]s are roughly equivalent to the
96       right hand side after the [:], but in goaljobs the requirements
97       can be much richer than simply "that file must exist".
98
99       Some simple rules don't need any [require]s.  Unlike with [make],
100       the requirements of a rule can be placed anywhere within the
101       rule, as long as you put them before they are needed. *)
102
103 (** {2 File and URL testing}
104
105     Various functions to test the existence of files, URLs.
106 *)
107
108 val file_exists : string -> bool
109   (** Return true if the named file exists.
110
111       This function also exists as a goal.  Writing:
112       {v require (file_exists "somefile");}
113       will die unless ["somefile"] exists. *)
114
115 val file_newer_than : string -> string -> bool
116   (** [file_newer_than file_a file_b] returns true if [file_a] is
117       newer than [file_b].  Note that if [file_a] does not exist, it
118       returns false.  If [file_b] does not exist, it is an error. *)
119
120 val url_exists : string -> bool
121   (** The URL is tested to see if it exists.
122
123       This function also exists as a goal.  Writing:
124       {v require (url_exists "http://example.com");}
125       will die unless the given URL exists. *)
126
127 (** {2 Shell}
128
129     Call out to the Unix shell.  [/bin/sh] is used unless you set
130     {!shell} to some other value.  Note that the environment variable
131     [SHELL] is {i not} used.
132
133     {!sh}, {!shout}, {!shlines} work like [printf].  ie. You can
134     substitute variables using [%s], [%d] and so on.  For example:
135
136     {v
137       sh "rsync foo-%s.tar.gz example.com:/html/" version
138     }
139
140     Each invocation of {!sh} (etc) is a single shell (this is slightly
141     different from how [make] works).  For example:
142
143     {v
144       sh "
145         package=foo-%s
146         tarball=$package.tar.gz
147         cp $HOME/$tarball .
148         tar zxf $tarball
149         cd $package
150         ./configure
151         make
152      " version
153     }
154
155     The shell error mode is set such that if any single command
156     returns an error then the {!sh} function as a whole exits with
157     an error.  Write:
158     {v command ||: }
159     to ignore the result of a command.
160
161     Each shell runs in a new temporary directory.  The temporary directory
162     and all its contents is deleted after the shell exits.  If you
163     want to save any data, [cd] somewhere.  For example you could start
164     the command sequence with:
165     {v cd $HOME/data/ }
166 *)
167
168 val sh : ('a, unit, string, unit) format4 -> 'a -> unit
169   (** Run the command(s). *)
170
171 (*
172 val shout : ('a, unit, string) format -> 'a
173   (** Run the command(s).  Anything printed on stdout is returned
174       as a single string (the trailing [\n] character, if any,
175       is not returned). *)
176
177 val shlines : ('a, unit, string) format -> 'a
178   (** Run the command(s).  Any lines printed to stdout are returned
179       as a list of strings.  Trailing [\n] characters not returned. *)
180
181 val shell : string ref
182   (** Set this variable to override the default shell ([/bin/sh]). *)
183 *)
184
185 (** {2 String functions}
186
187     Most string functions are provided by the OCaml standard
188     library (see the module [String]).  For convenience some
189     extra functions are provided here. *)
190
191 val replace_substring : string -> string -> string -> string
192   (** [replace_substring patt repl string] replaces all occurrences
193       of [patt] with [repl] in [string]. *)
194
195 val change_file_extension : string -> string -> string
196   (** [change_file_extension ext filename] changes the file extension
197       of [filename] to [.ext].  For example
198       [change_file_extension "o" "main.c"] returns ["main.o"].
199       If the original filename has no extension, this function
200       adds the extension. *)
201
202 val filter_file_extension : string -> string list -> string
203   (** [filter_file_extension ext filenames] returns only those
204       filenames in the list which have the given file extension.
205       For example [filter_file_extension "o" ["foo.c"; "bar.o"]]
206       would return [["bar.o"]] (a single element list). *)
207
208 (**/**)
209
210 (* Goal versions of some common functions.  You are using these
211  * versions when you write something like:
212  *   require (file_exists "foo");
213  * They work the same way as the regular function, except they die
214  * if the predicate returns false.
215  *)
216 val goal_file_exists : string -> unit
217 val goal_file_newer_than : string -> string -> unit
218 val goal_url_exists : string -> unit