* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*)
-(** {1 Goaljobs library of useful helper functions.} *)
+(** {1 Goaljobs library API} *)
-(** {2 Target and require}
-
- These are used to write goals.
-
- Normally you write a goal with zero or one [target] and
- zero or more [require]s, as the examples below should make
- clear.
-
- In the first example, the target is that the [o_file] (object) exists
- and is newer than the [c_file] (source). The goal
- 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
- v}
-
- In the second example, the goal 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
- v}
-
-*)
+(** {2 Target and require} *)
val target : bool -> unit
(** [target] {i condition} defines the target condition that {b will}
(** [target_exists [t1; t2; ...]] is the same as writing
[target (t1 || t2 || ...)] *)
-val require : (unit -> unit) -> unit
+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.
placed anywhere within the goal, as long as you put them
before they are needed. *)
-(** {2 Periodic jobs}
-
- If you want to have a goal 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)
- )
- v}
-
- 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. *)
+(** {2 Periodic jobs} *)
(* This is what lets you write '30 minutes' etc: *)
type period_t = Seconds | Days | Months | Years
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
- v}
-
- 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.
-val shlines : ('a, unit, string, string list) format4 -> 'a
+ The command runs in a newly created temporary directory (which
+ is deleted after the command exits), {i unless} you use
+ [~tmpdir:false]. *)
+
+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]). *)
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"
- v}
-
- 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
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 *~"
- v}
-
- can be used on the command line:
-
- {v ./script clean v}
-
- 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 goal 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.
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
(* 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