2 * Copyright (C) 2013 Red Hat Inc.
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.
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.
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.
19 (** {1 Goaljobs library of useful helper functions.} *)
21 (** {2 Targets and requires}
23 These are used to write goals.
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
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
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]);
40 sh "cc -c %s -o %s" c_file o_file
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:
48 let goal built program source =
49 target (more_recent [program] [source]);
51 require (compiled source);
53 let object = change_file_extension "o" source in
54 sh "cc %s -o %s" object program
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.
63 Goaljobs is much more flexible than [make]. In [make] only a
64 single type of target is possible. The following are roughly
71 let goal compiled () =
72 target (more_recent ["foo.o"] ["foo.c"]);
73 requires (file_exists "foo.c");
77 Targets in goaljobs can be any arbitrary expression, and you
78 can have any number of different targets.
80 Almost every rule should have one or more targets, which should
81 accurately state the outcome once the rule has been run.
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}
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. *)
95 val target_all : bool list -> unit
96 (** [target_all [t1; t2; ...]] is the same as writing
97 [target (t1 && t2 && ...)] *)
99 val target_exists : bool list -> unit
100 (** [target_exists [t1; t2; ...]] is the same as writing
101 [target (t1 || t2 || ...)] *)
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.
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".
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. *)
115 (** {2 File and URL testing}
117 Various functions to test the existence of files, URLs.
120 val file_exists : string -> bool
121 (** Return true if the named file exists.
123 This function also exists as a goal. Writing:
124 {v require (file_exists "somefile");}
125 will die unless ["somefile"] exists. *)
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. *)
132 val more_recent : string list -> string list -> bool
133 (** [more_recent objects sources] expresses the [make] relationship:
135 {v object(s) ...: source(s) ...}
140 let goal built objects sources =
141 target (more_recent objects sources);
142 ... code to rebuild ...
145 It is roughly equivalent to checking that all the object files
146 exist and are newer than all of the source files.
148 Note that both parameters are lists (since in [make] you can
149 have a list of source files and a list of object files). If you
150 don't want a list, pass a single-element list containing the
151 single the object/source file. *)
153 val url_exists : string -> bool
154 (** The URL is tested to see if it exists.
156 This function also exists as a goal. Writing:
157 {v require (url_exists "http://example.com");}
158 will die unless the given URL exists. *)
162 Call out to the Unix shell. [/bin/sh] is used unless you set
163 {!shell} to some other value. Note that the environment variable
164 [SHELL] is {i not} used.
166 {!sh}, {!shout}, {!shlines} work like [printf]. ie. You can
167 substitute variables using [%s], [%d] and so on. For example:
170 sh "rsync foo-%s.tar.gz example.com:/html/" version
173 Each invocation of {!sh} (etc) is a single shell (this is slightly
174 different from how [make] works). For example:
179 tarball=$package.tar.gz
188 The shell error mode is set such that if any single command
189 returns an error then the {!sh} function as a whole exits with
192 to ignore the result of a command.
194 Each shell runs in a new temporary directory. The temporary directory
195 and all its contents is deleted after the shell exits. If you
196 want to save any data, [cd] somewhere. For example you could start
197 the command sequence with:
201 val sh : ('a, unit, string, unit) format4 -> 'a
202 (** Run the command(s). *)
204 val shout : ('a, unit, string, string) format4 -> 'a
205 (** Run the command(s).
207 Anything printed on stdout is returned as a string.
208 The trailing [\n] character, if any, is not returned. *)
210 val shlines : ('a, unit, string, string list) format4 -> 'a
211 (** Run the command(s).
213 Any lines printed to stdout is returned as a list of strings.
214 Trailing [\n] characters are not returned. *)
217 val shell : string ref
218 (** Set this variable to override the default shell ([/bin/sh]). *)
221 (** {2 String functions}
223 Most string functions are provided by the OCaml standard
224 library (see the module [String]). For convenience some
225 extra functions are provided here. *)
228 val replace_substring : string -> string -> string -> string
229 (** [replace_substring patt repl string] replaces all occurrences
230 of [patt] with [repl] in [string]. *)
233 val change_file_extension : string -> string -> string
234 (** [change_file_extension ext filename] changes the file extension
235 of [filename] to [.ext]. For example
236 [change_file_extension "o" "main.c"] returns ["main.o"].
237 If the original filename has no extension, this function
238 adds the extension. *)
241 val filter_file_extension : string -> string list -> string
242 (** [filter_file_extension ext filenames] returns only those
243 filenames in the list which have the given file extension.
244 For example [filter_file_extension "o" ["foo.c"; "bar.o"]]
245 would return [["bar.o"]] (a single element list). *)
248 (** {2 Memory (persistent key/value storage)
250 "The Memory" is key/value storage which persists across goaljobs
251 sessions. It is stored in the file [$HOME/.goaljobs-memory]
252 (which is a binary file, but you can delete it if you want).
254 The Memory is locked during accesses, so it is safe to read
255 or write it from multiple parallel goaljobs sessions.
257 Keys and values are strings. The keys should be globally
258 unique, so it is suggested you use some application-specific
259 prefix. eg: "myapp-key"
264 let goal tested version =
265 let key = "myapp-tested-" ^ version in
266 target (memory_exists key);
268 ... some work to test version ...
273 Note in that example the value ["1"] is arbitrary. You just
274 want to store {i any} value so that a later call to {!memory_exists}
278 val memory_exists : string -> bool
279 (** [memory_exists key] checks that the named [key] exists in
280 the Memory. It doesn't matter what value it has.
282 This is also available as a goal, so you can write
283 [requires (memory_exists key)] *)
285 val memory_set : string -> string -> unit
286 (** Set [key] to [value] in the Memory. *)
288 val memory_get : string -> string option
289 (** Return the current value of [key] in the Memory. Returns [None]
290 if the key has never been set or was deleted. *)
292 val memory_delete : string -> unit
293 (** Delete the [key]. If the key doesn't exist, has no effect. *)
297 (* Goal versions of some common functions. You are using these
298 * versions when you write something like:
299 * require (file_exists "foo");
300 * They work the same way as the regular function, except they die
301 * if the predicate returns false.
303 val goal_file_exists : string -> unit
304 val goal_file_newer_than : string -> string -> unit
305 val goal_more_recent : string list -> string list -> unit
306 val goal_url_exists : string -> unit
307 val goal_memory_exists : string -> unit
309 (* Export this so the macros can catch these exceptions. *)
310 type goal_result_t = Goal_OK | Goal_failed of string
311 exception Goal_result of goal_result_t