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
open Mo_def
open Ic
module Traversals = Mo_frontend.Traversals
(*
This module traverses the syntax tree. For each `import` statement, it looks
at the given relative path and tries to resolve it to a full path (where
full means relative to the current working directory, so that source
directories do not creep into the build output). If no file can be found
there, this prints an error message, otherwise it stores the real path
in the second, mutable field of the ImportE statement.
It returns a list of all imported file names.
*)
type filepath = string
type url = string
type blob = string
type resolved_imports = Syntax.resolved_import Source.phrase list
(* This returns a map from Syntax.resolved_import
to the location of the first import of that library
*)
module RIM = Map.Make
(struct
type t = Syntax.resolved_import
let compare = compare
end)
(* The Set variant is used in the pipeline module *)
module S = Set.Make
(struct
type t = Syntax.resolved_import
let compare = compare
end)
(* a map of type package_map will map each package name to local, non-relative
filepath e.g.,
packages("std") = "/Users/home/username/.dfinity-sdk/src/mo-stdlib/0.1.0/"
packages("foo") = "/Users/home/username/fooPackage/1.2.3/src"
*)
module M = Map.Make(String)
type package_map = filepath M.t
open Syntax
open Source
let err_unrecognized_url msgs at url msg =
let open Diag in
add_msg msgs
(error_message
at
"M0006"
"import"
(Printf.sprintf "cannot parse import URL \"%s\": %s" url msg))
let err_unrecognized_alias msgs alias principal msg =
let open Diag in
add_msg msgs
(error_message
no_region
"M0007"
"actor-alias"
(Printf.sprintf "cannot parse principal \"%s\" for actor alias \"%s\": %s" principal alias msg))
let err_actor_import_without_idl_path msgs at =
let open Diag in
add_msg msgs
(error_message
at
"M0008"
"import"
(Printf.sprintf "cannot import canister urls without --actor-idl param"))
let err_file_does_not_exist' at full_path =
Diag.error_message
at
"M0009"
"import"
(Printf.sprintf "file \"%s\" does not exist" full_path)
let err_file_does_not_exist msgs at full_path =
Diag.add_msg msgs (err_file_does_not_exist' at full_path)
let err_package_not_defined msgs at pkg =
let open Diag in
add_msg msgs
(error_message
at
"M0010"
"import"
(Printf.sprintf "package \"%s\" not defined" pkg))
let err_alias_not_defined msgs at alias =
let open Diag in
add_msg msgs
(error_message
at
"M0011"
"import"
(Printf.sprintf "canister alias \"%s\" not defined" alias))
let err_package_file_does_not_exist msgs f pname =
let open Diag in
add_msg msgs
(error_message
no_region
"M0012"
"package"
(Printf.sprintf "file \"%s\" (for package `%s`) does not exist" f pname))
let err_prim_pkg msgs =
let open Diag in
add_msg msgs
(error_message
no_region
"M0013"
"package" "the \"prim\" package is built-in, and cannot be mapped to a directory")
let append_extension : (string -> bool) -> string -> string =
fun file_exists f ->
let file_path = f ^ ".mo" in
let lib_path = Filename.concat f "lib.mo" in
if Option.is_some (Lib.String.chop_suffix "/" f) then
lib_path
else if file_exists file_path then
file_path
else
lib_path
let resolve_lib_import at full_path : (string, Diag.message) result =
let full_path = append_extension Sys.file_exists full_path in
let full_path = Lib.FilePath.normalise full_path in
if Sys.file_exists full_path
then Ok full_path
else Error (err_file_does_not_exist' at full_path)
let add_lib_import msgs imported ri_ref at lib_path =
match resolve_lib_import at lib_path.path with
| Ok full_path -> begin
let ri = LibPath {lib_path with path = full_path} in
ri_ref := ri;
imported := RIM.add ri at !imported
end
| Error err ->
Diag.add_msg msgs err
let add_idl_import msgs imported ri_ref at full_path bytes =
if Sys.file_exists full_path
then begin
ri_ref := IDLPath (full_path, bytes);
imported := RIM.add (IDLPath (full_path, bytes)) at !imported
end else
err_file_does_not_exist msgs at full_path
let add_prim_import imported ri_ref at =
ri_ref := PrimPath;
imported := RIM.add PrimPath at !imported
let in_base base f =
if base = "."
then f
else Filename.concat base f
let resolve_import_string msgs base actor_idl_path aliases packages imported (f, ri_ref, at) =
let resolve_ic bytes = match actor_idl_path with
| None -> err_actor_import_without_idl_path msgs at
| Some actor_base ->
let full_path = in_base actor_base (Url.idl_basename_of_blob bytes) in
add_idl_import msgs imported ri_ref at full_path bytes
in
match Url.parse f with
| Ok (Url.Relative path) ->
(* TODO support importing local .did file *)
add_lib_import msgs imported ri_ref at
{ path = in_base base path; package = None }
| Ok (Url.Package (pkg,path)) ->
begin match M.find_opt pkg packages with
| Some pkg_path ->
add_lib_import msgs imported ri_ref at
{ path = in_base pkg_path path; package = Some pkg }
| None -> err_package_not_defined msgs at pkg
end
| Ok (Url.Ic bytes) ->
if String.length bytes > 29 then
err_unrecognized_url msgs at f "Principal too long"
else
resolve_ic bytes
| Ok (Url.IcAlias alias) ->
begin match M.find_opt alias aliases with
| Some bytes -> resolve_ic bytes
| None -> err_alias_not_defined msgs at alias
end
| Ok Url.Prim ->
add_prim_import imported ri_ref at
| Error msg ->
err_unrecognized_url msgs at f msg
(* Resolve the argument to --package. *)
let resolve_package_url (msgs:Diag.msg_store) (pname:string) (f:url) : filepath =
if pname = "prim" then (err_prim_pkg msgs ;"") else
let f = Lib.FilePath.normalise f in
if Sys.file_exists f
then f
else (err_package_file_does_not_exist msgs f pname;"")
(* Resolve the argument to --actor-alias. Check eagerly for well-formedness *)
let resolve_alias_principal (msgs:Diag.msg_store) (alias:string) (f:string) : blob =
match Url.decode_principal f with
| Ok bytes ->
if String.length bytes > 29 then
(err_unrecognized_alias msgs alias f "Principal too long"; "")
else bytes
| Error msg -> err_unrecognized_alias msgs alias f msg; ""
let prog_imports (p : prog): (url * resolved_import ref * region) list =
let res = ref [] in
let f e = match e.it with
| ImportE (f, fp) -> res := (f, fp, e.at) ::!res; e
| _ -> e in
let _ = ignore (Traversals.over_prog f p) in
List.rev !res
type actor_idl_path = filepath option
type package_urls = url M.t
type actor_aliases = url M.t
type aliases = blob M.t
let resolve_packages : package_urls -> package_map Diag.result = fun purls ->
Diag.with_message_store (fun msgs -> Some (M.mapi (resolve_package_url msgs) purls))
let resolve_aliases : actor_aliases -> aliases Diag.result = fun alias_principals ->
Diag.with_message_store (fun msgs -> Some (M.mapi (resolve_alias_principal msgs) alias_principals))
type flags = {
package_urls : package_urls;
actor_aliases : actor_aliases;
actor_idl_path : actor_idl_path;
include_all_libs : bool;
}
type resolved_flags = {
packages : package_map;
aliases : aliases;
actor_idl_path : actor_idl_path;
}
let list_files_recursively : string -> string list =
fun dir ->
let rec loop result = function
| f :: fs when Sys.is_directory f ->
Sys.readdir f
|> Array.to_list
|> List.map (Filename.concat f)
|> List.append fs
|> loop result
| f :: fs -> loop (f :: result) fs
| [] -> result
in
loop [] [ dir ]
let list_files : string -> string list =
fun source ->
let all_files = list_files_recursively source in
List.filter (fun f -> Filename.extension f = ".mo") all_files
let package_imports base packages =
let imports = M.fold (fun pname url acc ->
if base = url then
acc
else
let files = list_files url in
List.map (fun path -> LibPath {package = Some pname; path = path}) files::acc)
packages []
in
List.concat imports
let resolve_flags : flags -> resolved_flags Diag.result
= fun { actor_idl_path; package_urls; actor_aliases; _ } ->
let open Diag.Syntax in
let* packages = resolve_packages package_urls in
let* aliases = resolve_aliases actor_aliases in
Diag.return { packages; aliases; actor_idl_path }
let resolve
: flags -> Syntax.prog -> filepath -> resolved_imports Diag.result
= fun flags p base ->
let open Diag.Syntax in
let* { packages; aliases; actor_idl_path } = resolve_flags flags in
Diag.with_message_store (fun msgs ->
let base = if Sys.is_directory base then base else Filename.dirname base in
let imported =
ref (if flags.include_all_libs
then (* add all available package libraries *)
(List.fold_right (fun ri rim -> RIM.add ri Source.no_region rim)
(package_imports base packages) RIM.empty)
else
(* consider only the explicitly imported package libraries *)
RIM.empty)
in
List.iter (resolve_import_string msgs base actor_idl_path aliases packages imported)(prog_imports p);
Some (List.map (fun (rim, at) -> rim @@ at) (RIM.bindings !imported))
)
let collect_imports (p:prog) base : ((url * url option) list) Diag.result =
(* TODO unify the code path for resolve and collect_imports *)
let base = if Sys.is_directory base then base else Filename.dirname base in
Diag.with_message_store (fun msgs ->
let imports =
List.map (fun (f, _, at) ->
match Url.parse f with
| Ok (Url.Relative path) -> begin
match resolve_lib_import at (in_base base path) with
| Ok full_path ->
(f, Some full_path)
| Error err ->
Diag.add_msg msgs err;
(f, None)
end
| _ -> (f, None)
) (prog_imports p) in
Some imports
)