package zanuda

  1. Overview
  2. Docs
Linter for OCaml+dune projects

Install

Dune Dependency

Authors

Maintainers

Sources

v1.1.0.tar.gz
sha256=5b7deabdb016858a0e19ddfb7647f628a243065f88c5ae9f4c362500d51cea7a
sha512=ceb852103fbbb88b5eeb8130bc7aa8bffe7130df6645d3298e1bb9b8f7e8f6c7b323ccc474cf92a08d28e7b80e9a96d68fd53de2e51c7c0e7d8e3e82e436b4bc

doc/src/zanuda.core/Load_dune.ml.html

Source file Load_dune.ml

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
[@@@ocaml.text "/*"]

(** Copyright 2021-2024, Kakadu. *)

(** SPDX-License-Identifier: LGPL-3.0-or-later *)

[@@@ocaml.text "/*"]

(* TODO: It would be great not to depend on Base, but currently ppx_expect requires it *)
open Base
open Utils
open Dune_project

type w =
  | Wrapped of string
  | Non_wrapped

let pp_w ppf = function
  | Non_wrapped -> Format.fprintf ppf "Non_wrapped"
  | Wrapped s -> Format.fprintf ppf "Wrapped %S" s
;;

let fine_module { impl } =
  match impl with
  | Some s when String.is_suffix s ~suffix:".ml-gen" -> false
  | _ -> true
;;

let to_module_name name =
  if Char.is_uppercase name.[0]
  then name
  else String.mapi name ~f:(fun i c -> if i = 0 then Char.uppercase c else c)
;;

let discover_wrappness modules =
  let module W = struct
    type w =
      | W of string * string
      | NW of string

    let pp_w ppf = function
      | NW s -> Format.fprintf ppf "NW %S" s
      | W (pref, suf) -> Format.fprintf ppf "W (%s __ %s)" pref suf
    ;;

    let is_NW = function
      | NW _ -> true
      | _ -> false
    ;;

    let is_W_with name = function
      | W (s, _) when String.equal s name -> true
      | _ -> false
    ;;
  end
  in
  let extract str =
    let pos_slash = String.rindex_exn str '/' in
    let pos_dot = String.rindex_exn str '.' in
    let len = pos_dot - pos_slash - 1 in
    assert (len > 0);
    let name = String.sub str ~pos:(1 + pos_slash) ~len in
    match String.substr_index name ~pattern:"__" with
    | None -> [ W.NW name ]
    | Some i ->
      [ W.W (String.prefix name i, String.suffix name (String.length name - i - 2)) ]
  in
  let mm =
    List.concat_map modules ~f:(fun m -> Option.value_map m.cmt ~default:[] ~f:extract)
  in
  let nonw, wrapp = List.partition_tf ~f:W.is_NW mm in
  if List.for_all ~f:W.is_NW mm
  then Some Non_wrapped
  else (
    match nonw with
    | [ W.NW libname ] when List.for_all ~f:(W.is_W_with libname) wrapp ->
      Some (Wrapped (to_module_name libname))
    | _ -> None)
;;

let pp_maybe_wrapped ppf = function
  | None -> Format.pp_print_string ppf "None"
  | Some x -> Format.fprintf ppf "Some %a" pp_w x
;;

(* TODO: move these tests to a separate library *)

let%expect_test _ =
  let ans =
    discover_wrappness
      [ Dune_project.module_ "a" ~cmt:"/a.cmt" ~cmti:"/a.cmti"
      ; Dune_project.module_ "b" ~cmt:"/b.cmt" ~cmti:"/b.cmti"
      ]
  in
  Format.printf "%a\n%!" pp_maybe_wrapped ans;
  [%expect {| Some Non_wrapped |}]
;;

let%expect_test _ =
  let ans =
    discover_wrappness
      [ Dune_project.module_ "a" ~cmt:"/libname__a.cmt" ~cmti:"/libname__a.cmti"
      ; Dune_project.module_ "b" ~cmt:"/libname__b.cmt" ~cmti:"/libname__b.cmti"
      ]
  in
  Format.printf "%a\n%!" pp_maybe_wrapped ans;
  [%expect {| None |}]
;;

let%expect_test _ =
  let ans =
    discover_wrappness
      [ Dune_project.module_ "libname" ~cmt:"/libname.cmt"
      ; Dune_project.module_ "a" ~cmt:"/libname__a.cmt" ~cmti:"/libname__a.cmti"
      ]
  in
  Format.printf "%a\n%!" pp_maybe_wrapped ans;
  [%expect {| Some Wrapped "Libname" |}]
;;

let analyze_dir ~untyped:analyze_untyped ~cmt:analyze_cmt ~cmti:analyze_cmti path =
  Unix.chdir path;
  let s =
    let ch = Unix.open_process_in "dune describe" in
    let s = Sexplib.Sexp.input_sexp ch in
    Caml.close_in ch;
    s
  in
  let db = [%of_sexp: t list] s in
  (* List.iter db ~f:(fun x -> Format.printf "%a\n%!" Sexplib.Sexp.pp_hum (sexp_of_t x)); *)
  Lint_filesystem.check db;
  let on_module (is_wrapped : w) m =
    (* printf "\t Working on module %S (wrapped = %b)\n%!" m.name is_wrapped; *)
    (* we analyze syntax tree without expanding syntax extensions *)
    let try_untyped filename =
      try analyze_untyped filename with
      | Syntaxerr.Error _e ->
        Format.eprintf "Syntaxerr.Error in analysis of '%s'. Skipped.\n%!" filename
    in
    Option.iter m.impl ~f:try_untyped;
    Option.iter m.intf ~f:try_untyped;
    (* Now analyze Typedtree extracted from cmt[i] *)
    let on_cmti source_file (_cmi_info, cmt_info) =
      Option.iter cmt_info ~f:(fun cmt ->
        Collected_lints.clear_tdecls ();
        match cmt.Cmt_format.cmt_annots with
        | Cmt_format.Implementation stru -> analyze_cmt is_wrapped source_file stru
        | Interface sign -> analyze_cmti is_wrapped source_file sign
        | Packed _ | Partial_implementation _ | Partial_interface _ ->
          printfn "%s %d" Caml.__FILE__ Caml.__LINE__;
          Caml.exit 1)
    in
    List.iter
      [ m.impl, m.cmt; m.intf, m.cmti ]
      ~f:(function
        | None, None ->
          (* TODO: I'm not 100% sure when it happens *)
          (* Format.printf "%s %d\n%!" __FILE__ __LINE__; *)
          ()
        | Some filename, None ->
          Caml.Format.printf "Found ml[i] file '%s' without cmt[i] file\n" filename
        | None, Some filename ->
          Caml.Format.printf "Found ml[i] file '%s' without cmt[i] file\n" filename
        | Some source_filename, Some cmt_filename ->
          let build_dir = "_build/default/" in
          let wrap =
            (* Format.printf "checking for prefix %S in %s\n%!" build_dir cmt_filename; *)
            if String.is_prefix ~prefix:build_dir cmt_filename
            then
              if Caml.Sys.file_exists cmt_filename
              then (fun f ->
                Unix.chdir build_dir;
                let infos =
                  if Config.verbose ()
                  then printfn "Reading cmt[i] file '%s'" cmt_filename;
                  Cmt_format.read
                    (String.drop_prefix cmt_filename (String.length build_dir))
                in
                f infos;
                Unix.chdir "../..")
              else
                fun _ ->
                Format.eprintf
                  "File '%s' doesn't exist. Maybe some of source files are not compiled?\n\
                   %!"
                  cmt_filename
            else
              fun f ->
              printfn "Loading CMT %S" cmt_filename;
              let cmt = Cmt_format.read cmt_filename in
              f cmt
          in
          (* Format.printf "%s %d src=%S\n%!" __FILE__ __LINE__ source_filename; *)
          wrap (on_cmti source_filename))
  in
  let loop_database () =
    List.iter db ~f:(function
      | Build_context _ | Root _ -> ()
      | Executables { modules; requires = _ } ->
        List.iter modules ~f:(fun m ->
          (* Dune doesn't allow to specify 'wrapped' for executables *)
          if fine_module m then on_module Non_wrapped m)
      | Library { Library.modules; name; _ } ->
        let wrappedness = discover_wrappness modules in
        (match wrappedness with
         | None -> Stdlib.Printf.eprintf "Can't detect wrappedness for a library %S" name
         | Some wrappedness ->
           (* printfn "Discovered wrappedness: %a" pp_w wrappedness; *)
           List.iter modules ~f:(fun m ->
             (* Format.printf "Trying module %a...\n%!" Sexp.pp (Dune_project.sexp_of_module_ m); *)
             if fine_module m
             then on_module wrappedness m
             else if (* Usually this happend with 'fake' wrapped modules from dune *)
                     not (String.equal name (String.lowercase m.name))
             then if Config.verbose () then printfn "module %S is omitted" m.name)))
  in
  loop_database ()
;;
OCaml

Innovation. Community. Security.