* 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}
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 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. If you don't
- want the temporary directory creation, use [~tmpdir:false].
-
- The environment variable [$builddir] is exported to the script.
- This is the current directory when the goaljobs program was started.
-
- 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
- v}
-
- 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 ||: v}
- to ignore the result of a command.
-
-*)
+(** {2 Shell} *)
val sh : ?tmpdir:bool -> ('a, unit, string, unit) format4 -> 'a
(** Run the command(s).
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