and proc_reload_file () =
if !debug then Syslog.notice "remote call: reload_file";
- try reload_file (); `ok
+ try reload_files (); `ok
with Failure err -> `error err
and proc_set_variable (name, value) =
with
Failure msg -> `error msg
-(* Reload the jobs file. *)
-and reload_file () =
- let file = sprintf "%s/jobs.cmo" !jobsdir in
+(* Reload the jobs file(s). *)
+and reload_files () =
+ (* Get dir/*.cmo *)
+ let dir = !jobsdir in
+ let files = Array.to_list (Sys.readdir dir) in
+ let files = List.filter (
+ fun file ->
+ let n = String.length file in
+ n >= 5 && String.sub file (n-4) 4 = ".cmo"
+ ) files in
+ let files = List.map (fun file -> dir // file) files in
+ let files = List.sort compare files in
(* As we are reloading the file, we want to create a new state
* that has no jobs, but has all the variables from the previous
let s =
try
- Dynlink.loadfile file;
+ List.iter Dynlink.loadfile files;
let s = Whenfile.get_state () in
- Syslog.notice "loaded %d job(s) from %s" (Whenstate.nr_jobs s) file;
+ Syslog.notice "loaded %d job(s) from %d file(s)"
+ (Whenstate.nr_jobs s) (List.length files);
s
with
| Dynlink.Error err ->
The parameters are [jobsdir] and [debug]. *)
-val reload_file : unit -> unit
- (** (Re-)load the file [$jobsdir/jobs.cmo].
+val reload_files : unit -> unit
+ (** (Re-)load the file(s) [$jobsdir/*.cmo].
This can raise [Failure] if the operation fails. *)
let () =
let file = sprintf "%s/jobs.cmo" jobsdir in
if Sys.file_exists file then
- try Daemon.reload_file () with Failure _ -> () in
+ try Daemon.reload_files () with Failure _ -> () in
(* Go into main loop. *)
Daemon.main_loop ()
advisory (L<flock(2)>-style) exclusive lock on this file while it is
running.
-=item C<$HOME/.whenjobs/jobs.cmo>
+=item C<$HOME/.whenjobs/*.cmo>
-This is the compiled jobs specification which the daemon loads on
+The compiled jobs specification file(s) which the daemon loads on
start up, or reloads when instructed to by the L<whenjobs(1)> tool.
+Normally you only have one, called C<jobs.cmo>, corresponding to the
+source file C<jobs.ml> which is edited by C<whenjobs -e>. It is
+possible to have multiple files, see L<whenjobs(1)/MULTIPLE JOBS FILES>.
+
=item C<$HOME/.whenjobs/socket>
The daemon creates this socket and listens for incoming connections
close_in chan
and upload_file () =
- (* Recompile the jobs file. *)
- let file = get_jobs_filename () in
- let cmo_file = sprintf "%s/jobs.cmo" jobsdir in
- let cmd = sprintf "ocamlfind ocamlc -I +camlp4 -I %s -package unix,camlp4.lib -pp 'camlp4o %s/pa_when.cmo' -c %s -o %s"
- !libdir !libdir file cmo_file in
- if Sys.command cmd <> 0 then (
- eprintf "whenjobs: could not compile jobs script, see earlier error messages\n";
- eprintf "compile command was:\n%s\n" cmd;
- exit 1
- );
+ (* Recompile the jobs file(s). *)
+ let files = get_multijobs_filenames () in
+ List.iter (
+ fun file ->
+ let cmd = sprintf "ocamlfind ocamlc -I +camlp4 -I %s -package unix,camlp4.lib -pp 'camlp4o %s/pa_when.cmo' -c %s"
+ !libdir !libdir file in
+ if Sys.command cmd <> 0 then (
+ eprintf "whenjobs: %s: could not compile jobs script, see earlier errors\n"
+ file;
+ eprintf "compile command was:\n%s\n" cmd;
+ exit 1
+ )
+ ) files;
+
+ let cmo_files = List.map (
+ fun file ->
+ let n = String.length file in
+ if n < 4 then assert false;
+ String.sub file 0 (n-3) ^ ".cmo"
+ ) files in
- (* Test-load the jobs file to ensure it makes sense. *)
+ (* Test-load the jobs files to ensure they make sense. *)
Whenfile.init Whenstate.empty;
(try
- Dynlink.loadfile cmo_file
+ List.iter Dynlink.loadfile cmo_files
with
Dynlink.Error err ->
eprintf "whenjobs: %s\n" (Dynlink.error_message err);
- (* Since it failed, unlink it. *)
- (try unlink cmo_file with Unix_error _ -> ());
+ (* Since it failed, unlink the cmo files. *)
+ List.iter (
+ fun cmo_file ->
+ (try unlink cmo_file with Unix_error _ -> ())
+ ) cmo_files;
exit 1
);
and get_jobs_filename () =
sprintf "%s/jobs.ml" jobsdir
+and get_multijobs_filenames () =
+ (* Get dir/*.ml *)
+ let files = Array.to_list (Sys.readdir jobsdir) in
+ let files = List.filter (
+ fun file ->
+ let n = String.length file in
+ n >= 4 && String.sub file (n-3) 3 = ".ml"
+ ) files in
+ let files = List.map (fun file -> jobsdir // file) files in
+ List.sort compare files
+
and create_tutorial file =
let chan = open_out file in
output_string chan Tutorial.tutorial;
=item B<--upload>
-Compile the jobs script and upload it to the daemon, without editing.
+Compile the jobs file(s) and upload it to the daemon, without editing.
Note that the I<--edit> option does this automatically. Furthermore,
when the daemon is started it checks for a jobs script and loads it if
found.
+See also L</MULTIPLE JOBS FILES> below.
+
=item B<--variables>
Display all the variables and their values, in the format C<name=value>.
=back
+=head1 MULTIPLE JOBS FILES
+
+The whenjobs I<-e> and I<-l> options edit and list a file called
+C<$HOME/.whenjobs/jobs.ml>. This is an OCaml source file which is
+compiled behind the scenes into a bytecode file called
+C<$HOME/.whenjobs/jobs.cmo>. C<jobs.cmo> is what the daemon normally
+loads.
+
+You can also edit C<$HOME/.whenjobs/jobs.ml> by other means (eg. your
+own editor). After editing, to recompile and upload it, use:
+
+ whenjobs --upload
+
+When you have lots of jobs, it is convenient to split the jobs across
+multiple files. Any C<*.ml> files located in C<$HOME/.whenjobs> can
+be used (with some restrictions on filenames -- see below). These are
+compiled to the corresponding C<*.cmo> files and loaded into the
+daemon using the I<--upload> command.
+
+To create multiple jobs files, you cannot use the I<-e> or I<-l>
+options. Instead you have to create them yourself in
+C<$HOME/.whenjobs>, and when you have finished creating or editing
+them, upload them.
+
+=head2 FILENAME RESTRICTIONS ON JOBS FILES
+
+In OCaml, a file called C<jobs.ml> corresponds to an OCaml module
+called C<Jobs> (note the capitalization). OCaml module names can only
+contain ASCII alphanumeric characters, underscore, and C<'> (single
+quote), and they must begin with an alphabetic character. The same
+rules apply to jobs files.
+
+Furthermore, various OCaml module names are reserved (eg. C<Map>,
+C<List>). It is therefore better to prefix any names with something
+specific to your application.
+
+Examples of legal filenames are:
+
+ foo.ml
+ app_foo.ml
+ app_123.ml
+ jobs.ml
+
+Examples of illegal filenames are:
+
+ ann.txt # must end with .ml
+ 123.ml # must begin with alphabetic
+ app!.ml # must contain alphanumeric or underscore
+ app-foo.ml # must contain alphanumeric or underscore
+ map.ml # reserved module name
+
=head1 FILES