Fedora OCaml: Improve parallelism by not waiting for rebuilds unless they are require...
[goaljobs-goals.git] / fedora.ml
1 (* Various useful functions for handling Fedora packages & rebuilds. *)
2
3 open ExtString
4 open Printf
5
6 open Goaljobs
7
8 (* Repo dir, etc. *)
9 let fedora_dir = Sys.getenv "HOME" // "d/fedora"
10 let fedora_repo pkg branch = fedora_dir // pkg // branch
11 let fedora_specfile pkg branch =
12   sprintf "%s/%s.spec" (fedora_repo pkg branch) pkg
13
14 (* Get the current version of a package. *)
15 let fedora_verrel pkg branch =
16   shout "
17     cd %s
18     fedpkg verrel
19   " (fedora_repo pkg branch)
20
21 (* Note most of these assume that the package is already cloned
22  * under ~/d/fedora using:
23  *
24  * cd ~/d/fedora
25  * fedpkg clone -B pkgname
26  *)
27
28 (* Take a list of Fedora source packages and work out the dependencies
29  * (only within the list of source packages).  Returns a list of
30  * (package, [list of deps...])
31  *)
32 let dependencies branch source_packages =
33   (* For each source package, get the list of binary packages that it
34    * provides.  XXX Not sure if this is totally technically correct, but
35    * it seems to work.
36    *)
37   let bin_to_src =
38     List.concat (
39       List.map (
40         fun pkg ->
41           let provides =
42             shlines "rpmspec -q --provides %s | awk '{print $1}'"
43               (fedora_specfile pkg branch) in
44           List.map (fun bin -> (bin, pkg)) provides
45       ) source_packages
46     ) in
47
48   (* For each package, get the list of build requires that appear
49    * elsewhere in the list of packages.
50    *)
51   let mem dep = List.mem dep source_packages in
52
53   List.map (
54     fun pkg ->
55       let deps =
56         shlines "rpmspec -q --buildrequires %s | awk '{print $1}'"
57           (fedora_specfile pkg branch) in
58       let deps = List.map (
59         fun dep ->
60           try List.assoc dep bin_to_src
61           with Not_found -> "xxx" (* filtered out in next line *)
62       ) deps in
63       let deps = Utils.sort_uniq (List.filter mem deps) in
64       (* eprintf "%s <- %s\n" pkg (String.concat " " deps); *)
65       pkg, deps
66   ) source_packages
67
68 let contains_substring substr str =
69   try ignore (String.find str substr); true
70   with Invalid_string -> false
71
72 (* Not helped by the fact that the 'koji' tool actively
73  * resists automation: RHBZ#760924.
74  *)
75
76 (* Get build state. *)
77 let rec koji_build_state verrel =
78   fst (koji_build_state_task verrel)
79
80 (* Get build state and task ID. *)
81 and koji_build_state_task =
82   let state = Pcre.regexp "State: (\\w+)" in
83   let task_id = Pcre.regexp "Task: (\\d+)" in
84   let no_such_build = contains_substring "No such build" in
85   fun verrel ->
86     (* For speed, if a build is complete memoize it. *)
87     let key = sprintf "koji_build_complete_%s" verrel in
88     if memory_exists key then
89       `Complete, None
90     else (
91       let out = shout "timeout 120 koji buildinfo %s 2>&1 ||:" verrel in
92       if no_such_build out then
93         failwith (sprintf "koji_build_state_task: %s: no such build" verrel);
94       let state =
95         try
96           let subs = Pcre.exec ~rex:state out in
97           match Pcre.get_substring subs 1 with
98           | "BUILDING" -> `Building
99           | "COMPLETE" -> `Complete
100           | "DELETED" -> `Deleted
101           | "FAILED" -> `Failed
102           | "CANCELED" -> `Canceled
103           | sub ->
104             failwith (sprintf "koji_build_state_task: %s: unknown build state '%s'"
105                         verrel sub)
106         with
107           Not_found ->
108             failwith (sprintf "koji_build_state_task: %s: no build state found"
109                         verrel) in
110       let task =
111         try
112           let subs = Pcre.exec ~rex:task_id out in
113           Some (int_of_string (Pcre.get_substring subs 1))
114         with
115           Not_found -> None in
116
117       if state == `Complete then
118         memory_set key "1";
119
120       state, task
121     )
122
123 (* Perform a Koji build and wait until it finishes.  If it fails,
124  * throw an exception.
125  *)
126 let koji_build =
127   let created_task = Pcre.regexp "Created task: (\\d+)" in
128   let name_or_service_not_known =
129     contains_substring "Name or service not known" in
130   let completed_successfully = contains_substring "completed successfully" in
131   let failed = contains_substring "FAILED" in
132   fun ?(wait = true) pkg branch ->
133     let repodir = fedora_repo pkg branch in
134     let out =
135       shout "
136         cd %s
137         fedpkg build%s 2>&1 ||:
138     " repodir (if not wait then " --nowait" else "")
139     in
140     if wait then (
141       let task_id =
142         try
143           let subs = Pcre.exec ~rex:created_task out in
144           int_of_string (Pcre.get_substring subs 1)
145         with Not_found ->
146           failwith "could not find task ID in fedpkg build output" in
147       let rec loop out =
148         if name_or_service_not_known out then (
149           let out =
150             shout "cd %s && koji watch-task %d 2>&1 ||:" repodir task_id in
151           loop out
152         )
153         else if completed_successfully out then
154           ()
155         else if failed out then (
156           eprintf "%s\n%!" out;
157           failwith "koji build failed"
158         )
159         else
160           failwith (sprintf "koji_build: unknown output: %s" out)
161       in
162       loop out
163     )
164
165 let koji_wait_repo =
166   let successfully_waited = contains_substring "Successfully waited" in
167   fun target verrel ->
168     let out = shout "koji wait-repo %s --build=%s 2>&1 ||:" target verrel in
169     if successfully_waited out then
170       ()
171     else
172       failwith (sprintf "koji_wait_repo: unknown output: %s" out)