(* goaljobs * Copyright (C) 2013 Red Hat Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) (** {1 Goaljobs library of useful helper functions.} *) (** {2 Targets and requires} These are used to write goals. Normally you write a goal with one or more [target]s and zero or more [require]s, as the examples below should make clear. In the first example, there are two targets: that [o_file] (object) exists, and that it is newer than [c_file] (source). The rule meets that target by running the C compiler ([cc]) which, if it succeeds, will ensure that the object file exists and is newer than the source file. {v let goal compiled c_file = let o_file = change_file_extension "o" c_file in target (file_exists o_file); target (file_newer_than o_file c_file); sh "cc -c %s -o %s" c_file o_file } In the second example, the rule requires that several files have been compiled ([require (compiled ...)] before it can link the final program: {v let goal built program sources = target (file_exists program); target (file_newer_than program sources); List.iter (fun s -> require (compiled s)) sources; let objects = List.map (change_file_extension "o") sources in sh "cc %s -o %s" (String.concat " " objects) program } *) val target : bool -> unit (** [target] {i predicate} defines the target condition that will be met once the current rule has run. Goaljobs is much more flexible than [make]. In [make] only a single type of target is possible. The following are roughly equivalent: {v foo.o: foo.c ... let goal compiled () = target (file_exists "foo.o"); target (file_newer_than "foo.o" "foo.c"); ... } Targets in goaljobs can be any arbitrary expression, and you can have any number of different targets. Almost every rule should have one or more targets, which should accurately state the outcome once the rule has been run Normally you put the target(s) early on in the rule, before any running code and before any [require]s. This is not a hard-and-fast rule and it is not enforced, but doing it will ensure the rule runs most efficiently since if the target is met already then the rest of the rule doesn't run. *) val require : unit -> unit (** [require] {!goal} defines the requirements of this rule, that is, other goals that have to be met before this rule is able to run. In terms of [make], [require]s are roughly equivalent to the right hand side after the [:], but in goaljobs the requirements can be much richer than simply "that file must exist". Some simple rules don't need any [require]s. Unlike with [make], the requirements of a rule can be placed anywhere within the rule, as long as you put them before they are needed. *) (** {2 File and URL testing} Various functions to test the existence of files, URLs. *) val file_exists : string -> bool (** Return true if the named file exists. This function also exists as a goal. Writing: {v require (file_exists "somefile");} will die unless ["somefile"] exists. *) val file_newer_than : string -> string -> bool (** [file_newer_than file_a file_b] returns true if [file_a] is newer than [file_b]. Note that if [file_a] does not exist, it returns false. If [file_b] does not exist, it is an error. *) val url_exists : string -> bool (** The URL is tested to see if it exists. This function also exists as a goal. Writing: {v require (url_exists "http://example.com");} will die unless the given URL exists. *) (** {2 Shell} Call out to the Unix shell. [/bin/sh] is used unless you set {!shell} to some other value. Note that the environment variable [SHELL] is {i not} used. {!sh}, {!shout}, {!shlines} work like [printf]. ie. You can substitute variables using [%s], [%d] and so on. For example: {v sh "rsync foo-%s.tar.gz example.com:/html/" version } Each invocation of {!sh} (etc) is a single shell (this is slightly different from how [make] works). For example: {v sh " package=foo-%s tarball=$package.tar.gz cp $HOME/$tarball . tar zxf $tarball cd $package ./configure make " version } The shell error mode is set such that if any single command returns an error then the {!sh} function as a whole exits with an error. Write: {v command ||: } to ignore the result of a command. Each shell runs in a new temporary directory. The temporary directory and all its contents is deleted after the shell exits. If you want to save any data, [cd] somewhere. For example you could start the command sequence with: {v cd $HOME/data/ } *) val sh : ('a, unit, string, unit) format4 -> 'a -> unit (** Run the command(s). *) (* val shout : ('a, unit, string) format -> 'a (** Run the command(s). Anything printed on stdout is returned as a single string (the trailing [\n] character, if any, is not returned). *) val shlines : ('a, unit, string) format -> 'a (** Run the command(s). Any lines printed to stdout are returned as a list of strings. Trailing [\n] characters not returned. *) val shell : string ref (** Set this variable to override the default shell ([/bin/sh]). *) *) (** {2 String functions} Most string functions are provided by the OCaml standard library (see the module [String]). For convenience some extra functions are provided here. *) val replace_substring : string -> string -> string -> string (** [replace_substring patt repl string] replaces all occurrences of [patt] with [repl] in [string]. *) val change_file_extension : string -> string -> string (** [change_file_extension ext filename] changes the file extension of [filename] to [.ext]. For example [change_file_extension "o" "main.c"] returns ["main.o"]. If the original filename has no extension, this function adds the extension. *) val filter_file_extension : string -> string list -> string (** [filter_file_extension ext filenames] returns only those filenames in the list which have the given file extension. For example [filter_file_extension "o" ["foo.c"; "bar.o"]] would return [["bar.o"]] (a single element list). *) (**/**) (* Goal versions of some common functions. You are using these * versions when you write something like: * require (file_exists "foo"); * They work the same way as the regular function, except they die * if the predicate returns false. *) val goal_file_exists : string -> unit val goal_file_newer_than : string -> string -> unit val goal_url_exists : string -> unit