Add support for multiple jobs files.
authorRichard W.M. Jones <rjones@redhat.com>
Tue, 13 Mar 2012 18:56:59 +0000 (18:56 +0000)
committerRichard W.M. Jones <rjones@redhat.com>
Tue, 13 Mar 2012 19:12:23 +0000 (19:12 +0000)
daemon/daemon.ml
daemon/daemon.mli
daemon/whenjobsd.ml
daemon/whenjobsd.pod
tools/whenjobs.ml
tools/whenjobs.pod

index e18574d..c04bfcb 100644 (file)
@@ -99,7 +99,7 @@ let rec init j d =
 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) =
@@ -290,9 +290,18 @@ and proc_whisper_variables vars =
   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
@@ -303,9 +312,10 @@ and reload_file () =
 
   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 ->
index d860714..b7f7d57 100644 (file)
@@ -24,8 +24,8 @@ val init : string -> bool -> unit
 
       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. *)
 
index 1efde6c..647c7f2 100644 (file)
@@ -130,7 +130,7 @@ Options:
   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 ()
index 182a62d..7139cb3 100644 (file)
@@ -80,11 +80,15 @@ This contains the process ID of the daemon.  The daemon also holds an
 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
index 1334aec..9449abc 100644 (file)
@@ -307,26 +307,39 @@ and list_file () =
   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
   );
 
@@ -555,6 +568,17 @@ and suggest_check_server_logs () =
 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;
index 6d2a554..1f38d29 100644 (file)
@@ -252,11 +252,13 @@ The output is a list of job names that would run.
 
 =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>.
@@ -860,6 +862,57 @@ fields:
 
 =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