summaryrefslogtreecommitdiff
path: root/ocamlbuild/ocaml_dependencies.ml
blob: de2c11fab4c2851b6031ef8ce7aefdaf7c389ee1 (plain)
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
(***********************************************************************)
(*                                                                     *)
(*                             ocamlbuild                              *)
(*                                                                     *)
(*  Nicolas Pouillard, Berke Durak, projet Gallium, INRIA Rocquencourt *)
(*                                                                     *)
(*  Copyright 2007 Institut National de Recherche en Informatique et   *)
(*  en Automatique.  All rights reserved.  This file is distributed    *)
(*  under the terms of the Q Public License version 1.0.               *)
(*                                                                     *)
(***********************************************************************)


(* Original author: Nicolas Pouillard *)
open My_std
open Log
open Tools
open Ocaml_utils

let mydprintf fmt = dprintf 10 fmt

exception Circular_dependencies of string list * string

module type INPUT = sig
  val fold_dependencies : (string -> string -> 'a -> 'a) -> 'a -> 'a
  val fold_libraries : (string -> string list -> 'a -> 'a) -> 'a -> 'a
  val fold_packages : (string -> string list -> 'a -> 'a) -> 'a -> 'a
end

module Make (I : INPUT) = struct
  open I

  module SMap = Map.Make(String)

  module Resources = Resource.Resources

  module Utils = struct
    let add = SMap.add

    let empty = SMap.empty

    let find_all_set x acc =
      try SMap.find x acc with Not_found -> Resources.empty

    let smap_add_set src dst acc =
      SMap.add src (Resources.add dst (find_all_set src acc)) acc

    let print_smap pp f smap =
      Format.fprintf f "@[<hv0>{:@[<hv2>";
      SMap.iter begin fun k v ->
        Format.fprintf f "@ @[<2>%S =>@ %a@];" k pp v
      end smap;
      Format.fprintf f "@]@,:}@]"

    let print_smap_list = print_smap pp_l

    let print_smap_set = print_smap Resources.print

    let print_lazy pp f l = pp f !*l

    let find_all_list x acc =
      try SMap.find x acc with Not_found -> []

    let find_all_rec xs map =
      let visited = Hashtbl.create 32 in
      let rec self x acc =
        try
          Hashtbl.find visited x; acc
        with Not_found ->
          Hashtbl.replace visited x ();
          let acc = Resources.add x acc in
          try Resources.fold self (SMap.find x map) acc
          with Not_found -> acc
      in List.fold_right self xs Resources.empty

    let mkindex fold filter =
      fold begin fun name contents acc ->
        if filter name then
          List.fold_right begin fun elt acc ->
            add elt (name :: (find_all_list elt acc)) acc
          end contents acc
        else
          acc
      end empty

  end
  open Utils

  let caml_transitive_closure
        ?(caml_obj_ext="cmo")
        ?(caml_lib_ext="cma")
        ?(pack_mode=false)
        ?(used_libraries=[])
        ?(hidden_packages=[]) fns =

    let valid_link_exts =
      if pack_mode then [caml_obj_ext; "cmi"]
      else [caml_obj_ext; caml_lib_ext] in

    mydprintf "caml_transitive_closure@ ~caml_obj_ext:%S@ ~pack_mode:%b@ ~used_libraries:%a@ %a"
      caml_obj_ext pack_mode pp_l used_libraries pp_l fns;

    let packages = fold_packages (fun name _ -> Resources.add name) Resources.empty in
    mydprintf "packages:@ %a" Resources.print packages;

    let caml_obj_ext_of_cmi x =
      if Filename.check_suffix x ".cmi" then
        Pathname.update_extensions caml_obj_ext x
      else x in

    let maybe_caml_obj_ext_of_cmi x =
      if pack_mode then
        if Filename.check_suffix x ".cmi" then
          let caml_obj = Pathname.update_extensions caml_obj_ext x in
          if Resource.exists_in_build_dir caml_obj then
            caml_obj
          else
            x
        else
          x
      else
        if Filename.check_suffix x ".cmi" then
          Pathname.update_extensions caml_obj_ext x
        else x in

    let not_linkable x =
      not (List.exists (Pathname.check_extension x) valid_link_exts) in

    let dependency_map =
      fold_dependencies begin fun x y acc ->
        let x = maybe_caml_obj_ext_of_cmi x
        and y = maybe_caml_obj_ext_of_cmi y in
        if x = y || not_linkable x || not_linkable y then acc
        else smap_add_set x y acc
      end SMap.empty in
    mydprintf "dependency_map:@ %a" print_smap_set dependency_map;

    let used_files = find_all_rec fns dependency_map in
    mydprintf "used_files:@ %a" Resources.print used_files;

    let open_packages =
      Resources.fold begin fun file acc ->
        if Resources.mem file packages && not (List.mem file hidden_packages)
        then file :: acc else acc
      end used_files [] in
    mydprintf "open_packages:@ %a" pp_l open_packages;

    let index_filter ext list x =
      Pathname.check_extension x ext && List.mem x list in

    let lib_index =
      lazy (mkindex fold_libraries (index_filter caml_lib_ext used_libraries)) in
    mydprintf "lib_index:@ %a" (print_lazy print_smap_list) lib_index;

    let package_index =
      lazy (mkindex fold_packages (index_filter caml_obj_ext open_packages)) in

    let rec resolve_packages x =
      match find_all_list x !*package_index with
      | [] -> x
      | [x] -> resolve_packages x
      | pkgs ->
          failwith (sbprintf "the file %S is included in more than one active open package (%a)"
                             x pp_l pkgs) in

    let libs_of x = find_all_list x !*lib_index in

    let lib_of x =
      match libs_of x with
      | [] -> None
      | [lib] -> Some(lib)
      | libs ->
          failwith (sbprintf "the file %S is included in more than one active library (%a)"
                             x pp_l libs) in

    let convert_dependency src dst acc =
      let src = resolve_packages src in
      let dst = resolve_packages dst in
      let add_if_diff x y = if x = y then acc else smap_add_set x y acc in
      match (lib_of src, lib_of dst) with
      | None, None -> add_if_diff src dst
      | Some(liba), Some(libb) -> add_if_diff liba libb
      | Some(lib), None -> add_if_diff lib dst
      | None, Some(lib) -> add_if_diff src lib in

    let dependencies = lazy begin
      SMap.fold begin fun k ->
        Resources.fold (convert_dependency k)
      end dependency_map empty
    end in

    mydprintf "dependencies:@ %a" (print_lazy print_smap_set) dependencies;

    let dependencies_of x =
      try SMap.find x !*dependencies with Not_found -> Resources.empty in

    let needed = ref [] in
    let seen = ref [] in
    let rec aux fn =
      if sys_file_exists fn && not (List.mem fn !needed) then begin
        if List.mem fn !seen then raise (Circular_dependencies (!seen, fn));
        seen := fn :: !seen;
        Resources.iter begin fun f ->
          if sys_file_exists f then
            if Filename.check_suffix f ".cmi" then
              let f' = caml_obj_ext_of_cmi f in
              if f' <> fn then
                if sys_file_exists f' then aux f'
                else if pack_mode then aux f else ()
              else ()
            else aux f
        end (dependencies_of fn);
        needed := fn :: !needed
      end
    in
    List.iter aux fns;
    mydprintf "caml_transitive_closure:@ %a ->@ %a" pp_l fns pp_l !needed;
    List.rev !needed

end