X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=goaljobs.mli;h=43432f162a45c319a474574adc2256bcf02ce36e;hb=d6fddcd8789aa150d8c65d45a301df2a77a0686c;hp=043782665e608d48ae6cff1f203fc925ee4399c2;hpb=12f3afe1b516548612c67d9d25d08e5bf018c2f9;p=goaljobs.git diff --git a/goaljobs.mli b/goaljobs.mli index 0437826..43432f1 100644 --- a/goaljobs.mli +++ b/goaljobs.mli @@ -16,49 +16,16 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) -(** {1 Goaljobs library of useful helper functions.} *) +(** {1 Goaljobs library API} *) -(** {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 (more_recent [o_file] [c_file]); - - sh "cd $builddir && 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 source = - target (more_recent [program] [source]); - - require (compiled source); - - let object = change_file_extension "o" source in - sh "cd $builddir && cc %s -o %s" object program - } - -*) +(** {2 Target and require} *) val target : bool -> unit (** [target] {i condition} defines the target condition that {b will} - be met once the current rule has run. + be met once the current goal has run. + + You can think of the target as a promise or contract that you + make, which is met by running the rest of the goal. Goaljobs is much more flexible than [make]. In [make] only a single type of target is possible. The following are roughly @@ -67,30 +34,33 @@ val target : bool -> unit {v foo.o: foo.c ... + v} + {v let goal compiled () = target (more_recent ["foo.o"] ["foo.c"]); - requires (file_exists "foo.c"); + require (file_exists "foo.c"); ... - } + v} + + Targets in goaljobs can be any arbitrary expression. For + example, it can access network resources or test URLs. - Targets in goaljobs can be any arbitrary expression, and you - can have any number of different targets. + Almost every goal should have one target, which should + accurately state the outcome once the goal has been run. - Almost every rule should have one or more targets, which should - accurately state the outcome once the rule has been run. + It is possible to have no target. This means the goal + always runs (like using "force" in make). - If you have more than one [target]s then it's as if they have - been ORed together ({b not} ANDed which you might expect). - You can make this explicit by using a single target and [&&] - or [||] between the expressions. See also {!target_all} - and {!target_exists}. + You should not have multiple targets in a single goal. They + won't work the way you expect, and future versions of goaljobs + will likely stop you from doing this. - Normally you put the target(s) early on in the rule, before any + Normally you put the target(s) early on in the goal, 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. *) + ensure the goal runs most efficiently since if the target is met + already then the rest of the goal doesn't run. *) val target_all : bool list -> unit (** [target_all [t1; t2; ...]] is the same as writing @@ -100,65 +70,24 @@ val target_exists : bool list -> unit (** [target_exists [t1; t2; ...]] is the same as writing [target (t1 || t2 || ...)] *) -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. +val require : string -> (unit -> unit) -> unit + (** [require] {i goal} defines the requirements of this goal, that + is, other goals that have to be met before the rest of the + goal 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 very 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 Periodic jobs} - - If you want to have a rule that runs when some outside event - happens you have three choices: Manually run the script (this is - basically what [make] forces you to do). Have some sort of hook - that runs the script (eg. a git hook). Or use a periodic job to - poll for an event or change. - - Periodic jobs run regularly to poll for an outside event or - change. If a script has periodic jobs, then it runs continuously - (or until you kill it). - - An example of a script that checks for new git commits and when - it sees one it will ensure it passes the tests: - - {v - let repo = Sys.getenv "HOME" // "repo" - - let goal git_commit_tested commit = - let key = sprintf "repo-tested-%s" commit in - target (memory_exists key); - - sh " - git clone %s test - cd test - ./configure - make - make check - "; - - (* Record that this commit was tested successfully. *) - memory_set key "1" - - every 30 minutes (fun () -> - let commit = shout "cd %s && git rev-parse HEAD" repo in - (* Require that this commit has been tested. *) - require (git_commit_tested commit) - ) - } - - Some notes about the above example: Firstly only the current HEAD - commit is required to be tested. This is because older commits - are irrelevant and because if they failed the test before there is - not point retesting them (commits are immutable). Secondly we use - the Memory to remember that we have successfully tested a commit. - This is what stops the program from repeatedly testing the same - commit. *) + Some very simple goals don't need any [require]s. You can + have as many [require]s as you need in a goal, and you can + use a loop or make them conditional if you want. + + Unlike [make], the requirements of a goal can be + placed anywhere within the goal, as long as you put them + before they are needed. *) + +(** {2 Periodic jobs} *) (* This is what lets you write '30 minutes' etc: *) type period_t = Seconds | Days | Months | Years @@ -197,7 +126,7 @@ 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");} + {v require (file_exists "somefile"); v} will die unless ["somefile"] exists. *) val directory_exists : string -> bool @@ -215,7 +144,7 @@ val file_newer_than : string -> string -> bool val more_recent : string list -> string list -> bool (** [more_recent objects sources] expresses the [make] relationship: - {v object(s) ...: source(s) ...} + {v object(s) ...: source(s) ... v} in a convenient way: @@ -223,7 +152,7 @@ val more_recent : string list -> string list -> bool let goal built objects sources = target (more_recent objects sources); ... code to rebuild ... - } + v} It is roughly equivalent to checking that all the object files exist and are newer than all of the source files. @@ -258,64 +187,34 @@ val (//) : string -> string -> string val quote : string -> string (** Quote the string to make it safe to pass directly to the shell. *) -(** {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. The environment - variable [$builddir] is exported to the script. This is the - current directory when the goaljobs program was started. - - For example you could start the command sequence with - [cd $HOME/data/] or [cd $builddir]. -*) +(** {2 Shell} *) + +val sh : ?tmpdir:bool -> ('a, unit, string, unit) format4 -> 'a + (** Run the command(s). -val sh : ('a, unit, string, unit) format4 -> 'a - (** Run the command(s). *) + The command runs in a newly created temporary directory (which + is deleted after the command exits), {i unless} you use + [~tmpdir:false]. *) -val shout : ('a, unit, string, string) format4 -> 'a +val shout : ?tmpdir:bool -> ('a, unit, string, string) format4 -> 'a (** Run the command(s). Anything printed on stdout is returned as a string. - The trailing [\n] character, if any, is not returned. *) + The trailing [\n] character, if any, is not returned. + + The command runs in a newly created temporary directory (which + is deleted after the command exits), {i unless} you use + [~tmpdir:false]. *) -val shlines : ('a, unit, string, string list) format4 -> 'a +val shlines : ?tmpdir:bool -> ('a, unit, string, string list) format4 -> 'a (** Run the command(s). Any lines printed to stdout is returned as a list of strings. - Trailing [\n] characters are not returned. *) + Trailing [\n] characters are not returned. + + The command runs in a newly created temporary directory (which + is deleted after the command exits), {i unless} you use + [~tmpdir:false]. *) val shell : string ref (** Set this variable to override the default shell ([/bin/sh]). *) @@ -347,42 +246,14 @@ val filter_file_extension : string -> string list -> string would return [["bar.o"]] (a single element list). *) *) -(** {2 Memory (persistent key/value storage) - - "The Memory" is key/value storage which persists across goaljobs - sessions. It is stored in the file [$HOME/.goaljobs-memory] - (which is a binary file, but you can delete it if you want). - - The Memory is locked during accesses, so it is safe to read - or write it from multiple parallel goaljobs sessions. - - Keys and values are strings. The keys should be globally - unique, so it is suggested you use some application-specific - prefix. eg: "myapp-key" - - A common pattern is: - - {v - let goal tested version = - let key = "myapp-tested-" ^ version in - target (memory_exists key); - - ... some work to test version ... - - memory_set key "1" - } - - Note in that example the value ["1"] is arbitrary. You just - want to store {i any} value so that a later call to {!memory_exists} - will succeed. -*) +(** {2 Memory (persistent key/value storage)} *) val memory_exists : string -> bool (** [memory_exists key] checks that the named [key] exists in the Memory. It doesn't matter what value it has. This is also available as a goal, so you can write - [requires (memory_exists key)] *) + [require (memory_exists key)] *) val memory_set : string -> string -> unit (** Set [key] to [value] in the Memory. *) @@ -394,29 +265,7 @@ val memory_get : string -> string option val memory_delete : string -> unit (** Delete the [key]. If the key doesn't exist, has no effect. *) -(** {2 Publishing goals} - - To "publish" a goal means it's available on the command line - for users to use directly. - - Goals that have zero arguments are {b automatically published}. - So for example: - - {v - let goal clean () = sh "rm *~" - } - - can be used on the command line: - - {v ./script clean } - - The special goal called [all] (if it exists) is run implicitly - unless the user specifies another goal. Unlike [make], there is - nothing special about the first rule in the file. - - You can also publish goals, especially ones which take a non-zero - number of parameters, by calling {!publish}. -*) +(** {2 Publishing goals} *) val publish : string -> (string list -> unit) -> unit (** Publish the named goal. @@ -433,11 +282,11 @@ val publish : string -> (string list -> unit) -> unit let sources = List.tl args in require (compiled program sources) ) - } + v} This could be used as follows: - {v ./script compiled program main.c utils.c } + {v ./script compiled program main.c utils.c v} You will notice you have to write a bit of OCaml code to map the string arguments from the command line on to the @@ -447,6 +296,28 @@ val publish : string -> (string list -> unit) -> unit the place to perform string to int conversion, checks, and so on (remember that OCaml is strongly typed). *) +(** {2 Logging script output} *) + +val log_program_output : unit -> string + (** [log_program_output] should be called at most once, usually at + the top-level of the script. It creates a temporary file + and redirects stdout and stderr into this file (they are still + sent to the ordinary output, so it acts like [tee]). The + filename of the temporary file is returned. *) + +(** {2 Sending email} *) + +val mailto : ?from:string -> subject:string -> ?attach:string list-> string -> unit + (** Send email. + + Optional [?from] is the sender email address. + + Required [~subject] is the subject line. + + Optional [?attach] is a list of attachments (filenames). + + The bare argument is the destination email address. *) + (**/**) (* Goal versions of some common functions. You are using these @@ -472,3 +343,7 @@ val init : unit -> unit (* Export this so the macros can catch these exceptions. *) type goal_result_t = Goal_OK | Goal_failed of string exception Goal_result of goal_result_t + +(* Called to print debug message when we enter or leave a goal. *) +val _enter_goal : string -> unit +val _leave_goal : string -> unit