File: analyze.ml

package info (click to toggle)
ocaml-obuild 0.1.11-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 796 kB
  • sloc: ml: 6,570; sh: 171; ansic: 34; makefile: 11
file content (342 lines) | stat: -rw-r--r-- 11,179 bytes parent folder | download
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
open Ext.Fugue
open Ext.Filepath
open Ext.Compat
open Ext
open Helper
open Printf
open Gconf
open Target
open Dependencies

exception SublibraryDoesntExists of Libname.t
exception OcamlConfigMissing of string

(* differentiate if the dependency is system or is internal to the project *)
type dep_type =
  | System
  | Internal

type dependency_tag =
  | Target of Name.t
  | Dependency of Libname.t

type cpkg_config = {
  cpkg_conf_libs : string list;
  cpkg_conf_includes : filepath list;
}

(* this is a read only config of the project for configuring and building. *)
type project_config = {
  project_dep_data : (Libname.t, dep_type) Hashtbl.t;
  project_pkgdeps_dag : dependency_tag Dag.t;
  project_targets_dag : Name.t Dag.t;
  project_all_deps : dependency list;
  project_file : Project.t;
  project_ocamlcfg : (string, string) Hashtbl.t;
  project_ocamlmkcfg : (string, string) Hashtbl.t;
  project_cpkgs : (string, cpkg_config) Hashtbl.t;
}

let get_ocaml_config_key_hashtbl key h =
  try Hashtbl.find h key with Not_found -> raise (OcamlConfigMissing key)

let getOcamlConfigKey key =
  get_ocaml_config_key_hashtbl key (Prog.getOcamlConfig ())

let get_ocaml_config_key key project =
  get_ocaml_config_key_hashtbl key project.project_ocamlcfg

let get_pkg_deps target project =
  let pkgs =
    Taskdep.linearize project.project_pkgdeps_dag Taskdep.FromParent
      [ Target target.target_name ]
  in
  List.rev
    (list_filter_map
       (fun pkg ->
          match pkg with
          | Dependency d -> Some d
          | Target _ -> None)
       pkgs)

let get_c_pkg cname project =
  try Hashtbl.find project.project_cpkgs cname
  with Not_found ->
    failwith
      (sprintf "C package %s not found in the hashtbl: internal error" cname)

let is_pkg_internal project pkg =
  Hashtbl.find project.project_dep_data pkg = Internal

let is_pkg_system project pkg =
  Hashtbl.find project.project_dep_data pkg = System

let get_internal_library_deps project target =
  let internalDeps =
    Dag.getChildren project.project_targets_dag target.target_name
  in
  list_filter_map
    (fun name ->
       match name with
       | Name.Lib lname -> Some lname
       | _ -> None)
    internalDeps

(* all the standard libraries shipped with ocaml, comes *without* META files, so
 * we pre-populate the META cache with whatever we need by scanning the
 * directory that ocaml use as standard_library (found by running ocamlc -config).
 *
 * it allows to bootstrap better when ocamlfind has not been yet installed or
 * to detect difference of opinions of where the stdlib is, between ocamlfind and ocamlc.
*)
let initializeSystemStdlib ocamlCfg =
  let ocaml_ver = Hashtbl.find (Prog.getOcamlConfig ()) "version" in
  let stdlibPath =
    fp (get_ocaml_config_key_hashtbl "standard_library" ocamlCfg)
  in
  let stdlibLibs =
    Filesystem.list_dir_pred_map
      (fun n ->
         let ext = Filetype.of_filename n in
         if ext = Filetype.FileCMXA || ext = Filetype.FileCMA then
           Some n
         else
           None)
      stdlibPath
  in
  let libs =
    list_uniq
    $ List.map (fun f -> fn_to_string $ Filepath.chop_extension f) stdlibLibs
  in
  List.iter
    (fun lib ->
       (* skip .p library which are just variant of the no .p library *)
       if not (string_endswith ".p" lib) then (
         verbose Verbose "initializing standard library : package %s\n" lib;
         let libCmxa = lib ^ ".cmxa" in
         let libCma = lib ^ ".cma" in
         let archives =
           (if List.mem (fn libCmxa) stdlibLibs then
              [ ([ Meta.Predicate.Native ], libCmxa) ]
            else
              [])
           @
           if List.mem (fn libCma) stdlibLibs then
             [ ([ Meta.Predicate.Byte ], libCma) ]
           else
             []
         in
         let meta =
           {
             (Meta.Pkg.make lib) with
             Meta.Pkg.directory = fp_to_string stdlibPath;
             Meta.Pkg.requires = [] (* AFAIK this is always empty for stdlibs *);
             Meta.Pkg.version = ocaml_ver;
             Meta.Pkg.archives;
           }
         in
         Metacache.add lib (stdlibPath </> fn ("META-" ^ lib), meta)))
    libs

let readOcamlMkConfig filename =
  let lines =
    Utils.read_file_with
      (function
        | "" -> None
        | s when s.[0] = '#' -> None
        | s -> Some s)
      (filename ^ "/Makefile.config")
  in
  let h = Hashtbl.create 32 in
  List.iter
    (fun l ->
       let k, v = Utils.toKVeq l in
       Hashtbl.add h (string_lowercase k) (default "" v))
    lines;
  h

(* get all the dependencies required
 * and prepare the global bstate.of value *)
let prepare projFile user_flags =
  verbose Verbose "analyzing project\n%!";
  let ocamlCfg = Prog.getOcamlConfig () in
  let ocamlMkCfg =
    readOcamlMkConfig (Hashtbl.find ocamlCfg "standard_library")
  in

  let depsTable = Hashtbl.create 16 in
  let cpkgsTable = Hashtbl.create 1 in
  let depsDag = Dag.init () in
  let targetsDag = Dag.init () in

  let missingDeps = ref StringSet.empty in

  initializeSystemStdlib ocamlCfg;

  (* check for findlib / ocaml configuration mismatch *)
  let () =
    let stdlibPath =
      fp (get_ocaml_config_key_hashtbl "standard_library" ocamlCfg)
    in
    if
      not
        (List.exists
           (fun p ->
              string_startswith (fp_to_string p) (fp_to_string stdlibPath)
              || string_startswith (fp_to_string stdlibPath) (fp_to_string p))
           (FindlibConf.get_paths ()))
    then
      Meta.path_warning := true
  in

  let allTargets = Project.get_all_buildable_targets projFile user_flags in

  let internalLibs =
    List.map
      (fun lib -> lib.Project.Library.name.Libname.main_name)
      projFile.Project.libs
  in
  let isInternal lib = List.mem lib.Libname.main_name internalLibs in

  (* establish inter-dependencies in the project.
   * only consider internal libraries *)
  List.iter
    (fun target ->
       Dag.addNode target.target_name targetsDag;
       List.iter
         (fun (dep, _) ->
            if isInternal dep then (
              verbose Debug "  internal depends: %s\n" (Libname.to_string dep);
              Dag.addEdge target.target_name (Name.Lib dep) targetsDag))
         (Target.get_all_builddeps target))
    allTargets;

  let add_missing dep = missingDeps := StringSet.add dep !missingDeps in

  (* load every dependencies META files and at the same time generate the
   * graph of inter-dependencies.
   *
   * This recursively load all dependencies and dependencies's dependencies.
  *)
  let rec loop dep =
    let dataDep () =
      if isInternal dep then (
        let iLib = Project.find_lib projFile dep in
        let iLibDep = Dependency iLib.Project.Library.name in
        Dag.addNode iLibDep depsDag;
        List.iter
          (fun (reqDep, _) ->
             verbose Debug "  library %s depends on %s\n"
               (Libname.to_string iLib.Project.Library.name)
               (Libname.to_string reqDep);
             Dag.addEdge iLibDep (Dependency reqDep) depsDag;
             loop reqDep)
          iLib.Project.Library.target.target_obits.target_builddeps;
        Internal)
      else
        try
          let _, meta = Metacache.get dep.Libname.main_name in
          Dag.addNode (Dependency dep) depsDag;
          let pkg =
            try Meta.Pkg.find dep.Libname.subnames meta with
            | Not_found -> raise (SublibraryDoesntExists dep)
            | Meta.SubpackageNotFound _ -> raise (SublibraryDoesntExists dep)
          in
          List.iter
            (fun (preds, reqDeps) ->
               match preds with
               | [ Meta.Predicate.Toploop ] -> ()
               | _ ->
                 List.iter
                   (fun reqDep ->
                      verbose Debug "  library %s depends on %s\n"
                        (Libname.to_string dep) (Libname.to_string reqDep);
                      Dag.addEdge (Dependency dep) (Dependency reqDep) depsDag;
                      loop reqDep)
                   reqDeps)
            pkg.Meta.Pkg.requires;
          System
        with DependencyMissing dep ->
          add_missing dep;
          System
    in
    if not (Hashtbl.mem depsTable dep) then
      Hashtbl.add depsTable dep (dataDep ());
    ()
  in
  List.iter
    (fun target ->
       verbose Debug "  getting dependencies for target %s\n%!"
         (Target.get_target_name target);
       let nodeTarget = Target target.target_name in
       Dag.addNode nodeTarget depsDag;
       (* if a lib, then we insert ourself as dependency for executable or other library *)
       let insertEdgeForDependency =
         match target.target_name with
         | Name.Lib l ->
           Dag.addNode (Dependency l) depsDag;
           Dag.addEdge (Dependency l)
         | _ -> fun _ _ -> ()
       in
       List.iter
         (fun (dep, constr) ->
            maybe_unit
              (fun c ->
                 let _, pkg = Metacache.get dep.Libname.main_name in
                 if not (Expr.eval pkg.Meta.Pkg.version c) then
                   raise
                     (Dependencies.BuildDepAnalyzeFailed
                        (Libname.to_string dep ^ " (" ^ pkg.Meta.Pkg.version
                         ^ ") doesn't match the constraint " ^ Expr.to_string c)))
              constr;
            Dag.addEdge nodeTarget (Dependency dep) depsDag;
            insertEdgeForDependency (Dependency dep) depsDag;
            loop dep)
         (Target.get_all_builddeps target);

       if not (StringSet.is_empty !missingDeps) then
         raise (DependenciesMissing (StringSet.to_list !missingDeps));

       List.iter
         (fun (cpkg, cconstr) ->
            let ver = Prog.runPkgConfigVersion cpkg in
            (* TODO compare the constraints *)
            ignore cconstr;
            ignore ver;
            let pkgIncludes = List.map fp (Prog.runPkgConfigIncludes cpkg) in
            let pkgLibs = Prog.runPkgConfigLibs cpkg in
            let pkgConf =
              { cpkg_conf_libs = pkgLibs; cpkg_conf_includes = pkgIncludes }
            in
            Hashtbl.add cpkgsTable cpkg pkgConf)
         target.target_cbits.target_cpkgs)
    allTargets;

  if gconf.dump_dot then (
    let dotDir = Dist.create_build Dist.Dot in
    let path = dotDir </> fn "dependencies.dot" in
    let toString t =
      match t with
      | Target s -> "target(" ^ Name.to_string s ^ ")"
      | Dependency s -> Libname.to_string s
    in
    let dotContent = Dag.toDot toString "dependencies" true depsDag in
    Filesystem.writeFile path dotContent;

    let ipath = dotDir </> fn "internal-dependencies.dot" in
    let dotIContent =
      Dag.toDot Name.to_string "internal-dependencies" true targetsDag
    in
    Filesystem.writeFile ipath dotIContent);
  {
    project_dep_data = depsTable;
    project_pkgdeps_dag = depsDag;
    project_targets_dag = targetsDag;
    project_all_deps =
      List.concat
      $ List.map (fun target -> target.target_obits.target_builddeps) allTargets;
    project_ocamlcfg = ocamlCfg;
    project_ocamlmkcfg = ocamlMkCfg;
    project_file = projFile;
    project_cpkgs = cpkgsTable;
  }