Don't need to echo output on failure, since goaljobs now echos output from shout...
[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 (* XXX koji_build_state verrel: If you do a build and it fails, then
77  * do another build without bumping the release field, 'koji buildinfo'
78  * seems to always return the failed build, at least until the second
79  * build completes.  This means the code below fails.  Unclear how it
80  * can be fixed, but best to always bump the release to avoid the
81  * problem.
82  *)
83
84 (* Get build state. *)
85 let rec koji_build_state verrel =
86   fst (koji_build_state_task verrel)
87
88 (* Get build state and task ID. *)
89 and koji_build_state_task =
90   let state = Pcre.regexp "State: (\\w+)" in
91   let task_id = Pcre.regexp "Task: (\\d+)" in
92   let no_such_build = contains_substring "No such build" in
93   fun verrel ->
94     (* For speed, if a build is complete memoize it. *)
95     let key = sprintf "koji_build_complete_%s" verrel in
96     if memory_exists key then
97       `Complete, None
98     else (
99       let out = shout "timeout 120 koji buildinfo %s 2>&1 ||:" verrel in
100       if no_such_build out then
101         `No_such_build, None
102       else (
103         let state =
104           try
105             let subs = Pcre.exec ~rex:state out in
106             match Pcre.get_substring subs 1 with
107             | "BUILDING" -> `Building
108             | "COMPLETE" -> `Complete
109             | "DELETED" -> `Deleted
110             | "FAILED" -> `Failed
111             | "CANCELED" -> `Canceled
112             | sub ->
113               failwith (sprintf "koji_build_state_task: %s: unknown build state '%s'"
114                           verrel sub)
115           with
116             Not_found ->
117               failwith (sprintf "koji_build_state_task: %s: no build state found"
118                           verrel) in
119         let task =
120           try
121             let subs = Pcre.exec ~rex:task_id out in
122             Some (int_of_string (Pcre.get_substring subs 1))
123           with
124             Not_found -> None in
125
126         if state == `Complete then
127           memory_set key "1";
128
129         state, task
130       )
131     )
132
133 (* Perform a Koji build and wait until it finishes.  If it fails,
134  * throw an exception.
135  *)
136 let koji_build =
137   let created_task = Pcre.regexp "Created task: (\\d+)" in
138   let name_or_service_not_known =
139     contains_substring "Name or service not known" in
140   let completed_successfully = contains_substring "completed successfully" in
141   let failed = contains_substring "FAILED" in
142   fun ?(wait = true) pkg branch ->
143     let repodir = fedora_repo pkg branch in
144     let out =
145       shout "
146         cd %s
147         fedpkg build%s 2>&1
148     " repodir (if not wait then " --nowait" else "")
149     in
150     if not wait then (
151       (* Just check the task was created. *)
152       if not (Pcre.pmatch ~rex:created_task out) then (
153         failwith "fedpkg build: build failed to start"
154       )
155     ) else (
156       let task_id =
157         try
158           let subs = Pcre.exec ~rex:created_task out in
159           int_of_string (Pcre.get_substring subs 1)
160         with Not_found ->
161           failwith "could not find task ID in fedpkg build output" in
162       let rec loop out =
163         if name_or_service_not_known out then (
164           let out =
165             shout "cd %s && koji watch-task %d 2>&1 ||:" repodir task_id in
166           loop out
167         )
168         else if completed_successfully out then
169           ()
170         else if failed out then (
171           failwith "koji build failed"
172         )
173         else
174           failwith (sprintf "koji_build: unknown output: %s" out)
175       in
176       loop out
177     )
178
179 let koji_wait_repo =
180   let successfully_waited = contains_substring "Successfully waited" in
181   fun target verrel ->
182     let out = shout "koji wait-repo %s --build=%s 2>&1 ||:" target verrel in
183     if successfully_waited out then
184       ()
185     else
186       failwith (sprintf "koji_wait_repo: unknown output: %s" out)