From ece68a0d845a02388127bb5a68698ce92690894f Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 19 Sep 2013 12:54:08 +0100 Subject: [PATCH] Fedora OCaml: Improve parallelism by not waiting for rebuilds unless they are required by another build. --- fedora.ml | 117 +++++++++++++++++++++++++++++------------------- fedora_ocaml_rebuild.ml | 58 ++++++++++++++++++++---- 2 files changed, 121 insertions(+), 54 deletions(-) diff --git a/fedora.ml b/fedora.ml index 82324ff..edc9cfd 100644 --- 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 diff --git a/fedora_ocaml_rebuild.ml b/fedora_ocaml_rebuild.ml index b4445dd..2eccde1 100644 --- a/fedora_ocaml_rebuild.ml +++ b/fedora_ocaml_rebuild.ml @@ -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. *) -- 1.8.3.1