Skip to content

Commit 244a5fb

Browse files
authored
deduplicates dune plugins and ignores empty plugins (#1569)
Removes duplicating packages, provided that they have the same implementation. When dune executes a binary (either via `dune exec` or `dune test`) it sets an internal `DUNE_DIR_LOCATIONS` environment variable, which affects the selection of plugins in bap, even if bap is installed from an indepenent repository. I am still not sure if this is a bug or a feature of dune, but to prevent double-loading we have to deduplicate plugins. We silently remove duplicating names only if all implementation that match the name are the same. We compare implementation using the sum of md5 sums of files that comprise the plugin folder. The choice of md5 sum as the implementation witness instead of parsing the META files allows us to preserve the abstraction of dune plugins, as the contents of the folder is the implementation detail of dune. This increases the probability that this code will still work, when they will change the implementation. In addition, this change arounds another subtle bug in the dune plugin system. When a plugin is removed with `opam remove`, opam leaves an empty folder in the plugin folder, which we should ignore, otherwise dune plugin loader will fail on it.
1 parent cbdf732 commit 244a5fb

File tree

1 file changed

+50
-1
lines changed

1 file changed

+50
-1
lines changed

lib/bap_plugins/bap_plugins.ml

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,17 +306,66 @@ module Plugins = struct
306306
| Ok p -> Some p
307307
| Error _ -> None)
308308

309+
(* returns a sorted deduplicated list of dune plugin names.
310+
311+
dune sites might have duplicating folders, so we have to be
312+
careful that we do not try to load the same plugin twice. *)
309313
let list_packages () =
314+
let require cnd k = if cnd then k () else None in
315+
316+
(* for each occurence of [name] in the search paths
317+
returns its digest. The digest is the sum of md5 sums
318+
of all files that comprise the plugin folder. *)
319+
let digests name =
320+
Bap_common.Plugins.paths |>
321+
List.filter_map ~f:(fun dir ->
322+
let path = Filename.concat dir name in
323+
require (Sys.file_exists path) @@ fun () ->
324+
require (Sys.is_directory path) @@ fun () ->
325+
let contents =
326+
Sys.readdir path |>
327+
Array.map ~f:(Filename.concat path) in
328+
require (Array.length contents > 0) @@ fun () ->
329+
Option.return @@
330+
Array.sum ~f:Md5.digest_file_blocking (module struct
331+
type t = Md5.t
332+
let zero = Md5.digest_string ""
333+
let (+) x y =
334+
Md5.digest_string (Md5.to_binary x ^ Md5.to_binary y)
335+
end) contents) in
336+
337+
(* removes duplicating names provided that they have the same
338+
implementation. The implementation is digested as the md5 sum
339+
of files comprising the plugin folder. *)
340+
let deduplicate names =
341+
let init = Map.empty (module String) in
342+
List.fold names ~init ~f:(fun plugins name ->
343+
Map.update plugins name ~f:(function
344+
| None -> digests name
345+
| Some ds -> ds @ digests name)) |>
346+
Map.filteri ~f:(fun ~key:name ~data:digests ->
347+
match digests with
348+
| [] -> false
349+
| d::ds when List.for_all ds ~f:(Md5.equal d) -> true
350+
| _ ->
351+
invalid_argf "The plugin %S has ambiguous implementations"
352+
name ()) |>
353+
Map.keys in
354+
310355
Bap_common.Plugins.list () |>
356+
deduplicate |>
311357
List.map ~f:Plugin.of_package
312358

359+
360+
313361
let list ?env ?provides ?library () =
314362
list_bundles ?env ?provides ?library () @
315363
list_packages ()
316364

317365
let collect ?env ?provides ?library () =
318366
collect_bundles ?env ?provides ?library () @
319-
List.map ~f:Result.return @@list_packages ()
367+
List.map ~f:Result.return @@
368+
list_packages ()
320369

321370
let loaded,finished = Future.create ()
322371

0 commit comments

Comments
 (0)