File: bootstrapCommon.ml

package info (click to toggle)
botch 0.24-6.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,084,624 kB
  • sloc: xml: 11,924,892; ml: 4,489; python: 3,890; sh: 1,268; makefile: 334
file content (696 lines) | stat: -rw-r--r-- 27,894 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
(**************************************************************************)
(*                                                                        *)
(*  Copyright (C) 2012 Johannes 'josch' Schauer <j.schauer@email.de>      *)
(*  Copyright (C) 2012 Pietro Abate <pietro.abate@pps.jussieu.fr>         *)
(*                                                                        *)
(*  This library is free software: you can redistribute it and/or modify  *)
(*  it under the terms of the GNU Lesser General Public License as        *)
(*  published by the Free Software Foundation, either version 3 of the    *)
(*  License, or (at your option) any later version.  A special linking    *)
(*  exception to the GNU Lesser General Public License applies to this    *)
(*  library, see the COPYING file for more information.                   *)
(**************************************************************************)

open! ExtLib
open Dose_common
open Dose_debian
open Dose_algo
open Dose_extra

#define __label __FILE__
let label =  __label ;;
include Util.Logging(struct let label = label end) ;;

module CudfSet = CudfAdd.Cudf_set

module Int = struct type t = int let compare = Stdlib.compare end
module IntSet = Set.Make(Int)
module StringSet = Set.Make(String)

module type Ot = sig
  val options :
    ?status:int ->
    ?version:string ->
    ?suppress_usage:bool ->
    ?suppress_help:bool ->
    ?prog:string ->
    ?formatter:OptParse.Formatter.t -> unit -> OptParse.OptParser.t
end

module MakeOptions(O : Ot) = struct
  open OptParse ;;

  let verbose = StdOpt.incr_option ()
  let quiet = StdOpt.store_true ()
  let options = O.options ~version:"unreleased" () ;;

  open OptParser ;;

  add options ~short_name:'v' ~long_name:"verbose" ~help:"print additional information" verbose;
  add options ~long_name:"quiet" ~help:"do no print any messages" quiet;
end

let string_of pp arg =
  ignore(pp Format.str_formatter arg);
  Format.flush_str_formatter ()

(* this function receives a cudf package but expects that this package is the
 * encoding of a Debian binary or source package
 * it will thus print the cudf package in a Debian specific manner *)
let pp_package ?(noversion=false) fmt pkg =
  let name = try
      (CudfAdd.decode (Cudf.lookup_package_property pkg "name"))
    with Not_found ->
      failwith (Printf.sprintf "cannot find Debian name for cudf package %s"
                  (CudfAdd.string_of_package pkg))
  in
  let version = try
      (CudfAdd.decode (Cudf.lookup_package_property pkg "number"))
    with Not_found ->
      failwith (Printf.sprintf "cannot find Debian version for cudf package %s"
                  (CudfAdd.string_of_package pkg))
  in
  match (CudfAdd.decode (Cudf.lookup_package_property pkg "type")) with
  | "bin" -> begin
      let arch = try
          (CudfAdd.decode (Cudf.lookup_package_property pkg "architecture"))
        with Not_found ->
          failwith (Printf.sprintf
                      "cannot find Debian architecture for cudf package %s"
                      (CudfAdd.string_of_package pkg))
      in
      if noversion then
        Format.fprintf fmt "%s:%s" name arch
      else
        Format.fprintf fmt "%s:%s (= %s)" name arch version
    end
  | "src" -> begin
      if noversion then
        Format.fprintf fmt "src:%s" name
      else
        Format.fprintf fmt "src:%s (= %s)" name version
    end
  | t -> failwith (Printf.sprintf "invalid type %s for cudf package %s" t
                     (CudfAdd.string_of_package pkg))

let string_of_package ?(noversion=false) = string_of (pp_package ~noversion)

let string_of_list string_of_item sep l =
  let buf = Buffer.create 1023 in
  let rec aux = function
    | [] -> assert false
    | [last] -> (* last item, no trailing sep *)
        Buffer.add_string buf (string_of_item last)
    | item :: tl -> (* at least one item in tl *)
        Buffer.add_string buf (string_of_item item);
        Buffer.add_string buf sep;
        aux tl in
  let _ =
    match l with
      | [] -> ()
      | [sole] -> Buffer.add_string buf (string_of_item sole)
      | _ -> aux l in
  Buffer.contents buf
;;

let string_of_pkglist = string_of_list (string_of_package ~noversion:false) ", ";;

(* check if a package is member of a package list *)
let pkg_list_mem l pkg =
  List.exists (fun p -> (CudfAdd.compare p pkg)=0) l
;;

let pkg_is_arch_all pkg =
  try (Cudf.lookup_package_property pkg "architecture") = "all"
  with Not_found -> false
;;

let pkg_is_not_arch_all pkg = not(pkg_is_arch_all pkg);;

let debversion_of_cudfpkg pkg =
  try
    CudfAdd.decode (Cudf.lookup_package_property pkg "number")
  with Not_found ->
    failwith (Printf.sprintf "cudf package %s does not have Debian version"
                (CudfAdd.string_of_package pkg))

let debtype_of_cudfpkg pkg =
  try
    match CudfAdd.decode (Cudf.lookup_package_property pkg "type") with
    | "bin" -> `BinPkg
    | "src" -> `SrcPkg
    | _ -> failwith "invalid type for debcudf"
  with Not_found ->
    failwith (Printf.sprintf "cudf package %s does not have Debian type"
                (CudfAdd.string_of_package pkg))

let debarchitecture_of_cudfpkg pkg =
  (* these functions are for pretty printing Debian binary and source packages
   * as well as for uniquely identifying and comparing them. For both purposes,
   * the Debian source package architectures are irrelevant *)
  if debtype_of_cudfpkg pkg <> `BinPkg then
    failwith "can only get debarchitecture of binary packages";
  try
    CudfAdd.decode (Cudf.lookup_package_property pkg "architecture")
  with Not_found ->
    failwith (Printf.sprintf "cudf package %s does not have Debian architecture"
                (CudfAdd.string_of_package pkg))

let debname_of_cudfpkg pkg =
  try
    CudfAdd.decode (Cudf.lookup_package_property pkg "name")
  with Not_found ->
    failwith (Printf.sprintf "cudf package %s does not have Debian name"
                (CudfAdd.string_of_package pkg))

let debessential_of_cudfpkg pkg =
  if debtype_of_cudfpkg pkg <> `BinPkg then
    failwith "can only get essential property of binary packages";
  let ess = try
      CudfAdd.decode (Cudf.lookup_package_property pkg "essential")
    with Not_found -> "false"
  in
  match ess with
  | "true" -> true
  | "false" -> false
  | _ -> failwith (Printf.sprintf "invalid value for property essential: %s" ess)

let debbintriplet_of_cudfpkg pkg =
  if debtype_of_cudfpkg pkg <> `BinPkg then
    failwith "can only get debbintriplet of binary packages";
  (debname_of_cudfpkg pkg, debarchitecture_of_cudfpkg pkg, debversion_of_cudfpkg pkg)

let debsrctuple_of_cudfpkg pkg =
  if debtype_of_cudfpkg pkg <> `SrcPkg then
    failwith "can only get debsrctuple of source packages";
  (debname_of_cudfpkg pkg, debversion_of_cudfpkg pkg)

let debcudf_compare a b =
  let name_a = debname_of_cudfpkg a in
  let name_b = debname_of_cudfpkg b in
  (* since cudf versions are assigned such that they allow total ordering in
   * the same way that the Debian version would, we can use them for faster
   * comparison instead of slow Debian version comparsion *)
  let cudfver_a = a.Cudf.version in
  let cudfver_b = b.Cudf.version in
  let type_a = debtype_of_cudfpkg a in
  let type_b = debtype_of_cudfpkg b in
  (* comparison between source and binary packages is sorted as in buildGraph *)
  match type_a, type_b with
  | `SrcPkg, `BinPkg -> -1
  | `BinPkg, `SrcPkg -> 1
  | `SrcPkg, `SrcPkg -> begin
      let name_cmp = Stdlib.compare name_a name_b in
      if name_cmp <> 0 then
        name_cmp
      else
        let ver_cmp = Stdlib.compare cudfver_a cudfver_b in
        if ver_cmp = 0 then fatal "duplicate source package";
        ver_cmp
    end
  | `BinPkg, `BinPkg -> begin
      let name_cmp = Stdlib.compare name_a name_b in
      if name_cmp <> 0 then
        name_cmp
      else begin
        let ver_cmp = Stdlib.compare cudfver_a cudfver_b in
        if ver_cmp <> 0 then
          ver_cmp
        else begin
          let arch_a = debarchitecture_of_cudfpkg a in
          let arch_b = debarchitecture_of_cudfpkg b in
          let arch_cmp = Stdlib.compare arch_a arch_b in
          if arch_cmp = 0 then fatal "duplicate binary package";
          arch_cmp
        end
      end
    end

(* sort a package list by their name/version/architecture *)
let debcudf_sort pkgs = List.sort ~cmp:debcudf_compare pkgs;;

(*
 * return each line of a textfile in a list
 * allow comments, empty lines and spaces in the textfile
 * *)
let read_linebased_file filename =
  let ic = open_in filename in
  (* remove everything after the # and strip whitespaces *)
  let process_line line = String.strip (
    try String.sub line 0 (String.index line '#')
    with Not_found -> line)
  in
  (* process each line and only keep the non-empty ones *)
  let result = List.filter
    (fun line -> String.length line > 0)
    (List.map process_line (Std.input_list ic))
  in
  close_in ic;
  result
;;

(* given one or more package lists, returns the unique union of them *)
let unique ll =
  CudfSet.elements (List.fold_left (fun acc l ->
    CudfSet.union acc (CudfAdd.to_set l)
  ) CudfSet.empty ll)
;;

let optimal_subset ?(global_constraints=[]) ?(available=(fun _ -> true)) pkg univ closure =
  let dummy = { Depsolver.dummy_request with
                Cudf.depends =
                  List.map (fun (_,pkglist) ->
                      List.map (fun uid ->
                          let pkg = CudfAdd.inttopkg univ uid in
                          (pkg.Cudf.package,Some(`Eq,pkg.Cudf.version))
                        ) pkglist
                    ) global_constraints }
  in
  let cudf_closure = List.filter_map (fun i ->
      let pkg = try
          CudfAdd.inttopkg univ i
        with Not_found -> fatal "Cannot find pkg for int %d" i
      in
      if List.mem ("type", `String "src") pkg.Cudf.pkg_extra then
        Some pkg
      else begin
        let notavail = if available pkg then `Int 0 else `Int 1 in
        Some { pkg with Cudf.pkg_extra = ("notavailable", notavail) :: pkg.Cudf.pkg_extra }
      end
    ) closure
  in
  (* create a cudf request with
   *  - all packages in the universe
   *  - the notavailable property in the preamble
   *  - the request to install the current package
   *  - to optimize first by minimum number of not-available packages in the
   *    solution and then by solution size *)
  let newuniverse = Cudf.load_universe cudf_closure in
  let preamble = Debcudf.preamble in
  let preamble = CudfAdd.add_properties preamble [("notavailable",(`Int (Some 0)))] in
  let install = (pkg.Cudf.package, Some (`Eq, pkg.Cudf.version)) in
  let request = { Cudf.default_request
                  with Cudf.request_id = "";
                       Cudf.install = [install] }
  in
  let criteria = "-sum(solution,notavailable),-count(solution)" in
  let cmd = "aspcud $in $out $pref" in
  try
    Depsolver.check_request ~dummy ~cmd ~criteria (preamble,newuniverse,request)
  with e ->
    warning "exception when handling %s" pkg.Cudf.package;
    raise e
;;

(* split the installation set in a list of list of packages.
 * Each list is associated to a dependendency of the give package.
 * *)
(*
 * in case more than one package in a disjunction is part of the installation
 * set, it is sufficient to just pick any one package in the disjunction
 * because in the end it is not important that the union of all those choices
 * makes the original installation set but that the union of all these choices
 * creates any valid installation set. This is fulfilled by picking a valid
 * installation set for any single package in a disjunction.
 *
 *
 *)
let partition_deps ?(partition_optimizer=(fun _ s -> s)) pool univ iss pkg =
  let to_set l = List.fold_right IntSet.add l IntSet.empty in
  let globalid = Cudf.universe_size univ in
  let l = List.map (fun vpkglist ->
    let l = CudfAdd.resolve_vpkgs_int univ vpkglist in
    let s = to_set l in
    let intrs = IntSet.inter iss s in
    if IntSet.cardinal intrs > 1 then
      debug "More then one package in the intersection";
    if not(IntSet.is_empty intrs) then begin
      let pid = IntSet.choose intrs in
      let dc = Depsolver_int.dependency_closure_cache pool [pid] in
      (* the closure contains the globalid which we do not want *)
      let dcs = IntSet.remove globalid (to_set dc) in
      (* calculate the intersection between the chosen installation set and the
       * dependency closure of pid *)
      (* but the result will include "pid". This is important because the
       * buildGraph will make connections to their source packages of all
       * binary packages in the IS but not from the binary package the IS
       * belongs to *)
      let dcs = IntSet.inter iss dcs in
      (* pass the installation set to the optimizer *)
      let dcs = partition_optimizer pid dcs in
      (pid,dcs)
    end else
      fatal "the intersection between a dependency disjunction and the installation set must not be empty";
  ) pkg.Cudf.depends in
  l
;;

let compute_dependency_sets_opt ?(global_constraints=[]) ?(partition=true) ?(available=(fun _ -> true)) opt_partition_cache pool univ srcpkg =
  let id = CudfAdd.pkgtoint univ srcpkg in
  let globalid = Cudf.universe_size univ in
  (* remove the global id from the dependency closure *)
  let closure = List.filter_map (function
      |i when i = globalid -> None
      |i -> Some i)
      (Depsolver_int.dependency_closure_cache pool [id])
  in
  (* given a package and an installation set, passes that set to a solver to
   * find a smaller set
   *
   * results are cached and retrieved from the cache if it has them
   * caching is useful because only a about a third of all installation sets is
   * unique. Thus, caching reduces runtime by about a factor of about three *)
  let partition_optimizer pid dcs =
    let cache_key = (pid, (IntSet.elements dcs)) in
    match Hashtbl.find_option opt_partition_cache cache_key with
    | Some is -> is
    | None -> begin
        let r = optimal_subset ~global_constraints ~available (CudfAdd.inttopkg univ pid) univ (IntSet.elements dcs) in
        let is = match r with
          |Depsolver.Error s -> fatal "%s" s;
          |Depsolver.Unsat _ -> fatal "this must not happen";
          |Depsolver.Sat (_,soluniv) ->
            Cudf.fold_packages (fun acc pkg ->
                IntSet.add (CudfAdd.pkgtoint univ pkg) acc
              ) IntSet.empty soluniv
        in
        Hashtbl.add opt_partition_cache cache_key is;
        is
      end
  in
  (* compute an optimal installation set for the given source package
   *
   * since there are no duplicate source packages, the results of this do not
   * have to be cached *)
  let r = optimal_subset ~global_constraints ~available srcpkg univ closure in
  begin match r with
    |Depsolver.Error s -> fatal "%s" s;
    |Depsolver.Unsat diagnosis -> begin
        if Util.Debug.is_enabled "BootstrapCommon" then begin match diagnosis with
          | None -> ()
          | Some diagnosis -> Diagnostic.fprintf ~explain:true ~failure:true Format.err_formatter diagnosis
        end;
        warning "source package %s cannot be compiled"
          (string_of_package srcpkg);
        IntSet.empty, []
      end
    |Depsolver.Sat (_,soluniv) -> begin
        (* remove source package from installation set *)
        let iss = Cudf.fold_packages (fun acc pkg ->
            IntSet.add (CudfAdd.pkgtoint univ pkg) acc
          ) IntSet.empty soluniv
        in
        if partition then
          iss, (partition_deps ~partition_optimizer pool univ iss srcpkg)
        else
          iss, []
      end
  end
;;

(* compute_dependency_sets using low level integer interface *)
let compute_dependency_sets ?(global_constraints=[]) ?(partition=true) custom_is_ht pool univ srcpkg =
  let id = CudfAdd.pkgtoint univ srcpkg in
  let closure = Depsolver_int.dependency_closure_cache pool [id] in
  let solver = Depsolver_int.init_solver_closure ~global_constraints pool closure in
  let req = [id] in
  let excludeset = Hashtbl.find_option custom_is_ht srcpkg.Cudf.package in
  let explain = true in
  let d = match excludeset with
    | Some es -> begin
        (* generate an installation set without one or more packages *)
        (* get ids to not include *)
        let excludelits = List.filter_map (fun pid ->
          if pid = id then None
          else begin
            let pkg = CudfAdd.inttopkg univ pid in
            if StringSet.mem pkg.Cudf.package es then
              Some (Depsolver_int.S.lit_of_var (solver.Depsolver_int.map#vartoint pid) false)
            else None
          end
        ) closure in
        match excludelits with
          | [] -> begin (* empty list. Solve normally *)
              warning "list of packages to exclude from the IS of %s is empty" (srcpkg.Cudf.package);
              Depsolver_int.solve ?tested:None ~explain solver req
            end
          | _ -> begin
              let solver = Depsolver_int.copy_solver solver in
              Depsolver_int.S.add_rule solver.Depsolver_int.constraints (Array.of_list excludelits) [];
              Depsolver_int.solve ?tested:None ~explain solver req
            end
      end
    | None -> (* generate an installation set normally *)
        Depsolver_int.solve ?tested:None ~explain solver req
  in
  match d with
  |Diagnostic.SuccessInt f_int -> begin
    let globalid = solver.Depsolver_int.map#vartoint (Cudf.universe_size univ) in
    let cudfis = List.filter_map (function
            |i when i = globalid -> None
            |i -> Some (solver.Depsolver_int.map#inttovar i))
        (f_int ())
    in
    let iss = List.fold_right IntSet.add cudfis IntSet.empty in
    if partition then
      iss, (partition_deps pool univ iss srcpkg)
    else
      iss, []
  end
  | _ -> begin
      if Util.Debug.is_enabled "BootstrapCommon" then begin
        (*let result = Depsolver.diagnosis solver.Depsolver_int.map univ d req in*)
        Diagnostic.fprintf ~explain:true ~failure:true Format.err_formatter { Diagnostic.result = Diagnostic.result solver.Depsolver_int.map univ d; request = Diagnostic.request univ req }
      end;
      (* source package could not be compiled. If the installation set was chosen
       * manually, fail. Otherwise just throw a warning. *)
      match excludeset with
        | Some es -> failwith (Printf.sprintf "source package %s is not compilable after excluding %s" (srcpkg.Cudf.package) (String.concat "," (StringSet.elements es)))
        | None ->
          warning "source package %s cannot be compiled"
            (string_of_package srcpkg);
    IntSet.empty, []
  end
;;

let get_custom_is_ht arch custom_is_files =
  let lines = List.fold_left (fun l f ->
    List.rev_append (read_linebased_file f) l
  ) [] custom_is_files in
  let custom_is_ht = Hashtbl.create (List.length lines) in
  List.iter (fun line ->
    match String.nsplit line " " with
      | hd::tl ->
          let bins = List.fold_left (fun acc d ->
            StringSet.add (CudfAdd.encode (d^":"^arch)) acc
          ) StringSet.empty tl in
          let oldbins = Hashtbl.find_default custom_is_ht (CudfAdd.encode hd) StringSet.empty in
          Hashtbl.replace custom_is_ht (CudfAdd.encode hd) (StringSet.union bins oldbins)
      | _ -> ();
  ) lines;
  custom_is_ht
;;

let get_reduced_deps_ht ?(weak_file="./droppable/weak-build-dependencies.list") remove_weak archs srcpkglist reduced_deps_files =
  let lines = List.fold_left (fun l f ->
    List.rev_append (read_linebased_file f) l
  ) [] reduced_deps_files in
  let reduced_deps_ht = Hashtbl.create (List.length lines) in
  List.iter (fun line ->
    match String.nsplit line " " with
      | hd::tl ->
          let deps = List.fold_left (fun acc d ->
            List.fold_left (fun a arch ->
              StringSet.add (CudfAdd.encode (d^":"^arch)) a
            ) acc archs
          ) StringSet.empty tl in
          let olddeps = Hashtbl.find_default reduced_deps_ht (CudfAdd.encode hd) StringSet.empty in
          Hashtbl.replace reduced_deps_ht (CudfAdd.encode hd) (StringSet.union deps olddeps)
      | _ -> ();
  ) lines;
  (* get the set of weak dependencies *)
  let weak_deps_set = if weak_file <> "" then begin
    List.fold_left (fun acc line ->
      List.fold_left (fun a arch ->
        StringSet.add (CudfAdd.encode (line^":"^arch)) a
      ) acc archs
    ) StringSet.empty (read_linebased_file weak_file)
  end else StringSet.empty in
  (* make the weak build dependencies a build profile of all source packages in
   * the graph *)
  if not (StringSet.is_empty weak_deps_set) && remove_weak then begin
    List.iter (fun pkg ->
      let value = Hashtbl.find_default reduced_deps_ht (pkg.Cudf.package) StringSet.empty in
      Hashtbl.replace reduced_deps_ht (pkg.Cudf.package) (StringSet.union value weak_deps_set)
    ) srcpkglist;
  end;
  reduced_deps_ht, weak_deps_set
;;

let get_src_package ?(allowmismatch=false) universe binpkg =
  try Sources.get_src_package universe binpkg
  with Sources.MismatchSrc sl -> begin (* names matches but version doesnt *)
    if allowmismatch then begin
      warning "binary package %s does not have an associated source package - falling back to highest version"
        (string_of_package binpkg);
      List.hd (List.sort ~cmp:(Cudf.(>%)) sl)
    end else
      raise Sources.NotfoundSrc
  end
;;

(* given a universe, return a hashtable mapping source packages to a list of
 * binary packages *)
let srcbin_table ?(available=CudfAdd.Cudf_set.empty) ?(allowmismatch=false) ?(ignoresrclessbin=false) universe =
  let h = CudfAdd.Cudf_hashtbl.create (Cudf.universe_size universe) in
  let aux binpkg =
    if CudfAdd.get_property "type" binpkg = "bin" then begin
      try
        let srcpkg = get_src_package ~allowmismatch universe binpkg in
        try let l = CudfAdd.Cudf_hashtbl.find h srcpkg in l := binpkg::!l
        with Not_found -> CudfAdd.Cudf_hashtbl.add h srcpkg (ref [binpkg])
      with Sources.NotfoundSrc ->
        (* No source was found for this binary. That's okay if this binary is
         * member of the available set *)
        if CudfAdd.Cudf_set.mem binpkg available then
          ()
        else
          (* it's also okay if the user requested to ignore source-less binaries *)
          if ignoresrclessbin then begin
            warning "binary package %s does not have an associated source package - ignoring"
              (string_of_package binpkg);
            ()
          end else
            failwith (Printf.sprintf "can't find source package for binary package %s"
                        (string_of_package binpkg))
    end
  in
  Cudf.iter_packages aux universe ;
  h
;;

let get_bin_packages h srcpkg =
  try !(CudfAdd.Cudf_hashtbl.find h srcpkg)
  with Not_found ->
    warning "Source package %s is not associated to any binary package"
      (string_of_package srcpkg);
    []
;;

let parse_packages ?(noindep=false) parse_cmdline build host foreign = function
  |[] | [_] -> fatal
    "You must provide a list of Debian Packages files and \
     a Debian Sources file"
  |l ->
      begin match List.rev l with
      |h::t ->
          let (fg,bg) = parse_cmdline (`Deb,false) [h] in
          let fgl = Sources.input_raw ~archs:[build] fg in
          let bgl = Sources.input_raw ~archs:[build] bg in
          let fgsrcl = Sources.sources2packages ~noindep build host fgl in
          let bgsrcl = Sources.sources2packages ~noindep build host bgl in
          let pkgl = Packages.input_raw ~archs:(build::host::foreign) t in
          (pkgl, (fgsrcl,bgsrcl), fgl)
      |_ -> assert false
      end
;;

let read_package_file ?(archs=[]) tocudf f =
  let l = Packages.input_raw ~archs [f] in
  List.fold_left (fun acc pkg ->
    let cudfpkg =
      try tocudf pkg
      with Not_found ->
        failwith (Printf.sprintf "cannot find cudf version for %s - do \
                  your foreground packages contain it?" (pkg#name));
    in
    CudfAdd.Cudf_set.add cudfpkg acc
  ) CudfAdd.Cudf_set.empty l
;;

let more_problems_callback printer universe results summary d =
  if summary then Diagnostic.collect results d ;
  match d with
  |{Diagnostic.result = Diagnostic.Failure (f) } -> begin
      let new_reasons = f () in
      List.iter (fun reason ->
          match reason with
          |Diagnostic.Conflict (i,j,vpkg) -> begin
              info "handling conflict between %s and %s because of %s"
                (string_of_package i) (string_of_package j)
                (Cudf_types_pp.string_of_vpkg vpkg);
              let filter k l = List.filter (fun (p,c) ->
                  let other = p=k.Cudf.package && Cudf.version_matches k.Cudf.version c in
                  let provides = List.exists (function
                      | n,None -> p=n
                      | n,Some (`Eq, version) ->
                        p=n && Cudf.version_matches version c
                    ) k.Cudf.provides
                  in
                  not other && not provides
                ) l.Cudf.conflicts in
              let new_i = { i with Cudf.conflicts = filter j i } in
              let new_j = { j with Cudf.conflicts = filter i j } in
              (* modify i to no longer contain the conflict vpkg *)
              let pkglist = Cudf.fold_packages (fun acc pkg ->
                  if Cudf.(=%) pkg i then      new_i::acc
                  else if Cudf.(=%) pkg j then new_j::acc
                  else pkg::acc
                ) [] !universe in
              universe := Cudf.load_universe pkglist;
            end
          |Diagnostic.Missing (i,vpkgs) -> begin
              info "handling missing dependency of %s on %s"
                (string_of_package i)
                (Cudf_types_pp.string_of_vpkglist vpkgs);
              let new_i = {
                i with
                Cudf.depends = List.filter ((<>) vpkgs) i.Cudf.depends
              } in
              (* modify i to no longer contain the conflict vpkg *)
              let pkglist = Cudf.fold_packages (fun acc pkg ->
                  if Cudf.(=%) pkg i then new_i::acc
                  else pkg::acc
                ) [] !universe in
              universe := Cudf.load_universe pkglist;
            end
          |Diagnostic.Dependency _ -> () (* we ignore dependency paths *)
        ) new_reasons;
      printer d
    end
  |{Diagnostic.result = Diagnostic.Success _} -> ()
;;

let parse_debian_pkgstring universe native_arch pkgstring =
  let add_name_arch n a = CudfAdd.encode (Printf.sprintf "%s:%s" n a) in
  let parse_vpkglist s =
    let _loc = Format822.dummy_loc in
    List.map (function
        |((n,a),Some("=",v)) -> (n,a,v)
        |(_,None) ->
          raise (Format822.ParseError ([],s,"you must specify a version" ))
        |_ -> raise (Format822.ParseError ([],s,""))
      ) (Dose_pef.Packages.parse_vpkglist ("pkgstring", (_loc,s)))
  in
  List.fold_left (fun acc (n,a,v) ->
      let cudfname = match a with
        | None -> failwith (Printf.sprintf "package %s (= %s) is without architecture" n v)
        | Some "all" -> add_name_arch n native_arch
        | Some a -> add_name_arch n a
      in
      (* we convert cudf versions to Debian versions and compare that
       * with the version we got and use this to filter the result
       * instead of just converting the cudf version to the Debian
       * version directly because to do that we'd need access
       * to the cudf table but we don't *)
      let pkgs = List.filter (fun p ->
          CudfAdd.string_of_version p = v
        ) (Cudf.lookup_packages universe cudfname) in
      let pkg = match pkgs with
        | [] -> failwith (Printf.sprintf "cannot find %s" cudfname)
        | [p] -> p
        | _ -> failwith (Printf.sprintf "more than one match for %s" cudfname)
      in
      IntSet.add (CudfAdd.pkgtoint universe pkg) acc
    ) IntSet.empty (parse_vpkglist pkgstring)
;;