*.cmxa
*.o
*.a
+*.1
Makefile.in
Makefile
goaljobs_config.ml.in \
goaljobs.ml \
goaljobs.mli \
+ goaljobs.pod \
+ goaljobs-reference.pod \
goaljobs.spec \
goaljobs.spec.in \
+ html/.gitignore \
META.in \
NOTES \
pa_goal.ml \
rpm: dist
rpmbuild -ta $(PACKAGE_NAME)-$(PACKAGE_VERSION).tar.gz
+if HAVE_POD2MAN
+
+man_MANS = goaljobs.1 goaljobs-reference.1
+
+goaljobs.1: goaljobs.pod
+ $(POD2MAN) -c "Goaljobs" --release $(PACKAGE)-$(VERSION) $< > $@-t
+ mv $@-t $@
+
+goaljobs-reference.1: goaljobs-reference.pod
+ $(POD2MAN) -c "Goaljobs" --release $(PACKAGE)-$(VERSION) $< > $@-t
+ mv $@-t $@
+
+endif
+
if HAVE_OCAMLDOC
# HTML library documentation.
[test "x$OCAMLDOC" != "xno"])
dnl Check for POD (for manual pages).
-AC_CHECK_PROG(PERLDOC,perldoc,perldoc)
-if test "x$PERLDOC" = "x"; then
- AC_MSG_ERROR([You must install the 'perldoc' program])
+AC_CHECK_PROG(POD2MAN,pod2man,pod2man,no)
+if test "x$POD2MAN" = "x"; then
+ AC_MSG_ERROR([You must install the 'pod2man' program])
fi
+AM_CONDITIONAL([HAVE_POD2MAN],
+ [test "x$POD2MAN" != "xno"])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([goaljobs_config.ml
--- /dev/null
+=encoding utf8
+
+=head1 NAME
+
+goaljobs-reference - reference documentation for writing goaljobs scripts
+
+=head1 SUMMARY
+
+ open Unix
+ open Printf
+ open Goaljobs
+
+ let goal name args... =
+ target (condition);
+ require (goal1 ...);
+ require (goal2 ...);
+ (* code to implement the goal *)
+
+ let goal all () =
+ require (name args...)
+
+ every 30 minutes (
+ fun () ->
+ require (goal1 ...)
+ )
+
+=head1 DESCRIPTION
+
+Goaljobs is a flexible build system and business rules manager similar
+to make and cron, but much more powerful. You can use it to automate
+many complex tasks that have multiple steps (even with manual steps)
+that have to be carried out in dependency order.
+
+For a tutorial-like introduction to goaljobs, see
+L<http://rwmj.wordpress.com/tag/goaljobs/>
+
+For examples, see the C<examples/> directory in the source and
+L<http://git.annexia.org/?p=goals.git;a=summary>
+
+For reference documentation on how to write scripts, see below.
+
+Note this man page does not cover the whole Goaljobs API. To read
+about the Goaljobs API, look for the file C<goaljobs.mli> (in the
+source code or installed as part of the goaljobs package), or see the
+HTML documentation installed as part of the goaljobs package.
+
+=head1 THE SCRIPT FILE
+
+The script file should usually start with opening these modules (none
+of this are required, it's just useful to have them open):
+
+ open Unix
+ open Printf
+ open Goaljobs
+
+This is followed by goals and/or functions and/or top-level OCaml
+statements and/or C<every> statements (a.k.a periodic jobs).
+
+You can use multiple script files to make up a goaljobs program. You
+have to list them in dependency order on the goaljobs command line
+(earlier files required by later files), the same way that the OCaml
+compiler works. So usually you end up writing:
+
+ goaljobs utils.ml another_library.ml script.ml
+
+where C<script.ml> requires the functions from the utils/library.
+Note that circular dependencies are not possible.
+
+=head1 GOALS
+
+Each goal should have the following basic form:
+
+ let goal name args... =
+ target (condition);
+ require (goal1 ...);
+ require (goal2 ...);
+ (* code to implement the goal *)
+
+There is no hard-and-fast rule about this. In particular you can put
+arbitrary OCaml statements anywhere inside a goal (since a goal is
+just a special form of OCaml function), but sticking to this overall
+plan is a good idea.
+
+There should be zero or one target. Multiple target statements should
+not be used in a goal. The target should come as early as possible,
+and the target condition should be as simple and fast to evaluate as
+is practical (see L</THE MEMORY> below).
+
+There should be zero or any number of C<require> statements. Each
+require statement should name a single goal (with optional parameters
+for that goal).
+
+After that should come the code that implements the goal, which might
+be, for example, a series of shell commands, but could even be
+user-interactive.
+
+As with ordinary OCaml functions, you can define goals recursively
+or with mutual recursion using:
+
+ let rec goal1 args... =
+ ...
+ and goal2 args... =
+ ...
+ and goal3 args... =
+ ...
+
+A goal can also have no arguments:
+
+ let goal all () =
+ ...
+
+This defines the common goal called C<all>, which acts the same way as
+C<make all>, ie. if you run the program without any arguments, it will
+run the C<all> goal if one exists.
+
+=head2 PUBLISHING GOALS
+
+If a goal is "published" it means it is available to be run directly
+from the command line. All no-arg goals are published by default.
+You do not need to do anything special for them. For example:
+
+ let goal clean () = sh "rm *~"
+
+can be used on the command line:
+
+ ./myscript clean
+
+For goals which take any parameters, you have to define a small code
+snippet that converts command line arguments to goal parameters (the
+reason has to do with OCaml being strongly typed, and because goal
+parameters might not all be strings).
+
+ let goal compile program sources =
+ target (more_recent [program] sources);
+ ...
+
+ let () =
+ publish "compile" (
+ fun args ->
+ let program = List.hd args in
+ let sources = List.tl args in
+ require (compiled program sources)
+ )
+
+Then you can use:
+
+ ./myscript compile program main.c utils.c
+
+=head1 TARGET AND REQUIRE
+
+The target is promise or contract that you make that the given
+condition I<will> be true when the goal has finished running.
+
+In the first example, the target is that the C<o_file> (object) exists
+and is newer than the C<c_file> (source). The goal meets that target
+by running the C compiler (C<cc>) which, if it succeeds, will ensure
+that the object file exists and is newer than the source file.
+
+ 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 goal requires that several files have been
+compiled (C<require (compiled ...)>) before it can link the final
+program:
+
+ 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
+
+=head1 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 C<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:
+
+ 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
+ " repo_url;
+
+ (* 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.
+
+=head1 SHELL
+
+You can call out to the Unix shell using one of the functions
+C<Goaljobs.sh>, C<Goaljobs.shout> or C<Goaljobs.shlines>. (These
+functions are documented in the C<goaljobs.mli> file / API
+documentation.)
+
+C<sh> runs the command(s). C<shout> collects the output of the
+command (to stdout only) and returns it as a single string.
+C<shlines> collects the output and returns it as a list of lines.
+
+C<sh>, C<shout>, C<shlines> work like printf. ie. You can substitute
+variables using C<%s>, C<%d> and so on. For example:
+
+ sh "rsync foo-%s.tar.gz example.com:/html/" version
+
+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, C<cd> somewhere. If you don't want the temporary
+directory creation, use C<~tmpdir:false>.
+
+The environment variable C<$builddir> is exported to the script. This
+is the current directory when the goaljobs program was started.
+
+Each invocation of C<sh> (etc) is a single shell (this is slightly
+different from how C<make> works). For example:
+
+ 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 C<sh> function as a whole exits with
+an error. Write:
+
+ command ||:
+
+to ignore the result of a command.
+
+C</bin/sh> is used unless you set C<Goaljobs.shell> to some other
+value. Note that the environment variable C<SHELL> is I<never> used.
+
+=head1 THE MEMORY
+
+"The Memory" is key/value storage which persists across goaljobs
+sessions. It is stored in the file C<$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:
+C<myapp-key>
+
+A common pattern is:
+
+let goal tested version =
+ let key = "myapp-tested-" ^ version in
+ target (memory_exists key);
+
+ (* some code to test this version *)
+
+ memory_set key "1"
+
+Note in that example the value C<1> is arbitrary. You just want to
+store I<any> value so that a later call to C<memory_exists> will
+succeed.
+
+For information about C<Goaljobs.memory_*> APIs see the
+C<goaljobs.mli> file / API documentation.
+
+=head1 FILES
+
+=over 4
+
+=item C</bin/sh>
+
+This is the default shell used by C<sh*> APIs. You can change
+the shell by setting the C<Goaljobs.shell> reference.
+
+=item C<curl>
+
+The curl program (on the path) is used to check for and download
+URLs by APIs such as C<Goaljobs.url_exists>.
+
+=item C<~/.goaljobs-memory>
+
+Persistent key/value store used when you use the C<Goaljobs.memory_*>
+APIs.
+
+=back
+
+=head1 ENVIRONMENT VARIABLES
+
+=over 4
+
+=item B<builddir>
+
+This environment variable is set to the current directory when the
+goals program starts, and is available in goals, shell scripts, etc.
+
+=back
+
+=head1 SEE ALSO
+
+L<goaljobs(1)>
+
+=head1 AUTHORS
+
+Richard W.M. Jones <rjones @ redhat . com>
+
+=head1 COPYRIGHT
+
+(C) Copyright 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.
* 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.
--- /dev/null
+=encoding utf8
+
+=head1 NAME
+
+goaljobs - make and cron replacement and business rules manager
+
+=head1 SUMMARY
+
+ goaljobs [-o output] [lib.ml...] goals.ml
+
+ ./goals -l
+
+ ./goals all
+ ./goals
+
+=head1 DESCRIPTION
+
+Goaljobs is a flexible build system and business rules manager similar
+to make and cron, but much more powerful. You can use it to automate
+many complex tasks that have multiple steps (even with manual steps)
+that have to be carried out in dependency order.
+
+=head2 GETTING STARTED
+
+For a tutorial-like introduction to goaljobs, see
+L<http://rwmj.wordpress.com/tag/goaljobs/>
+
+For examples, see the C<examples/> directory in the source and
+L<http://git.annexia.org/?p=goals.git;a=summary>
+
+For reference documentation on how to write scripts, see the
+L<goaljobs-reference(1)> man page.
+
+To read about the Goaljobs API, look for the file C<goaljobs.mli> (in
+the source code or installed as part of the goaljobs package), or see
+the HTML documentation installed as part of the goaljobs package.
+
+=head2 GOALJOBS WRAPPER SCRIPT
+
+C<goaljobs> is a wrapper script that compiles your goals into a binary
+(program) which can then be run. It takes a set of input files
+(C<*.ml>) and writes a single output program. The output program has
+the same name as the last input file minus C<.ml>, but you can
+override this using the I<-o> option.
+
+Note that the C<goaljobs> script is just a simple wrapper around the
+OCaml compiler (L<ocamlopt(1)>).
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display short help and exit.
+
+=item B<-g>
+
+Enable debugging symbols in the output binary.
+
+=item B<-I> directory
+
+=item B<-I> +package
+
+Pass these options directly to the OCaml compiler. You can use this
+to include OCaml modules from another directory. The C<+package> form
+includes a package relative to the OCaml library directory
+(C<ocamlc -where>).
+
+=item B<-o> output
+
+Set the name of the output binary.
+
+The default is to use the name of the final source file, after
+removing the C<.ml> extension.
+
+=item B<--package> package(s)
+
+This passes I<-package package(s)> to L<ocamlfind(1)>. You can use it
+to enable OCaml packages, eg:
+
+ goaljobs --package bigarray,libvirt source.ml
+
+=item B<--pkgdir> dir
+
+If goaljobs is installed in a non-standard directory, pass the name of
+the directory using this option. You can also use this if you want to
+run goaljobs without installing it (pass the goaljobs source
+directory).
+
+=back
+
+=head1 SEE ALSO
+
+L<goaljobs-reference(1)>,
+L<ocamlopt(1)>
+
+=head1 AUTHORS
+
+Richard W.M. Jones <rjones @ redhat . com>
+
+=head1 COPYRIGHT
+
+(C) Copyright 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.
BuildRequires: ocaml-camlp4-devel
# For building manual pages.
-BuildRequires: /usr/bin/perldoc
+BuildRequires: perl-podlators
# Requires camlp4 and ocamlfind at runtime.
Requires: /usr/bin/ocamlc
%else
%{_libdir}/ocaml/goaljobs/goaljobs.cma
%endif
-#%{_mandir}/man1/*.1*
+%{_mandir}/man1/*.1*
%changelog