Fedora OCaml: Improve parallelism by not waiting for rebuilds unless they are require...
authorRichard W.M. Jones <rjones@redhat.com>
Thu, 19 Sep 2013 11:54:08 +0000 (12:54 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Thu, 19 Sep 2013 11:54:08 +0000 (12:54 +0100)
fedora.ml
fedora_ocaml_rebuild.ml

index 82324ff..edc9cfd 100644 (file)
--- a/fedora.ml
+++ b/fedora.ml
@@ -69,37 +69,55 @@ let contains_substring substr str =
   try ignore (String.find str substr); true
   with Invalid_string -> false
 
-(* Check if a Koji (completed successfully) build exists. *)
 (* Not helped by the fact that the 'koji' tool actively
  * resists automation: RHBZ#760924.
  *)
-let koji_build_exists =
-  let state_complete = contains_substring "State: COMPLETE" in
-  let state_other = contains_substring "State: " in
+
+(* Get build state. *)
+let rec koji_build_state verrel =
+  fst (koji_build_state_task verrel)
+
+(* Get build state and task ID. *)
+and koji_build_state_task =
+  let state = Pcre.regexp "State: (\\w+)" in
+  let task_id = Pcre.regexp "Task: (\\d+)" in
   let no_such_build = contains_substring "No such build" in
   fun verrel ->
-    (* Once a build is known to be complete, memoize it. *)
+    (* For speed, if a build is complete memoize it. *)
     let key = sprintf "koji_build_complete_%s" verrel in
     if memory_exists key then
-      true
+      `Complete, None
     else (
-      let rec loop () =
-        let out = shout "timeout 120 koji buildinfo %s 2>&1 ||:" verrel in
-        if state_complete out then
-          true
-        else if state_other out then
-          false
-        else if no_such_build out then
-          false
-        else (
-          eprintf "%s\n" out;
-          eprintf "koji_build_exists: unknown output\nretrying ...\n%!";
-          loop ()
-        )
-      in
-      let r = loop () in
-      memory_set key "1";
-      r
+      let out = shout "timeout 120 koji buildinfo %s 2>&1 ||:" verrel in
+      if no_such_build out then
+        failwith (sprintf "koji_build_state_task: %s: no such build" verrel);
+      let state =
+        try
+          let subs = Pcre.exec ~rex:state out in
+          match Pcre.get_substring subs 1 with
+          | "BUILDING" -> `Building
+          | "COMPLETE" -> `Complete
+          | "DELETED" -> `Deleted
+          | "FAILED" -> `Failed
+          | "CANCELED" -> `Canceled
+          | sub ->
+            failwith (sprintf "koji_build_state_task: %s: unknown build state '%s'"
+                        verrel sub)
+        with
+          Not_found ->
+            failwith (sprintf "koji_build_state_task: %s: no build state found"
+                        verrel) in
+      let task =
+        try
+          let subs = Pcre.exec ~rex:task_id out in
+          Some (int_of_string (Pcre.get_substring subs 1))
+        with
+          Not_found -> None in
+
+      if state == `Complete then
+        memory_set key "1";
+
+      state, task
     )
 
 (* Perform a Koji build and wait until it finishes.  If it fails,
@@ -111,31 +129,38 @@ let koji_build =
     contains_substring "Name or service not known" in
   let completed_successfully = contains_substring "completed successfully" in
   let failed = contains_substring "FAILED" in
-  fun pkg branch ->
+  fun ?(wait = true) pkg branch ->
     let repodir = fedora_repo pkg branch in
-    let out = shout "cd %s && fedpkg build 2>&1 ||:" repodir in
-    let task_id =
-      try
-        let subs = Pcre.exec ~rex:created_task out in
-        int_of_string (Pcre.get_substring subs 1)
-      with Not_found ->
-        failwith "could not find task ID in fedpkg build output" in
-    let rec loop out =
-      if name_or_service_not_known out then (
-        let out =
-          shout "cd %s && koji watch-task %d 2>&1 ||:" repodir task_id in
-        loop out
-      )
-      else if completed_successfully out then
-        ()
-      else if failed out then (
-        eprintf "%s\n%!" out;
-        failwith "koji build failed"
-      )
-      else
-        failwith (sprintf "koji_build: unknown output: %s" out)
+    let out =
+      shout "
+        cd %s
+        fedpkg build%s 2>&1 ||:
+    " repodir (if not wait then " --nowait" else "")
     in
-    loop out
+    if wait then (
+      let task_id =
+        try
+          let subs = Pcre.exec ~rex:created_task out in
+          int_of_string (Pcre.get_substring subs 1)
+        with Not_found ->
+          failwith "could not find task ID in fedpkg build output" in
+      let rec loop out =
+        if name_or_service_not_known out then (
+          let out =
+            shout "cd %s && koji watch-task %d 2>&1 ||:" repodir task_id in
+          loop out
+        )
+        else if completed_successfully out then
+          ()
+        else if failed out then (
+          eprintf "%s\n%!" out;
+          failwith "koji build failed"
+        )
+        else
+          failwith (sprintf "koji_build: unknown output: %s" out)
+      in
+      loop out
+    )
 
 let koji_wait_repo =
   let successfully_waited = contains_substring "Successfully waited" in
index b4445dd..2eccde1 100644 (file)
@@ -1,5 +1,6 @@
 (* Perform a complete Fedora OCaml rebuild, in build order. *)
 
+open Unix
 open Printf
 
 open Goaljobs
@@ -35,10 +36,50 @@ let pkg_deps = dependencies branch source_packages
 
 (* Goal: rebuild all packages. *)
 let rec goal all () =
-  List.iter (fun pkg -> require (rebuilt pkg)) source_packages
+  List.iter (fun pkg -> require (rebuild_started pkg)) source_packages
 
 (* Goal: That 'package' has been rebuilt and exists in Koji. *)
 and rebuilt pkg =
+  let specfile = fedora_specfile pkg branch in
+
+  (* Note: verrel may change as we go along, so don't assign it to
+   * variable.
+   *)
+
+  (* Note the target must be both of these because the old verrel
+   * could exist as a koji build without it having been part of the
+   * rebuild.
+   *)
+  target (file_contains_string specfile rebuild_name &&
+          koji_build_state (fedora_verrel pkg branch) == `Complete);
+
+  (* Start the rebuild. *)
+  require (rebuild_started pkg);
+
+  (* Wait for the build state to reach a conclusion. *)
+  let rec loop () =
+    match koji_build_state (fedora_verrel pkg branch) with
+    | `Building ->
+      sleep 30;
+      loop ()
+    | `Complete ->
+      ()
+    | `Deleted ->
+      failwith (sprintf "rebuild of package %s: deleted" pkg)
+    | `Failed ->
+      failwith (sprintf "rebuild of package %s: failed" pkg)
+    | `Canceled ->
+      failwith (sprintf "rebuild of package %s: canceled" pkg)
+  in
+  loop ();
+
+  (* Wait for the build to appear in Koji repo. *)
+  koji_wait_repo koji_target (fedora_verrel pkg branch)
+
+(* Goal: The rebuild of the package has started, but we haven't waited
+ * for it to finish.
+ *)
+and rebuild_started pkg =
   let deps = List.assoc pkg pkg_deps in
   let specfile = fedora_specfile pkg branch in
 
@@ -47,19 +88,20 @@ and rebuilt pkg =
    * rebuild.
    *)
   target (file_contains_string specfile rebuild_name &&
-            koji_build_exists (fedora_verrel pkg branch));
+            (match koji_build_state (fedora_verrel pkg branch) with
+            | `Building | `Complete -> true
+            | `Deleted | `Failed | `Canceled -> false));
 
-  (* All dependent packages must have been done first. *)
+  (* All dependent packages must have been fully rebuilt and in the
+   * repo first.
+   *)
   List.iter (fun dep -> require (rebuilt dep)) deps;
 
   (* A local test build must succeed. *)
   require (local_build_succeeded pkg);
 
-  (* Rebuild the package in Koji. *)
-  koji_build pkg branch;
-
-  (* Wait for the build to appear in Koji repo.  Note verrel may change. *)
-  koji_wait_repo koji_target (fedora_verrel pkg branch)
+  (* Rebuild the package in Koji, but don't wait. *)
+  koji_build ~wait:false pkg branch
 
 and local_build_succeeded pkg =
   (* The specfile must have been updated. *)