This commit is contained in:
parent
aa2ff7b2f0
commit
2f3113f55d
11742 changed files with 1223940 additions and 0 deletions
145
unikernel/duniverse/dune_/bin/alias.ml
Normal file
145
unikernel/duniverse/dune_/bin/alias.ml
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
open Import
|
||||
module Alias = Dune_engine.Alias
|
||||
module Alias0 = Dune_rules.Alias
|
||||
module Alias_builder = Dune_rules.Alias_builder
|
||||
|
||||
type t =
|
||||
{ name : Alias.Name.t
|
||||
; recursive : bool
|
||||
; dir : Path.Source.t
|
||||
; contexts : Dune_rules.Context.t list
|
||||
}
|
||||
|
||||
let pp { name; recursive; dir; contexts = _ } =
|
||||
let open Pp.O in
|
||||
let s =
|
||||
(if recursive then "@" else "@@")
|
||||
^ Path.Source.to_string (Path.Source.relative dir (Alias.Name.to_string name))
|
||||
in
|
||||
let pp = Pp.verbatim "alias" ++ Pp.space ++ Pp.verbatim s in
|
||||
if recursive then Pp.verbatim "recursive" ++ Pp.space ++ pp else pp
|
||||
;;
|
||||
|
||||
let in_dir ~name ~recursive ~contexts dir =
|
||||
let checked = Util.check_path contexts dir in
|
||||
match checked with
|
||||
| External _ ->
|
||||
User_error.raise
|
||||
[ Pp.textf "@@ on the command line must be followed by a relative path" ]
|
||||
| In_source_dir dir -> { dir; recursive; name; contexts }
|
||||
| In_private_context _ ->
|
||||
User_error.raise [ Pp.textf "no aliases in the testing context" ]
|
||||
| In_install_dir _ ->
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"Invalid alias: %s."
|
||||
(Path.to_string_maybe_quoted
|
||||
(Path.build Install.Context.install_context.build_dir))
|
||||
; Pp.textf "There are no aliases in %s." (Path.to_string_maybe_quoted dir)
|
||||
]
|
||||
| In_build_dir (ctx, dir) ->
|
||||
{ dir
|
||||
; recursive
|
||||
; name
|
||||
; contexts =
|
||||
[ List.find_exn contexts ~f:(fun c ->
|
||||
Context_name.equal (Context.name c) (Context.name ctx))
|
||||
]
|
||||
}
|
||||
;;
|
||||
|
||||
let of_string (root : Workspace_root.t) ~recursive s ~contexts =
|
||||
let path = Path.relative Path.root (root.reach_from_root_prefix ^ s) in
|
||||
if Path.is_root path
|
||||
then
|
||||
User_error.raise
|
||||
[ Pp.textf "@ on the command line must be followed by a valid alias name" ]
|
||||
else (
|
||||
let dir = Path.parent_exn path in
|
||||
let name = Alias.Name.of_string (Path.basename path) in
|
||||
in_dir ~name ~recursive ~contexts dir)
|
||||
;;
|
||||
|
||||
let find_dir_specified_on_command_line ~dir =
|
||||
let open Memo.O in
|
||||
Source_tree.find_dir dir
|
||||
>>| function
|
||||
| Some dir -> dir
|
||||
| None ->
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"Don't know about directory %s specified on the command line!"
|
||||
(Path.Source.to_string_maybe_quoted dir)
|
||||
]
|
||||
;;
|
||||
|
||||
let dep_on_alias_multi_contexts ~dir ~name ~contexts =
|
||||
ignore (find_dir_specified_on_command_line ~dir : _ Memo.t);
|
||||
let context_to_alias_expansion ctx =
|
||||
let ctx_dir = Context_name.build_dir ctx in
|
||||
let dir = Path.Build.(append_source ctx_dir dir) in
|
||||
Alias_builder.alias (Alias.make ~dir name)
|
||||
in
|
||||
Action_builder.all_unit (List.map contexts ~f:context_to_alias_expansion)
|
||||
;;
|
||||
|
||||
let dep_on_alias_rec_multi_contexts ~dir:src_dir ~name ~contexts =
|
||||
let open Action_builder.O in
|
||||
let* dir = Action_builder.of_memo (find_dir_specified_on_command_line ~dir:src_dir) in
|
||||
let* alias_statuses =
|
||||
Action_builder.all
|
||||
(List.map contexts ~f:(fun ctx ->
|
||||
let dir =
|
||||
Path.Build.append_source
|
||||
(Context_name.build_dir ctx)
|
||||
(Source_tree.Dir.path dir)
|
||||
in
|
||||
Dune_rules.Alias_rec.dep_on_alias_rec name dir))
|
||||
in
|
||||
match
|
||||
Alias0.is_standard name
|
||||
|| List.exists alias_statuses ~f:(fun (x : Alias_builder.Alias_status.t) ->
|
||||
match x with
|
||||
| Defined -> true
|
||||
| Not_defined -> false)
|
||||
with
|
||||
| true -> Action_builder.return ()
|
||||
| false ->
|
||||
let* load_dir =
|
||||
Action_builder.all
|
||||
@@ List.map contexts ~f:(fun ctx ->
|
||||
let dir =
|
||||
Source_tree.Dir.path dir
|
||||
|> Path.Build.append_source (Context_name.build_dir ctx)
|
||||
|> Path.build
|
||||
in
|
||||
Action_builder.of_memo @@ Load_rules.load_dir ~dir)
|
||||
in
|
||||
let hints =
|
||||
let candidates =
|
||||
Alias.Name.Set.union_map load_dir ~f:(function
|
||||
| Load_rules.Loaded.Build build -> Alias.Name.Set.of_keys build.aliases
|
||||
| _ -> Alias.Name.Set.empty)
|
||||
in
|
||||
User_message.did_you_mean
|
||||
(Alias.Name.to_string name)
|
||||
~candidates:(Alias.Name.Set.to_list_map ~f:Alias.Name.to_string candidates)
|
||||
in
|
||||
User_error.raise
|
||||
~hints
|
||||
[ Pp.textf
|
||||
"Alias %S specified on the command line is empty."
|
||||
(Alias.Name.to_string name)
|
||||
; Pp.textf
|
||||
"It is not defined in %s or any of its descendants."
|
||||
(Path.Source.to_string_maybe_quoted src_dir)
|
||||
]
|
||||
;;
|
||||
|
||||
let request { name; recursive; dir; contexts } =
|
||||
let contexts = List.map ~f:Context.name contexts in
|
||||
(if recursive then dep_on_alias_rec_multi_contexts else dep_on_alias_multi_contexts)
|
||||
~dir
|
||||
~name
|
||||
~contexts
|
||||
;;
|
||||
25
unikernel/duniverse/dune_/bin/alias.mli
Normal file
25
unikernel/duniverse/dune_/bin/alias.mli
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
open Import
|
||||
|
||||
type t = private
|
||||
{ name : Dune_engine.Alias.Name.t
|
||||
; recursive : bool
|
||||
; dir : Path.Source.t
|
||||
; contexts : Context.t list
|
||||
}
|
||||
|
||||
val in_dir
|
||||
: name:Dune_engine.Alias.Name.t
|
||||
-> recursive:bool
|
||||
-> contexts:Context.t list
|
||||
-> Path.t
|
||||
-> t
|
||||
|
||||
val of_string
|
||||
: Workspace_root.t
|
||||
-> recursive:bool
|
||||
-> string
|
||||
-> contexts:Context.t list
|
||||
-> t
|
||||
|
||||
val pp : t -> _ Pp.t
|
||||
val request : t -> unit Action_builder.t
|
||||
155
unikernel/duniverse/dune_/bin/arg.ml
Normal file
155
unikernel/duniverse/dune_/bin/arg.ml
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
open Stdune
|
||||
include Cmdliner.Arg
|
||||
|
||||
include struct
|
||||
open Dune_lang
|
||||
module Stanza = Stanza
|
||||
module String_with_vars = String_with_vars
|
||||
module Profile = Profile
|
||||
module Pform = Pform
|
||||
module Lib_name = Lib_name
|
||||
module Dep_conf = Dep_conf
|
||||
end
|
||||
|
||||
module Package = Dune_lang.Package
|
||||
module Context_name = Dune_engine.Context_name
|
||||
|
||||
let package_name = conv Package.Name.conv
|
||||
|
||||
module Path = struct
|
||||
module External = struct
|
||||
type t = string
|
||||
|
||||
let path p = Path.External.of_filename_relative_to_initial_cwd p
|
||||
let arg s = s
|
||||
let conv = conv ((fun p -> Ok p), Format.pp_print_string)
|
||||
end
|
||||
|
||||
type t = string
|
||||
|
||||
let path p = Path.of_filename_relative_to_initial_cwd p
|
||||
let arg s = s
|
||||
let conv = conv ((fun p -> Ok p), Format.pp_print_string)
|
||||
end
|
||||
|
||||
let path = Path.conv
|
||||
let external_path = Path.External.conv
|
||||
let profile = conv Profile.conv
|
||||
|
||||
module Dep = struct
|
||||
module Dep_conf = Dep_conf
|
||||
|
||||
type t = Dep_conf.t
|
||||
|
||||
let equal = Dep_conf.equal
|
||||
let file s = Dep_conf.File (String_with_vars.make_text Loc.none s)
|
||||
|
||||
let make_alias_sw ~dir s =
|
||||
let path =
|
||||
Dune_engine.Alias.Name.to_string s
|
||||
|> Stdune.Path.Local.relative dir
|
||||
|> Stdune.Path.Local.to_string
|
||||
in
|
||||
String_with_vars.make_text Loc.none path
|
||||
;;
|
||||
|
||||
let alias ~dir s = Dep_conf.Alias (make_alias_sw ~dir s)
|
||||
let alias_rec ~dir s = Dep_conf.Alias_rec (make_alias_sw ~dir s)
|
||||
|
||||
let parse_alias s =
|
||||
if not (String.is_prefix s ~prefix:"@")
|
||||
then None
|
||||
else (
|
||||
let pos, recursive =
|
||||
if String.length s >= 2 && s.[1] = '@' then 2, false else 1, true
|
||||
in
|
||||
let s = String_with_vars.make_text Loc.none (String.drop s pos) in
|
||||
Some (if recursive then Dep_conf.Alias_rec s else Dep_conf.Alias s))
|
||||
;;
|
||||
|
||||
let dep_parser =
|
||||
Dune_lang.Syntax.set
|
||||
Stanza.syntax
|
||||
(Active Stanza.latest_version)
|
||||
(String_with_vars.set_decoding_env
|
||||
(Pform.Env.initial ~stanza:Stanza.latest_version ~extensions:[])
|
||||
Dep_conf.decode)
|
||||
;;
|
||||
|
||||
let parser s =
|
||||
match parse_alias s with
|
||||
| Some dep -> Ok dep
|
||||
| None ->
|
||||
(match
|
||||
Dune_lang.Decoder.parse
|
||||
dep_parser
|
||||
Univ_map.empty
|
||||
(Dune_lang.Parser.parse_string
|
||||
~fname:"command line"
|
||||
~mode:Dune_lang.Parser.Mode.Single
|
||||
s)
|
||||
with
|
||||
| x -> Ok x
|
||||
| exception User_error.E msg -> Error (User_message.to_string msg))
|
||||
;;
|
||||
|
||||
let string_of_alias ~recursive sv =
|
||||
let prefix = if recursive then "@" else "@@" in
|
||||
String_with_vars.text_only sv |> Option.map ~f:(fun s -> prefix ^ s)
|
||||
;;
|
||||
|
||||
let printer ppf t =
|
||||
let s =
|
||||
match t with
|
||||
| Dep_conf.Alias sv -> string_of_alias ~recursive:false sv
|
||||
| Alias_rec sv -> string_of_alias ~recursive:true sv
|
||||
| File sv -> Some (Dune_lang.to_string (String_with_vars.encode sv))
|
||||
| _ -> None
|
||||
in
|
||||
let s =
|
||||
match s with
|
||||
| Some s -> s
|
||||
| None -> Dune_lang.to_string (Dep_conf.encode t)
|
||||
in
|
||||
Format.pp_print_string ppf s
|
||||
;;
|
||||
|
||||
let conv = conv' (parser, printer)
|
||||
let to_string_maybe_quoted t = String.maybe_quoted (Format.asprintf "%a" printer t)
|
||||
|
||||
let alias_arg =
|
||||
let parse x = Ok (Dep_conf.Alias (String_with_vars.make_text Loc.none x)) in
|
||||
conv' (parse, printer)
|
||||
;;
|
||||
|
||||
let alias_rec_arg =
|
||||
let parse x = Ok (Dep_conf.Alias_rec (String_with_vars.make_text Loc.none x)) in
|
||||
conv' (parse, printer)
|
||||
;;
|
||||
end
|
||||
|
||||
let dep = Dep.conv
|
||||
|
||||
let bytes =
|
||||
let decode repr =
|
||||
let ast =
|
||||
Dune_lang.Parser.parse_string
|
||||
~fname:"command line"
|
||||
~mode:Dune_lang.Parser.Mode.Single
|
||||
repr
|
||||
in
|
||||
match Dune_lang.Decoder.parse Dune_lang.Decoder.bytes_unit Univ_map.empty ast with
|
||||
| x -> Result.Ok x
|
||||
| exception User_error.E msg -> Result.Error (`Msg (User_message.to_string msg))
|
||||
in
|
||||
let pp_print_int64 state i = Format.pp_print_string state (Int64.to_string i) in
|
||||
conv (decode, pp_print_int64)
|
||||
;;
|
||||
|
||||
let graph_format : Dune_graph.Graph.File_format.t conv =
|
||||
conv Dune_graph.Graph.File_format.conv
|
||||
;;
|
||||
|
||||
let context_name : Context_name.t conv = conv Context_name.conv
|
||||
let lib_name = conv Lib_name.conv
|
||||
let version = pair ~sep:'.' int int
|
||||
42
unikernel/duniverse/dune_/bin/arg.mli
Normal file
42
unikernel/duniverse/dune_/bin/arg.mli
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
open Stdune
|
||||
|
||||
include module type of struct
|
||||
include Cmdliner.Arg
|
||||
end
|
||||
|
||||
module Path : sig
|
||||
module External : sig
|
||||
type t
|
||||
|
||||
val path : t -> Path.External.t
|
||||
val arg : t -> string
|
||||
end
|
||||
|
||||
type t
|
||||
|
||||
val path : t -> Path.t
|
||||
val arg : t -> string
|
||||
end
|
||||
|
||||
module Dep : sig
|
||||
type t = Dune_lang.Dep_conf.t
|
||||
|
||||
val equal : t -> t -> bool
|
||||
val file : string -> t
|
||||
val alias : dir:Stdune.Path.Local.t -> Dune_engine.Alias.Name.t -> t
|
||||
val alias_rec : dir:Stdune.Path.Local.t -> Dune_engine.Alias.Name.t -> t
|
||||
val to_string_maybe_quoted : t -> string
|
||||
val alias_arg : t conv
|
||||
val alias_rec_arg : t conv
|
||||
end
|
||||
|
||||
val bytes : int64 conv
|
||||
val context_name : Dune_engine.Context_name.t conv
|
||||
val dep : Dep.t conv
|
||||
val graph_format : Dune_graph.Graph.File_format.t conv
|
||||
val path : Path.t conv
|
||||
val external_path : Path.External.t conv
|
||||
val package_name : Dune_lang.Package.Name.t conv
|
||||
val profile : Dune_lang.Profile.t conv
|
||||
val lib_name : Dune_lang.Lib_name.t conv
|
||||
val version : Dune_lang.Syntax.Version.t conv
|
||||
215
unikernel/duniverse/dune_/bin/build.ml
Normal file
215
unikernel/duniverse/dune_/bin/build.ml
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
open Import
|
||||
|
||||
let with_metrics ~common f =
|
||||
let start_time = Unix.gettimeofday () in
|
||||
Fiber.finalize f ~finally:(fun () ->
|
||||
let duration = Unix.gettimeofday () -. start_time in
|
||||
if Common.print_metrics common
|
||||
then (
|
||||
let gc_stat = Gc.quick_stat () in
|
||||
(* We reset Memo counters below, unconditionally. *)
|
||||
let memo_counters_report = Memo.Metrics.report ~reset_after_reporting:false in
|
||||
Console.print_user_message
|
||||
(User_message.make
|
||||
([ Pp.textf "%s" memo_counters_report
|
||||
; Pp.textf
|
||||
"(%.2fs total, %.1fM heap words)"
|
||||
duration
|
||||
(float_of_int gc_stat.heap_words /. 1_000_000.)
|
||||
; Pp.text "Timers:"
|
||||
]
|
||||
@ List.map
|
||||
~f:(fun (timer, { Metrics.Timer.Measure.cumulative_time; count }) ->
|
||||
Pp.textf
|
||||
"%s - time spent = %.2fs, count = %d"
|
||||
timer
|
||||
cumulative_time
|
||||
count)
|
||||
(String.Map.to_list (Metrics.Timer.aggregated_timers ())))));
|
||||
Memo.Metrics.reset ();
|
||||
Fiber.return ())
|
||||
;;
|
||||
|
||||
let run_build_system ~common ~request =
|
||||
let run ~(toplevel : unit Memo.Lazy.t) =
|
||||
with_metrics ~common (fun () -> build (fun () -> Memo.Lazy.force toplevel))
|
||||
in
|
||||
let open Fiber.O in
|
||||
Fiber.finalize
|
||||
(fun () ->
|
||||
(* CR-someday amokhov: Currently we invalidate cached timestamps on every
|
||||
incremental rebuild. This conservative approach helps us to work around
|
||||
some [mtime] resolution problems (e.g. on Mac OS). It would be nice to
|
||||
find a way to avoid doing this. In fact, this may be unnecessary even
|
||||
for the initial build if we assume that the user does not modify files
|
||||
in the [_build] directory. For now, it's unclear if optimising this is
|
||||
worth the effort. *)
|
||||
Cached_digest.invalidate_cached_timestamps ();
|
||||
let* setup = Import.Main.setup () in
|
||||
let request =
|
||||
Action_builder.bind (Action_builder.of_memo setup) ~f:(fun setup ->
|
||||
request setup)
|
||||
in
|
||||
(* CR-someday cmoseley: Can we avoid creating a new lazy memo node every
|
||||
time the build system is rerun? *)
|
||||
(* This top-level node is used for traversing the whole Memo graph. *)
|
||||
let toplevel_cell, toplevel =
|
||||
Memo.Lazy.Expert.create ~name:"toplevel" (fun () ->
|
||||
let open Memo.O in
|
||||
let+ (), (_ : Dep.Fact.t Dep.Map.t) =
|
||||
Action_builder.evaluate_and_collect_facts request
|
||||
in
|
||||
())
|
||||
in
|
||||
let* res = run ~toplevel in
|
||||
let+ () =
|
||||
match Common.dump_memo_graph_file common with
|
||||
| None -> Fiber.return ()
|
||||
| Some file ->
|
||||
let path = Path.external_ file in
|
||||
let+ graph =
|
||||
Memo.dump_cached_graph
|
||||
~time_nodes:(Common.dump_memo_graph_with_timing common)
|
||||
toplevel_cell
|
||||
in
|
||||
Graph.serialize graph ~path ~format:(Common.dump_memo_graph_format common)
|
||||
(* CR-someday cmoseley: It would be nice to use Persistent to dump a
|
||||
copy of the graph's internal representation here, so it could be used
|
||||
without needing to re-run the build*)
|
||||
in
|
||||
res)
|
||||
~finally:(fun () ->
|
||||
Hooks.End_of_build.run ();
|
||||
Fiber.return ())
|
||||
;;
|
||||
|
||||
let poll_handling_rpc_build_requests ~(common : Common.t) ~config =
|
||||
let open Fiber.O in
|
||||
let rpc =
|
||||
match Common.rpc common with
|
||||
| `Allow server -> server
|
||||
| `Forbid_builds -> Code_error.raise "rpc server must be allowed in passive mode" []
|
||||
in
|
||||
Scheduler.Run.poll_passive
|
||||
~get_build_request:
|
||||
(let+ (Build (targets, ivar)) = Dune_rpc_impl.Server.pending_build_action rpc in
|
||||
let request setup =
|
||||
Target.interpret_targets (Common.root common) config setup targets
|
||||
in
|
||||
run_build_system ~common ~request, ivar)
|
||||
;;
|
||||
|
||||
let run_build_command_poll_eager ~(common : Common.t) ~config ~request : unit =
|
||||
Scheduler.go_with_rpc_server_and_console_status_reporting ~common ~config (fun () ->
|
||||
let open Fiber.O in
|
||||
(* Run two fibers concurrently. One is responible for rebuilding targets
|
||||
named on the command line in reaction to file system changes. The other
|
||||
is responsible for building targets named in RPC build requests. *)
|
||||
let+ () = Scheduler.Run.poll (run_build_system ~common ~request)
|
||||
and+ () = poll_handling_rpc_build_requests ~common ~config in
|
||||
())
|
||||
;;
|
||||
|
||||
let run_build_command_poll_passive ~common ~config ~request:_ : unit =
|
||||
(* CR-someday aalekseyev: It would've been better to complain if [request] is
|
||||
non-empty, but we can't check that here because [request] is a function.*)
|
||||
Scheduler.go_with_rpc_server_and_console_status_reporting ~common ~config (fun () ->
|
||||
poll_handling_rpc_build_requests ~common ~config)
|
||||
;;
|
||||
|
||||
let run_build_command_once ~(common : Common.t) ~config ~request =
|
||||
let open Fiber.O in
|
||||
let once () =
|
||||
let+ res = run_build_system ~common ~request in
|
||||
match res with
|
||||
| Error `Already_reported -> raise Dune_util.Report_error.Already_reported
|
||||
| Ok () -> ()
|
||||
in
|
||||
Scheduler.go_with_rpc_server ~common ~config once
|
||||
;;
|
||||
|
||||
let run_build_command ~(common : Common.t) ~config ~request =
|
||||
(match Common.watch common with
|
||||
| Yes Eager -> run_build_command_poll_eager
|
||||
| Yes Passive -> run_build_command_poll_passive
|
||||
| No -> run_build_command_once)
|
||||
~common
|
||||
~config
|
||||
~request
|
||||
;;
|
||||
|
||||
let build_via_rpc_server ~print_on_success ~targets =
|
||||
Rpc_common.wrap_build_outcome_exn
|
||||
~print_on_success
|
||||
(Rpc.Build.build ~wait:true)
|
||||
targets
|
||||
()
|
||||
;;
|
||||
|
||||
let build =
|
||||
let doc = "Build the given targets, or the default ones if none are given." in
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P {|Targets starting with a $(b,@) are interpreted as aliases.|}
|
||||
; `Blocks Common.help_secs
|
||||
; Common.examples
|
||||
[ "Build all targets in the current source tree", "dune build"
|
||||
; "Build targets in the `./foo/bar' directory", "dune build ./foo/bar"
|
||||
; ( "Build the minimal set of targets required for tooling such as Merlin \
|
||||
(useful for quickly detecting errors)"
|
||||
, "dune build @check" )
|
||||
; "Run all code formatting tools in-place", "dune build --auto-promote @fmt"
|
||||
]
|
||||
]
|
||||
in
|
||||
let name_ = Arg.info [] ~docv:"TARGET" in
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ targets = Arg.(value & pos_all dep [] name_)
|
||||
and+ aliases_rec = Arg.(value & opt_all Dep.alias_rec_arg [] & info [ "alias-rec" ])
|
||||
and+ aliases = Arg.(value & opt_all Dep.alias_arg [] & info [ "alias" ]) in
|
||||
let targets = List.concat [ targets; aliases; aliases_rec ] in
|
||||
let targets =
|
||||
match targets with
|
||||
| [] -> [ Common.Builder.default_target builder ]
|
||||
| _ :: _ -> targets
|
||||
in
|
||||
let common, config = Common.init builder in
|
||||
(* Here we need to find out whether another instance of dune already holds
|
||||
the global build lock, as this will determine whether the current
|
||||
instance of dune will perform the build itself or send a build request
|
||||
to the RPC server in an already-running dune process. The method of
|
||||
checking whether another dune instance holds the lock is to simply try
|
||||
to take the lock. If taking the lock succeeds then the current process
|
||||
will perform the build itself, and future attempts by this process to
|
||||
take the lock are guaranteed to succeed. If taking the lock fails then
|
||||
we know that another instance of dune must have it, and the current
|
||||
process will send a build RPC request to that dune instance. Checking
|
||||
the status of the lock by taking prevents a race condition where the
|
||||
state of the lock could otherwise change between checking it and taking
|
||||
it. *)
|
||||
match Dune_util.Global_lock.lock ~timeout:None with
|
||||
| Error lock_held_by ->
|
||||
(* This case is reached if dune detects that another instance of dune
|
||||
is already running. Rather than performing the build itself, the
|
||||
current instance of dune will instruct the already-running instance to
|
||||
perform the build by sending an RPC message. As only one RPC server
|
||||
can run at a time we need to use a fiber scheduler which does not run
|
||||
an RPC server in the background to schedule the fiber which will
|
||||
perform the RPC call.
|
||||
*)
|
||||
Rpc_common.run_via_rpc
|
||||
~builder
|
||||
~common
|
||||
~config
|
||||
lock_held_by
|
||||
(Rpc.Build.build ~wait:true)
|
||||
targets
|
||||
| Ok () ->
|
||||
let request setup =
|
||||
Target.interpret_targets (Common.root common) config setup targets
|
||||
in
|
||||
run_build_command ~common ~config ~request
|
||||
in
|
||||
Cmd.v (Cmd.info "build" ~doc ~man ~envs:Common.envs) term
|
||||
;;
|
||||
24
unikernel/duniverse/dune_/bin/build.mli
Normal file
24
unikernel/duniverse/dune_/bin/build.mli
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
open Import
|
||||
|
||||
(** Connect to an RPC server (waiting for the server to start if necessary) and
|
||||
then send a request to the server to build the specified targets. If the
|
||||
build fails then a diagnostic error message is printed. If
|
||||
[print_on_success] is true then this function will also print a message
|
||||
after the build succeeds. *)
|
||||
val build_via_rpc_server
|
||||
: print_on_success:bool
|
||||
-> targets:Dune_lang.Dep_conf.t list
|
||||
-> unit Fiber.t
|
||||
|
||||
val run_build_system
|
||||
: common:Common.t
|
||||
-> request:(Dune_rules.Main.build_system -> unit Action_builder.t)
|
||||
-> (unit, [ `Already_reported ]) result Fiber.t
|
||||
|
||||
val build : unit Cmd.t
|
||||
|
||||
val run_build_command
|
||||
: common:Common.t
|
||||
-> config:Dune_config.t
|
||||
-> request:(Dune_rules.Main.build_system -> unit Action_builder.t)
|
||||
-> unit
|
||||
117
unikernel/duniverse/dune_/bin/cache.ml
Normal file
117
unikernel/duniverse/dune_/bin/cache.ml
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
open Import
|
||||
|
||||
(* CR-someday amokhov: Implement other commands supported by Jenga. *)
|
||||
|
||||
let trim =
|
||||
let info =
|
||||
let doc = "Trim the Dune cache." in
|
||||
let man =
|
||||
[ `P "Trim the Dune cache to a specified size or by a specified amount."
|
||||
; `S "EXAMPLES"
|
||||
; `Pre
|
||||
{|Trimming the Dune cache to 1 GB.
|
||||
|
||||
\$ dune cache trim --size=1GB |}
|
||||
; `Pre
|
||||
{|Trimming 500 MB from the Dune cache.
|
||||
|
||||
\$ dune cache trim --trimmed-size=500MB |}
|
||||
]
|
||||
in
|
||||
Cmd.info "trim" ~doc ~man
|
||||
in
|
||||
Cmd.v info
|
||||
@@ let+ trimmed_size =
|
||||
Arg.(
|
||||
value
|
||||
& opt (some bytes) None
|
||||
& info
|
||||
~docv:"BYTES"
|
||||
[ "trimmed-size" ]
|
||||
~doc:"Size to trim from the cache. $(docv) is the same as for --size.")
|
||||
and+ size =
|
||||
Arg.(
|
||||
value
|
||||
& opt (some bytes) None
|
||||
& info
|
||||
~docv:"BYTES"
|
||||
[ "size" ]
|
||||
~doc:
|
||||
(sprintf
|
||||
"Size to trim the cache to. $(docv) is the number of bytes followed by \
|
||||
a unit. Byte units can be one of %s."
|
||||
(String.enumerate_or
|
||||
(List.map
|
||||
~f:(fun (units, _) -> List.hd units)
|
||||
Bytes_unit.conversion_table))))
|
||||
in
|
||||
Log.init_disabled ();
|
||||
let open Result.O in
|
||||
match
|
||||
let+ goal =
|
||||
match trimmed_size, size with
|
||||
| Some trimmed_size, None -> Result.Ok trimmed_size
|
||||
| None, Some size ->
|
||||
Result.Ok (Int64.sub (Dune_cache.Trimmer.overhead_size ()) size)
|
||||
| _ -> Result.Error "please specify either --size or --trimmed-size"
|
||||
in
|
||||
Dune_cache.Trimmer.trim ~goal
|
||||
with
|
||||
| Error s -> User_error.raise [ Pp.text s ]
|
||||
| Ok { trimmed_bytes; number_of_files_removed } ->
|
||||
User_message.print
|
||||
(User_message.make
|
||||
[ Pp.textf
|
||||
"Freed %s (%d files removed)"
|
||||
(Bytes_unit.pp trimmed_bytes)
|
||||
number_of_files_removed
|
||||
])
|
||||
;;
|
||||
|
||||
let size =
|
||||
let info =
|
||||
let doc = "Query the size of the Dune cache." in
|
||||
let man =
|
||||
[ `P
|
||||
"Compute the total size of files in the Dune cache which are not hardlinked \
|
||||
from any build directory and output it in a human-readable form."
|
||||
]
|
||||
in
|
||||
Cmd.info "size" ~doc ~man
|
||||
in
|
||||
Cmd.v info
|
||||
@@ let+ machine_readable =
|
||||
Arg.(
|
||||
value
|
||||
& flag
|
||||
& info [ "machine-readable" ] ~doc:"Outputs size as a plain number of bytes.")
|
||||
in
|
||||
let size = Dune_cache.Trimmer.overhead_size () in
|
||||
if machine_readable
|
||||
then User_message.print (User_message.make [ Pp.textf "%Ld" size ])
|
||||
else User_message.print (User_message.make [ Pp.textf "%s" (Bytes_unit.pp size) ])
|
||||
;;
|
||||
|
||||
let clear =
|
||||
let info =
|
||||
let doc = "Clear the Dune cache." in
|
||||
let man = [ `P "Remove any traces of the Dune cache." ] in
|
||||
Cmd.info "clear" ~doc ~man
|
||||
in
|
||||
Cmd.v info @@ Term.(const Dune_cache_storage.clear $ const ())
|
||||
;;
|
||||
|
||||
let command =
|
||||
let info =
|
||||
let doc = "Manage Dune's shared cache of build artifacts." in
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
"Dune can share build artifacts between workspaces. We currently only support \
|
||||
a few subcommands; however, we plan to provide more functionality soon."
|
||||
]
|
||||
in
|
||||
Cmd.info "cache" ~doc ~man
|
||||
in
|
||||
Cmd.group info [ trim; size; clear ]
|
||||
;;
|
||||
3
unikernel/duniverse/dune_/bin/cache.mli
Normal file
3
unikernel/duniverse/dune_/bin/cache.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
25
unikernel/duniverse/dune_/bin/clean.ml
Normal file
25
unikernel/duniverse/dune_/bin/clean.ml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
open Import
|
||||
|
||||
let command =
|
||||
let doc = "Clean the project." in
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P {|Removes files added by dune such as _build, <package>.install, and .merlin|}
|
||||
; `Blocks Common.help_secs
|
||||
]
|
||||
in
|
||||
let term =
|
||||
let+ builder = Common.Builder.term in
|
||||
(* Disable log file creation. Indeed, we are going to delete the whole build directory
|
||||
right after and that includes deleting the log file. Not only would creating the
|
||||
log file be useless but with some FS this also causes [dune clean] to fail (cf
|
||||
https://github.com/ocaml/dune/issues/2964). *)
|
||||
let builder = Common.Builder.disable_log_file builder in
|
||||
let _common, _config = Common.init builder in
|
||||
Dune_util.Global_lock.lock_exn ~timeout:None;
|
||||
Dune_engine.Target_promotion.files_in_source_tree_to_delete ()
|
||||
|> Path.Source.Set.iter ~f:(fun p -> Path.unlink_no_err (Path.source p));
|
||||
Path.rm_rf Path.build_dir
|
||||
in
|
||||
Cmd.v (Cmd.info "clean" ~doc ~man) term
|
||||
;;
|
||||
3
unikernel/duniverse/dune_/bin/clean.mli
Normal file
3
unikernel/duniverse/dune_/bin/clean.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
1480
unikernel/duniverse/dune_/bin/common.ml
Normal file
1480
unikernel/duniverse/dune_/bin/common.ml
Normal file
File diff suppressed because it is too large
Load diff
84
unikernel/duniverse/dune_/bin/common.mli
Normal file
84
unikernel/duniverse/dune_/bin/common.mli
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
open Dune_config_file
|
||||
open Stdune
|
||||
|
||||
type t
|
||||
|
||||
val x : t -> Dune_engine.Context_name.t option
|
||||
val capture_outputs : t -> bool
|
||||
val root : t -> Workspace_root.t
|
||||
|
||||
val rpc
|
||||
: t
|
||||
-> [ `Allow of Dune_lang.Dep_conf.t Dune_rpc_impl.Server.t
|
||||
(** Will run rpc if in watch mode and acquire the build lock *)
|
||||
| `Forbid_builds (** Promise not to build anything. For now, this isn't checked *)
|
||||
]
|
||||
|
||||
val watch_exclusions : t -> string list
|
||||
val stats : t -> Dune_stats.t option
|
||||
val print_metrics : t -> bool
|
||||
val dump_memo_graph_file : t -> Path.External.t option
|
||||
val dump_memo_graph_format : t -> Dune_graph.Graph.File_format.t
|
||||
val dump_memo_graph_with_timing : t -> bool
|
||||
val watch : t -> Dune_rpc_impl.Watch_mode_config.t
|
||||
val file_watcher : t -> Dune_engine.Scheduler.Run.file_watcher
|
||||
val prefix_target : t -> string -> string
|
||||
|
||||
(** [Builder] describes how to initialize Dune. *)
|
||||
module Builder : sig
|
||||
type t
|
||||
|
||||
val equal : t -> t -> bool
|
||||
val root : t -> string option
|
||||
val set_root : t -> string -> t
|
||||
val forbid_builds : t -> t
|
||||
val default_root_is_cwd : t -> bool
|
||||
val set_default_root_is_cwd : t -> bool -> t
|
||||
val set_log_file : t -> Dune_util.Log.File.t -> t
|
||||
val disable_log_file : t -> t
|
||||
val set_promote : t -> Dune_engine.Clflags.Promote.t -> t
|
||||
val default_target : t -> Arg.Dep.t
|
||||
val term : t Cmdliner.Term.t
|
||||
val default : t
|
||||
end
|
||||
|
||||
(** [init] creates a [Common.t] by executing a sequence of side-effecting actions to
|
||||
initialize Dune's working environment based on the options determined in the\
|
||||
[Builder.t].
|
||||
|
||||
Return the [Common.t] and the final configuration, which is the same as the one
|
||||
returned in the [config] field of [Dune_rules.Workspace.workspace ()]) *)
|
||||
val init : Builder.t -> t * Dune_config_file.Dune_config.t
|
||||
|
||||
(** [examples [("description", "dune cmd foo"); ...]] is an [EXAMPLES] manpage
|
||||
section of enumerated examples illustrating how to run the documented
|
||||
commands. *)
|
||||
val examples : (string * string) list -> Cmdliner.Manpage.block
|
||||
|
||||
(** [command_synopsis subcommands] is a custom [SYNOPSIS] manpage section
|
||||
listing the given [subcommands]. Each subcommand is prefixed with the `dune`
|
||||
top-level command. *)
|
||||
val command_synopsis : string list -> Cmdliner.Manpage.block list
|
||||
|
||||
val help_secs : Cmdliner.Manpage.block list
|
||||
val footer : Cmdliner.Manpage.block
|
||||
val envs : Cmdliner.Cmd.Env.info list
|
||||
val debug_backtraces : bool Cmdliner.Term.t
|
||||
val config_from_config_file : Dune_config.Partial.t Cmdliner.Term.t
|
||||
val display_term : Dune_config.Display.t option Cmdliner.Term.t
|
||||
val context_arg : doc:string -> Dune_engine.Context_name.t Cmdliner.Term.t
|
||||
|
||||
(** A [--build-info] command line argument that print build information
|
||||
(included in [term]) *)
|
||||
val build_info : unit Cmdliner.Term.t
|
||||
|
||||
val default_build_dir : string
|
||||
|
||||
module Let_syntax : sig
|
||||
val ( let+ ) : 'a Cmdliner.Term.t -> ('a -> 'b) -> 'b Cmdliner.Term.t
|
||||
val ( and+ ) : 'a Cmdliner.Term.t -> 'b Cmdliner.Term.t -> ('a * 'b) Cmdliner.Term.t
|
||||
end
|
||||
|
||||
(** [one_of term1 term2] allows options from [term1] or exclusively options from
|
||||
[term2]. If the user passes options from both terms, an error is reported. *)
|
||||
val one_of : 'a Cmdliner.Term.t -> 'a Cmdliner.Term.t -> 'a Cmdliner.Term.t
|
||||
7
unikernel/duniverse/dune_/bin/coq/coq.ml
Normal file
7
unikernel/duniverse/dune_/bin/coq/coq.ml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
open Import
|
||||
|
||||
let doc = "Command group related to Coq."
|
||||
let sub_commands_synopsis = Common.command_synopsis [ "coq top FILE -- ARGS" ]
|
||||
let man = [ `Blocks sub_commands_synopsis ]
|
||||
let info = Cmd.info ~doc ~man "coq"
|
||||
let group = Cmd.group info [ Coqtop.command ]
|
||||
3
unikernel/duniverse/dune_/bin/coq/coq.mli
Normal file
3
unikernel/duniverse/dune_/bin/coq/coq.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val group : unit Cmd.t
|
||||
159
unikernel/duniverse/dune_/bin/coq/coqtop.ml
Normal file
159
unikernel/duniverse/dune_/bin/coq/coqtop.ml
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
open Import
|
||||
|
||||
let doc = "Execute a Coq toplevel with the local configuration."
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
{|$(b,dune coq top FILE -- ARGS) runs the Coq toplevel to process the
|
||||
given $(b,FILE). The given arguments are completed according to the
|
||||
local configuration. This is equivalent to running $(b,coqtop ARGS)
|
||||
with a $(b,_CoqProject) file containing the local configurations
|
||||
from the $(b,dune) files, but does not require maintaining a
|
||||
$(b,_CoqProject) file.|}
|
||||
; `Blocks Common.help_secs
|
||||
]
|
||||
;;
|
||||
|
||||
let info = Cmd.info "top" ~doc ~man
|
||||
|
||||
let term =
|
||||
let+ default_builder = Common.Builder.term
|
||||
and+ context =
|
||||
let doc = "Run the Coq toplevel in this build context." in
|
||||
Common.context_arg ~doc
|
||||
and+ coqtop =
|
||||
let doc = "Run the given toplevel command instead of the default." in
|
||||
Arg.(value & opt string "coqtop" & info [ "toplevel" ] ~docv:"CMD" ~doc)
|
||||
and+ coq_file_arg =
|
||||
Arg.(required & pos 0 (some string) None (Arg.info [] ~docv:"COQFILE"))
|
||||
and+ extra_args = Arg.(value & pos_right 0 string [] (Arg.info [] ~docv:"ARGS"))
|
||||
and+ no_rebuild =
|
||||
Arg.(
|
||||
value
|
||||
& flag
|
||||
& info [ "no-build" ] ~doc:"Don't rebuild dependencies before executing.")
|
||||
in
|
||||
let common, config =
|
||||
let builder =
|
||||
if no_rebuild then Common.Builder.forbid_builds default_builder else default_builder
|
||||
in
|
||||
Common.init builder
|
||||
in
|
||||
let coq_file_arg = Common.prefix_target common coq_file_arg |> Path.Local.of_string in
|
||||
let coqtop, args, env =
|
||||
Scheduler.go_with_rpc_server ~common ~config
|
||||
@@ fun () ->
|
||||
let open Fiber.O in
|
||||
let* setup = Import.Main.setup () in
|
||||
let* setup = Memo.run setup in
|
||||
let sctx = Import.Main.find_scontext_exn setup ~name:context in
|
||||
let context = Dune_rules.Super_context.context sctx in
|
||||
let coq_file_build =
|
||||
Path.Build.append_local (Context.build_dir context) coq_file_arg
|
||||
in
|
||||
let dir =
|
||||
(match Path.Local.parent coq_file_arg with
|
||||
| None -> Path.Local.root
|
||||
| Some dir -> dir)
|
||||
|> Path.Build.append_local (Context.build_dir context)
|
||||
in
|
||||
let* coqtop, args, env =
|
||||
build_exn
|
||||
@@ fun () ->
|
||||
let open Memo.O in
|
||||
let* (tr : Dune_rules.Dir_contents.triage) =
|
||||
Dune_rules.Dir_contents.triage sctx ~dir
|
||||
in
|
||||
let dir =
|
||||
match tr with
|
||||
| Group_part dir -> dir
|
||||
| Standalone_or_root _ -> dir
|
||||
in
|
||||
let* dc = Dune_rules.Dir_contents.get sctx ~dir in
|
||||
let* coq_src = Dune_rules.Dir_contents.coq dc in
|
||||
let coq_module =
|
||||
let source = coq_file_build in
|
||||
match Dune_rules.Coq.Coq_sources.find_module ~source coq_src with
|
||||
| Some m -> snd m
|
||||
| None ->
|
||||
let hints =
|
||||
[ Pp.textf "Is the file part of a stanza?"
|
||||
; Pp.textf "Has the file been written to disk?"
|
||||
]
|
||||
in
|
||||
User_error.raise
|
||||
~hints
|
||||
[ Pp.textf "Cannot find file: %s" (coq_file_arg |> Path.Local.to_string) ]
|
||||
in
|
||||
let stanza = Dune_rules.Coq.Coq_sources.lookup_module coq_src coq_module in
|
||||
let args, use_stdlib, coq_lang_version, wrapper_name, mode =
|
||||
match stanza with
|
||||
| None ->
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"File not part of any stanza: %s"
|
||||
(coq_file_arg |> Path.Local.to_string)
|
||||
]
|
||||
| Some (`Theory theory) ->
|
||||
( Dune_rules.Coq.Coq_rules.coqtop_args_theory
|
||||
~sctx
|
||||
~dir
|
||||
~dir_contents:dc
|
||||
theory
|
||||
coq_module
|
||||
, theory.buildable.use_stdlib
|
||||
, theory.buildable.coq_lang_version
|
||||
, Dune_rules.Coq.Coq_lib_name.wrapper (snd theory.name)
|
||||
, theory.buildable.mode )
|
||||
| Some (`Extraction extr) ->
|
||||
( Dune_rules.Coq.Coq_rules.coqtop_args_extraction ~sctx ~dir extr coq_module
|
||||
, extr.buildable.use_stdlib
|
||||
, extr.buildable.coq_lang_version
|
||||
, "DuneExtraction"
|
||||
, extr.buildable.mode )
|
||||
in
|
||||
(* Run coqdep *)
|
||||
let* (_ : unit * Dep.Fact.t Dep.Map.t) =
|
||||
let deps_of =
|
||||
if no_rebuild
|
||||
then Action_builder.return ()
|
||||
else (
|
||||
let mode =
|
||||
match mode with
|
||||
| None -> Dune_rules.Coq.Coq_mode.VoOnly
|
||||
| Some mode -> mode
|
||||
in
|
||||
Dune_rules.Coq.Coq_rules.deps_of
|
||||
~dir
|
||||
~use_stdlib
|
||||
~wrapper_name
|
||||
~mode
|
||||
~coq_lang_version
|
||||
coq_module)
|
||||
in
|
||||
Action_builder.evaluate_and_collect_facts deps_of
|
||||
in
|
||||
(* Get args *)
|
||||
let* (args, _) : string list * Dep.Fact.t Dep.Map.t =
|
||||
let* args = args in
|
||||
let dir = Path.external_ Path.External.initial_cwd in
|
||||
let args = Dune_rules.Command.expand ~dir (S args) in
|
||||
Action_builder.evaluate_and_collect_facts args.build
|
||||
in
|
||||
let* prog = Super_context.resolve_program_memo sctx ~dir ~loc:None coqtop in
|
||||
let prog = Action.Prog.ok_exn prog in
|
||||
let* () = Build_system.build_file prog in
|
||||
let+ env = Super_context.context_env sctx in
|
||||
Path.to_string prog, args, env
|
||||
in
|
||||
let args =
|
||||
let topfile = Path.to_absolute_filename (Path.build coq_file_build) in
|
||||
("-topfile" :: topfile :: args) @ extra_args
|
||||
in
|
||||
Fiber.return (coqtop, args, env)
|
||||
in
|
||||
restore_cwd_and_execve (Common.root common) coqtop args env
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
3
unikernel/duniverse/dune_/bin/coq/coqtop.mli
Normal file
3
unikernel/duniverse/dune_/bin/coq/coqtop.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
141
unikernel/duniverse/dune_/bin/describe/aliases_targets.ml
Normal file
141
unikernel/duniverse/dune_/bin/describe/aliases_targets.ml
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
open Import
|
||||
|
||||
let ls_term (fetch_results : Path.Build.t -> string list Action_builder.t) =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ paths = Arg.(value & pos_all string [ "." ] & info [] ~docv:"DIR")
|
||||
and+ context =
|
||||
Common.context_arg ~doc:"The context to look in. Defaults to the default context."
|
||||
in
|
||||
let common, config = Common.init builder in
|
||||
let request (_ : Dune_rules.Main.build_system) =
|
||||
let header = List.length paths > 1 in
|
||||
let open Action_builder.O in
|
||||
let+ paragraphs =
|
||||
Action_builder.List.map paths ~f:(fun path ->
|
||||
(* The user supplied directory *)
|
||||
let dir = Path.of_string path in
|
||||
(* The _build and source tree version of this directory *)
|
||||
let build_dir, src_dir =
|
||||
match (dir : Path.t) with
|
||||
| In_source_tree d ->
|
||||
Path.Build.append_source (Dune_engine.Context_name.build_dir context) d, d
|
||||
| In_build_dir d ->
|
||||
let src_dir =
|
||||
(* We only drop the build context if it is correct. *)
|
||||
match Path.Build.extract_build_context d with
|
||||
| Some (dir_context_name, d) ->
|
||||
if
|
||||
Dune_engine.Context_name.equal
|
||||
context
|
||||
(Dune_engine.Context_name.of_string dir_context_name)
|
||||
then d
|
||||
else
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"Directory %s is not in context %S."
|
||||
(Path.to_string_maybe_quoted dir)
|
||||
(Dune_engine.Context_name.to_string context)
|
||||
]
|
||||
| None -> Code_error.raise "aliases_targets: build dir without context" []
|
||||
in
|
||||
d, src_dir
|
||||
| External _ ->
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"Directories outside of the project are not supported: %s"
|
||||
(Path.to_string_maybe_quoted dir)
|
||||
]
|
||||
in
|
||||
(* Check if the directory exists. *)
|
||||
let* () =
|
||||
Action_builder.of_memo
|
||||
@@
|
||||
let open Memo.O in
|
||||
Source_tree.find_dir src_dir
|
||||
>>= function
|
||||
| Some _ -> Memo.return ()
|
||||
| None ->
|
||||
(* The directory didn't exist. We therefore check if it was a
|
||||
directory target and error for the user accordingly. *)
|
||||
let+ is_dir_target =
|
||||
Load_rules.is_under_directory_target (Path.build build_dir)
|
||||
in
|
||||
if is_dir_target
|
||||
then
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"Directory %s is a directory target. This command does not support \
|
||||
the inspection of directory targets."
|
||||
(Path.to_string dir)
|
||||
]
|
||||
else
|
||||
User_error.raise
|
||||
[ Pp.textf "Directory %s does not exist." (Path.to_string dir) ]
|
||||
in
|
||||
let+ targets = fetch_results build_dir in
|
||||
(* If we are printing multiple directories, we print the directory
|
||||
name as a header. *)
|
||||
(if header then [ Pp.textf "%s:" (Path.to_string dir) ] else [])
|
||||
@ [ Pp.concat_map targets ~f:Pp.text ~sep:Pp.space ]
|
||||
|> Pp.concat ~sep:Pp.space)
|
||||
in
|
||||
Console.print
|
||||
[ Pp.vbox @@ Pp.concat_map ~f:Pp.vbox paragraphs ~sep:(Pp.seq Pp.space Pp.space) ]
|
||||
in
|
||||
Scheduler.go_with_rpc_server ~common ~config
|
||||
@@ fun () ->
|
||||
let open Fiber.O in
|
||||
Build.run_build_system ~common ~request
|
||||
>>| fun (_ : (unit, [ `Already_reported ]) result) -> ()
|
||||
;;
|
||||
|
||||
module Aliases_cmd = struct
|
||||
let fetch_results (dir : Path.Build.t) =
|
||||
let open Action_builder.O in
|
||||
let+ alias_targets =
|
||||
let+ load_dir =
|
||||
Action_builder.of_memo (Load_rules.load_dir ~dir:(Path.build dir))
|
||||
in
|
||||
match load_dir with
|
||||
| Load_rules.Loaded.Build build -> Dune_engine.Alias.Name.Map.keys build.aliases
|
||||
| _ -> []
|
||||
in
|
||||
List.map ~f:Dune_engine.Alias.Name.to_string alias_targets
|
||||
;;
|
||||
|
||||
let term = ls_term fetch_results
|
||||
|
||||
let command =
|
||||
let doc = "Print aliases in a given directory. Works similarly to ls." in
|
||||
Cmd.v (Cmd.info "aliases" ~doc ~envs:Common.envs) term
|
||||
;;
|
||||
end
|
||||
|
||||
module Targets_cmd = struct
|
||||
let fetch_results (dir : Path.Build.t) =
|
||||
let open Action_builder.O in
|
||||
let+ targets =
|
||||
let open Memo.O in
|
||||
Target.all_direct_targets (Some (Path.Build.drop_build_context_exn dir))
|
||||
>>| Path.Build.Map.to_list
|
||||
|> Action_builder.of_memo
|
||||
in
|
||||
List.filter_map targets ~f:(fun (path, kind) ->
|
||||
match Path.Build.equal (Path.Build.parent_exn path) dir with
|
||||
| false -> None
|
||||
| true ->
|
||||
(* directory targets can be distinguied by the trailing path separator
|
||||
*)
|
||||
Some
|
||||
(match kind with
|
||||
| Target.File -> Path.Build.basename path
|
||||
| Directory -> Path.Build.basename path ^ Filename.dir_sep))
|
||||
;;
|
||||
|
||||
let term = ls_term fetch_results
|
||||
|
||||
let command =
|
||||
let doc = "Print targets in a given directory. Works similarly to ls." in
|
||||
Cmd.v (Cmd.info "targets" ~doc ~envs:Common.envs) term
|
||||
;;
|
||||
end
|
||||
15
unikernel/duniverse/dune_/bin/describe/aliases_targets.mli
Normal file
15
unikernel/duniverse/dune_/bin/describe/aliases_targets.mli
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
open Import
|
||||
|
||||
(** ls like commands for showing aliases and targets *)
|
||||
|
||||
module Aliases_cmd : sig
|
||||
(** The aliases command lists all the aliases available in the given
|
||||
directory, defaulting to the current working directory. *)
|
||||
val command : unit Cmd.t
|
||||
end
|
||||
|
||||
module Targets_cmd : sig
|
||||
(** The targets command lists all the targets available in the given
|
||||
directory, defaulting to the current working directory. *)
|
||||
val command : unit Cmd.t
|
||||
end
|
||||
56
unikernel/duniverse/dune_/bin/describe/describe.ml
Normal file
56
unikernel/duniverse/dune_/bin/describe/describe.ml
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
open Import
|
||||
|
||||
(* This command is not yet versioned, but some people are using it in
|
||||
non-released tools. If you change the format of the output, please contact:
|
||||
|
||||
- rotor people for "describe workspace"
|
||||
|
||||
- duniverse people for "describe opam-files" *)
|
||||
|
||||
let subcommands =
|
||||
[ Describe_workspace.command
|
||||
; Describe_external_lib_deps.command
|
||||
; Describe_opam_files.command
|
||||
; Describe_pp.command
|
||||
; Printenv.command
|
||||
; Print_rules.command
|
||||
; Installed_libraries.command
|
||||
; Aliases_targets.Targets_cmd.command
|
||||
; Aliases_targets.Aliases_cmd.command
|
||||
; Package_entries.command
|
||||
; Describe_pkg.command
|
||||
; Describe_contexts.command
|
||||
; Describe_depexts.command
|
||||
; Describe_location.command
|
||||
]
|
||||
;;
|
||||
|
||||
let group =
|
||||
let doc = "Describe the workspace." in
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
{|Describe what is in the current workspace in either human or
|
||||
machine readable form.
|
||||
|
||||
By default, this command output a human readable description of
|
||||
the current workspace. This output is aimed at human and is not
|
||||
suitable for machine processing. In particular, it is not versioned.
|
||||
|
||||
If you want to interpret the output of this command from a program,
|
||||
you must use the $(b,--format) option to specify a machine readable
|
||||
format as well as the $(b,--lang) option to get a stable output.|}
|
||||
; `Blocks Common.help_secs
|
||||
]
|
||||
in
|
||||
let info = Cmd.info "describe" ~doc ~man in
|
||||
let default = Describe_workspace.term in
|
||||
Cmd.group ~default info subcommands
|
||||
;;
|
||||
|
||||
module Show = struct
|
||||
let group =
|
||||
let doc = "Command group for showing information about the workspace" in
|
||||
Cmd.group (Cmd.info ~doc "show") subcommands
|
||||
;;
|
||||
end
|
||||
9
unikernel/duniverse/dune_/bin/describe/describe.mli
Normal file
9
unikernel/duniverse/dune_/bin/describe/describe.mli
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
open Import
|
||||
|
||||
(** Command group for dune describe *)
|
||||
val group : unit Cmd.t
|
||||
|
||||
module Show : sig
|
||||
(** Command group for dune show (alias of describe) *)
|
||||
val group : unit Cmd.t
|
||||
end
|
||||
23
unikernel/duniverse/dune_/bin/describe/describe_contexts.ml
Normal file
23
unikernel/duniverse/dune_/bin/describe/describe_contexts.ml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
open Import
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config
|
||||
@@ fun () ->
|
||||
let open Fiber.O in
|
||||
let* setup = Import.Main.setup () in
|
||||
let+ setup = Memo.run setup in
|
||||
let ctxts =
|
||||
List.map
|
||||
~f:(fun (name, _) -> Context_name.to_string name)
|
||||
(Context_name.Map.to_list setup.scontexts)
|
||||
in
|
||||
List.iter ctxts ~f:print_endline
|
||||
;;
|
||||
|
||||
let command =
|
||||
let doc = "List the build contexts available in the workspace." in
|
||||
let info = Cmd.info ~doc "contexts" in
|
||||
Cmd.v info term
|
||||
;;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
open Import
|
||||
|
||||
(** Dune command to print out the available build contexts.*)
|
||||
val command : unit Cmd.t
|
||||
24
unikernel/duniverse/dune_/bin/describe/describe_depexts.ml
Normal file
24
unikernel/duniverse/dune_/bin/describe/describe_depexts.ml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
open Import
|
||||
|
||||
let print_depexts context_name =
|
||||
let open Fiber.O in
|
||||
let+ depexts =
|
||||
build_exn (fun () -> Dune_rules.Pkg_rules.all_filtered_depexts context_name)
|
||||
in
|
||||
Console.print [ Pp.concat_map ~sep:Pp.newline ~f:Pp.verbatim depexts ]
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ context_name = Common.context_arg ~doc:"Build context to use." in
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config (fun () -> print_depexts context_name)
|
||||
;;
|
||||
|
||||
let info =
|
||||
let doc = "Print the list of all the available depexts" in
|
||||
Cmd.info "depexts" ~doc
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
open Import
|
||||
|
||||
(** Command to print all depexts *)
|
||||
val command : unit Cmd.t
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
open Import
|
||||
module Lib_dep = Dune_lang.Lib_dep
|
||||
|
||||
module Kind = struct
|
||||
type t =
|
||||
| Required
|
||||
| Optional
|
||||
|
||||
let to_dyn : t -> Dyn.t = function
|
||||
| Required -> String "required"
|
||||
| Optional -> String "optional"
|
||||
;;
|
||||
end
|
||||
|
||||
type lib_dep =
|
||||
{ name : Lib_name.t
|
||||
; kind : Kind.t
|
||||
}
|
||||
|
||||
let lib_dep_to_dyn t =
|
||||
let open Dyn in
|
||||
List [ String (Lib_name.to_string t.name); Kind.to_dyn t.kind ]
|
||||
;;
|
||||
|
||||
module Item = struct
|
||||
module Kind = struct
|
||||
type t =
|
||||
| Executables
|
||||
| Library
|
||||
| Tests
|
||||
|
||||
let to_string = function
|
||||
| Executables -> "executables"
|
||||
| Library -> "library"
|
||||
| Tests -> "tests"
|
||||
;;
|
||||
end
|
||||
|
||||
type t =
|
||||
{ kind : Kind.t
|
||||
; dir : Path.Source.t
|
||||
; external_deps : lib_dep list
|
||||
; internal_deps : lib_dep list
|
||||
; names : string list
|
||||
; package : Package.t option
|
||||
; extensions : string list
|
||||
}
|
||||
|
||||
let to_dyn { kind; dir; external_deps; internal_deps; names; package; extensions } =
|
||||
let open Dyn in
|
||||
let record =
|
||||
record
|
||||
[ "names", (list string) names
|
||||
; "extensions", (list string) extensions
|
||||
; "package", option Package.Name.to_dyn (Option.map ~f:Package.name package)
|
||||
; "source_dir", String (Path.Source.to_string dir)
|
||||
; "external_deps", list lib_dep_to_dyn external_deps
|
||||
; "internal_deps", list lib_dep_to_dyn internal_deps
|
||||
]
|
||||
in
|
||||
Variant (Kind.to_string kind, [ record ])
|
||||
;;
|
||||
end
|
||||
|
||||
type dep =
|
||||
| Local of lib_dep
|
||||
| External of lib_dep
|
||||
|
||||
let is_external db name =
|
||||
let open Memo.O in
|
||||
let+ lib = Dune_rules.Lib.DB.find_even_when_hidden db name in
|
||||
match lib with
|
||||
| None -> true
|
||||
| Some t ->
|
||||
(match Dune_rules.Lib_info.status (Dune_rules.Lib.info t) with
|
||||
| Installed_private | Public _ | Private _ -> false
|
||||
| Installed -> true)
|
||||
;;
|
||||
|
||||
let resolve_lib db name kind =
|
||||
let open Memo.O in
|
||||
let+ is_external = is_external db name in
|
||||
if is_external then External { name; kind } else Local { name; kind }
|
||||
;;
|
||||
|
||||
let resolve_lib_pps db preprocess =
|
||||
let open Memo.O in
|
||||
Dune_rules.Instrumentation.with_instrumentation
|
||||
preprocess
|
||||
~instrumentation_backend:(Dune_rules.Lib.DB.instrumentation_backend db)
|
||||
|> Resolve.Memo.read_memo
|
||||
>>| Dune_lang.Preprocess.Per_module.pps
|
||||
>>= Memo.parallel_map ~f:(fun (_, name) -> resolve_lib db name Kind.Required)
|
||||
;;
|
||||
|
||||
let resolve_lib_deps db lib_deps =
|
||||
let open Memo.O in
|
||||
Memo.parallel_map lib_deps ~f:(fun (lib : Lib_dep.t) ->
|
||||
match lib with
|
||||
| Direct (_, name) | Re_export (_, name) ->
|
||||
let+ v = resolve_lib db name Kind.Required in
|
||||
[ v ]
|
||||
| Select select ->
|
||||
select.choices
|
||||
|> Memo.parallel_map ~f:(fun (choice : Lib_dep.Select.Choice.t) ->
|
||||
Lib_name.Set.to_string_list choice.required
|
||||
@ Lib_name.Set.to_string_list choice.forbidden
|
||||
|> Memo.parallel_map ~f:(fun name ->
|
||||
let name = Lib_name.of_string name in
|
||||
resolve_lib db name Kind.Optional))
|
||||
>>| List.concat)
|
||||
>>| List.concat
|
||||
;;
|
||||
|
||||
let resolve_libs db dir libraries preprocess names package kind extensions =
|
||||
let open Memo.O in
|
||||
let open Item in
|
||||
let* lib_deps = resolve_lib_deps db libraries in
|
||||
let+ lib_pps = resolve_lib_pps db preprocess in
|
||||
let deps = lib_deps @ lib_pps in
|
||||
let internal_deps, external_deps =
|
||||
deps
|
||||
|> List.partition_map ~f:(function
|
||||
| Local lib -> Either.Left lib
|
||||
| External lib -> Either.Right lib)
|
||||
in
|
||||
{ external_deps; internal_deps; kind; names; package; dir; extensions }
|
||||
;;
|
||||
|
||||
let exes_extensions (lib_config : Dune_rules.Lib_config.t) modes =
|
||||
Dune_rules.Executables.Link_mode.Map.to_list modes
|
||||
|> List.map ~f:(fun (m, loc) ->
|
||||
Dune_rules.Executables.Link_mode.extension
|
||||
m
|
||||
~loc
|
||||
~ext_obj:lib_config.ext_obj
|
||||
~ext_dll:lib_config.ext_dll)
|
||||
;;
|
||||
|
||||
let libs db (context : Context.t) =
|
||||
let open Memo.O in
|
||||
let* dune_files = Context.name context |> Dune_rules.Dune_load.dune_files in
|
||||
Memo.parallel_map dune_files ~f:(fun (dune_file : Dune_rules.Dune_file.t) ->
|
||||
Dune_file.stanzas dune_file
|
||||
>>= Memo.parallel_map ~f:(fun stanza ->
|
||||
let dir = Dune_file.dir dune_file in
|
||||
match Stanza.repr stanza with
|
||||
| Dune_rules.Executables.T exes ->
|
||||
let* ocaml = Context.ocaml context in
|
||||
resolve_libs
|
||||
db
|
||||
dir
|
||||
exes.buildable.libraries
|
||||
exes.buildable.preprocess
|
||||
(List.map (Nonempty_list.to_list exes.names) ~f:snd)
|
||||
exes.package
|
||||
Item.Kind.Executables
|
||||
(exes_extensions ocaml.lib_config exes.modes)
|
||||
>>| List.singleton
|
||||
| Dune_rules.Library.T lib ->
|
||||
resolve_libs
|
||||
db
|
||||
dir
|
||||
lib.buildable.libraries
|
||||
lib.buildable.preprocess
|
||||
[ Dune_rules.Library.best_name lib |> Lib_name.to_string ]
|
||||
(Dune_rules.Library.package lib)
|
||||
Item.Kind.Library
|
||||
[]
|
||||
>>| List.singleton
|
||||
| Dune_rules.Tests.T tests ->
|
||||
let* ocaml = Context.ocaml context in
|
||||
resolve_libs
|
||||
db
|
||||
dir
|
||||
tests.exes.buildable.libraries
|
||||
tests.exes.buildable.preprocess
|
||||
(List.map (Nonempty_list.to_list tests.exes.names) ~f:snd)
|
||||
(if Option.is_none tests.package then tests.exes.package else tests.package)
|
||||
Item.Kind.Tests
|
||||
(exes_extensions ocaml.lib_config tests.exes.modes)
|
||||
>>| List.singleton
|
||||
| _ -> Memo.return [])
|
||||
>>| List.concat)
|
||||
>>| List.concat
|
||||
;;
|
||||
|
||||
let external_resolved_libs (context : Context.t) =
|
||||
let open Memo.O in
|
||||
let* scope = Dune_rules.Scope.DB.find_by_dir (Context.build_dir context) in
|
||||
let db = Dune_rules.Scope.libs scope in
|
||||
libs db context
|
||||
>>| List.filter ~f:(fun (x : Item.t) ->
|
||||
not (List.is_empty x.external_deps && List.is_empty x.internal_deps))
|
||||
;;
|
||||
|
||||
let to_dyn context_name external_resolved_libs =
|
||||
let open Dyn in
|
||||
Tuple [ String context_name; list Item.to_dyn external_resolved_libs ]
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ context_name = Common.context_arg ~doc:"Build context to use."
|
||||
and+ _ = Describe_lang_compat.arg
|
||||
and+ format = Describe_format.arg in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config
|
||||
@@ fun () ->
|
||||
let open Fiber.O in
|
||||
let* setup = Import.Main.setup () in
|
||||
let* setup = Memo.run setup in
|
||||
let super_context = Import.Main.find_scontext_exn setup ~name:context_name in
|
||||
build_exn
|
||||
@@ fun () ->
|
||||
let open Memo.O in
|
||||
let context_name =
|
||||
Super_context.context super_context
|
||||
|> Context.name
|
||||
|> Dune_engine.Context_name.to_string
|
||||
in
|
||||
external_resolved_libs (Super_context.context super_context)
|
||||
>>| to_dyn context_name
|
||||
>>| Describe_format.print_dyn format
|
||||
;;
|
||||
|
||||
let command =
|
||||
let doc =
|
||||
"Print out external libraries needed to build the project. It's an approximated set \
|
||||
of libraries."
|
||||
in
|
||||
let info = Cmd.info ~doc "external-lib-deps" in
|
||||
Cmd.v info term
|
||||
;;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
open Import
|
||||
|
||||
(** Dune command to describe the external library dependencies *)
|
||||
val command : unit Cmd.t
|
||||
34
unikernel/duniverse/dune_/bin/describe/describe_format.ml
Normal file
34
unikernel/duniverse/dune_/bin/describe/describe_format.ml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
open Import
|
||||
|
||||
type t =
|
||||
| Sexp
|
||||
| Csexp
|
||||
|
||||
let all = [ "sexp", Sexp; "csexp", Csexp ]
|
||||
|
||||
let arg =
|
||||
let doc = Printf.sprintf "$(docv) must be %s" (Arg.doc_alts_enum all) in
|
||||
Arg.(value & opt (enum all) Sexp & info [ "format" ] ~docv:"FORMAT" ~doc)
|
||||
;;
|
||||
|
||||
let print_as_sexp dyn =
|
||||
let rec dune_lang_of_sexp : Sexp.t -> Dune_lang.t = function
|
||||
| Atom s -> Dune_lang.atom_or_quoted_string s
|
||||
| List l -> List (List.map l ~f:dune_lang_of_sexp)
|
||||
in
|
||||
let cst =
|
||||
dyn
|
||||
|> Sexp.of_dyn
|
||||
|> dune_lang_of_sexp
|
||||
|> Dune_lang.Ast.add_loc ~loc:Loc.none
|
||||
|> Dune_lang.Cst.concrete
|
||||
in
|
||||
let version = Dune_lang.Syntax.greatest_supported_version_exn Stanza.syntax in
|
||||
Pp.to_fmt Stdlib.Format.std_formatter (Dune_lang.Format.pp_top_sexps ~version [ cst ])
|
||||
;;
|
||||
|
||||
let print_dyn t dyn =
|
||||
match t with
|
||||
| Csexp -> Csexp.to_channel stdout (Sexp.of_dyn dyn)
|
||||
| Sexp -> print_as_sexp dyn
|
||||
;;
|
||||
13
unikernel/duniverse/dune_/bin/describe/describe_format.mli
Normal file
13
unikernel/duniverse/dune_/bin/describe/describe_format.mli
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
open Import
|
||||
|
||||
(** Formatting utilities for dune describe commands *)
|
||||
|
||||
type t =
|
||||
| Sexp
|
||||
| Csexp
|
||||
|
||||
(** Command line option for taking a serialisation format *)
|
||||
val arg : t Term.t
|
||||
|
||||
(** [print_dyn t dyn] prints the dyn to stdout serialised as configured in [t] *)
|
||||
val print_dyn : t -> Dyn.t -> unit
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
let arg =
|
||||
Arg.(
|
||||
value
|
||||
& opt (some string) None
|
||||
& info
|
||||
[ "lang" ]
|
||||
~docv:"VERSION"
|
||||
~doc:
|
||||
"This argument has no effect and is deprecated. It exists solely for backwards \
|
||||
compatibility.")
|
||||
;;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
(** Dune describe commands used to take a --lang argument that did nothing
|
||||
expect for dune describe workspace. To keep compatilbility with accepting
|
||||
such an argument we provide a dummy argument here that can be used. It's
|
||||
value will typically be ignored. *)
|
||||
val arg : string option Cmdliner.Term.t
|
||||
43
unikernel/duniverse/dune_/bin/describe/describe_location.ml
Normal file
43
unikernel/duniverse/dune_/bin/describe/describe_location.ml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
open! Import
|
||||
|
||||
let doc =
|
||||
"Print the path to the executable using the same resolution logic as [dune exec]."
|
||||
;;
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
{|$(b,dune describe location NAME) prints the path to the executable NAME using the same logic as:
|
||||
|}
|
||||
; `Pre "$ dune exec NAME"
|
||||
; `P
|
||||
"Dune will first try to resolve the executable within the public executables in \
|
||||
the current project, then inside the \"bin\" directory of each package among the \
|
||||
project's dependencies (when using dune package management), and finally within \
|
||||
the directories listed in the $PATH environment variable."
|
||||
]
|
||||
;;
|
||||
|
||||
let info = Cmd.info "location" ~doc ~man
|
||||
|
||||
let term : unit Term.t =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ context = Common.context_arg ~doc:{|Run the command in this build context.|}
|
||||
and+ prog =
|
||||
Arg.(required & pos 0 (some Exec.Cmd_arg.conv) None (Arg.info [] ~docv:"PROG"))
|
||||
in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config
|
||||
@@ fun () ->
|
||||
let open Fiber.O in
|
||||
let* setup = Import.Main.setup () in
|
||||
build_exn
|
||||
@@ fun () ->
|
||||
let open Memo.O in
|
||||
let* sctx = setup >>| Import.Main.find_scontext_exn ~name:context in
|
||||
let* prog = Exec.Cmd_arg.expand ~root:(Common.root common) ~sctx prog in
|
||||
let+ path = Exec.get_path common sctx ~prog >>| Path.to_string in
|
||||
Dune_console.printf "%s" path
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
open! Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
open Import
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ format = Describe_format.arg
|
||||
and+ _ = Describe_lang_compat.arg in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config
|
||||
@@ fun () ->
|
||||
build_exn
|
||||
@@ fun () ->
|
||||
let open Memo.O in
|
||||
let+ project = Source_tree.root () >>| Source_tree.Dir.project in
|
||||
let packages = Dune_project.packages project |> Package.Name.Map.values in
|
||||
let opam_file_to_dyn pkg =
|
||||
let opam_file = Path.source (Package.opam_file pkg) in
|
||||
let contents =
|
||||
if Dune_project.generate_opam_files project
|
||||
then (
|
||||
let template_file = Dune_rules.Opam_create.template_file opam_file in
|
||||
let template =
|
||||
if Path.exists template_file
|
||||
then Some (template_file, Io.read_file template_file)
|
||||
else None
|
||||
in
|
||||
Dune_rules.Opam_create.generate project pkg ~template)
|
||||
else Io.read_file opam_file
|
||||
in
|
||||
Dyn.Tuple [ String (Path.to_string opam_file); String contents ]
|
||||
in
|
||||
packages |> Dyn.list opam_file_to_dyn |> Describe_format.print_dyn format
|
||||
;;
|
||||
|
||||
let command =
|
||||
let doc = "Print information about the opam files that have been discovered." in
|
||||
let info = Cmd.info ~doc "opam-files" in
|
||||
Cmd.v info term
|
||||
;;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
open Import
|
||||
|
||||
(** Dune command to describe the opam files in a workspace *)
|
||||
val command : unit Cmd.t
|
||||
200
unikernel/duniverse/dune_/bin/describe/describe_pkg.ml
Normal file
200
unikernel/duniverse/dune_/bin/describe/describe_pkg.ml
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
open Import
|
||||
module Lock_dir = Dune_pkg.Lock_dir
|
||||
module Local_package = Dune_pkg.Local_package
|
||||
|
||||
module Show_lock = struct
|
||||
let print_lock lock_dir_arg () =
|
||||
let open Fiber.O in
|
||||
let* lock_dir_paths =
|
||||
Memo.run (Workspace.workspace ())
|
||||
>>| Pkg_common.Lock_dirs_arg.lock_dirs_of_workspace lock_dir_arg
|
||||
in
|
||||
Fiber.parallel_map lock_dir_paths ~f:(fun lock_dir_path ->
|
||||
let+ platform = Pkg_common.solver_env_from_system_and_context ~lock_dir_path in
|
||||
let lock_dir = Lock_dir.read_disk_exn lock_dir_path in
|
||||
let packages =
|
||||
Lock_dir.Packages.pkgs_on_platform_by_name lock_dir.packages ~platform
|
||||
|> Package_name.Map.values
|
||||
in
|
||||
Pp.concat
|
||||
~sep:Pp.space
|
||||
[ Pp.hovbox
|
||||
@@ Pp.textf "Contents of %s:" (Path.Source.to_string_maybe_quoted lock_dir_path)
|
||||
; Pkg_common.pp_packages packages
|
||||
]
|
||||
|> Pp.vbox)
|
||||
>>| Console.print
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ lock_dir_arg = Pkg_common.Lock_dirs_arg.term in
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config @@ print_lock lock_dir_arg
|
||||
;;
|
||||
|
||||
let command =
|
||||
let doc = "Display packages in a lock file" in
|
||||
let info = Cmd.info ~doc "lock" in
|
||||
Cmd.v info term
|
||||
;;
|
||||
end
|
||||
|
||||
module Dependency_hash = struct
|
||||
let print_local_packages_hash () =
|
||||
let open Fiber.O in
|
||||
let+ local_packages =
|
||||
Pkg_common.find_local_packages
|
||||
|> Memo.run
|
||||
>>| Package_name.Map.values
|
||||
>>| List.map ~f:Local_package.for_solver
|
||||
in
|
||||
let hash =
|
||||
Local_package.For_solver.non_local_dependencies local_packages
|
||||
|> Local_package.Dependency_hash.of_dependency_formula
|
||||
in
|
||||
match hash with
|
||||
| None -> User_error.raise [ Pp.text "No non-local dependencies" ]
|
||||
| Some dependency_hash ->
|
||||
print_endline (Local_package.Dependency_hash.to_string dependency_hash)
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term in
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config print_local_packages_hash
|
||||
;;
|
||||
|
||||
let info =
|
||||
let doc =
|
||||
"Print the hash of the project's non-local dependencies such as what would appear \
|
||||
in the \"dependency_hash\" field of a a lock.dune file."
|
||||
in
|
||||
Cmd.info "dependency-hash" ~doc
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
end
|
||||
|
||||
module List_locked_dependencies = struct
|
||||
module Package_universe = Dune_pkg.Package_universe
|
||||
module Lock_dir = Dune_pkg.Lock_dir
|
||||
module Opam_repo = Dune_pkg.Opam_repo
|
||||
module Package_version = Dune_pkg.Package_version
|
||||
module Opam_solver = Dune_pkg.Opam_solver
|
||||
|
||||
let info =
|
||||
let doc = "List the dependencies locked by a lockdir" in
|
||||
let man = [ `S "DESCRIPTION"; `P "List the dependencies locked by a lockdir" ] in
|
||||
Cmd.info "list-locked-dependencies" ~doc ~man
|
||||
;;
|
||||
|
||||
let package_deps_in_lock_dir_pp package_universe package_name ~transitive =
|
||||
let traverse, traverse_word =
|
||||
if transitive then `Transitive, "Transitive" else `Immediate, "Immediate"
|
||||
in
|
||||
let opam_package =
|
||||
Package_universe.opam_package_of_package package_universe package_name
|
||||
in
|
||||
let list_dependencies which =
|
||||
Package_universe.opam_package_dependencies_of_package
|
||||
package_universe
|
||||
package_name
|
||||
~which
|
||||
~traverse
|
||||
in
|
||||
Pp.concat
|
||||
~sep:Pp.cut
|
||||
[ Pp.hbox
|
||||
(Pp.textf
|
||||
"%s dependencies of local package %s"
|
||||
traverse_word
|
||||
(OpamPackage.to_string opam_package))
|
||||
; Pp.enumerate (list_dependencies `Non_test) ~f:(fun opam_package ->
|
||||
Pp.text (OpamPackage.to_string opam_package))
|
||||
; Pp.enumerate (list_dependencies `Test_only) ~f:(fun opam_package ->
|
||||
Pp.textf "%s (test only)" (OpamPackage.to_string opam_package))
|
||||
]
|
||||
|> Pp.vbox
|
||||
;;
|
||||
|
||||
let enumerate_lock_dirs_by_path workspace ~lock_dirs =
|
||||
let lock_dirs = Pkg_common.Lock_dirs_arg.lock_dirs_of_workspace lock_dirs workspace in
|
||||
List.filter_map lock_dirs ~f:(fun lock_dir_path ->
|
||||
if Path.exists (Path.source lock_dir_path)
|
||||
then (
|
||||
try Some (lock_dir_path, Lock_dir.read_disk_exn lock_dir_path) with
|
||||
| User_error.E e ->
|
||||
User_warning.emit
|
||||
[ Pp.textf
|
||||
"Failed to parse lockdir %s:"
|
||||
(Path.Source.to_string_maybe_quoted lock_dir_path)
|
||||
; User_message.pp e
|
||||
];
|
||||
None)
|
||||
else None)
|
||||
;;
|
||||
|
||||
let list_locked_dependencies ~transitive ~lock_dirs () =
|
||||
let open Fiber.O in
|
||||
let* lock_dirs_by_path, local_packages =
|
||||
let open Memo.O in
|
||||
Memo.both
|
||||
(Workspace.workspace () >>| enumerate_lock_dirs_by_path ~lock_dirs)
|
||||
Pkg_common.find_local_packages
|
||||
|> Memo.run
|
||||
in
|
||||
let+ pp =
|
||||
Fiber.parallel_map lock_dirs_by_path ~f:(fun (lock_dir_path, lock_dir) ->
|
||||
let+ platform = Pkg_common.solver_env_from_system_and_context ~lock_dir_path in
|
||||
let package_universe =
|
||||
Package_universe.create ~platform local_packages lock_dir |> User_error.ok_exn
|
||||
in
|
||||
Pp.vbox
|
||||
(Pp.concat
|
||||
~sep:Pp.cut
|
||||
[ Pp.hbox
|
||||
(Pp.textf
|
||||
"Dependencies of local packages locked in %s"
|
||||
(Path.Source.to_string_maybe_quoted lock_dir_path))
|
||||
; Pp.enumerate
|
||||
(Package_name.Map.keys local_packages)
|
||||
~f:(package_deps_in_lock_dir_pp package_universe ~transitive)
|
||||
|> Pp.box
|
||||
]))
|
||||
>>| Pp.concat ~sep:Pp.cut
|
||||
>>| Pp.vbox
|
||||
in
|
||||
Console.print [ pp ]
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ transitive =
|
||||
Arg.(
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "transitive" ]
|
||||
~doc:
|
||||
"Display transitive dependencies (by default only immediate dependencies \
|
||||
are displayed)")
|
||||
and+ lock_dirs = Pkg_common.Lock_dirs_arg.term in
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config
|
||||
@@ list_locked_dependencies ~transitive ~lock_dirs
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
end
|
||||
|
||||
let command =
|
||||
let doc = "Subcommands related to package management" in
|
||||
let info = Cmd.info ~doc "pkg" in
|
||||
Cmd.group
|
||||
info
|
||||
[ Show_lock.command; List_locked_dependencies.command; Dependency_hash.command ]
|
||||
;;
|
||||
3
unikernel/duniverse/dune_/bin/describe/describe_pkg.mli
Normal file
3
unikernel/duniverse/dune_/bin/describe/describe_pkg.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
183
unikernel/duniverse/dune_/bin/describe/describe_pp.ml
Normal file
183
unikernel/duniverse/dune_/bin/describe/describe_pp.ml
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
open Import
|
||||
module Dialect = Dune_lang.Dialect
|
||||
|
||||
let dialect_and_ml_kind file =
|
||||
let open Memo.O in
|
||||
let _base, ext =
|
||||
let file = Path.of_string file in
|
||||
Path.split_extension file
|
||||
in
|
||||
let+ project = Source_tree.root () >>| Source_tree.Dir.project in
|
||||
let dialects = Dune_project.dialects project in
|
||||
match Dialect.DB.find_by_extension dialects ext with
|
||||
| None -> User_error.raise [ Pp.textf "unsupported extension: %s" ext ]
|
||||
| Some x -> x
|
||||
;;
|
||||
|
||||
let execute_pp_action ~sctx file pp_file dump_file =
|
||||
let open Memo.O in
|
||||
let* expander =
|
||||
let bindings =
|
||||
Dune_lang.Pform.Map.singleton
|
||||
(Var Input_file)
|
||||
[ Dune_lang.Value.Path (Path.build (pp_file |> Path.as_in_build_dir_exn)) ]
|
||||
in
|
||||
let dir = pp_file |> Path.parent_exn |> Path.as_in_build_dir_exn in
|
||||
Super_context.expander sctx ~dir >>| Dune_rules.Expander.add_bindings ~bindings
|
||||
in
|
||||
let context = Dune_rules.Expander.context expander in
|
||||
let build_dir = Context_name.build_dir context in
|
||||
let* input =
|
||||
let* action, _observing_facts =
|
||||
let* loc, action =
|
||||
let+ dialect, ml_kind = dialect_and_ml_kind file in
|
||||
match Dialect.print_ast dialect ml_kind with
|
||||
| Some print_ast -> print_ast
|
||||
| None ->
|
||||
(* fall back to the OCaml print_ast function, known to exist, if one
|
||||
doesn't exist for this dialect. *)
|
||||
Dialect.print_ast Dialect.ocaml ml_kind |> Option.value_exn
|
||||
in
|
||||
let build =
|
||||
let open Action_builder.O in
|
||||
let+ build =
|
||||
Dune_rules.For_tests.Action_unexpanded.expand_no_targets
|
||||
action
|
||||
~chdir:build_dir
|
||||
~loc
|
||||
~expander
|
||||
~deps:[]
|
||||
~what:"describe pp"
|
||||
in
|
||||
Action.with_outputs_to dump_file build.action
|
||||
in
|
||||
Action_builder.evaluate_and_collect_facts build
|
||||
in
|
||||
let+ env = Dune_rules.Super_context.context_env sctx
|
||||
and+ execution_parameters = Dune_engine.Execution_parameters.default in
|
||||
let targets =
|
||||
let unvalidated = Targets.File.create dump_file in
|
||||
match Targets.validate unvalidated with
|
||||
| Valid targets -> targets
|
||||
| No_targets
|
||||
| Inconsistent_parent_dir
|
||||
| File_and_directory_target_with_the_same_name _ -> assert false
|
||||
in
|
||||
{ Dune_engine.Action_exec.targets = Some targets
|
||||
; root = Path.build build_dir
|
||||
; context = Some (Dune_engine.Build_context.create ~name:context)
|
||||
; env
|
||||
; rule_loc = Loc.none
|
||||
; execution_parameters
|
||||
; action
|
||||
}
|
||||
in
|
||||
let ok =
|
||||
let open Fiber.O in
|
||||
let build_deps deps = Build_system.build_deps deps |> Memo.run in
|
||||
let* result = Dune_engine.Action_exec.exec input ~build_deps in
|
||||
Dune_engine.Action_exec.Exec_result.ok_exn result >>| ignore
|
||||
in
|
||||
Memo.of_non_reproducible_fiber ok
|
||||
;;
|
||||
|
||||
let print_pped_file =
|
||||
let dump_file pp_file ~ml_kind =
|
||||
Path.set_extension
|
||||
pp_file
|
||||
~ext:
|
||||
(match (ml_kind : Ocaml.Ml_kind.t) with
|
||||
| Intf -> ".cmi.dump"
|
||||
| Impl -> ".cmo.dump")
|
||||
|> Path.as_in_build_dir_exn
|
||||
in
|
||||
fun ~sctx file pp_file ~ml_kind ->
|
||||
let open Memo.O in
|
||||
let dump_file = dump_file pp_file ~ml_kind in
|
||||
let+ () = execute_pp_action ~sctx file pp_file dump_file in
|
||||
let dump_file = Path.build dump_file in
|
||||
match Path.stat dump_file with
|
||||
| Ok { st_kind = S_REG; _ } ->
|
||||
Io.cat dump_file;
|
||||
Path.unlink_no_err dump_file
|
||||
| _ ->
|
||||
User_error.raise
|
||||
[ Pp.textf "cannot find a dump file: %s" (Path.to_string dump_file) ]
|
||||
;;
|
||||
|
||||
let find_module ~sctx file =
|
||||
let open Memo.O in
|
||||
let src = Path.drop_optional_build_context_src_exn (Path.build file) in
|
||||
Dune_rules.Top_module.find_module sctx src
|
||||
>>| function
|
||||
| None -> None
|
||||
| Some (m, _, _, origin) ->
|
||||
(match
|
||||
Dune_rules.Ml_sources.Origin.preprocess origin
|
||||
|> Dune_lang.Preprocess.Per_module.find (Dune_rules.Module.name m)
|
||||
with
|
||||
| Pps { staged = true; loc; _ } -> Some (`Staged_pps loc)
|
||||
| _ -> Some (`Module m))
|
||||
;;
|
||||
|
||||
let get_pped_file super_context file =
|
||||
let open Memo.O in
|
||||
let context = Super_context.context super_context in
|
||||
let in_build_dir file =
|
||||
file |> Path.to_string |> Path.Build.relative (Context.build_dir context)
|
||||
in
|
||||
let file_in_build_dir =
|
||||
if String.is_empty file
|
||||
then User_error.raise [ Pp.textf "No file given." ]
|
||||
else Path.of_string file |> in_build_dir
|
||||
in
|
||||
let* ml_kind =
|
||||
let+ _, ml_kind = dialect_and_ml_kind file in
|
||||
ml_kind
|
||||
in
|
||||
let file_not_found () =
|
||||
User_error.raise
|
||||
[ Pp.textf "%s does not exist" (Path.Build.to_string_maybe_quoted file_in_build_dir)
|
||||
]
|
||||
in
|
||||
find_module ~sctx:super_context file_in_build_dir
|
||||
>>= function
|
||||
| None -> file_not_found ()
|
||||
| Some (`Module m) ->
|
||||
(match
|
||||
Dune_rules.Module.source m ~ml_kind |> Option.map ~f:Dune_rules.Module.File.path
|
||||
with
|
||||
| None -> file_not_found ()
|
||||
| Some pp_file ->
|
||||
let+ () = Build_system.build_file pp_file in
|
||||
Ok (pp_file, ml_kind))
|
||||
| Some (`Staged_pps loc) ->
|
||||
User_error.raise ~loc [ Pp.text "staged_pps are not supported." ]
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ context_name = Common.context_arg ~doc:"Build context to use."
|
||||
and+ _ = Describe_lang_compat.arg
|
||||
and+ file = Arg.(required & pos 0 (some string) None (Arg.info [] ~docv:"FILE")) in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config
|
||||
@@ fun () ->
|
||||
let open Fiber.O in
|
||||
let* setup = Import.Main.setup () in
|
||||
let* setup = Memo.run setup in
|
||||
let sctx = Import.Main.find_scontext_exn setup ~name:context_name in
|
||||
build_exn
|
||||
@@ fun () ->
|
||||
let open Memo.O in
|
||||
let* result = get_pped_file sctx file in
|
||||
match result with
|
||||
| Error file -> Io.cat file |> Memo.return
|
||||
| Ok (pp_file, ml_kind) -> print_pped_file ~sctx file pp_file ~ml_kind
|
||||
;;
|
||||
|
||||
let command =
|
||||
let doc = "Build a given FILE and print the preprocessed output." in
|
||||
let info = Cmd.info ~doc "pp" in
|
||||
Cmd.v info term
|
||||
;;
|
||||
4
unikernel/duniverse/dune_/bin/describe/describe_pp.mli
Normal file
4
unikernel/duniverse/dune_/bin/describe/describe_pp.mli
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
open Import
|
||||
|
||||
(** Dune command to show the preprocessed version of a file. *)
|
||||
val command : unit Cmd.t
|
||||
687
unikernel/duniverse/dune_/bin/describe/describe_workspace.ml
Normal file
687
unikernel/duniverse/dune_/bin/describe/describe_workspace.ml
Normal file
|
|
@ -0,0 +1,687 @@
|
|||
open Import
|
||||
|
||||
module Options = struct
|
||||
(* Option flags for what to do while crawling the workspace *)
|
||||
type t =
|
||||
{ with_deps : bool (* whether to compute direct dependencies between modules *)
|
||||
; with_pps : bool
|
||||
(* whether to include the dependencies to ppx-rewriters (that are
|
||||
used at compile time) *)
|
||||
}
|
||||
|
||||
(* whether to sanitize absolute paths of workspace items, and their UIDs, to
|
||||
ensure reproducible tests *)
|
||||
let sanitize_for_tests = ref false
|
||||
|
||||
let arg_with_deps =
|
||||
let open Arg in
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "with-deps" ]
|
||||
~doc:"Whether the dependencies between modules should be printed."
|
||||
;;
|
||||
|
||||
let arg_with_pps =
|
||||
let open Arg in
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "with-pps" ]
|
||||
~doc:
|
||||
"Whether the dependencies towards ppx-rewriters (that are called at compile \
|
||||
time) should be taken into account."
|
||||
;;
|
||||
|
||||
let arg_sanitize_for_tests =
|
||||
let open Arg in
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "sanitize-for-tests" ]
|
||||
~doc:
|
||||
"Sanitize the absolute paths in workspace items, and the associated UIDs, so \
|
||||
that the output is reproducible."
|
||||
;;
|
||||
|
||||
let arg : t Term.t =
|
||||
let+ with_deps = arg_with_deps
|
||||
and+ with_pps = arg_with_pps
|
||||
and+ sanitize_for_tests_value = arg_sanitize_for_tests in
|
||||
sanitize_for_tests := sanitize_for_tests_value;
|
||||
{ with_deps; with_pps }
|
||||
;;
|
||||
end
|
||||
|
||||
(* The module [Descr] is a typed representation of the description of a
|
||||
workspace, that is provided by the ``dune describe workspace`` command.
|
||||
|
||||
Each sub-module contains a [to_dyn] function, that translates the
|
||||
descriptors to a value of type [Dyn.t].
|
||||
|
||||
The typed representation aims at precisely describing the structure of the
|
||||
information computed by ``dune describe``, and hopefully make users' life
|
||||
easier in decoding the S-expressions into meaningful contents. *)
|
||||
module Descr = struct
|
||||
(* [dyn_path p] converts a path to a value of type [Dyn.t]. Remark: this is
|
||||
different from Path.to_dyn, that produces extra tags from a variant
|
||||
datatype. *)
|
||||
let dyn_path (p : Path.t) : Dyn.t = String (Path.to_string p)
|
||||
|
||||
(* Description of the dependencies of a module *)
|
||||
module Mod_deps = struct
|
||||
type t =
|
||||
{ for_intf : Dune_rules.Module_name.t list
|
||||
(* direct module dependencies for the interface *)
|
||||
; for_impl : Dune_rules.Module_name.t list
|
||||
(* direct module dependencies for the implementation *)
|
||||
}
|
||||
|
||||
(* Conversion to the [Dyn.t] type *)
|
||||
let to_dyn { for_intf; for_impl } =
|
||||
let open Dyn in
|
||||
record
|
||||
[ "for_intf", list Dune_rules.Module_name.to_dyn for_intf
|
||||
; "for_impl", list Dune_rules.Module_name.to_dyn for_impl
|
||||
]
|
||||
;;
|
||||
end
|
||||
|
||||
(* Description of modules *)
|
||||
module Mod = struct
|
||||
type t =
|
||||
{ name : Dune_rules.Module_name.t (* name of the module *)
|
||||
; impl : Path.t option (* path to the .ml file, if any *)
|
||||
; intf : Path.t option (* path to the .mli file, if any *)
|
||||
; cmt : Path.t option (* path to the .cmt file, if any *)
|
||||
; cmti : Path.t option (* path to the .cmti file, if any *)
|
||||
; module_deps : Mod_deps.t (* direct module dependencies *)
|
||||
}
|
||||
|
||||
(* Conversion to the [Dyn.t] type *)
|
||||
let to_dyn { Options.with_deps; _ } { name; impl; intf; cmt; cmti; module_deps }
|
||||
: Dyn.t
|
||||
=
|
||||
let open Dyn in
|
||||
let optional_fields =
|
||||
let module_deps =
|
||||
if with_deps then Some ("module_deps", Mod_deps.to_dyn module_deps) else None
|
||||
in
|
||||
(* we build a list of options, that is later filtered, so that adding
|
||||
new optional fields in the future can be done easily *)
|
||||
match module_deps with
|
||||
| None -> []
|
||||
| Some module_deps -> [ module_deps ]
|
||||
in
|
||||
record
|
||||
@@ [ "name", Dune_rules.Module_name.to_dyn name
|
||||
; "impl", option dyn_path impl
|
||||
; "intf", option dyn_path intf
|
||||
; "cmt", option dyn_path cmt
|
||||
; "cmti", option dyn_path cmti
|
||||
]
|
||||
@ optional_fields
|
||||
;;
|
||||
end
|
||||
|
||||
(* Description of executables *)
|
||||
module Exe = struct
|
||||
type t =
|
||||
{ names : string list (* names of the executable *)
|
||||
; requires : Digest.t list
|
||||
(* list of direct dependencies to libraries, identified by their
|
||||
digests *)
|
||||
; modules : Mod.t list (* list of the modules the executable is composed of *)
|
||||
; include_dirs : Path.t list (* list of include directories *)
|
||||
}
|
||||
|
||||
let map_path t ~f = { t with include_dirs = List.map ~f t.include_dirs }
|
||||
|
||||
(* Conversion to the [Dyn.t] type *)
|
||||
let to_dyn options { names; requires; modules; include_dirs } : Dyn.t =
|
||||
let open Dyn in
|
||||
record
|
||||
[ "names", List (List.map ~f:(fun name -> String name) names)
|
||||
; "requires", Dyn.(list string) (List.map ~f:Digest.to_string requires)
|
||||
; "modules", list (Mod.to_dyn options) modules
|
||||
; "include_dirs", list dyn_path include_dirs
|
||||
]
|
||||
;;
|
||||
end
|
||||
|
||||
(* Description of libraries *)
|
||||
|
||||
module Lib = struct
|
||||
type t =
|
||||
{ name : Lib_name.t (* name of the library *)
|
||||
; uid : Digest.t (* digest of the library *)
|
||||
; local : bool (* whether this library is local *)
|
||||
; requires : Digest.t list
|
||||
(* list of direct dependendies to libraries, identified by their
|
||||
digests *)
|
||||
; source_dir : Path.t
|
||||
(* path to the directory that contains the sources of this library *)
|
||||
; modules : Mod.t list (* list of the modules the executable is composed of *)
|
||||
; include_dirs : Path.t list (* list of include directories *)
|
||||
}
|
||||
|
||||
let map_path t ~f =
|
||||
{ t with source_dir = f t.source_dir; include_dirs = List.map ~f t.include_dirs }
|
||||
;;
|
||||
|
||||
(* Conversion to the [Dyn.t] type *)
|
||||
let to_dyn options { name; uid; local; requires; source_dir; modules; include_dirs }
|
||||
: Dyn.t
|
||||
=
|
||||
let open Dyn in
|
||||
record
|
||||
[ "name", Lib_name.to_dyn name
|
||||
; "uid", String (Digest.to_string uid)
|
||||
; "local", Bool local
|
||||
; "requires", (list string) (List.map ~f:Digest.to_string requires)
|
||||
; "source_dir", dyn_path source_dir
|
||||
; "modules", list (Mod.to_dyn options) modules
|
||||
; "include_dirs", (list dyn_path) include_dirs
|
||||
]
|
||||
;;
|
||||
end
|
||||
|
||||
(* Description of items: executables, or libraries *)
|
||||
module Item = struct
|
||||
type t =
|
||||
| Executables of Exe.t
|
||||
| Library of Lib.t
|
||||
| Root of Path.t
|
||||
| Build_context of Path.t
|
||||
|
||||
let map_path t ~f =
|
||||
match t with
|
||||
| Executables exe -> Executables (Exe.map_path exe ~f)
|
||||
| Library lib -> Library (Lib.map_path lib ~f)
|
||||
| Root r -> Root (f r)
|
||||
| Build_context c -> Build_context (f c)
|
||||
;;
|
||||
|
||||
(* Conversion to the [Dyn.t] type *)
|
||||
let to_dyn options : t -> Dyn.t = function
|
||||
| Executables exe_descr -> Variant ("executables", [ Exe.to_dyn options exe_descr ])
|
||||
| Library lib_descr -> Variant ("library", [ Lib.to_dyn options lib_descr ])
|
||||
| Root root -> Variant ("root", [ String (Path.to_absolute_filename root) ])
|
||||
| Build_context build_ctxt ->
|
||||
Variant ("build_context", [ String (Path.to_string build_ctxt) ])
|
||||
;;
|
||||
end
|
||||
|
||||
(* Description of a workspace: a list of items *)
|
||||
module Workspace = struct
|
||||
type t = Item.t list
|
||||
|
||||
(* Conversion to the [Dyn.t] type *)
|
||||
let to_dyn options (items : t) : Dyn.t = Dyn.list (Item.to_dyn options) items
|
||||
end
|
||||
end
|
||||
|
||||
module Lang = struct
|
||||
type t = Dune_lang.Syntax.Version.t
|
||||
|
||||
let arg_conv =
|
||||
let parser s =
|
||||
match Scanf.sscanf s "%u.%u" (fun a b -> a, b) with
|
||||
| Ok t -> Ok t
|
||||
| Error () -> Error (`Msg "Expected version of the form NNN.NNN.")
|
||||
in
|
||||
let printer ppf t =
|
||||
Stdlib.Format.fprintf ppf "%s" (Dune_lang.Syntax.Version.to_string t)
|
||||
in
|
||||
Arg.conv ~docv:"VERSION" (parser, printer)
|
||||
;;
|
||||
|
||||
let arg : t Term.t =
|
||||
Term.ret
|
||||
@@ let+ v =
|
||||
Arg.(
|
||||
value
|
||||
& opt arg_conv (0, 1)
|
||||
& info
|
||||
[ "lang" ]
|
||||
~docv:"VERSION"
|
||||
~doc:"Behave the same as this version of Dune.")
|
||||
in
|
||||
if v = (0, 1)
|
||||
then `Ok v
|
||||
else (
|
||||
let msg =
|
||||
let pp =
|
||||
"Only --lang 0.1 is available at the moment as this command is not yet \
|
||||
stabilised. If you would like to release a software that relies on the \
|
||||
output of 'dune describe', please open a ticket on \
|
||||
https://github.com/ocaml/dune."
|
||||
|> Pp.text
|
||||
in
|
||||
Stdlib.Format.asprintf "%a" Pp.to_fmt pp
|
||||
in
|
||||
`Error (true, msg))
|
||||
;;
|
||||
end
|
||||
|
||||
(* The following module is responsible sanitizing the output of
|
||||
[dune describe workspace], so that the absolute paths and the UIDs that
|
||||
depend on them are stable for tests. These paths may differ, depending on
|
||||
the machine they are run on. *)
|
||||
module Sanitize_for_tests = struct
|
||||
module Workspace = struct
|
||||
let fake_findlib = lazy (Path.External.of_string "/FINDLIB")
|
||||
let fake_workspace = lazy (Path.External.of_string "/WORKSPACE_ROOT")
|
||||
|
||||
let sanitize_with_findlib ~findlib_paths path =
|
||||
let path = Path.external_ path in
|
||||
List.find_map findlib_paths ~f:(fun candidate ->
|
||||
let open Option.O in
|
||||
let* candidate = Path.as_external candidate in
|
||||
(* if the path to rename is an external path, try to find the
|
||||
OCaml root inside, and replace it with a fixed string *)
|
||||
let+ without_prefix = Path.drop_prefix ~prefix:(Path.external_ candidate) path in
|
||||
(* we have found the OCaml root path: let's replace it with a
|
||||
constant string *)
|
||||
Path.External.append_local (Lazy.force fake_findlib) without_prefix)
|
||||
;;
|
||||
|
||||
(* Sanitizes a workspace description, by renaming non-reproducible UIDs and
|
||||
paths *)
|
||||
let really_sanitize ~findlib_paths items =
|
||||
let rename_path = function
|
||||
(* we have found a path for OCaml's root: let's define the renaming
|
||||
function *)
|
||||
| Path.External path ->
|
||||
sanitize_with_findlib ~findlib_paths path
|
||||
|> Option.value ~default:path
|
||||
|> Path.external_
|
||||
| In_source_tree p ->
|
||||
(* Replace the workspace root with a fixed string *)
|
||||
Path.External.append_local (Lazy.force fake_workspace) (Path.Source.to_local p)
|
||||
|> Path.external_
|
||||
| path ->
|
||||
(* Otherwise, it should not be changed *)
|
||||
path
|
||||
in
|
||||
(* now, we rename the UIDs in the [requires] field , while reversing the
|
||||
list of items, so that we get back the original ordering *)
|
||||
List.map ~f:(Descr.Item.map_path ~f:rename_path) items
|
||||
;;
|
||||
|
||||
(* Sanitizes a workspace description when options ask to do so, or performs
|
||||
no change at all otherwise *)
|
||||
let sanitize ~findlib_paths items =
|
||||
if !Options.sanitize_for_tests then really_sanitize ~findlib_paths items else items
|
||||
;;
|
||||
end
|
||||
end
|
||||
|
||||
(* Crawl the workspace to get all the data *)
|
||||
module Crawl = struct
|
||||
open Dune_rules
|
||||
open Dune_engine
|
||||
open Memo.O
|
||||
|
||||
(* Computes the digest of a library *)
|
||||
let uid_of_library (lib : Lib.t) : Digest.t =
|
||||
let name = Lib.name lib in
|
||||
if Lib.is_local lib
|
||||
then (
|
||||
let source_dir = Lib_info.src_dir (Lib.info lib) in
|
||||
Digest.generic (name, Path.to_string source_dir))
|
||||
else Digest.generic name
|
||||
;;
|
||||
|
||||
let immediate_deps_of_module ~options ~obj_dir ~modules unit =
|
||||
match (options : Options.t) with
|
||||
| { with_deps = false; _ } ->
|
||||
Action_builder.return { Ocaml.Ml_kind.Dict.intf = []; impl = [] }
|
||||
| { with_deps = true; _ } ->
|
||||
let deps ml_kind =
|
||||
Dune_rules.Dep_rules.immediate_deps_of unit modules ~obj_dir ~ml_kind
|
||||
in
|
||||
let open Action_builder.O in
|
||||
let+ intf, impl = Action_builder.both (deps Intf) (deps Impl) in
|
||||
{ Ocaml.Ml_kind.Dict.intf; impl }
|
||||
;;
|
||||
|
||||
(* Builds the description of a module from a module and its object directory *)
|
||||
let module_
|
||||
~obj_dir
|
||||
~(deps_for_intf : Module.t list)
|
||||
~(deps_for_impl : Module.t list)
|
||||
(m : Module.t)
|
||||
: Descr.Mod.t
|
||||
=
|
||||
let source ml_kind = Option.map (Module.source m ~ml_kind) ~f:Module.File.path in
|
||||
let cmt ml_kind =
|
||||
Dune_rules.Obj_dir.Module.cmt_file obj_dir m ~ml_kind ~cm_kind:(Ocaml Cmi)
|
||||
in
|
||||
{ Descr.Mod.name = Module.name m
|
||||
; impl = source Impl
|
||||
; intf = source Intf
|
||||
; cmt = cmt Impl
|
||||
; cmti = cmt Intf
|
||||
; module_deps =
|
||||
{ for_intf = List.map ~f:Module.name deps_for_intf
|
||||
; for_impl = List.map ~f:Module.name deps_for_impl
|
||||
}
|
||||
}
|
||||
;;
|
||||
|
||||
(* Builds the list of modules *)
|
||||
let modules ~obj_dir ~deps_of modules_ : Descr.Mod.t list Memo.t =
|
||||
modules_
|
||||
|> Modules.With_vlib.drop_vlib
|
||||
|> Modules.fold ~init:(Memo.return []) ~f:(fun m macc ->
|
||||
let* acc = macc in
|
||||
let deps = deps_of m in
|
||||
let+ { Ocaml.Ml_kind.Dict.intf = deps_for_intf; impl = deps_for_impl }, _ =
|
||||
Dune_engine.Action_builder.evaluate_and_collect_facts deps
|
||||
in
|
||||
module_ ~obj_dir ~deps_for_intf ~deps_for_impl m :: acc)
|
||||
;;
|
||||
|
||||
(* Builds a workspace item for the provided executables object *)
|
||||
let executables sctx ~options ~project ~dir (exes : Executables.t)
|
||||
: (Descr.Item.t * Lib.Set.t) option Memo.t
|
||||
=
|
||||
let* expander = Super_context.expander sctx ~dir in
|
||||
Expander.eval_blang expander exes.enabled_if
|
||||
>>= function
|
||||
| false -> Memo.return None
|
||||
| true ->
|
||||
let first_exe = snd (Nonempty_list.hd exes.names) in
|
||||
let* scope =
|
||||
Scope.DB.find_by_project (Super_context.context sctx |> Context.name) project
|
||||
in
|
||||
let* modules_, obj_dir =
|
||||
let+ modules_, obj_dir =
|
||||
Dir_contents.get sctx ~dir
|
||||
>>= Dir_contents.ocaml
|
||||
>>= Ml_sources.modules_and_obj_dir
|
||||
~libs:(Scope.libs scope)
|
||||
~for_:(Exe { first_exe })
|
||||
in
|
||||
Modules.With_vlib.modules modules_, obj_dir
|
||||
in
|
||||
let* pp_map =
|
||||
let+ version =
|
||||
let+ ocaml = Super_context.context sctx |> Context.ocaml in
|
||||
ocaml.version
|
||||
in
|
||||
Staged.unstage
|
||||
@@ Pp_spec.pped_modules_map
|
||||
(Dune_lang.Preprocess.Per_module.without_instrumentation
|
||||
exes.buildable.preprocess)
|
||||
version
|
||||
in
|
||||
let deps_of module_ =
|
||||
let module_ = pp_map module_ in
|
||||
immediate_deps_of_module ~options ~obj_dir ~modules:modules_ module_
|
||||
in
|
||||
let obj_dir = Obj_dir.of_local obj_dir in
|
||||
let* modules_ = modules ~obj_dir ~deps_of modules_ in
|
||||
let+ requires =
|
||||
let* compile_info = Exe_rules.compile_info ~scope exes in
|
||||
let open Resolve.Memo.O in
|
||||
let* requires = Lib.Compile.direct_requires compile_info in
|
||||
if options.with_pps
|
||||
then
|
||||
let+ pps = Lib.Compile.pps compile_info in
|
||||
pps @ requires
|
||||
else Resolve.Memo.return requires
|
||||
in
|
||||
(match Resolve.peek requires with
|
||||
| Error () -> None
|
||||
| Ok libs ->
|
||||
let include_dirs = Obj_dir.all_cmis obj_dir in
|
||||
let exe_descr =
|
||||
{ Descr.Exe.names = List.map ~f:snd (Nonempty_list.to_list exes.names)
|
||||
; requires = List.map ~f:uid_of_library libs
|
||||
; modules = modules_
|
||||
; include_dirs
|
||||
}
|
||||
in
|
||||
Some (Descr.Item.Executables exe_descr, Lib.Set.of_list libs))
|
||||
;;
|
||||
|
||||
(* Builds a workspace item for the provided library object *)
|
||||
let library sctx ~options (lib : Lib.t) : Descr.Item.t option Memo.t =
|
||||
let* requires = Lib.requires lib in
|
||||
match Resolve.peek requires with
|
||||
| Error () -> Memo.return None
|
||||
| Ok requires ->
|
||||
let name = Lib.name lib in
|
||||
let info = Lib.info lib in
|
||||
let src_dir = Lib_info.src_dir info in
|
||||
let obj_dir = Lib_info.obj_dir info in
|
||||
let+ modules_ =
|
||||
match Lib.is_local lib with
|
||||
| false -> Memo.return []
|
||||
| true ->
|
||||
(* XXX why do we have a second object directory? *)
|
||||
let* modules_, obj_dir_ =
|
||||
let* libs =
|
||||
Scope.DB.find_by_dir (Path.as_in_build_dir_exn src_dir) >>| Scope.libs
|
||||
in
|
||||
let+ modules_, obj_dir_ =
|
||||
Dir_contents.get sctx ~dir:(Path.as_in_build_dir_exn src_dir)
|
||||
>>= Dir_contents.ocaml
|
||||
>>= Ml_sources.modules_and_obj_dir
|
||||
~libs
|
||||
~for_:(Library (Lib_info.lib_id info |> Lib_id.to_local_exn))
|
||||
in
|
||||
Modules.With_vlib.modules modules_, obj_dir_
|
||||
in
|
||||
let* pp_map =
|
||||
let+ version =
|
||||
let+ ocaml = Super_context.context sctx |> Context.ocaml in
|
||||
ocaml.version
|
||||
in
|
||||
Staged.unstage
|
||||
@@ Pp_spec.pped_modules_map
|
||||
(Dune_lang.Preprocess.Per_module.without_instrumentation
|
||||
(Lib_info.preprocess info))
|
||||
version
|
||||
in
|
||||
let deps_of module_ =
|
||||
immediate_deps_of_module
|
||||
~options
|
||||
~obj_dir:obj_dir_
|
||||
~modules:modules_
|
||||
(pp_map module_)
|
||||
in
|
||||
modules ~obj_dir ~deps_of modules_
|
||||
in
|
||||
let include_dirs = Obj_dir.all_cmis obj_dir in
|
||||
let lib_descr =
|
||||
{ Descr.Lib.name
|
||||
; uid = uid_of_library lib
|
||||
; local = Lib.is_local lib
|
||||
; requires = List.map requires ~f:uid_of_library
|
||||
; source_dir = src_dir
|
||||
; modules = modules_
|
||||
; include_dirs
|
||||
}
|
||||
in
|
||||
Some (Descr.Item.Library lib_descr)
|
||||
;;
|
||||
|
||||
(* [source_path_is_in_dirs dirs p] tests whether the source path [p] is a
|
||||
descendant of some of the provided directory [dirs]. If [dirs = None],
|
||||
then it always succeeds. If [dirs = Some l], then a matching directory is
|
||||
search in the list [l]. *)
|
||||
let source_path_is_in_dirs dirs (p : Path.Source.t) =
|
||||
match dirs with
|
||||
| None -> true
|
||||
| Some dirs -> List.exists ~f:(fun dir -> Path.Source.is_descendant p ~of_:dir) dirs
|
||||
;;
|
||||
|
||||
(* Tests whether a dune file is located in a path that is a descendant of
|
||||
some directory *)
|
||||
let dune_file_is_in_dirs dirs dune_file =
|
||||
Dune_file.dir dune_file |> source_path_is_in_dirs dirs
|
||||
;;
|
||||
|
||||
(* Tests whether a library is located in a path that is a descendant of some
|
||||
directory *)
|
||||
let lib_is_in_dirs dirs (lib : Lib.t) =
|
||||
source_path_is_in_dirs
|
||||
dirs
|
||||
(Path.drop_build_context_exn @@ Lib_info.best_src_dir @@ Lib.info lib)
|
||||
;;
|
||||
|
||||
(* Builds a workspace item for the root path *)
|
||||
let root () = Descr.Item.Root Path.root
|
||||
|
||||
(* Builds a workspace item for the build directory path *)
|
||||
let build_ctxt (context : Context.t) : Descr.Item.t =
|
||||
Descr.Item.Build_context (Path.build (Context.build_dir context))
|
||||
;;
|
||||
|
||||
(* Builds a workspace description for the provided dune setup and context *)
|
||||
let workspace
|
||||
options
|
||||
({ Dune_rules.Main.contexts = _; scontexts } : Dune_rules.Main.build_system)
|
||||
(context : Context.t)
|
||||
dirs
|
||||
: Descr.Workspace.t Memo.t
|
||||
=
|
||||
let context_name = Context.name context in
|
||||
let sctx = Context_name.Map.find_exn scontexts context_name in
|
||||
let open Memo.O in
|
||||
let* dune_files =
|
||||
Dune_load.dune_files context_name >>| List.filter ~f:(dune_file_is_in_dirs dirs)
|
||||
in
|
||||
let* exes, exe_libs =
|
||||
(* the list of workspace items that describe executables, and the list of
|
||||
their direct library dependencies *)
|
||||
Memo.parallel_map dune_files ~f:(fun (dune_file : Dune_file.t) ->
|
||||
Dune_file.stanzas dune_file
|
||||
>>= Memo.parallel_map ~f:(fun stanza ->
|
||||
match Stanza.repr stanza with
|
||||
| Executables.T exes ->
|
||||
let dir =
|
||||
Path.Build.append_source
|
||||
(Context.build_dir context)
|
||||
(Dune_file.dir dune_file)
|
||||
in
|
||||
let project = Dune_file.project dune_file in
|
||||
executables sctx ~options ~project ~dir exes
|
||||
| _ -> Memo.return None)
|
||||
>>| List.filter_opt)
|
||||
>>| List.concat
|
||||
>>| List.split
|
||||
in
|
||||
let exe_libs =
|
||||
(* conflate the dependencies of executables into a single set *)
|
||||
Lib.Set.union_all exe_libs
|
||||
in
|
||||
let* project_libs =
|
||||
(* the list of libraries declared in the project *)
|
||||
Dune_load.projects ()
|
||||
>>= Memo.parallel_map ~f:(fun project ->
|
||||
Scope.DB.find_by_project (Context.name context) project
|
||||
>>| Scope.libs
|
||||
>>= Lib.DB.all)
|
||||
>>| Lib.Set.union_all
|
||||
>>| Lib.Set.filter ~f:(lib_is_in_dirs dirs)
|
||||
in
|
||||
let+ libs =
|
||||
(* the executables' libraries, and the project's libraries *)
|
||||
Lib.Set.union exe_libs project_libs
|
||||
|> Lib.Set.to_list
|
||||
|> Lib.descriptive_closure ~with_pps:options.with_pps
|
||||
>>= Memo.parallel_map ~f:(library ~options sctx)
|
||||
>>| List.filter_opt
|
||||
in
|
||||
let root = root () in
|
||||
let build_ctxt = build_ctxt context in
|
||||
root :: build_ctxt :: (exes @ libs)
|
||||
;;
|
||||
end
|
||||
|
||||
let find_dir common dir =
|
||||
let p = Path.Source.(relative root) (Common.prefix_target common dir) in
|
||||
let s = Path.source p in
|
||||
if not @@ Path.exists s
|
||||
then User_error.raise [ Pp.textf "No such file or directory: %s" (Path.to_string s) ];
|
||||
if not @@ Path.is_directory s
|
||||
then
|
||||
User_error.raise
|
||||
[ Pp.textf "File exists, but is not a directory: %s" (Path.to_string s) ];
|
||||
Memo.return p
|
||||
;;
|
||||
|
||||
let term : unit Term.t =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ what =
|
||||
Arg.(
|
||||
value
|
||||
& pos_all string []
|
||||
& info
|
||||
[]
|
||||
~docv:"DIRS"
|
||||
~doc:
|
||||
"prints a description of the workspace's structure. If some directories DIRS \
|
||||
are provided, then only those directories of the workspace are considered.")
|
||||
and+ context_name = Common.context_arg ~doc:"Build context to use."
|
||||
and+ format = Describe_format.arg
|
||||
and+ lang = Lang.arg
|
||||
and+ options = Options.arg in
|
||||
let common, config = Common.init builder in
|
||||
let dirs =
|
||||
let args = "workspace" :: what in
|
||||
let parse =
|
||||
Dune_lang.Syntax.set Stanza.syntax (Active lang)
|
||||
@@
|
||||
let open Dune_lang.Decoder in
|
||||
fields
|
||||
@@ field "workspace"
|
||||
@@ let+ dirs = repeat relative_file in
|
||||
(* [None] means that all directories should be accepted,
|
||||
whereas [Some l] means that only the directories in the
|
||||
list [l] should be accepted. The checks on whether the
|
||||
paths exist and whether they are directories are performed
|
||||
later in the [describe] function. *)
|
||||
let dirs = if List.is_empty dirs then None else Some dirs in
|
||||
dirs
|
||||
in
|
||||
let ast =
|
||||
Dune_lang.Ast.add_loc
|
||||
~loc:Loc.none
|
||||
(List (List.map args ~f:Dune_lang.atom_or_quoted_string))
|
||||
in
|
||||
Dune_lang.Decoder.parse parse Univ_map.empty ast
|
||||
in
|
||||
Scheduler.go_with_rpc_server ~common ~config
|
||||
@@ fun () ->
|
||||
let open Fiber.O in
|
||||
let* setup = Import.Main.setup () in
|
||||
build_exn
|
||||
@@ fun () ->
|
||||
let open Memo.O in
|
||||
let* setup = setup in
|
||||
let super_context = Import.Main.find_scontext_exn setup ~name:context_name in
|
||||
let context = Super_context.context super_context in
|
||||
let* findlib_paths = Context.findlib_paths context in
|
||||
(* prefix directories with the workspace root, so that the
|
||||
command also works correctly when it is run from a
|
||||
subdirectory *)
|
||||
Memo.Option.map dirs ~f:(Memo.List.map ~f:(find_dir common))
|
||||
>>= Crawl.workspace options setup context
|
||||
>>| Sanitize_for_tests.Workspace.sanitize ~findlib_paths
|
||||
>>| Descr.Workspace.to_dyn options
|
||||
>>| Describe_format.print_dyn format
|
||||
;;
|
||||
|
||||
let command =
|
||||
let doc =
|
||||
"Print a description of the workspace's structure. If some directories DIRS are \
|
||||
provided, then only those directories of the workspace are considered."
|
||||
in
|
||||
let info = Cmd.info ~doc "workspace" in
|
||||
Cmd.v info term
|
||||
;;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
open Import
|
||||
|
||||
val term : unit Term.t
|
||||
|
||||
(** Dune command that describes the workspace *)
|
||||
val command : unit Cmd.t
|
||||
26
unikernel/duniverse/dune_/bin/describe/package_entries.ml
Normal file
26
unikernel/duniverse/dune_/bin/describe/package_entries.ml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
open Import
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ context_name = Common.context_arg ~doc:"Build context to use."
|
||||
and+ format = Describe_format.arg in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config
|
||||
@@ fun () ->
|
||||
let open Fiber.O in
|
||||
let* setup = Import.Main.setup () in
|
||||
let* setup = Memo.run setup in
|
||||
let super_context = Import.Main.find_scontext_exn setup ~name:context_name in
|
||||
build_exn
|
||||
@@ fun () ->
|
||||
let open Memo.O in
|
||||
Dune_rules.Install_rules.stanzas_to_entries super_context
|
||||
>>| Package.Name.Map.to_dyn (Dyn.list Install.Entry.Sourced.to_dyn)
|
||||
>>| Describe_format.print_dyn format
|
||||
;;
|
||||
|
||||
let command =
|
||||
let doc = "prints information about the entries per package." in
|
||||
let info = Cmd.info ~doc "package-entries" in
|
||||
Cmd.v info term
|
||||
;;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
open Import
|
||||
|
||||
(** Dune command to print out information about the entries per package.*)
|
||||
val command : unit Cmd.t
|
||||
38
unikernel/duniverse/dune_/bin/diagnostics.ml
Normal file
38
unikernel/duniverse/dune_/bin/diagnostics.ml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
open Import
|
||||
|
||||
let exec () =
|
||||
let open Fiber.O in
|
||||
let where = Rpc_common.active_server_exn () in
|
||||
let module Client = Dune_rpc_client.Client in
|
||||
let+ errors =
|
||||
let* connect = Client.Connection.connect_exn where in
|
||||
Dune_rpc_impl.Client.client
|
||||
connect
|
||||
(Dune_rpc_private.Initialize.Request.create
|
||||
~id:(Dune_rpc_private.Id.make (Sexp.Atom "diagnostics_cmd")))
|
||||
~f:(fun cli ->
|
||||
let* decl =
|
||||
Client.Versioned.prepare_request cli Dune_rpc_private.Public.Request.diagnostics
|
||||
in
|
||||
match decl with
|
||||
| Error e -> raise (Dune_rpc_private.Version_error.E e)
|
||||
| Ok decl -> Client.request cli decl ())
|
||||
in
|
||||
match errors with
|
||||
| Ok errors ->
|
||||
List.iter errors ~f:(fun err ->
|
||||
Console.print_user_message (Dune_rpc.Diagnostic.to_user_message err))
|
||||
| Error e -> Rpc_common.raise_rpc_error e
|
||||
;;
|
||||
|
||||
let info =
|
||||
let doc = "Fetch and return errors from the current build." in
|
||||
Cmd.info "diagnostics" ~doc
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ (builder : Common.Builder.t) = Common.Builder.term in
|
||||
Rpc_common.client_term builder exec
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
3
unikernel/duniverse/dune_/bin/diagnostics.mli
Normal file
3
unikernel/duniverse/dune_/bin/diagnostics.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
87
unikernel/duniverse/dune_/bin/dune
Normal file
87
unikernel/duniverse/dune_/bin/dune
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
(include_subdirs unqualified)
|
||||
|
||||
(executable
|
||||
(name main)
|
||||
(public_name dune)
|
||||
(package dune)
|
||||
(enabled_if
|
||||
(<> %{profile} dune-bootstrap))
|
||||
(libraries
|
||||
memo
|
||||
promote
|
||||
ocaml
|
||||
ocaml_config
|
||||
dune_lang
|
||||
predicate_lang
|
||||
fiber
|
||||
fiber_event_bus
|
||||
stdune
|
||||
dune_console
|
||||
unix
|
||||
install
|
||||
dune_findlib
|
||||
dune_metrics
|
||||
dune_digest
|
||||
dune_cache
|
||||
dune_cache_storage
|
||||
dune_graph
|
||||
dune_rules
|
||||
dune_vcs
|
||||
dune_engine
|
||||
dune_targets
|
||||
dune_util
|
||||
dune_upgrader
|
||||
dune_pkg
|
||||
cmdliner
|
||||
threads
|
||||
; Kept to keep implicit_transitive_deps false working in 4.x
|
||||
threads.posix
|
||||
build_info
|
||||
dune_config
|
||||
dune_config_file
|
||||
chrome_trace
|
||||
dune_stats
|
||||
csexp
|
||||
csexp_rpc
|
||||
dune_rpc_impl
|
||||
dune_rules_rpc
|
||||
dune_rpc_private
|
||||
dune_rpc_client
|
||||
dune_spawn
|
||||
opam_format
|
||||
source
|
||||
xdg)
|
||||
(bootstrap_info bootstrap-info))
|
||||
|
||||
; Installing the dune binary depends on the kind of build:
|
||||
; - for bootstrap builds, dune.exe is copied from ../dune.exe
|
||||
; and installed using a manual install stanza
|
||||
; - for non-bootstrap builds (building dune with another dune),
|
||||
; the executable stanza does everything (and attached it to the
|
||||
; right package, which is important for build-info to succeed)
|
||||
; but we still need to setup a dummy dune.exe so that profiles
|
||||
; agree on the targets.
|
||||
|
||||
(rule
|
||||
(enabled_if
|
||||
(<> %{profile} dune-bootstrap))
|
||||
(action
|
||||
(with-stdout-to dune.exe (progn))))
|
||||
|
||||
(rule
|
||||
(action
|
||||
(copy ../_boot/dune.exe dune.exe))
|
||||
(enabled_if
|
||||
(= %{profile} dune-bootstrap)))
|
||||
|
||||
(install
|
||||
(section bin)
|
||||
(enabled_if
|
||||
(= %{profile} dune-bootstrap))
|
||||
(package dune)
|
||||
(files
|
||||
(dune.exe as dune)))
|
||||
|
||||
(deprecated_library_name
|
||||
(old_public_name dune.configurator)
|
||||
(new_public_name dune-configurator))
|
||||
611
unikernel/duniverse/dune_/bin/dune_init.ml
Normal file
611
unikernel/duniverse/dune_/bin/dune_init.ml
Normal file
|
|
@ -0,0 +1,611 @@
|
|||
open Import
|
||||
|
||||
(** Because the dune_init utility deals with the addition of stanzas and fields
|
||||
to dune projects and files, we need to inspect and manipulate the concrete
|
||||
syntax tree (CST) a good deal. *)
|
||||
module Cst = Dune_lang.Cst
|
||||
|
||||
(** Abstractions around the kinds of files handled during initialization *)
|
||||
module File = struct
|
||||
type dune =
|
||||
{ path : Path.t
|
||||
; name : string
|
||||
; content : Cst.t list
|
||||
}
|
||||
|
||||
type text =
|
||||
{ path : Path.t
|
||||
; name : string
|
||||
; content : string
|
||||
}
|
||||
|
||||
type t =
|
||||
| Dune of dune
|
||||
| Text of text
|
||||
|
||||
let make_text path name content = Text { path; name; content }
|
||||
|
||||
let full_path = function
|
||||
| Dune { path; name; _ } | Text { path; name; _ } -> Path.relative path name
|
||||
;;
|
||||
|
||||
(** Inspection and manipulation of stanzas in a file *)
|
||||
module Stanza = struct
|
||||
let pp s =
|
||||
match Cst.to_sexp s with
|
||||
| None -> Pp.nop
|
||||
| Some s -> Dune_lang.pp s
|
||||
;;
|
||||
|
||||
let libraries_conflict (a : Library.t) (b : Library.t) = a.name = b.name
|
||||
|
||||
let executables_conflict (a : Dune_rules.Executables.t) (b : Dune_rules.Executables.t)
|
||||
=
|
||||
let a_names = String.Set.of_list_map ~f:snd (Nonempty_list.to_list a.names) in
|
||||
let b_names = String.Set.of_list_map ~f:snd (Nonempty_list.to_list b.names) in
|
||||
String.Set.inter a_names b_names |> String.Set.is_empty |> not
|
||||
;;
|
||||
|
||||
let tests_conflict (a : Dune_rules.Tests.t) (b : Dune_rules.Tests.t) =
|
||||
executables_conflict a.exes b.exes
|
||||
;;
|
||||
|
||||
let stanzas_conflict (a : Stanza.t) (b : Stanza.t) =
|
||||
match Stanza.repr a, Stanza.repr b with
|
||||
| Dune_rules.Executables.T a, Dune_rules.Executables.T b -> executables_conflict a b
|
||||
| Library.T a, Library.T b -> libraries_conflict a b
|
||||
| Dune_rules.Tests.T a, Dune_rules.Tests.T b -> tests_conflict a b
|
||||
(* NOTE No other stanza types currently supported *)
|
||||
| _ -> false
|
||||
;;
|
||||
|
||||
let csts_conflict project (a : Cst.t) (b : Cst.t) =
|
||||
let of_ast = Dune_rules.Stanzas.of_ast project in
|
||||
(let open Option.O in
|
||||
let* a_ast = Cst.abstract a in
|
||||
let+ b_ast = Cst.abstract b in
|
||||
let a_asts = of_ast a_ast in
|
||||
let b_asts = of_ast b_ast in
|
||||
List.exists ~f:(fun x -> List.exists ~f:(stanzas_conflict x) a_asts) b_asts)
|
||||
|> Option.value ~default:false
|
||||
;;
|
||||
|
||||
(* TODO(shonfeder): replace with stanza merging *)
|
||||
let find_conflicting project new_stanzas existing_stanzas =
|
||||
let conflicting_stanza stanza =
|
||||
match List.find ~f:(csts_conflict project stanza) existing_stanzas with
|
||||
| Some conflict -> Some (stanza, conflict)
|
||||
| None -> None
|
||||
in
|
||||
List.find_map ~f:conflicting_stanza new_stanzas
|
||||
;;
|
||||
|
||||
let add (project : Dune_project.t) stanzas = function
|
||||
| Text f -> Text f (* Adding a stanza to a text file isn't meaningful *)
|
||||
| Dune f ->
|
||||
(match find_conflicting project stanzas f.content with
|
||||
| None -> Dune { f with content = f.content @ stanzas }
|
||||
| Some (a, b) ->
|
||||
User_error.raise
|
||||
[ Pp.text "Updating existing stanzas is not yet supported."
|
||||
; Pp.text "A preexisting dune stanza conflicts with a generated stanza:"
|
||||
; Pp.nop
|
||||
; Pp.text "Generated stanza:"
|
||||
; pp a
|
||||
; Pp.nop
|
||||
; Pp.text "Pre-existing stanza:"
|
||||
; pp b
|
||||
])
|
||||
;;
|
||||
end
|
||||
|
||||
(* Stanza *)
|
||||
|
||||
let create_dir path =
|
||||
try Path.mkdir_p path with
|
||||
| Unix.Unix_error (EACCES, _, _) ->
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"A project directory cannot be created or accessed: Lacking permissions \
|
||||
needed to create directory %s"
|
||||
(Path.to_string_maybe_quoted path)
|
||||
]
|
||||
;;
|
||||
|
||||
let load_dune_file ~path =
|
||||
let name = "dune" in
|
||||
let full_path = Path.relative path name in
|
||||
let content =
|
||||
if not (Path.exists full_path)
|
||||
then []
|
||||
else if Path.is_directory full_path
|
||||
then
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"\"%s\" already exists and is a directory"
|
||||
(Path.to_absolute_filename full_path)
|
||||
]
|
||||
else (
|
||||
match Io.with_lexbuf_from_file ~f:Dune_lang.Format.parse full_path with
|
||||
| Dune_lang.Format.Sexps content -> content
|
||||
| Dune_lang.Format.OCaml_syntax _ ->
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"Cannot load dune file %s because it uses OCaml syntax"
|
||||
(Path.to_string_maybe_quoted full_path)
|
||||
])
|
||||
in
|
||||
Dune { path; name; content }
|
||||
;;
|
||||
|
||||
let write_dune_file (dune_file : dune) =
|
||||
let path = Path.relative dune_file.path dune_file.name in
|
||||
let version =
|
||||
Dune_lang.Syntax.greatest_supported_version_exn Dune_lang.Stanza.syntax
|
||||
in
|
||||
Io.with_file_out
|
||||
~binary:true
|
||||
(* Why do we pass [~binary:true] but not anywhere else when formatting? *)
|
||||
path
|
||||
~f:(fun oc ->
|
||||
let fmt = Format.formatter_of_out_channel oc in
|
||||
Format.fprintf
|
||||
fmt
|
||||
"%a%!"
|
||||
Pp.to_fmt
|
||||
(Dune_lang.Format.pp_top_sexps ~version dune_file.content))
|
||||
;;
|
||||
|
||||
let write f =
|
||||
let path = full_path f in
|
||||
match f with
|
||||
| Dune f -> Ok (write_dune_file f)
|
||||
| Text f ->
|
||||
if Path.exists path
|
||||
then Error path
|
||||
else Ok (Io.write_file ~binary:false path f.content)
|
||||
;;
|
||||
end
|
||||
|
||||
(** The context in which the initialization is executed *)
|
||||
module Init_context = struct
|
||||
open Dune_config_file
|
||||
|
||||
type t =
|
||||
{ dir : Path.t
|
||||
; project : Dune_project.t
|
||||
; defaults : Dune_config.Project_defaults.t
|
||||
}
|
||||
|
||||
let make path defaults =
|
||||
let open Memo.O in
|
||||
let+ project =
|
||||
(* CR-someday rgrinberg: why not get the project from the source tree? *)
|
||||
Dune_project.load
|
||||
~dir:Path.Source.root
|
||||
~files:Filename.Set.empty
|
||||
~infer_from_opam_files:true
|
||||
~load_opam_file_with_contents:Dune_pkg.Opam_file.load_opam_file_with_contents
|
||||
>>| function
|
||||
| Some p -> p
|
||||
| None ->
|
||||
Dune_project.anonymous
|
||||
~dir:Path.Source.root
|
||||
Package_info.empty
|
||||
Package.Name.Map.empty
|
||||
in
|
||||
let dir =
|
||||
match path with
|
||||
| None -> Path.root
|
||||
| Some p -> Path.of_string p
|
||||
in
|
||||
File.create_dir dir;
|
||||
{ dir; project; defaults }
|
||||
;;
|
||||
end
|
||||
|
||||
let check_module_name name =
|
||||
let s = Dune_lang.Atom.to_string name in
|
||||
let (_ : Dune_rules.Module_name.t) =
|
||||
Dune_rules.Module_name.of_string_user_error (Loc.none, s) |> User_error.ok_exn
|
||||
in
|
||||
()
|
||||
;;
|
||||
|
||||
module Public_name = struct
|
||||
include Lib_name
|
||||
module Pkg = Dune_lang.Package_name.Opam_compatible
|
||||
|
||||
let is_opam_compatible l =
|
||||
Lib_name.package_name l |> Dune_lang.Package_name.is_opam_compatible
|
||||
;;
|
||||
|
||||
let of_string_user_error (loc, s) =
|
||||
let open Result.O in
|
||||
let* l = of_string_user_error (loc, s) in
|
||||
if is_opam_compatible l
|
||||
then Ok l
|
||||
else
|
||||
Error
|
||||
(User_error.make
|
||||
[ Pp.text
|
||||
"Public names are composed of an opam package name and optional \
|
||||
dot-separated string suffixes."
|
||||
; Pkg.description_of_valid_string
|
||||
])
|
||||
;;
|
||||
|
||||
let of_name_exn name =
|
||||
let s = Dune_lang.Atom.to_string name in
|
||||
of_string_user_error (Loc.none, s) |> User_error.ok_exn
|
||||
;;
|
||||
end
|
||||
|
||||
module Component = struct
|
||||
module Options = struct
|
||||
module Common = struct
|
||||
type t =
|
||||
{ name : Dune_lang.Atom.t
|
||||
; public : Public_name.t option
|
||||
; libraries : Dune_lang.Atom.t list
|
||||
; pps : Dune_lang.Atom.t list
|
||||
}
|
||||
|
||||
let package_name common =
|
||||
let name =
|
||||
match common.public with
|
||||
| None -> Dune_lang.Atom.to_string common.name
|
||||
| Some public -> Public_name.to_string public
|
||||
in
|
||||
Package.Name.of_string name
|
||||
;;
|
||||
end
|
||||
|
||||
module Executable = struct
|
||||
type t = unit
|
||||
end
|
||||
|
||||
module Library = struct
|
||||
type t = { inline_tests : bool }
|
||||
end
|
||||
|
||||
module Project = struct
|
||||
module Template = struct
|
||||
type t =
|
||||
| Exec
|
||||
| Lib
|
||||
|
||||
let of_string = function
|
||||
| "executable" -> Some Exec
|
||||
| "library" -> Some Lib
|
||||
| _ -> None
|
||||
;;
|
||||
|
||||
let commands = [ "executable", Exec; "library", Lib ]
|
||||
end
|
||||
|
||||
module Pkg = struct
|
||||
type t =
|
||||
| Opam
|
||||
| Esy
|
||||
|
||||
let commands = [ "opam", Opam; "esy", Esy ]
|
||||
end
|
||||
|
||||
type t =
|
||||
{ template : Template.t
|
||||
; inline_tests : bool
|
||||
; pkg : Pkg.t
|
||||
}
|
||||
end
|
||||
|
||||
module Test = struct
|
||||
type t = unit
|
||||
end
|
||||
|
||||
type 'options t =
|
||||
{ context : Init_context.t
|
||||
; common : Common.t
|
||||
; options : 'options
|
||||
}
|
||||
end
|
||||
|
||||
(* Options *)
|
||||
|
||||
type 'options t =
|
||||
| Executable : Options.Executable.t Options.t -> Options.Executable.t t
|
||||
| Library : Options.Library.t Options.t -> Options.Library.t t
|
||||
| Project : Options.Project.t Options.t -> Options.Project.t t
|
||||
| Test : Options.Test.t Options.t -> Options.Test.t t
|
||||
|
||||
(** Internal representation of the files comprising a component *)
|
||||
type target =
|
||||
{ dir : Path.t
|
||||
; files : File.t list
|
||||
}
|
||||
|
||||
(** Creates Dune language CST stanzas describing components *)
|
||||
module Stanza_cst = struct
|
||||
open Dune_lang
|
||||
|
||||
module Field = struct
|
||||
let inline_tests = Encoder.field_b "inline_tests"
|
||||
let pps_encoder pps = Encoder.list Encoder.string ("pps" :: pps)
|
||||
|
||||
let preprocess_field = function
|
||||
| [] -> []
|
||||
| pps -> [ Encoder.field "preprocess" pps_encoder pps ]
|
||||
;;
|
||||
|
||||
let common (options : Options.Common.t) =
|
||||
[ Encoder.field "name" Encoder.string (Atom.to_string options.name)
|
||||
; Encoder.field_l
|
||||
"libraries"
|
||||
Encoder.string
|
||||
(List.map ~f:Atom.to_string options.libraries)
|
||||
]
|
||||
@ preprocess_field (List.map ~f:Atom.to_string options.pps)
|
||||
;;
|
||||
end
|
||||
|
||||
(* Make CST representation of a stanza for the given `kind` *)
|
||||
let make kind common_options fields =
|
||||
Encoder.named_record_fields kind (fields @ Field.common common_options)
|
||||
(* Convert to a CST *)
|
||||
|> Dune_lang.Ast.add_loc ~loc:Loc.none
|
||||
|> Cst.concrete
|
||||
(* Package as a list CSTs *) |> List.singleton
|
||||
;;
|
||||
|
||||
let add_to_list_set elem set =
|
||||
if List.mem ~equal:Dune_lang.Atom.equal set elem then set else elem :: set
|
||||
;;
|
||||
|
||||
let public_name_field = Encoder.field_o "public_name" Public_name.encode
|
||||
|
||||
let executable (common : Options.Common.t) (() : Options.Executable.t) =
|
||||
make "executable" common [ public_name_field common.public ]
|
||||
;;
|
||||
|
||||
let library (common : Options.Common.t) { Options.Library.inline_tests } =
|
||||
check_module_name common.name;
|
||||
let common =
|
||||
if inline_tests
|
||||
then (
|
||||
let pps =
|
||||
add_to_list_set (Dune_lang.Atom.of_string "ppx_inline_test") common.pps
|
||||
in
|
||||
{ common with pps })
|
||||
else common
|
||||
in
|
||||
make
|
||||
"library"
|
||||
common
|
||||
[ public_name_field common.public; Field.inline_tests inline_tests ]
|
||||
;;
|
||||
|
||||
let test common (() : Options.Test.t) = make "test" common []
|
||||
|
||||
(* A list of CSTs for dune-project file content *)
|
||||
let dune_project
|
||||
~opam_file_gen
|
||||
~(defaults : Dune_config_file.Dune_config.Project_defaults.t)
|
||||
dir
|
||||
(common : Options.Common.t)
|
||||
=
|
||||
let cst =
|
||||
let package =
|
||||
Package.create
|
||||
~name:(Options.Common.package_name common)
|
||||
~loc:Loc.none
|
||||
~version:None
|
||||
~conflicts:[]
|
||||
~depopts:[]
|
||||
~info:Package_info.empty
|
||||
~sites:Site.Map.empty
|
||||
~allow_empty:false
|
||||
~deprecated_package_names:Package.Name.Map.empty
|
||||
~has_opam_file:(Exists false)
|
||||
~original_opam_file:None
|
||||
~dir
|
||||
~synopsis:(Some "A short synopsis")
|
||||
~description:(Some "A longer description")
|
||||
~tags:[ "add topics"; "to describe"; "your"; "project" ]
|
||||
~depends:
|
||||
[ { Package_dependency.name = Package.Name.of_string "ocaml"
|
||||
; constraint_ = None
|
||||
}
|
||||
]
|
||||
in
|
||||
let packages = Package.Name.Map.singleton (Package.name package) package in
|
||||
let info =
|
||||
Package_info.example
|
||||
~authors:defaults.authors
|
||||
~maintainers:defaults.maintainers
|
||||
~license:defaults.license
|
||||
in
|
||||
Dune_project.anonymous ~dir info packages
|
||||
|> Dune_project.set_generate_opam_files opam_file_gen
|
||||
|> Dune_project.encode
|
||||
|> List.map ~f:(fun exp ->
|
||||
exp |> Dune_lang.Ast.add_loc ~loc:Loc.none |> Cst.concrete)
|
||||
in
|
||||
List.append
|
||||
cst
|
||||
[ Cst.Comment
|
||||
( Loc.none
|
||||
, [ " See the complete stanza docs at \
|
||||
https://dune.readthedocs.io/en/stable/reference/dune-project/index.html"
|
||||
] )
|
||||
]
|
||||
;;
|
||||
end
|
||||
|
||||
(* TODO Support for merging in changes to an existing stanza *)
|
||||
let add_stanza_to_dune_file ~(project : Dune_project.t) ~dir stanza =
|
||||
File.load_dune_file ~path:dir |> File.Stanza.add project stanza
|
||||
;;
|
||||
|
||||
(* Functions to make the various components, represented as lists of files *)
|
||||
module Make = struct
|
||||
let bin ({ context; common; options } : Options.Executable.t Options.t) =
|
||||
let dir = context.dir in
|
||||
let bin_dune =
|
||||
Stanza_cst.executable common options
|
||||
|> add_stanza_to_dune_file ~project:context.project ~dir
|
||||
in
|
||||
let bin_ml =
|
||||
let name = sprintf "%s.ml" (Dune_lang.Atom.to_string common.name) in
|
||||
let content = sprintf "let () = print_endline \"Hello, World!\"\n" in
|
||||
File.make_text dir name content
|
||||
in
|
||||
let files = [ bin_dune; bin_ml ] in
|
||||
[ { dir; files } ]
|
||||
;;
|
||||
|
||||
let src ({ context; common; options } : Options.Library.t Options.t) =
|
||||
let dir = context.dir in
|
||||
let lib_dune =
|
||||
Stanza_cst.library common options
|
||||
|> add_stanza_to_dune_file ~project:context.project ~dir
|
||||
in
|
||||
let files = [ lib_dune ] in
|
||||
[ { dir; files } ]
|
||||
;;
|
||||
|
||||
let test ({ context; common; options } : Options.Test.t Options.t) =
|
||||
(* Marking the current absence of test-specific options *)
|
||||
let dir = context.dir in
|
||||
let test_dune =
|
||||
Stanza_cst.test common options
|
||||
|> add_stanza_to_dune_file ~project:context.project ~dir
|
||||
in
|
||||
let test_ml =
|
||||
let name = sprintf "%s.ml" (Dune_lang.Atom.to_string common.name) in
|
||||
let content = "" in
|
||||
File.make_text dir name content
|
||||
in
|
||||
let files = [ test_dune; test_ml ] in
|
||||
[ { dir; files } ]
|
||||
;;
|
||||
|
||||
let dune_project_file dir ({ context; common; options } : Options.Project.t Options.t)
|
||||
=
|
||||
let opam_file_gen =
|
||||
match options.pkg with
|
||||
| Opam -> true
|
||||
| Esy -> false
|
||||
in
|
||||
let content =
|
||||
Stanza_cst.dune_project
|
||||
~opam_file_gen
|
||||
~defaults:context.defaults
|
||||
Path.(as_in_source_tree_exn context.dir)
|
||||
common
|
||||
in
|
||||
File.Dune { path = dir; content; name = "dune-project" }
|
||||
;;
|
||||
|
||||
let proj_exec dir ({ context; common; options } : Options.Project.t Options.t) =
|
||||
let lib_target =
|
||||
src
|
||||
{ context = { context with dir = Path.relative dir "lib" }
|
||||
; options = { inline_tests = options.inline_tests }
|
||||
; common = { common with public = None }
|
||||
}
|
||||
in
|
||||
let test_target =
|
||||
let test_name = "test_" ^ Dune_lang.Atom.to_string common.name in
|
||||
test
|
||||
{ context = { context with dir = Path.relative dir "test" }
|
||||
; options = ()
|
||||
; common = { common with name = Dune_lang.Atom.of_string test_name }
|
||||
}
|
||||
in
|
||||
let bin_target =
|
||||
(* Add the lib_target as a library to the executable*)
|
||||
let libraries = Stanza_cst.add_to_list_set common.name common.libraries in
|
||||
bin
|
||||
{ context = { context with dir = Path.relative dir "bin" }
|
||||
; options = ()
|
||||
; common = { common with libraries; name = Dune_lang.Atom.of_string "main" }
|
||||
}
|
||||
in
|
||||
bin_target @ lib_target @ test_target
|
||||
;;
|
||||
|
||||
let proj_lib dir ({ context; common; options } : Options.Project.t Options.t) =
|
||||
let lib_target =
|
||||
src
|
||||
{ context = { context with dir = Path.relative dir "lib" }
|
||||
; options = { inline_tests = options.inline_tests }
|
||||
; common
|
||||
}
|
||||
in
|
||||
let test_target =
|
||||
let test_name = "test_" ^ Dune_lang.Atom.to_string common.name in
|
||||
test
|
||||
{ context = { context with dir = Path.relative dir "test" }
|
||||
; options = ()
|
||||
; common = { common with name = Dune_lang.Atom.of_string test_name }
|
||||
}
|
||||
in
|
||||
lib_target @ test_target
|
||||
;;
|
||||
|
||||
let proj ({ common; options; _ } as opts : Options.Project.t Options.t) =
|
||||
let ({ template; pkg; _ } : Options.Project.t) = options in
|
||||
let dir = Path.Source.root in
|
||||
let proj_target =
|
||||
let package_files =
|
||||
match (pkg : Options.Project.Pkg.t) with
|
||||
| Opam ->
|
||||
let name = Options.Common.package_name common in
|
||||
let opam_file = Path.source @@ Package_name.file name ~dir in
|
||||
[ File.make_text (Path.parent_exn opam_file) (Path.basename opam_file) "" ]
|
||||
| Esy -> [ File.make_text (Path.source dir) "package.json" "" ]
|
||||
in
|
||||
let dir = Path.source dir in
|
||||
{ dir; files = dune_project_file dir opts :: package_files }
|
||||
in
|
||||
let component_targets =
|
||||
(match (template : Options.Project.Template.t) with
|
||||
| Exec -> proj_exec
|
||||
| Lib -> proj_lib)
|
||||
(Path.source dir)
|
||||
opts
|
||||
in
|
||||
proj_target :: component_targets
|
||||
;;
|
||||
end
|
||||
|
||||
let report_uncreated_file = function
|
||||
| Ok _ -> ()
|
||||
| Error path ->
|
||||
let open Pp.O in
|
||||
User_warning.emit
|
||||
[ Pp.textf "File "
|
||||
++ Pp.tag
|
||||
User_message.Style.Kwd
|
||||
(Pp.verbatim (Path.to_string_maybe_quoted path))
|
||||
++ Pp.text " was not created because it already exists"
|
||||
]
|
||||
;;
|
||||
|
||||
(** Creates a component, writing the files to disk *)
|
||||
let create target =
|
||||
File.create_dir target.dir;
|
||||
List.map ~f:File.write target.files
|
||||
;;
|
||||
|
||||
let init (type options) (t : options t) =
|
||||
let target =
|
||||
match t with
|
||||
| Executable params -> Make.bin params
|
||||
| Library params -> Make.src params
|
||||
| Project params -> Make.proj params
|
||||
| Test params -> Make.test params
|
||||
in
|
||||
List.concat_map ~f:create target |> List.iter ~f:report_uncreated_file
|
||||
;;
|
||||
end
|
||||
103
unikernel/duniverse/dune_/bin/dune_init.mli
Normal file
103
unikernel/duniverse/dune_/bin/dune_init.mli
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
(** Initialize dune components *)
|
||||
|
||||
open Import
|
||||
|
||||
(** The context in which the initialization is executed *)
|
||||
module Init_context : sig
|
||||
open Dune_config_file
|
||||
|
||||
type t =
|
||||
{ dir : Path.t
|
||||
; project : Dune_project.t
|
||||
; defaults : Dune_config.Project_defaults.t
|
||||
}
|
||||
|
||||
val make : string option -> Dune_config.Project_defaults.t -> t Memo.t
|
||||
end
|
||||
|
||||
module Public_name : sig
|
||||
type t
|
||||
|
||||
val to_string : t -> string
|
||||
val of_string_user_error : Loc.t * string -> (t, User_message.t) result
|
||||
val of_name_exn : Dune_lang.Atom.t -> t
|
||||
end
|
||||
|
||||
(** A [Component.t] is a set of files that can be built or included as part of a
|
||||
build. *)
|
||||
module Component : sig
|
||||
(** Options determining the details of a generated component *)
|
||||
module Options : sig
|
||||
(** The common options shared by all components *)
|
||||
module Common : sig
|
||||
type t =
|
||||
{ name : Dune_lang.Atom.t
|
||||
; public : Public_name.t option
|
||||
; libraries : Dune_lang.Atom.t list
|
||||
; pps : Dune_lang.Atom.t list
|
||||
}
|
||||
end
|
||||
|
||||
(** Options for executable components *)
|
||||
module Executable : sig
|
||||
(** NOTE: no options supported yet *)
|
||||
type t = unit
|
||||
end
|
||||
|
||||
(** Options for library components *)
|
||||
module Library : sig
|
||||
type t = { inline_tests : bool }
|
||||
end
|
||||
|
||||
(** Options for test components *)
|
||||
module Test : sig
|
||||
(** NOTE: no options supported yet *)
|
||||
type t = unit
|
||||
end
|
||||
|
||||
(** Options for project components (which consist of several sub-components) *)
|
||||
module Project : sig
|
||||
(** Determines whether this is a library project or an executable project *)
|
||||
module Template : sig
|
||||
type t =
|
||||
| Exec
|
||||
| Lib
|
||||
|
||||
val of_string : string -> t option
|
||||
val commands : (string * t) list
|
||||
end
|
||||
|
||||
(** The package manager used for a project *)
|
||||
module Pkg : sig
|
||||
type t =
|
||||
| Opam
|
||||
| Esy
|
||||
|
||||
val commands : (string * t) list
|
||||
end
|
||||
|
||||
type t =
|
||||
{ template : Template.t
|
||||
; inline_tests : bool
|
||||
; pkg : Pkg.t
|
||||
}
|
||||
end
|
||||
|
||||
type 'a t =
|
||||
{ context : Init_context.t
|
||||
; common : Common.t
|
||||
; options : 'a
|
||||
}
|
||||
end
|
||||
|
||||
(** All the the supported types of components *)
|
||||
type 'options t =
|
||||
| Executable : Options.Executable.t Options.t -> Options.Executable.t t
|
||||
| Library : Options.Library.t Options.t -> Options.Library.t t
|
||||
| Project : Options.Project.t Options.t -> Options.Project.t t
|
||||
| Test : Options.Test.t Options.t -> Options.Test.t t
|
||||
|
||||
(** Create or update the component specified by the ['options t], where
|
||||
['options] is *)
|
||||
val init : 'options t -> unit
|
||||
end
|
||||
328
unikernel/duniverse/dune_/bin/exec.ml
Normal file
328
unikernel/duniverse/dune_/bin/exec.ml
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
open Import
|
||||
|
||||
let doc = "Execute a command in a similar environment as if installation was performed."
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
{|$(b,dune exec -- COMMAND) should behave in the same way as if you
|
||||
do:|}
|
||||
; `Pre " \\$ dune install\n \\$ COMMAND"
|
||||
; `P
|
||||
{|In particular if you run $(b,dune exec ocaml), you will have
|
||||
access to the libraries defined in the workspace using your usual
|
||||
directives ($(b,#require) for instance)|}
|
||||
; `P
|
||||
{|When a leading / is present in the command (absolute path), then the
|
||||
path is interpreted as an absolute path|}
|
||||
; `P
|
||||
{|When a / is present at any other position (relative path), then the
|
||||
path is interpreted as relative to the build context + current
|
||||
working directory (or the value of $(b,--root) when ran outside of
|
||||
the project root)|}
|
||||
; `Blocks Common.help_secs
|
||||
; Common.examples
|
||||
[ "Run the executable named `my_exec'", "dune exec my_exec"
|
||||
; ( "Run the executable defined in `foo.ml' with the argument `arg'"
|
||||
, "dune exec -- ./foo.exe arg" )
|
||||
]
|
||||
]
|
||||
;;
|
||||
|
||||
let info = Cmd.info "exec" ~doc ~man
|
||||
|
||||
module Cmd_arg = struct
|
||||
type t =
|
||||
| Expandable of Dune_lang.String_with_vars.t * string
|
||||
| Terminal of string
|
||||
|
||||
let parse s =
|
||||
match Arg.conv_parser Arg.dep s with
|
||||
| Ok (File sw) when Dune_lang.String_with_vars.has_pforms sw -> Expandable (sw, s)
|
||||
| _ -> Terminal s
|
||||
;;
|
||||
|
||||
let pp pps = function
|
||||
| Expandable (_, s) -> Format.fprintf pps "%s" s
|
||||
| Terminal s -> Format.fprintf pps "%s" s
|
||||
;;
|
||||
|
||||
let expand t ~root ~sctx =
|
||||
let open Memo.O in
|
||||
match t with
|
||||
| Terminal s -> Memo.return s
|
||||
| Expandable (sw, _) ->
|
||||
let+ path, _ =
|
||||
Target.expand_path_from_root root sctx sw
|
||||
|> Action_builder.evaluate_and_collect_facts
|
||||
in
|
||||
let context = Dune_rules.Super_context.context sctx in
|
||||
(* TODO Why are we stringifying this path? *)
|
||||
Path.to_string (Path.build (Path.Build.relative (Context.build_dir context) path))
|
||||
;;
|
||||
|
||||
let conv = Arg.conv ((fun s -> Ok (parse s)), pp)
|
||||
end
|
||||
|
||||
let not_found ~hints ~prog =
|
||||
User_error.raise
|
||||
~hints
|
||||
[ Pp.concat
|
||||
~sep:Pp.space
|
||||
[ Pp.text "Program"; User_message.command prog; Pp.text "not found!" ]
|
||||
]
|
||||
;;
|
||||
|
||||
let not_found_with_suggestions ~dir ~prog =
|
||||
let open Memo.O in
|
||||
let+ hints =
|
||||
(* Good candidates for the "./x.exe" instead of "x.exe" error are
|
||||
executables present in the current directory. Note: we do not
|
||||
check directory targets here; even if they do indeed include a
|
||||
matching executable, they would be located in a subdirectory of
|
||||
[dir], so it's unclear if that's what the user wanted. *)
|
||||
let+ candidates =
|
||||
let+ filename_set = Build_system.files_of ~dir:(Path.build dir) in
|
||||
Filename_set.filenames filename_set
|
||||
|> Filename.Set.to_list
|
||||
|> List.filter ~f:(fun filename -> Filename.extension filename = ".exe")
|
||||
|> List.map ~f:(fun filename -> "./" ^ filename)
|
||||
in
|
||||
User_message.did_you_mean prog ~candidates
|
||||
in
|
||||
not_found ~hints ~prog
|
||||
;;
|
||||
|
||||
let program_not_built_yet prog =
|
||||
User_error.raise
|
||||
[ Pp.concat
|
||||
~sep:Pp.space
|
||||
[ Pp.text "Program"
|
||||
; User_message.command prog
|
||||
; Pp.text "isn't built yet. You need to build it first or remove the"
|
||||
; User_message.command "--no-build"
|
||||
; Pp.text "option."
|
||||
]
|
||||
]
|
||||
;;
|
||||
|
||||
let build_prog ~no_rebuild ~prog p =
|
||||
if no_rebuild
|
||||
then if Path.exists p then Memo.return p else program_not_built_yet prog
|
||||
else
|
||||
let open Memo.O in
|
||||
let+ () = Build_system.build_file p in
|
||||
p
|
||||
;;
|
||||
|
||||
let dir_of_context common sctx =
|
||||
let context = Dune_rules.Super_context.context sctx in
|
||||
Path.Build.relative (Context.build_dir context) (Common.prefix_target common "")
|
||||
;;
|
||||
|
||||
let get_path common sctx ~prog =
|
||||
let open Memo.O in
|
||||
let dir = dir_of_context common sctx in
|
||||
match Filename.analyze_program_name prog with
|
||||
| In_path ->
|
||||
Super_context.resolve_program_memo sctx ~dir ~loc:None prog
|
||||
>>= (function
|
||||
| Error (_ : Action.Prog.Not_found.t) -> not_found_with_suggestions ~dir ~prog
|
||||
| Ok p -> Memo.return p)
|
||||
| Relative_to_current_dir ->
|
||||
let path = Path.relative_to_source_in_build_or_external ~dir prog in
|
||||
Build_system.file_exists path
|
||||
>>= (function
|
||||
| true -> Memo.return path
|
||||
| false -> not_found_with_suggestions ~dir ~prog)
|
||||
| Absolute ->
|
||||
(match
|
||||
let prog = Path.of_string prog in
|
||||
if Path.exists prog
|
||||
then Some prog
|
||||
else if not Sys.win32
|
||||
then None
|
||||
else (
|
||||
let prog = Path.extend_basename prog ~suffix:Bin.exe in
|
||||
Option.some_if (Path.exists prog) prog)
|
||||
with
|
||||
| Some prog -> Memo.return prog
|
||||
| None -> not_found_with_suggestions ~dir ~prog)
|
||||
;;
|
||||
|
||||
let get_path_and_build_if_necessary common sctx ~no_rebuild ~prog =
|
||||
let open Memo.O in
|
||||
let* path = get_path common sctx ~prog in
|
||||
match Filename.analyze_program_name prog with
|
||||
| In_path | Relative_to_current_dir -> build_prog ~no_rebuild ~prog path
|
||||
| Absolute -> Memo.return path
|
||||
;;
|
||||
|
||||
let step ~prog ~args ~common ~no_rebuild ~context ~on_exit () =
|
||||
let open Memo.O in
|
||||
let* sctx = Super_context.find_exn context in
|
||||
let* path =
|
||||
let* prog = Cmd_arg.expand ~root:(Common.root common) ~sctx prog in
|
||||
get_path_and_build_if_necessary common sctx ~no_rebuild ~prog
|
||||
and* args =
|
||||
Memo.parallel_map args ~f:(Cmd_arg.expand ~root:(Common.root common) ~sctx)
|
||||
in
|
||||
let* env = Super_context.context_env sctx in
|
||||
Memo.of_non_reproducible_fiber
|
||||
@@ Dune_engine.Process.run_inherit_std_in_out
|
||||
~dir:(Path.of_string Fpath.initial_cwd)
|
||||
~env
|
||||
path
|
||||
args
|
||||
>>| function
|
||||
| 0 -> ()
|
||||
| exit_code -> on_exit exit_code
|
||||
;;
|
||||
|
||||
(* Similar to [get_path_and_build_if_necessary] but doesn't require the build
|
||||
system (ie. it sequences with [Fiber] rather than with [Memo]) and builds
|
||||
targets via an RPC server. Some functionality is not available but it can be
|
||||
run concurrently while a second Dune process holds the global build
|
||||
directory lock.
|
||||
|
||||
Returns the absolute path to the executable. *)
|
||||
let build_prog_via_rpc_if_necessary ~dir ~no_rebuild prog =
|
||||
match Filename.analyze_program_name prog with
|
||||
| In_path ->
|
||||
(* This case is reached if [dune exec] is passed the name of an
|
||||
executable (rather than a path to an executable). When dune is running
|
||||
directly, dune will try to resolve the executbale name within the public
|
||||
executables defined in the current project and its dependencies, and
|
||||
only if no executable with the given name is found will dune then
|
||||
resolve the name within the $PATH variable instead. Looking up an
|
||||
executable's name within the current project requires running the
|
||||
build system, but running the build system is not allowed while
|
||||
another dune instance holds the global build directory lock. In this
|
||||
case dune will only resolve the executable's name within $PATH.
|
||||
Because this behaviour is different from the default, print a warning
|
||||
so users are hopefully less surprised.
|
||||
*)
|
||||
User_warning.emit
|
||||
[ Pp.textf
|
||||
"As this is not the main instance of Dune it is unable to locate the \
|
||||
executable %S within this project. Dune will attempt to resolve the \
|
||||
executable's name within your PATH only."
|
||||
prog
|
||||
];
|
||||
let path = Env_path.path Env.initial in
|
||||
(match Bin.which ~path prog with
|
||||
| None -> not_found ~hints:[] ~prog
|
||||
| Some prog_path -> Fiber.return (Path.to_absolute_filename prog_path))
|
||||
| Relative_to_current_dir ->
|
||||
let open Fiber.O in
|
||||
let path = Path.relative_to_source_in_build_or_external ~dir prog in
|
||||
let+ () =
|
||||
if no_rebuild
|
||||
then if Path.exists path then Fiber.return () else program_not_built_yet prog
|
||||
else (
|
||||
let target =
|
||||
Dune_lang.Dep_conf.File
|
||||
(Dune_lang.String_with_vars.make_text Loc.none (Path.to_string path))
|
||||
in
|
||||
Build.build_via_rpc_server ~print_on_success:false ~targets:[ target ])
|
||||
in
|
||||
Path.to_absolute_filename path
|
||||
| Absolute ->
|
||||
if Path.exists (Path.of_string prog)
|
||||
then Fiber.return prog
|
||||
else not_found ~hints:[] ~prog
|
||||
;;
|
||||
|
||||
let exec_building_via_rpc_server ~common ~prog ~args ~no_rebuild =
|
||||
let open Fiber.O in
|
||||
let ensure_terminal v =
|
||||
match (v : Cmd_arg.t) with
|
||||
| Terminal s -> s
|
||||
| Expandable (_, raw) ->
|
||||
(* Variables cannot be expanded without running the build system. *)
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"The term %S contains a variable but Dune is unable to expand variables when \
|
||||
building via RPC."
|
||||
raw
|
||||
]
|
||||
in
|
||||
let context = Common.x common |> Option.value ~default:Context_name.default in
|
||||
let dir = Context_name.build_dir context in
|
||||
let prog = ensure_terminal prog in
|
||||
let args = List.map args ~f:ensure_terminal in
|
||||
let+ prog = build_prog_via_rpc_if_necessary ~dir ~no_rebuild prog in
|
||||
restore_cwd_and_execve (Common.root common) prog args Env.initial
|
||||
;;
|
||||
|
||||
let exec_building_directly ~common ~config ~context ~prog ~args ~no_rebuild =
|
||||
match Common.watch common with
|
||||
| Yes Passive ->
|
||||
User_error.raise [ Pp.textf "passive watch mode is unsupported by exec" ]
|
||||
| Yes Eager ->
|
||||
Scheduler.go_with_rpc_server_and_console_status_reporting ~common ~config
|
||||
@@ fun () ->
|
||||
let open Fiber.O in
|
||||
let on_exit = Console.printf "Program exited with code [%d]" in
|
||||
Scheduler.Run.poll
|
||||
@@
|
||||
let* () = Fiber.return @@ Scheduler.maybe_clear_screen ~details_hum:[] config in
|
||||
build @@ step ~prog ~args ~common ~no_rebuild ~context ~on_exit
|
||||
| No ->
|
||||
Scheduler.go_with_rpc_server ~common ~config
|
||||
@@ fun () ->
|
||||
let open Fiber.O in
|
||||
let* setup = Import.Main.setup () in
|
||||
build_exn (fun () ->
|
||||
let open Memo.O in
|
||||
let* sctx = setup >>| Import.Main.find_scontext_exn ~name:context in
|
||||
let* env = Super_context.context_env sctx
|
||||
and* prog =
|
||||
let* prog = Cmd_arg.expand ~root:(Common.root common) ~sctx prog in
|
||||
get_path_and_build_if_necessary common sctx ~no_rebuild ~prog >>| Path.to_string
|
||||
and* args =
|
||||
Memo.parallel_map ~f:(Cmd_arg.expand ~root:(Common.root common) ~sctx) args
|
||||
in
|
||||
restore_cwd_and_execve (Common.root common) prog args env)
|
||||
;;
|
||||
|
||||
let term : unit Term.t =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ context = Common.context_arg ~doc:{|Run the command in this build context.|}
|
||||
and+ prog = Arg.(required & pos 0 (some Cmd_arg.conv) None (Arg.info [] ~docv:"PROG"))
|
||||
and+ no_rebuild =
|
||||
Arg.(value & flag & info [ "no-build" ] ~doc:"don't rebuild target before executing")
|
||||
and+ args = Arg.(value & pos_right 0 Cmd_arg.conv [] (Arg.info [] ~docv:"ARGS")) in
|
||||
(* TODO we should make sure to finalize the current backend before exiting dune.
|
||||
For watch mode, we should finalize the backend and then restart it in between
|
||||
runs. *)
|
||||
let common, config = Common.init builder in
|
||||
match Dune_util.Global_lock.lock ~timeout:None with
|
||||
| Error lock_held_by ->
|
||||
(match Common.watch common with
|
||||
| Yes _ ->
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"Another instance of dune%s has locked the _build directory. Refusing to \
|
||||
start a new watch server until no other instances of dune are running."
|
||||
(match lock_held_by with
|
||||
| Unknown -> ""
|
||||
| Pid_from_lockfile pid -> sprintf " (pid: %d)" pid)
|
||||
]
|
||||
| No ->
|
||||
if not (Common.Builder.equal builder Common.Builder.default)
|
||||
then
|
||||
User_warning.emit
|
||||
[ Pp.textf
|
||||
"Your build request is being forwarded to a running Dune instance%s. Note \
|
||||
that certain command line arguments may be ignored."
|
||||
(match lock_held_by with
|
||||
| Unknown -> ""
|
||||
| Pid_from_lockfile pid -> sprintf " (pid: %d)" pid)
|
||||
];
|
||||
Scheduler.go_without_rpc_server ~common ~config
|
||||
@@ fun () -> exec_building_via_rpc_server ~common ~prog ~args ~no_rebuild)
|
||||
| Ok () -> exec_building_directly ~common ~config ~context ~prog ~args ~no_rebuild
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
24
unikernel/duniverse/dune_/bin/exec.mli
Normal file
24
unikernel/duniverse/dune_/bin/exec.mli
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
open Import
|
||||
|
||||
module Cmd_arg : sig
|
||||
type t
|
||||
|
||||
val conv : t Arg.conv
|
||||
val expand : t -> root:Workspace_root.t -> sctx:Super_context.t -> string Memo.t
|
||||
end
|
||||
|
||||
(** Returns the path to the executable [prog] as it will be resolved by dune:
|
||||
- if [prog] is the name of an executable defined by the project then the path
|
||||
to that executable will be returned, and evaluating the returned memo will
|
||||
build the executable if necessary.
|
||||
- otherwise if [prog] is the name of an executable in the "bin" directory of
|
||||
a package in this project's dependency cone then the path to that executable
|
||||
file will be returned. Note that for this reason all dependencies of the
|
||||
project will be built when the returned memo is evaluated (unless the first
|
||||
case is hit).
|
||||
- otherwise if [prog] is the name of an executable in one of the directories
|
||||
listed in the PATH environment variable, the path to that executable will be
|
||||
returned. *)
|
||||
val get_path : Common.t -> Super_context.t -> prog:string -> Path.t Memo.t
|
||||
|
||||
val command : unit Cmd.t
|
||||
20
unikernel/duniverse/dune_/bin/exit_code.ml
Normal file
20
unikernel/duniverse/dune_/bin/exit_code.ml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
type t =
|
||||
| Success
|
||||
| Error
|
||||
| Signal
|
||||
|
||||
let all = [ Success; Error; Signal ]
|
||||
|
||||
let code = function
|
||||
| Success -> 0
|
||||
| Error -> 1
|
||||
| Signal -> 130
|
||||
;;
|
||||
|
||||
let doc = function
|
||||
| Success -> "on success."
|
||||
| Error -> "if an error happened."
|
||||
| Signal -> "if it was interrupted by a signal."
|
||||
;;
|
||||
|
||||
let info e = Cmdliner.Cmd.Exit.info (code e) ~doc:(doc e)
|
||||
8
unikernel/duniverse/dune_/bin/exit_code.mli
Normal file
8
unikernel/duniverse/dune_/bin/exit_code.mli
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
type t =
|
||||
| Success
|
||||
| Error
|
||||
| Signal
|
||||
|
||||
val all : t list
|
||||
val info : t -> Cmdliner.Cmd.Exit.info
|
||||
val code : t -> int
|
||||
28
unikernel/duniverse/dune_/bin/external_lib_deps.ml
Normal file
28
unikernel/duniverse/dune_/bin/external_lib_deps.ml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
open Import
|
||||
|
||||
let doc = "Moved to dune describe external-lib-deps."
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
"This subcommand used to print out an approximate set of external libraries that \
|
||||
were required for building a given set of targets, without running the build. \
|
||||
While this feature was useful, over time the quality of approximation had \
|
||||
degraded and the cost of maintenance had increased, so we decided to remove it.\n"
|
||||
; `Blocks Common.help_secs
|
||||
]
|
||||
;;
|
||||
|
||||
let info = Cmd.info "external-lib-deps" ~doc ~man
|
||||
|
||||
let term =
|
||||
Term.ret
|
||||
@@ let+ _ = Common.Builder.term
|
||||
and+ _ = Arg.(value & flag & info [ "missing" ] ~doc:{|unused|})
|
||||
and+ _ = Arg.(value & pos_all dep [] & Arg.info [] ~docv:"TARGET")
|
||||
and+ _ = Arg.(value & flag & info [ "unstable-by-dir" ] ~doc:{|unused|})
|
||||
and+ _ = Arg.(value & flag & info [ "sexp" ] ~doc:{|unused|}) in
|
||||
`Error (false, "This subcommand has been moved to dune describe external-lib-deps.")
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
3
unikernel/duniverse/dune_/bin/external_lib_deps.mli
Normal file
3
unikernel/duniverse/dune_/bin/external_lib_deps.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
67
unikernel/duniverse/dune_/bin/fmt.ml
Normal file
67
unikernel/duniverse/dune_/bin/fmt.ml
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
open Import
|
||||
|
||||
let doc = "Format source code."
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
{|$(b,dune fmt) runs the formatter on the source code. The formatter is
|
||||
automatically selected. ocamlformat is used to format OCaml source code
|
||||
( *.ml and *.mli files) and refmt is used to format Reason source code
|
||||
( *.re and *.rei files).|}
|
||||
; `Blocks Common.help_secs
|
||||
]
|
||||
;;
|
||||
|
||||
let lock_ocamlformat () =
|
||||
if Lazy.force Lock_dev_tool.is_enabled
|
||||
then
|
||||
(* Note that generating the ocamlformat lockdir here means
|
||||
that it will be created when a user runs `dune fmt` but not
|
||||
when a user runs `dune build @fmt`. It's important that
|
||||
this logic remain outside of `dune build`, as `dune
|
||||
build` is intended to only build targets, and generating
|
||||
a lockdir is not building a target. *)
|
||||
Lock_dev_tool.lock_dev_tool Ocamlformat |> Memo.run
|
||||
else Fiber.return ()
|
||||
;;
|
||||
|
||||
let run_fmt_command ~(common : Common.t) ~config =
|
||||
let open Fiber.O in
|
||||
let once () =
|
||||
let* () = lock_ocamlformat () in
|
||||
let request (setup : Import.Main.build_system) =
|
||||
let dir = Path.(relative root) (Common.prefix_target common ".") in
|
||||
Alias.in_dir ~name:Dune_rules.Alias.fmt ~recursive:true ~contexts:setup.contexts dir
|
||||
|> Alias.request
|
||||
in
|
||||
Build.run_build_system ~common ~request
|
||||
>>| function
|
||||
| Ok () -> ()
|
||||
| Error `Already_reported -> raise Dune_util.Report_error.Already_reported
|
||||
in
|
||||
Scheduler.go_with_rpc_server ~common ~config once
|
||||
;;
|
||||
|
||||
let command =
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ no_promote =
|
||||
Arg.(
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "preview" ]
|
||||
~doc:
|
||||
"Just print the changes that would be made without actually applying them. \
|
||||
This takes precedence over auto-promote as that flag is assumed for this \
|
||||
command.")
|
||||
in
|
||||
let builder =
|
||||
Common.Builder.set_promote builder (if no_promote then Never else Automatically)
|
||||
in
|
||||
let common, config = Common.init builder in
|
||||
run_fmt_command ~common ~config
|
||||
in
|
||||
Cmd.v (Cmd.info "fmt" ~doc ~man ~envs:Common.envs) term
|
||||
;;
|
||||
3
unikernel/duniverse/dune_/bin/fmt.mli
Normal file
3
unikernel/duniverse/dune_/bin/fmt.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
57
unikernel/duniverse/dune_/bin/format_dune_file.ml
Normal file
57
unikernel/duniverse/dune_/bin/format_dune_file.ml
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
open Import
|
||||
|
||||
let doc = "Format dune files."
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
{|$(b,dune format-dune-file) reads a dune file and outputs a formatted
|
||||
version. This is a low-level command, meant to implement editor
|
||||
support for example. To reformat a dune project, see the "Automatic
|
||||
formatting" section in the manual.|}
|
||||
]
|
||||
;;
|
||||
|
||||
let info = Cmd.info "format-dune-file" ~doc ~man
|
||||
|
||||
let format_file ~version ~input =
|
||||
let with_input =
|
||||
match input with
|
||||
| Some path -> fun f -> Io.with_lexbuf_from_file path ~f
|
||||
| None ->
|
||||
fun f ->
|
||||
Exn.protect
|
||||
~f:(fun () -> f (Lexing.from_channel stdin))
|
||||
~finally:(fun () -> close_in_noerr stdin)
|
||||
in
|
||||
match with_input Dune_lang.Format.parse with
|
||||
| Sexps sexps ->
|
||||
Format.fprintf
|
||||
Format.std_formatter
|
||||
"%a%!"
|
||||
Pp.to_fmt
|
||||
(Dune_lang.Format.pp_top_sexps ~version sexps)
|
||||
| OCaml_syntax loc ->
|
||||
(match input with
|
||||
| None -> User_error.raise ~loc [ Pp.text "OCaml syntax is not supported." ]
|
||||
| Some path -> Io.with_file_in path ~f:(fun ic -> Io.copy_channels ic stdout))
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ path_opt =
|
||||
let docv = "FILE" in
|
||||
let doc = "Path to the dune file to parse." in
|
||||
Arg.(value & pos 0 (some path) None & info [] ~docv ~doc)
|
||||
and+ version =
|
||||
let docv = "VERSION" in
|
||||
let doc = "Which version of Dune language to use." in
|
||||
let default =
|
||||
Dune_lang.Syntax.greatest_supported_version_exn Dune_lang.Stanza.syntax
|
||||
in
|
||||
Arg.(value & opt version default & info [ "dune-version" ] ~docv ~doc)
|
||||
in
|
||||
let input = Option.map ~f:Arg.Path.path path_opt in
|
||||
format_file ~version ~input
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
3
unikernel/duniverse/dune_/bin/format_dune_file.mli
Normal file
3
unikernel/duniverse/dune_/bin/format_dune_file.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
128
unikernel/duniverse/dune_/bin/help.ml
Normal file
128
unikernel/duniverse/dune_/bin/help.ml
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
open Import
|
||||
|
||||
let config =
|
||||
( ("dune-config", 5, "", "Dune", "Dune manual")
|
||||
, [ `S Manpage.s_name
|
||||
; `P {|dune-config - configuring the dune build system|}
|
||||
; `S Manpage.s_synopsis
|
||||
; `Pre "~/.config/dune/config"
|
||||
; `S Manpage.s_description
|
||||
; `P
|
||||
{|Unless $(b,--no-config) or $(b,-p) is passed, Dune will read a
|
||||
configuration file from the user home directory. This file is used
|
||||
to control various aspects of the behavior of Dune.|}
|
||||
; `P
|
||||
{|The configuration file is normally $(b,~/.config/dune/config) on
|
||||
Unix systems and $(b,Local Settings/dune/config) in the User home
|
||||
directory on Windows. However, it is possible to specify an
|
||||
alternative configuration file with the $(b,--config-file) option.|}
|
||||
; `P
|
||||
{|The first line of the file must be of the form (lang dune X.Y)
|
||||
where X.Y is the version of the dune language used in the file.|}
|
||||
; `P
|
||||
{|The rest of the file must be written in S-expression syntax and be
|
||||
composed of a list of stanzas. The following sections describe
|
||||
the stanzas available.|}
|
||||
; `S "CACHING"
|
||||
; `P {|Syntax: $(b,\(cache ENABLED\))|}
|
||||
; `P
|
||||
{| This stanza determines whether dune's build caching is enabled.
|
||||
See https://dune.readthedocs.io/en/stable/caching.html for details.
|
||||
Valid values for $(b, ENABLED) are $(b, enabled) or $(b, disabled).|}
|
||||
; `S "DISPLAY MODES"
|
||||
; `P {|Syntax: $(b,\(display MODE\))|}
|
||||
; `P
|
||||
{|This stanza controls how Dune reports what it is doing to the user.
|
||||
This parameter can also be set from the command line via $(b,--display MODE).
|
||||
The following display modes are available:|}
|
||||
; `Blocks
|
||||
(List.map
|
||||
~f:(fun (x, desc) -> `I (sprintf "$(b,%s)" x, desc))
|
||||
[ ( "progress"
|
||||
, {|This is the default, Dune shows and update a
|
||||
status line as build goals are being completed.|}
|
||||
)
|
||||
; "quiet", {|Only display errors.|}
|
||||
; ( "short"
|
||||
, {|Print one line per command being executed, with the
|
||||
binary name on the left and the reason it is being executed for
|
||||
on the right.|}
|
||||
)
|
||||
; ( "verbose"
|
||||
, {|Print the full command lines of programs being
|
||||
executed by Dune, with some colors to help differentiate
|
||||
programs.|}
|
||||
)
|
||||
])
|
||||
; `P
|
||||
{|Note that when the selected display mode is $(b,progress) and the
|
||||
output is not a terminal then the $(b,quiet) mode is selected
|
||||
instead. This rule doesn't apply when running Dune inside Emacs.
|
||||
Dune detects whether it is executed from inside Emacs or not by
|
||||
looking at the environment variable $(b,INSIDE_EMACS) that is set by
|
||||
Emacs. If you want the same behavior with another editor, you can set
|
||||
this variable. If your editor already sets another variable,
|
||||
please open a ticket on the ocaml/dune GitHub project so that we can
|
||||
add support for it.|}
|
||||
; `S "JOBS"
|
||||
; `P {|Syntax: $(b,\(jobs NUMBER\))|}
|
||||
; `P
|
||||
{|Set the maximum number of jobs Dune might run in parallel.
|
||||
This can also be set from the command line via $(b,-j NUMBER).|}
|
||||
; `P {|The default for this value is the number of processors.|}
|
||||
; `S "SANDBOXING"
|
||||
; `P {|Syntax: $(b,\(sandboxing_preference MODE ...\))|}
|
||||
; `P
|
||||
{|Controls the sandboxing mode preference order used by dune. Dune will
|
||||
use the earliest item from this list that's allowed by the action dependency
|
||||
specification, or fall back on the hard-coded default. See $(b,man dune-build)
|
||||
for the description of individual modes.|}
|
||||
; Common.footer
|
||||
] )
|
||||
;;
|
||||
|
||||
type what =
|
||||
| Man of Manpage.t
|
||||
| List_topics
|
||||
|
||||
let commands = [ "config", Man config; "topics", List_topics ]
|
||||
let doc = "Additional Dune help."
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
{|$(b,dune help TOPIC) provides additional help on the given topic.
|
||||
The following topics are available:|}
|
||||
; `Blocks
|
||||
(List.concat_map commands ~f:(fun (s, what) ->
|
||||
match what with
|
||||
| List_topics -> []
|
||||
| Man ((title, _, _, _, _), _) -> [ `I (sprintf "$(b,%s)" s, title) ]))
|
||||
; Common.footer
|
||||
]
|
||||
;;
|
||||
|
||||
let info = Cmd.info "help" ~doc ~man ~envs:Common.envs
|
||||
|
||||
let term =
|
||||
Term.ret
|
||||
@@ let+ man_format = Arg.man_format
|
||||
and+ what = Arg.(value & pos 0 (some (enum commands)) None & info [] ~docv:"TOPIC")
|
||||
and+ () = Common.build_info in
|
||||
match what with
|
||||
| None -> `Help (man_format, Some "help")
|
||||
| Some (Man man_page) ->
|
||||
Format.printf "%a@?" (Manpage.print man_format) man_page;
|
||||
`Ok ()
|
||||
| Some List_topics ->
|
||||
List.filter_map commands ~f:(fun (s, what) ->
|
||||
match what with
|
||||
| List_topics -> None
|
||||
| _ -> Some s)
|
||||
|> List.sort ~compare:String.compare
|
||||
|> String.concat ~sep:"\n"
|
||||
|> print_endline;
|
||||
`Ok ()
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
3
unikernel/duniverse/dune_/bin/help.mli
Normal file
3
unikernel/duniverse/dune_/bin/help.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
288
unikernel/duniverse/dune_/bin/import.ml
Normal file
288
unikernel/duniverse/dune_/bin/import.ml
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
include Stdune
|
||||
include Dune_config_file
|
||||
include Dune_vcs
|
||||
|
||||
include struct
|
||||
open Dune_engine
|
||||
module Build_config = Build_config
|
||||
module Build_system = Build_system
|
||||
module Build_system_error = Build_system_error
|
||||
module Load_rules = Load_rules
|
||||
module Hooks = Hooks
|
||||
module Action_builder = Dune_rules.Action_builder
|
||||
module Action = Action
|
||||
module Dep = Dep
|
||||
module Action_to_sh = Action_to_sh
|
||||
module Dpath = Dpath
|
||||
module Findlib = Dune_rules.Findlib
|
||||
module Diff_promotion = Diff_promotion
|
||||
module Targets = Targets
|
||||
module Context_name = Context_name
|
||||
end
|
||||
|
||||
module Cached_digest = Dune_digest.Cached_digest
|
||||
|
||||
include struct
|
||||
open Source
|
||||
module Source_tree = Source_tree
|
||||
module Source_dir_status = Source_dir_status
|
||||
module Workspace = Workspace
|
||||
end
|
||||
|
||||
include struct
|
||||
open Dune_rules
|
||||
module Super_context = Super_context
|
||||
module Context = Context
|
||||
module Dune_package = Dune_package
|
||||
module Resolve = Resolve
|
||||
module Dune_file = Dune_file
|
||||
module Library = Library
|
||||
module Melange = Melange
|
||||
module Melange_stanzas = Melange_stanzas
|
||||
module Executables = Executables
|
||||
end
|
||||
|
||||
include struct
|
||||
open Cmdliner
|
||||
module Term = Term
|
||||
module Manpage = Manpage
|
||||
|
||||
module Cmd = struct
|
||||
include Cmd
|
||||
|
||||
let default_exits = List.map ~f:Exit_code.info Exit_code.all
|
||||
|
||||
let info ?docs ?doc ?man ?envs ?version name =
|
||||
info ?docs ?doc ?man ?envs ?version ~exits:default_exits name
|
||||
;;
|
||||
end
|
||||
end
|
||||
|
||||
module Digest = Dune_digest
|
||||
module Metrics = Dune_metrics
|
||||
module Console = Dune_console
|
||||
|
||||
include struct
|
||||
open Dune_lang
|
||||
module Stanza = Stanza
|
||||
module Profile = Profile
|
||||
module Lib_name = Lib_name
|
||||
module Package_name = Package_name
|
||||
module Package = Package
|
||||
module Package_version = Package_version
|
||||
module Source_kind = Source_kind
|
||||
module Package_info = Package_info
|
||||
module Section = Section
|
||||
module Dune_project_name = Dune_project_name
|
||||
module Dune_project = Dune_project
|
||||
end
|
||||
|
||||
module Log = Dune_util.Log
|
||||
module Dune_rpc = Dune_rpc_private
|
||||
module Graph = Dune_graph.Graph
|
||||
include Common.Let_syntax
|
||||
|
||||
module Main : sig
|
||||
include module type of struct
|
||||
include Dune_rules.Main
|
||||
end
|
||||
|
||||
val setup : unit -> build_system Memo.t Fiber.t
|
||||
end = struct
|
||||
include Dune_rules.Main
|
||||
|
||||
let setup () =
|
||||
let open Fiber.O in
|
||||
let* scheduler = Dune_engine.Scheduler.t () in
|
||||
Console.Status_line.set
|
||||
(Live
|
||||
(fun () ->
|
||||
match Fiber.Svar.read Build_system.state with
|
||||
| Initializing
|
||||
| Restarting_current_build
|
||||
| Build_succeeded__now_waiting_for_changes
|
||||
| Build_failed__now_waiting_for_changes -> Pp.nop
|
||||
| Building
|
||||
{ Build_system.Progress.number_of_rules_executed = done_
|
||||
; number_of_rules_discovered = total
|
||||
; number_of_rules_failed = failed
|
||||
} ->
|
||||
Pp.verbatim
|
||||
(sprintf
|
||||
"Done: %u%% (%u/%u, %u left%s) (jobs: %u)"
|
||||
(if total = 0 then 0 else done_ * 100 / total)
|
||||
done_
|
||||
total
|
||||
(total - done_)
|
||||
(if failed = 0 then "" else sprintf ", %u failed" failed)
|
||||
(Dune_engine.Scheduler.running_jobs_count scheduler))));
|
||||
Fiber.return (Memo.of_thunk get)
|
||||
;;
|
||||
end
|
||||
|
||||
module Scheduler = struct
|
||||
include Dune_engine.Scheduler
|
||||
|
||||
let maybe_clear_screen ~details_hum (dune_config : Dune_config.t) =
|
||||
match Execution_env.inside_dune with
|
||||
| true -> (* Don't print anything here to make tests less verbose *) ()
|
||||
| false ->
|
||||
(match dune_config.terminal_persistence with
|
||||
| Clear_on_rebuild -> Console.reset ()
|
||||
| Clear_on_rebuild_and_flush_history -> Console.reset_flush_history ()
|
||||
| Preserve ->
|
||||
let message =
|
||||
sprintf
|
||||
"********** NEW BUILD (%s) **********"
|
||||
(String.concat ~sep:", " details_hum)
|
||||
in
|
||||
Console.print_user_message
|
||||
(User_message.make
|
||||
[ Pp.nop; Pp.tag User_message.Style.Success (Pp.verbatim message); Pp.nop ]))
|
||||
;;
|
||||
|
||||
let on_event dune_config _config = function
|
||||
| Run.Event.Tick -> Console.Status_line.refresh ()
|
||||
| Source_files_changed { details_hum } -> maybe_clear_screen ~details_hum dune_config
|
||||
| Build_interrupted ->
|
||||
Console.Status_line.set
|
||||
(Live
|
||||
(fun () ->
|
||||
let progression =
|
||||
match Fiber.Svar.read Build_system.state with
|
||||
| Initializing
|
||||
| Restarting_current_build
|
||||
| Build_succeeded__now_waiting_for_changes
|
||||
| Build_failed__now_waiting_for_changes -> Build_system.Progress.init
|
||||
| Building progress -> progress
|
||||
in
|
||||
Pp.seq
|
||||
(Pp.tag User_message.Style.Error (Pp.verbatim "Source files changed"))
|
||||
(Pp.verbatim
|
||||
(sprintf
|
||||
", restarting current build... (%u/%u)"
|
||||
progression.number_of_rules_executed
|
||||
progression.number_of_rules_discovered))))
|
||||
| Build_finish build_result ->
|
||||
let message =
|
||||
match build_result with
|
||||
| Success -> Pp.tag User_message.Style.Success (Pp.verbatim "Success")
|
||||
| Failure ->
|
||||
let failure_message =
|
||||
match
|
||||
Build_system_error.(
|
||||
Id.Map.cardinal (Set.current (Fiber.Svar.read Build_system.errors)))
|
||||
with
|
||||
| 1 -> Pp.textf "Had 1 error"
|
||||
| n -> Pp.textf "Had %d errors" n
|
||||
in
|
||||
Pp.tag User_message.Style.Error failure_message
|
||||
in
|
||||
Console.Status_line.set
|
||||
(Constant (Pp.seq message (Pp.verbatim ", waiting for filesystem changes...")))
|
||||
;;
|
||||
|
||||
let rpc server =
|
||||
{ Dune_engine.Rpc.run = Dune_rpc_impl.Server.run server
|
||||
; stop = Dune_rpc_impl.Server.stop server
|
||||
; ready = Dune_rpc_impl.Server.ready server
|
||||
}
|
||||
;;
|
||||
|
||||
let go_without_rpc_server ~(common : Common.t) ~config:dune_config f =
|
||||
let stats = Common.stats common in
|
||||
let config =
|
||||
let watch_exclusions = Common.watch_exclusions common in
|
||||
Dune_config.for_scheduler
|
||||
dune_config
|
||||
stats
|
||||
~print_ctrl_c_warning:true
|
||||
~watch_exclusions
|
||||
in
|
||||
Dune_rules.Clflags.concurrency := config.concurrency;
|
||||
Run.go config ~on_event:(on_event dune_config) f
|
||||
;;
|
||||
|
||||
let go_with_rpc_server ~common ~config f =
|
||||
let f =
|
||||
match Common.rpc common with
|
||||
| `Allow server -> fun () -> Dune_engine.Rpc.with_background_rpc (rpc server) f
|
||||
| `Forbid_builds -> f
|
||||
in
|
||||
go_without_rpc_server ~common ~config f
|
||||
;;
|
||||
|
||||
let go_with_rpc_server_and_console_status_reporting
|
||||
~(common : Common.t)
|
||||
~config:dune_config
|
||||
run
|
||||
=
|
||||
let server =
|
||||
match Common.rpc common with
|
||||
| `Allow server -> rpc server
|
||||
| `Forbid_builds -> Code_error.raise "rpc must be enabled in polling mode" []
|
||||
in
|
||||
let stats = Common.stats common in
|
||||
let config =
|
||||
let watch_exclusions = Common.watch_exclusions common in
|
||||
Dune_config.for_scheduler
|
||||
dune_config
|
||||
stats
|
||||
~print_ctrl_c_warning:true
|
||||
~watch_exclusions
|
||||
in
|
||||
Dune_rules.Clflags.concurrency := config.concurrency;
|
||||
let file_watcher = Common.file_watcher common in
|
||||
let run () =
|
||||
let open Fiber.O in
|
||||
Dune_engine.Rpc.with_background_rpc server
|
||||
@@ fun () ->
|
||||
let* () = Dune_engine.Rpc.ensure_ready () in
|
||||
run ()
|
||||
in
|
||||
Run.go config ~file_watcher ~on_event:(on_event dune_config) run
|
||||
;;
|
||||
end
|
||||
|
||||
let string_path_relative_to_specified_root (root : Workspace_root.t) path =
|
||||
if Filename.is_relative path then Filename.concat root.dir path else path
|
||||
;;
|
||||
|
||||
let restore_cwd_and_execve root prog args env =
|
||||
let prog = string_path_relative_to_specified_root root prog in
|
||||
Proc.restore_cwd_and_execve prog args ~env
|
||||
;;
|
||||
|
||||
(* Adapted from
|
||||
https://github.com/ocaml/opam/blob/fbbe93c3f67034da62d28c8666ec6b05e0a9b17c/src/client/opamArg.ml#L759 *)
|
||||
let command_alias ?orig_name cmd term name =
|
||||
let orig =
|
||||
match orig_name with
|
||||
| Some s -> s
|
||||
| None -> Cmd.name cmd
|
||||
in
|
||||
let doc = Printf.sprintf "An alias for $(b,%s)." orig in
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P (Printf.sprintf "$(mname)$(b, %s) is an alias for $(mname)$(b, %s)." name orig)
|
||||
; `P (Printf.sprintf "See $(mname)$(b, %s --help) for details." orig)
|
||||
; `Blocks Common.help_secs
|
||||
]
|
||||
in
|
||||
Cmd.v (Cmd.info name ~docs:"COMMAND ALIASES" ~doc ~man) term
|
||||
;;
|
||||
|
||||
(* The build system has some global state which makes it unsafe for
|
||||
multiple instances of it to be executed concurrently, so we ensure
|
||||
serialization by holding this mutex while running the build system. *)
|
||||
let build_system_mutex = Fiber.Mutex.create ()
|
||||
|
||||
let build f =
|
||||
Hooks.End_of_build.once Promote.Diff_promotion.finalize;
|
||||
Fiber.Mutex.with_lock build_system_mutex ~f:(fun () -> Build_system.run f)
|
||||
;;
|
||||
|
||||
let build_exn f =
|
||||
Hooks.End_of_build.once Promote.Diff_promotion.finalize;
|
||||
Fiber.Mutex.with_lock build_system_mutex ~f:(fun () -> Build_system.run_exn f)
|
||||
;;
|
||||
341
unikernel/duniverse/dune_/bin/init.ml
Normal file
341
unikernel/duniverse/dune_/bin/init.ml
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
open Import
|
||||
open Dune_init
|
||||
|
||||
(** {1 Helper functions} *)
|
||||
|
||||
(** {2 Cmdliner Argument Converters} *)
|
||||
|
||||
let atom_parser s =
|
||||
match Dune_lang.Atom.parse s with
|
||||
| Some s -> Ok s
|
||||
| None -> Error (`Msg "expected a valid dune atom")
|
||||
;;
|
||||
|
||||
let atom_printer ppf a = Format.pp_print_string ppf (Dune_lang.Atom.to_string a)
|
||||
|
||||
let component_name_parser s =
|
||||
(* TODO refactor to use Lib_name.Local.conv *)
|
||||
let err_msg () =
|
||||
User_error.make
|
||||
[ Pp.textf "invalid component name `%s'" s; Lib_name.Local.valid_format_doc ]
|
||||
|> User_message.to_string
|
||||
|> fun m -> `Msg m
|
||||
in
|
||||
let open Result.O in
|
||||
let* atom = atom_parser s in
|
||||
let* _ =
|
||||
match Lib_name.Local.of_string_opt s with
|
||||
| None -> Error (err_msg ())
|
||||
| Some s -> Ok s
|
||||
in
|
||||
Ok atom
|
||||
;;
|
||||
|
||||
let project_name_parser s =
|
||||
(* TODO refactor Dune_project_name to be Stringlike *)
|
||||
match Dune_project_name.named Loc.none s with
|
||||
| v -> Ok v
|
||||
| exception User_error.E _ ->
|
||||
User_error.make
|
||||
[ Pp.textf "invalid project name `%s'" s
|
||||
; Pp.text
|
||||
"Project names must start with a letter and be composed only of letters, \
|
||||
numbers, '-' or '_'"
|
||||
]
|
||||
|> User_message.to_string
|
||||
|> fun m -> Error (`Msg m)
|
||||
;;
|
||||
|
||||
let project_name_printer ppf p =
|
||||
Format.pp_print_string ppf (Dune_project_name.to_string_hum p)
|
||||
;;
|
||||
|
||||
let atom_conv = Arg.conv (atom_parser, atom_printer)
|
||||
let component_name_conv = Arg.conv (component_name_parser, atom_printer)
|
||||
let project_name_conv = Arg.conv (project_name_parser, project_name_printer)
|
||||
|
||||
(** {2 Status reporting} *)
|
||||
|
||||
let print_completion kind name =
|
||||
let open Pp.O in
|
||||
Console.print_user_message
|
||||
(User_message.make
|
||||
[ Pp.tag User_message.Style.Ok (Pp.verbatim "Success")
|
||||
++ Pp.textf ": initialized %s component named " kind
|
||||
++ Pp.tag User_message.Style.Kwd (Pp.verbatim (Dune_lang.Atom.to_string name))
|
||||
])
|
||||
;;
|
||||
|
||||
(** {1 CLI} *)
|
||||
|
||||
let path =
|
||||
let docv = "PATH" in
|
||||
Arg.(value & pos 1 (some string) None & info [] ~docv)
|
||||
;;
|
||||
|
||||
let context_cwd : Init_context.t Term.t =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ path = path in
|
||||
let builder = Common.Builder.set_default_root_is_cwd builder true in
|
||||
let common, config = Common.init builder in
|
||||
let project_defaults = config.project_defaults in
|
||||
Scheduler.go_with_rpc_server ~common ~config (fun () ->
|
||||
Memo.run (Init_context.make path project_defaults))
|
||||
;;
|
||||
|
||||
module Public_name = struct
|
||||
type t =
|
||||
| Use_name
|
||||
| Public_name of Public_name.t
|
||||
|
||||
let public_name_to_string = function
|
||||
| Use_name -> "<default>"
|
||||
| Public_name p -> Public_name.to_string p
|
||||
;;
|
||||
|
||||
let public_name default_name = function
|
||||
| None -> None
|
||||
| Some Use_name -> Some (Public_name.of_name_exn default_name)
|
||||
| Some (Public_name n) -> Some n
|
||||
;;
|
||||
|
||||
let conv =
|
||||
let parser s =
|
||||
if String.is_empty s
|
||||
then Ok Use_name
|
||||
else (
|
||||
match Public_name.of_string_user_error (Loc.none, s) with
|
||||
| Ok n -> Ok (Public_name n)
|
||||
| Error e -> Error (`Msg (User_message.to_string e)))
|
||||
in
|
||||
let printer ppf public_name =
|
||||
Format.pp_print_string ppf (public_name_to_string public_name)
|
||||
in
|
||||
Arg.conv (parser, printer)
|
||||
;;
|
||||
end
|
||||
|
||||
let libraries =
|
||||
let docv = "LIBRARIES" in
|
||||
let doc = "A comma separated list of libraries on which the component depends" in
|
||||
Arg.(value & opt (list atom_conv) [] & info [ "libs" ] ~docv ~doc)
|
||||
;;
|
||||
|
||||
let pps =
|
||||
let docv = "PREPROCESSORS" in
|
||||
let doc = "A comma separated list of ppx preprocessors used by the component" in
|
||||
Arg.(value & opt (list atom_conv) [] & info [ "ppx" ] ~docv ~doc)
|
||||
;;
|
||||
|
||||
let public : Public_name.t option Term.t =
|
||||
let docv = "PUBLIC_NAME" in
|
||||
let doc =
|
||||
"If called with an argument, make the component public under the given PUBLIC_NAME. \
|
||||
If supplied without an argument, use NAME."
|
||||
in
|
||||
Arg.(
|
||||
value
|
||||
& opt ~vopt:(Some Public_name.Use_name) (some Public_name.conv) None
|
||||
& info [ "public" ] ~docv ~doc)
|
||||
;;
|
||||
|
||||
let common : Component.Options.Common.t Term.t =
|
||||
let+ name =
|
||||
let docv = "NAME" in
|
||||
Arg.(required & pos 0 (some component_name_conv) None & info [] ~docv)
|
||||
and+ public = public
|
||||
and+ libraries = libraries
|
||||
and+ pps = pps in
|
||||
let public = Public_name.public_name name public in
|
||||
{ Component.Options.Common.name; public; libraries; pps }
|
||||
;;
|
||||
|
||||
let project_common : Component.Options.Common.t Term.t =
|
||||
let+ project_name =
|
||||
let docv = "NAME" in
|
||||
Arg.(required & pos 0 (some project_name_conv) None & info [] ~docv)
|
||||
and+ libraries = libraries
|
||||
and+ pps = pps in
|
||||
let public = Dune_project_name.to_string_hum project_name in
|
||||
let name =
|
||||
String.map
|
||||
~f:(function
|
||||
| '-' -> '_'
|
||||
| c -> c)
|
||||
public
|
||||
|> Dune_lang.Atom.of_string
|
||||
in
|
||||
let public =
|
||||
Some (Dune_lang.Atom.of_string public |> Dune_init.Public_name.of_name_exn)
|
||||
in
|
||||
{ Component.Options.Common.name; public; libraries; pps }
|
||||
;;
|
||||
|
||||
let inline_tests : bool Term.t =
|
||||
let docv = "USE_INLINE_TESTS" in
|
||||
let doc =
|
||||
"Whether to use inline tests. Only applicable for $(b,library) and $(b,project) \
|
||||
components."
|
||||
in
|
||||
Arg.(value & flag & info [ "inline-tests" ] ~docv ~doc)
|
||||
;;
|
||||
|
||||
let opt_default ~default term = Term.(const (Option.value ~default) $ term)
|
||||
|
||||
let executable =
|
||||
let doc = "A binary executable." in
|
||||
let man = [] in
|
||||
let kind = "executable" in
|
||||
Cmd.v (Cmd.info kind ~doc ~man)
|
||||
@@ let+ context = context_cwd
|
||||
and+ common = common in
|
||||
Component.init (Executable { context; common; options = () });
|
||||
print_completion kind common.name
|
||||
;;
|
||||
|
||||
let library =
|
||||
let doc = "An OCaml library." in
|
||||
let man = [] in
|
||||
let kind = "library" in
|
||||
Cmd.v (Cmd.info kind ~doc ~man)
|
||||
@@ let+ context = context_cwd
|
||||
and+ common = common
|
||||
and+ inline_tests = inline_tests in
|
||||
Component.init (Library { context; common; options = { inline_tests } });
|
||||
print_completion kind common.name
|
||||
;;
|
||||
|
||||
let test =
|
||||
let doc =
|
||||
"A test harness. (For inline tests, use the $(b,--inline-tests) flag along with the \
|
||||
other component kinds.)"
|
||||
in
|
||||
let man = [] in
|
||||
let kind = "test" in
|
||||
Cmd.v (Cmd.info kind ~doc ~man)
|
||||
@@ let+ context = context_cwd
|
||||
and+ common = common in
|
||||
Component.init (Test { context; common; options = () });
|
||||
print_completion kind common.name
|
||||
;;
|
||||
|
||||
let project =
|
||||
let module Builder = Common.Builder in
|
||||
let doc =
|
||||
"A project is a predefined composition of components arranged in a standard \
|
||||
directory structure. The kind of project initialized is determined by the value of \
|
||||
the $(b,--kind) flag and defaults to an executable project, composed of a library, \
|
||||
an executable, and a test component."
|
||||
in
|
||||
let man = [] in
|
||||
Cmd.v (Cmd.info "project" ~doc ~man)
|
||||
@@ let+ common_builder = Builder.term
|
||||
and+ path = path
|
||||
and+ common = project_common
|
||||
and+ inline_tests = inline_tests
|
||||
and+ template =
|
||||
let docv = "PROJECT_KIND" in
|
||||
let doc =
|
||||
"The kind of project to initialize. Valid options are $(b,e[xecutable]) or \
|
||||
$(b,l[ibrary]). Defaults to $(b,executable). Only applicable for $(b,project) \
|
||||
components."
|
||||
in
|
||||
opt_default
|
||||
~default:Component.Options.Project.Template.Exec
|
||||
Arg.(
|
||||
value
|
||||
& opt (some (enum Component.Options.Project.Template.commands)) None
|
||||
& info [ "kind" ] ~docv ~doc)
|
||||
and+ pkg =
|
||||
let docv = "PACKAGE_MANAGER" in
|
||||
let doc =
|
||||
"Which package manager to use. Valid options are $(b,o[pam]) or $(b,e[sy]). \
|
||||
Defaults to $(b,opam). Only applicable for $(b,project) components."
|
||||
in
|
||||
opt_default
|
||||
~default:Component.Options.Project.Pkg.Opam
|
||||
Arg.(
|
||||
value
|
||||
& opt (some (enum Component.Options.Project.Pkg.commands)) None
|
||||
& info [ "pkg" ] ~docv ~doc)
|
||||
in
|
||||
let name =
|
||||
match common.public with
|
||||
| None -> Dune_lang.Atom.to_string common.name
|
||||
| Some public -> Dune_init.Public_name.to_string public
|
||||
in
|
||||
let context =
|
||||
let init_context = Init_context.make path in
|
||||
let root =
|
||||
match path with
|
||||
(* If a path is given, we use that for the root during project
|
||||
initialization, creating the path to it if needed. *)
|
||||
| Some path -> path
|
||||
(* Otherwise we will use the project's given name, and create a
|
||||
directory accordingly. *)
|
||||
| None -> name
|
||||
in
|
||||
let builder = Builder.set_root common_builder root in
|
||||
let (_ : Fpath.mkdir_p_result) = Fpath.mkdir_p root in
|
||||
let common, config = Common.init builder in
|
||||
let project_defaults = config.project_defaults in
|
||||
Scheduler.go_with_rpc_server ~common ~config (fun () ->
|
||||
Memo.run @@ init_context project_defaults)
|
||||
in
|
||||
Component.init
|
||||
(Project { context; common; options = { template; inline_tests; pkg } });
|
||||
print_completion "project" (Dune_lang.Atom.of_string name)
|
||||
;;
|
||||
|
||||
let group =
|
||||
let doc = "Command group for initializing Dune components." in
|
||||
let synopsis =
|
||||
Common.command_synopsis
|
||||
[ "init project NAME [PATH] [OPTION]... "
|
||||
; "init executable NAME [PATH] [OPTION]... "
|
||||
; "init library NAME [PATH] [OPTION]... "
|
||||
; "init test NAME [PATH] [OPTION]... "
|
||||
]
|
||||
in
|
||||
let man =
|
||||
[ `Blocks synopsis
|
||||
; `S "DESCRIPTION"
|
||||
; `P
|
||||
{|$(b,dune init COMPONENT NAME [PATH] [OPTION]...) initializes a new dune
|
||||
configuration for a component of the kind specified by the subcommand
|
||||
$(b,COMPONENT), named $(b,NAME), with fields determined by the supplied
|
||||
$(b,OPTION)s.|}
|
||||
; `P
|
||||
{|Run a subcommand with $(b, --help) for for details on it's supported arguments|}
|
||||
; `P
|
||||
{|If the optional $(b,PATH) is provided, it must be a path to a directory, and
|
||||
the component will be created there. Otherwise, it is created in a child of the
|
||||
current working directory, called $(b, NAME). To initialize a component in the
|
||||
current working directory, use `.` as the $(b,PATH).|}
|
||||
; `P
|
||||
{|Any prefix of a $(b,COMMAND)'s name can be supplied in place of
|
||||
full name (as illustrated in the synopsis).|}
|
||||
; `P
|
||||
{|For more details, see https://dune.readthedocs.io/en/stable/usage.html#initializing-components|}
|
||||
; Common.examples
|
||||
[ ( {|Generate a project skeleton for an executable named `myproj' in a
|
||||
new directory named `myproj', depending on the bos library and
|
||||
using inline tests along with ppx_inline_test |}
|
||||
, {|dune init project myproj --libs bos --ppx ppx_inline_test --inline-tests|} )
|
||||
; ( {|Configure an executable component named `myexe' in a dune file in the
|
||||
current directory|}
|
||||
, {|dune init executable myexe|} )
|
||||
; ( {|Configure a library component named `mylib' in a dune file in the ./src
|
||||
directory depending on the core and cmdliner libraries, the ppx_let
|
||||
and ppx_inline_test preprocessors, and declared as using inline
|
||||
tests|}
|
||||
, {|dune init library mylib src --libs core,cmdliner --ppx ppx_let,ppx_inline_test --inline-tests|}
|
||||
)
|
||||
; ( {|Configure a test component named `mytest' in a dune file in the
|
||||
./test directory that depends on `mylib'|}
|
||||
, {|dune init test mytest test --libs mylib|} )
|
||||
]
|
||||
]
|
||||
in
|
||||
Cmd.group (Cmd.info "init" ~doc ~man) [ executable; project; library; test ]
|
||||
;;
|
||||
3
unikernel/duniverse/dune_/bin/init.mli
Normal file
3
unikernel/duniverse/dune_/bin/init.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val group : unit Cmd.t
|
||||
856
unikernel/duniverse/dune_/bin/install_uninstall.ml
Normal file
856
unikernel/duniverse/dune_/bin/install_uninstall.ml
Normal file
|
|
@ -0,0 +1,856 @@
|
|||
open Import
|
||||
module Artifact_substitution = Dune_rules.Artifact_substitution
|
||||
|
||||
let synopsis =
|
||||
[ `P "The installation directories used are defined by priority:"
|
||||
; `Noblank
|
||||
; `P
|
||||
"- directories set on the command line of $(i,dune install), or corresponding \
|
||||
environment variables"
|
||||
; `Noblank
|
||||
; `P
|
||||
"- directories set in dune binary. They are setup before the compilation of dune \
|
||||
with $(i,./configure)"
|
||||
; `Noblank
|
||||
; `P "- inferred from the environment variable $(i,OPAM_SWITCH_PREFIX) if present"
|
||||
]
|
||||
;;
|
||||
|
||||
let print_line ~(verbosity : Dune_engine.Display.t) fmt =
|
||||
Printf.ksprintf
|
||||
(fun s ->
|
||||
match verbosity with
|
||||
| Quiet -> ()
|
||||
| _ -> Console.print [ Pp.verbatim s ])
|
||||
fmt
|
||||
;;
|
||||
|
||||
let interpret_destdir ~destdir path =
|
||||
match destdir with
|
||||
| None -> path
|
||||
| Some destdir -> Path.append_local destdir (Path.local_part path)
|
||||
;;
|
||||
|
||||
let get_dirs context ~prefix_from_command_line ~from_command_line =
|
||||
let open Fiber.O in
|
||||
let module Roots = Install.Roots in
|
||||
let prefix_from_command_line = Option.map ~f:Path.of_string prefix_from_command_line in
|
||||
let+ roots =
|
||||
match prefix_from_command_line with
|
||||
| None -> Memo.run (Context.roots context)
|
||||
| Some prefix ->
|
||||
Roots.opam_from_prefix prefix ~relative:Path.relative
|
||||
|> Roots.map ~f:(fun s -> Some s)
|
||||
|> Fiber.return
|
||||
in
|
||||
let roots = Roots.first_has_priority from_command_line roots in
|
||||
let must_be_defined name v =
|
||||
match v with
|
||||
| Some v -> v
|
||||
| None ->
|
||||
(* We suggest that the user sets --prefix first rather than the specific
|
||||
missing option, since this is the most common case. *)
|
||||
User_error.raise
|
||||
[ Pp.textf "The %s installation directory is unknown." name ]
|
||||
~hints:
|
||||
[ Pp.concat
|
||||
~sep:Pp.space
|
||||
[ Pp.text "It can be specified with"
|
||||
; User_message.command "--prefix"
|
||||
; Pp.textf "or by setting"
|
||||
; User_message.command (sprintf "--%s" name)
|
||||
]
|
||||
|> Pp.hovbox
|
||||
]
|
||||
in
|
||||
{ Roots.lib_root = must_be_defined "libdir" roots.lib_root
|
||||
; libexec_root = must_be_defined "libexecdir" roots.libexec_root
|
||||
; bin = must_be_defined "bindir" roots.bin
|
||||
; sbin = must_be_defined "sbindir" roots.sbin
|
||||
; etc_root = must_be_defined "etcdir" roots.etc_root
|
||||
; doc_root = must_be_defined "docdir" roots.doc_root
|
||||
; share_root = must_be_defined "datadir" roots.share_root
|
||||
; man = must_be_defined "mandir" roots.man
|
||||
}
|
||||
;;
|
||||
|
||||
module Workspace = struct
|
||||
type t =
|
||||
{ packages : Package.t Package.Name.Map.t
|
||||
; contexts : Context.t list
|
||||
}
|
||||
|
||||
let get () =
|
||||
let open Memo.O in
|
||||
Memo.run
|
||||
(let+ packages = Dune_rules.Dune_load.packages ()
|
||||
and+ contexts = Context.DB.all () in
|
||||
{ packages; contexts })
|
||||
;;
|
||||
|
||||
let package_install_file t ~findlib_toolchain pkg =
|
||||
match Package.Name.Map.find t.packages pkg with
|
||||
| None -> Error ()
|
||||
| Some p ->
|
||||
let name = Package.name p in
|
||||
let dir = Package.dir p in
|
||||
Ok
|
||||
(Path.Source.relative
|
||||
dir
|
||||
(Dune_rules.Install_rules.install_file ~package:name ~findlib_toolchain))
|
||||
;;
|
||||
end
|
||||
|
||||
let resolve_package_install workspace ~findlib_toolchain pkg =
|
||||
match Workspace.package_install_file workspace ~findlib_toolchain pkg with
|
||||
| Ok path -> path
|
||||
| Error () ->
|
||||
let pkg = Package.Name.to_string pkg in
|
||||
User_error.raise
|
||||
[ Pp.textf "Unknown package %s!" pkg ]
|
||||
~hints:
|
||||
(User_message.did_you_mean
|
||||
pkg
|
||||
~candidates:
|
||||
(Package.Name.Map.keys workspace.packages
|
||||
|> List.map ~f:Package.Name.to_string))
|
||||
;;
|
||||
|
||||
let print_unix_error f =
|
||||
try f () with
|
||||
| Unix.Unix_error (error, syscall, arg) ->
|
||||
let error = Unix_error.Detailed.create error ~syscall ~arg in
|
||||
User_message.prerr (User_error.make [ Unix_error.Detailed.pp error ])
|
||||
;;
|
||||
|
||||
module Special_file = struct
|
||||
type t =
|
||||
| META
|
||||
| Dune_package
|
||||
|
||||
let of_entry (e : _ Install.Entry.t) =
|
||||
match e.section with
|
||||
| Lib ->
|
||||
let dst = Install.Entry.Dst.to_string e.dst in
|
||||
if dst = Dune_findlib.Findlib.Package.meta_fn
|
||||
then Some META
|
||||
else if dst = Dune_package.fn
|
||||
then Some Dune_package
|
||||
else None
|
||||
| _ -> None
|
||||
;;
|
||||
end
|
||||
|
||||
type copy_kind =
|
||||
| Substitute (** Use [Artifact_substitution.copy_file]. Will scan all bytes. *)
|
||||
| Special of Special_file.t (** Hooks to add version numbers, replace sections, etc *)
|
||||
|
||||
type rmdir_mode =
|
||||
| Fail
|
||||
| Warn
|
||||
|
||||
(** Operations that act on real files or just pretend to (for --dry-run) *)
|
||||
module type File_operations = sig
|
||||
val copy_file
|
||||
: src:Path.t
|
||||
-> dst:Path.t
|
||||
-> executable:bool
|
||||
-> kind:copy_kind
|
||||
-> package:Package.Name.t
|
||||
-> conf:Artifact_substitution.Conf.t
|
||||
-> unit Fiber.t
|
||||
|
||||
val mkdir_p : Path.t -> unit
|
||||
val remove_file_if_exists : Path.t -> unit
|
||||
val remove_dir_if_exists : if_non_empty:rmdir_mode -> Path.t -> unit
|
||||
end
|
||||
|
||||
module File_ops_dry_run (Verbosity : sig
|
||||
val verbosity : Dune_engine.Display.t
|
||||
end) : File_operations = struct
|
||||
open Verbosity
|
||||
|
||||
let print_line fmt = print_line ~verbosity fmt
|
||||
|
||||
let copy_file ~src ~dst ~executable ~kind:_ ~package:_ ~conf:_ =
|
||||
print_line
|
||||
"Copying %s to %s (executable: %b)"
|
||||
(Path.to_string_maybe_quoted src)
|
||||
(Path.to_string_maybe_quoted dst)
|
||||
executable;
|
||||
Fiber.return ()
|
||||
;;
|
||||
|
||||
let mkdir_p path = print_line "Creating directory %s" (Path.to_string_maybe_quoted path)
|
||||
|
||||
let remove_file_if_exists path =
|
||||
print_line "Removing (if it exists) %s" (Path.to_string_maybe_quoted path)
|
||||
;;
|
||||
|
||||
let remove_dir_if_exists ~if_non_empty path =
|
||||
print_line
|
||||
"Removing directory (%s if not empty) %s"
|
||||
(match if_non_empty with
|
||||
| Fail -> "fail"
|
||||
| Warn -> "warn")
|
||||
(Path.to_string_maybe_quoted path)
|
||||
;;
|
||||
end
|
||||
|
||||
module File_ops_real (W : sig
|
||||
val verbosity : Dune_engine.Display.t
|
||||
val workspace : Workspace.t
|
||||
end) : File_operations = struct
|
||||
open W
|
||||
|
||||
let print_line = print_line ~verbosity
|
||||
let get_vcs p = Source_tree.nearest_vcs p
|
||||
|
||||
type copy_special_file_status =
|
||||
| Done
|
||||
| Use_plain_copy
|
||||
|
||||
let with_ppf oc ~f =
|
||||
let ppf = Format.formatter_of_out_channel oc in
|
||||
f ppf;
|
||||
Format.pp_print_flush ppf ()
|
||||
;;
|
||||
|
||||
let copy_special_file ~src ~package ~ic ~oc ~f =
|
||||
let open Fiber.O in
|
||||
let get_version () =
|
||||
let* packages =
|
||||
match Package.Name.Map.find workspace.packages package with
|
||||
| None -> Fiber.return None
|
||||
| Some package -> Memo.run (get_vcs (Package.dir package))
|
||||
in
|
||||
match packages with
|
||||
| None -> Fiber.return None
|
||||
| Some vcs -> Memo.run (Vcs.describe vcs)
|
||||
in
|
||||
try f ~get_version ic ~src oc with
|
||||
| _ (* XXX should we really be catching everything here? *) ->
|
||||
User_warning.emit
|
||||
~loc:(Loc.in_file src)
|
||||
[ Pp.text "Failed to parse file, not adding version and locations information." ];
|
||||
Fiber.return Use_plain_copy
|
||||
;;
|
||||
|
||||
let process_meta ~get_version ic ~src:_ oc =
|
||||
let module Meta = Dune_findlib.Findlib.Meta in
|
||||
let lb = Lexing.from_channel ic in
|
||||
let meta : Meta.t = { name = None; entries = Meta.parse_entries lb } in
|
||||
let need_more_versions =
|
||||
try
|
||||
let (_ : Meta.t) =
|
||||
Meta.add_versions meta ~get_version:(fun _ -> raise_notrace Exit)
|
||||
in
|
||||
false
|
||||
with
|
||||
| Exit -> true
|
||||
in
|
||||
if not need_more_versions
|
||||
then Fiber.return Use_plain_copy
|
||||
else
|
||||
let open Fiber.O in
|
||||
let+ version = get_version () in
|
||||
with_ppf oc ~f:(fun ppf ->
|
||||
let meta = Meta.add_versions meta ~get_version:(fun _ -> version) in
|
||||
Pp.to_fmt ppf (Meta.pp meta.entries));
|
||||
Done
|
||||
;;
|
||||
|
||||
let process_dune_package ~get_version ~get_location ic ~src oc =
|
||||
let lb = Lexing.from_channel ic in
|
||||
let dune_version = Dune_lang.Syntax.greatest_supported_version_exn Stanza.syntax in
|
||||
match Dune_package.Or_meta.parse src lb |> User_error.ok_exn with
|
||||
| Use_meta ->
|
||||
with_ppf oc ~f:(Dune_package.Or_meta.pp_use_meta ~dune_version);
|
||||
Fiber.return Done
|
||||
| Dune_package dp ->
|
||||
let open Fiber.O in
|
||||
(* replace sites with external path in the file *)
|
||||
let dp, replace_info = Dune_package.replace_site_sections ~get_location dp in
|
||||
(* replace version if needed in the file *)
|
||||
let need_version = Option.is_none dp.version in
|
||||
let+ dp =
|
||||
if need_version
|
||||
then
|
||||
let+ version_opt = get_version () in
|
||||
match version_opt with
|
||||
| Some version -> { dp with version = Some (Package_version.of_string version) }
|
||||
| None -> dp
|
||||
else Fiber.return dp
|
||||
in
|
||||
with_ppf oc ~f:(fun ppf ->
|
||||
(* CR-emillon: we should write absolute paths only if necessary *)
|
||||
Dune_package.Or_meta.pp
|
||||
~dune_version
|
||||
ppf
|
||||
(Dune_package dp)
|
||||
~encoding:(Absolute replace_info));
|
||||
Done
|
||||
;;
|
||||
|
||||
let copy_file
|
||||
~src
|
||||
~dst
|
||||
~executable
|
||||
~kind
|
||||
~package
|
||||
~(conf : Artifact_substitution.Conf.t)
|
||||
=
|
||||
let chmod = if executable then fun _ -> 0o755 else fun _ -> 0o644 in
|
||||
let plain_copy () = Io.copy_file ~chmod ~src ~dst () in
|
||||
match kind with
|
||||
| Substitute -> Artifact_substitution.copy_file ~conf ~src ~dst ~chmod ()
|
||||
| Special sf ->
|
||||
let open Fiber.O in
|
||||
let ic, oc = Io.setup_copy ~chmod ~src ~dst () in
|
||||
let+ status =
|
||||
Fiber.finalize
|
||||
~finally:(fun () ->
|
||||
Io.close_both (ic, oc);
|
||||
Fiber.return ())
|
||||
(fun () ->
|
||||
let f =
|
||||
match sf with
|
||||
| META -> process_meta
|
||||
| Dune_package ->
|
||||
process_dune_package
|
||||
~get_location:(Artifact_substitution.Conf.get_location conf)
|
||||
in
|
||||
copy_special_file ~src ~package ~ic ~oc ~f)
|
||||
in
|
||||
(match status with
|
||||
| Done -> ()
|
||||
| Use_plain_copy -> plain_copy ())
|
||||
;;
|
||||
|
||||
let remove_file_if_exists dst =
|
||||
if Path.exists dst
|
||||
then (
|
||||
print_line "Deleting %s" (Path.to_string_maybe_quoted dst);
|
||||
print_unix_error (fun () -> Path.unlink_exn dst))
|
||||
;;
|
||||
|
||||
let remove_dir_if_exists ~if_non_empty dir =
|
||||
match Path.readdir_unsorted dir with
|
||||
| Error (Unix.ENOENT, _, _) -> ()
|
||||
| Ok [] ->
|
||||
print_line "Deleting empty directory %s" (Path.to_string_maybe_quoted dir);
|
||||
print_unix_error (fun () -> Path.rmdir dir)
|
||||
| Error (e, _, _) ->
|
||||
User_message.prerr (User_error.make [ Pp.text (Unix.error_message e) ])
|
||||
| _ ->
|
||||
let dir = Path.to_string_maybe_quoted dir in
|
||||
(match if_non_empty with
|
||||
| Warn ->
|
||||
User_message.prerr
|
||||
(User_error.make
|
||||
[ Pp.textf "Directory %s is not empty, cannot delete (ignoring)." dir ])
|
||||
| Fail ->
|
||||
User_error.raise
|
||||
[ Pp.textf "Please delete non-empty directory %s manually." dir ])
|
||||
;;
|
||||
|
||||
let mkdir_p p =
|
||||
(* CR-someday amokhov: We should really change [Path.mkdir_p dir] to fail if
|
||||
it turns out that [dir] exists and is not a directory. Even better, make
|
||||
[Path.mkdir_p] return an explicit variant to deal with. *)
|
||||
match Fpath.mkdir_p (Path.to_string p) with
|
||||
| Created -> ()
|
||||
| Already_exists ->
|
||||
(match Path.is_directory p with
|
||||
| true -> ()
|
||||
| false ->
|
||||
User_error.raise
|
||||
[ Pp.textf "Please delete file %s manually." (Path.to_string_maybe_quoted p) ])
|
||||
;;
|
||||
end
|
||||
|
||||
module Sections = struct
|
||||
type t =
|
||||
| All
|
||||
| Only of Section.Set.t
|
||||
|
||||
let sections_conv =
|
||||
let all =
|
||||
Section.all
|
||||
|> Section.Set.to_list
|
||||
|> List.map ~f:(fun section -> Section.to_string section, section)
|
||||
in
|
||||
Arg.list ~sep:',' (Arg.enum all)
|
||||
;;
|
||||
|
||||
let term =
|
||||
let doc = "sections that should be installed" in
|
||||
let open Cmdliner.Arg in
|
||||
let+ sections = value & opt (some sections_conv) None & info [ "sections" ] ~doc in
|
||||
match sections with
|
||||
| None -> All
|
||||
| Some sections -> Only (Section.Set.of_list sections)
|
||||
;;
|
||||
|
||||
let should_install t section =
|
||||
match t with
|
||||
| All -> true
|
||||
| Only set -> Section.Set.mem set section
|
||||
;;
|
||||
end
|
||||
|
||||
let file_operations ~verbosity ~dry_run ~workspace : (module File_operations) =
|
||||
if dry_run
|
||||
then
|
||||
(module File_ops_dry_run (struct
|
||||
let verbosity = verbosity
|
||||
end))
|
||||
else
|
||||
(module File_ops_real (struct
|
||||
let workspace = workspace
|
||||
let verbosity = verbosity
|
||||
end))
|
||||
;;
|
||||
|
||||
let package_is_vendored (pkg : Package.t) =
|
||||
let dir = Package.dir pkg in
|
||||
Memo.run (Source_tree.is_vendored dir)
|
||||
;;
|
||||
|
||||
type what =
|
||||
| Install
|
||||
| Uninstall
|
||||
|
||||
let pp_what fmt = function
|
||||
| Install -> Format.pp_print_string fmt "Install"
|
||||
| Uninstall -> Format.pp_print_string fmt "Uninstall"
|
||||
;;
|
||||
|
||||
let cmd_what = function
|
||||
| Install -> "install"
|
||||
| Uninstall -> "uninstall"
|
||||
;;
|
||||
|
||||
let install_entry
|
||||
~ops
|
||||
~conf
|
||||
~package
|
||||
~dir
|
||||
~create_install_files
|
||||
(entry : Path.t Install.Entry.t)
|
||||
~dst
|
||||
~verbosity
|
||||
=
|
||||
let module Ops = (val ops : File_operations) in
|
||||
let open Fiber.O in
|
||||
let special_file = Special_file.of_entry entry in
|
||||
(match special_file with
|
||||
| _ when not create_install_files -> Fiber.return true
|
||||
| Some Special_file.META | Some Special_file.Dune_package -> Fiber.return true
|
||||
| None ->
|
||||
Artifact_substitution.test_file ~src:entry.src ()
|
||||
>>| (function
|
||||
| Some_substitution -> true
|
||||
| No_substitution -> false))
|
||||
>>= function
|
||||
| false -> Fiber.return entry
|
||||
| true ->
|
||||
let+ () =
|
||||
(match Path.is_directory dst with
|
||||
| true -> Ops.remove_dir_if_exists ~if_non_empty:Fail dst
|
||||
| false -> Ops.remove_file_if_exists dst);
|
||||
print_line
|
||||
~verbosity
|
||||
"%s %s"
|
||||
(if create_install_files then "Copying to" else "Installing")
|
||||
(Path.to_string_maybe_quoted dst);
|
||||
Ops.mkdir_p dir;
|
||||
let executable = Section.should_set_executable_bit entry.section in
|
||||
let kind =
|
||||
match special_file with
|
||||
| Some special -> Special special
|
||||
| None ->
|
||||
(* CR-emillon: for most cases we could use a fast copy here, but some
|
||||
kinds of files do need artifact substitution(at least
|
||||
executable files and artifacts built from generated sites
|
||||
modules), but it's too late to know without reading the file. *)
|
||||
Substitute
|
||||
in
|
||||
Ops.copy_file ~src:entry.src ~dst ~executable ~kind ~package ~conf
|
||||
in
|
||||
Install.Entry.set_src entry dst
|
||||
;;
|
||||
|
||||
let run
|
||||
what
|
||||
context
|
||||
common
|
||||
pkgs
|
||||
sections
|
||||
(config : Dune_config.t)
|
||||
~dry_run
|
||||
~destdir
|
||||
~relocatable
|
||||
~create_install_files
|
||||
~prefix_from_command_line
|
||||
~(from_command_line : _ Install.Roots.t)
|
||||
=
|
||||
let open Fiber.O in
|
||||
let* workspace = Workspace.get () in
|
||||
let contexts =
|
||||
match context with
|
||||
| None ->
|
||||
(match Common.x common with
|
||||
| Some findlib_toolchain ->
|
||||
let contexts =
|
||||
List.filter workspace.contexts ~f:(fun (ctx : Context.t) ->
|
||||
match Context.findlib_toolchain ctx with
|
||||
| None -> false
|
||||
| Some ctx_findlib_toolchain ->
|
||||
Dune_engine.Context_name.equal ctx_findlib_toolchain findlib_toolchain)
|
||||
in
|
||||
contexts
|
||||
| None -> workspace.contexts)
|
||||
| Some name ->
|
||||
(match
|
||||
List.find workspace.contexts ~f:(fun c ->
|
||||
Dune_engine.Context_name.equal (Context.name c) name)
|
||||
with
|
||||
| Some ctx -> [ ctx ]
|
||||
| None ->
|
||||
User_error.raise
|
||||
[ Pp.textf "Context %S not found!" (Dune_engine.Context_name.to_string name) ])
|
||||
in
|
||||
let* pkgs =
|
||||
match pkgs with
|
||||
| _ :: _ -> Fiber.return pkgs
|
||||
| [] ->
|
||||
Package.Name.Map.values workspace.packages
|
||||
|> Fiber.parallel_map ~f:(fun pkg ->
|
||||
package_is_vendored pkg
|
||||
>>| function
|
||||
| true -> None
|
||||
| false -> Some (Package.name pkg))
|
||||
>>| List.filter_opt
|
||||
in
|
||||
let install_files, missing_install_files =
|
||||
List.concat_map pkgs ~f:(fun pkg ->
|
||||
List.map contexts ~f:(fun (ctx : Context.t) ->
|
||||
let fn =
|
||||
let fn =
|
||||
resolve_package_install
|
||||
workspace
|
||||
~findlib_toolchain:(Context.findlib_toolchain ctx)
|
||||
pkg
|
||||
in
|
||||
Path.append_source (Path.build (Context.build_dir ctx)) fn
|
||||
in
|
||||
if Path.exists fn then Left (ctx, (pkg, fn)) else Right fn))
|
||||
|> List.partition_map ~f:Fun.id
|
||||
in
|
||||
if missing_install_files <> []
|
||||
then
|
||||
User_error.raise
|
||||
[ Pp.textf "The following <package>.install are missing:"
|
||||
; Pp.enumerate missing_install_files ~f:(fun p -> Pp.text (Path.to_string p))
|
||||
]
|
||||
~hints:
|
||||
[ Pp.concat
|
||||
~sep:Pp.space
|
||||
[ Pp.text "try running"
|
||||
; User_message.command "dune build [-p <pkg>] @install"
|
||||
]
|
||||
|> Pp.hovbox
|
||||
];
|
||||
(match contexts, prefix_from_command_line, from_command_line.lib_root with
|
||||
| _ :: _ :: _, Some _, _ | _ :: _ :: _, _, Some _ ->
|
||||
User_error.raise
|
||||
[ Pp.concat
|
||||
~sep:Pp.space
|
||||
[ Pp.text "Cannot specify"
|
||||
; User_message.command "--prefix"
|
||||
; Pp.text "or"
|
||||
; User_message.command "--libdir"
|
||||
; Pp.text "when installing into multiple contexts!"
|
||||
]
|
||||
]
|
||||
| _ -> ());
|
||||
let install_files_by_context =
|
||||
let module CMap = Map.Make (Context) in
|
||||
CMap.of_list_multi install_files
|
||||
|> CMap.to_list_map ~f:(fun context install_files ->
|
||||
let entries_per_package =
|
||||
List.map install_files ~f:(fun (package, install_file) ->
|
||||
let entries =
|
||||
Install.Entry.load_install_file install_file Path.of_local
|
||||
|> List.filter ~f:(fun (entry : Path.t Install.Entry.t) ->
|
||||
Sections.should_install sections entry.section)
|
||||
in
|
||||
match
|
||||
List.filter_map entries ~f:(fun entry ->
|
||||
(* CR rgrinberg: this is ignoring optional entries *)
|
||||
Option.some_if (not (Path.exists entry.src)) entry.src)
|
||||
with
|
||||
| [] -> package, entries
|
||||
| missing_files ->
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"The following files which are listed in %s cannot be installed \
|
||||
because they do not exist:"
|
||||
(Path.to_string_maybe_quoted install_file)
|
||||
; Pp.enumerate missing_files ~f:(fun p ->
|
||||
Pp.verbatim (Path.to_string_maybe_quoted p))
|
||||
])
|
||||
in
|
||||
context, entries_per_package)
|
||||
in
|
||||
let destdir =
|
||||
Option.map
|
||||
~f:Path.of_string
|
||||
(if create_install_files
|
||||
then
|
||||
(* CR-rgrinberg: why are we silently ignoring an argument instead
|
||||
of erroring given that they mutually exclusive? *)
|
||||
Some (Option.value ~default:"_destdir" destdir)
|
||||
else destdir)
|
||||
in
|
||||
let relocatable =
|
||||
if relocatable
|
||||
then (
|
||||
match prefix_from_command_line with
|
||||
| Some dir -> Some (Path.of_string dir)
|
||||
| None ->
|
||||
User_error.raise
|
||||
[ Pp.concat
|
||||
~sep:Pp.space
|
||||
[ Pp.text "Option"
|
||||
; User_message.command "--prefix"
|
||||
; Pp.text "is needed with"
|
||||
; User_message.command "--relocation"
|
||||
]
|
||||
|> Pp.hovbox
|
||||
])
|
||||
else None
|
||||
in
|
||||
let verbosity =
|
||||
match config.display with
|
||||
| Simple display -> display.verbosity
|
||||
| Tui -> Quiet
|
||||
in
|
||||
let open Fiber.O in
|
||||
let (module Ops) = file_operations ~verbosity ~dry_run ~workspace in
|
||||
let files_deleted_in = ref Path.Set.empty in
|
||||
let+ () =
|
||||
Fiber.sequential_iter
|
||||
install_files_by_context
|
||||
~f:(fun (context, entries_per_package) ->
|
||||
let* roots = get_dirs context ~prefix_from_command_line ~from_command_line in
|
||||
let conf = Artifact_substitution.Conf.of_install ~relocatable ~roots ~context in
|
||||
Fiber.sequential_iter entries_per_package ~f:(fun (package, entries) ->
|
||||
let+ entries =
|
||||
(* CR rgrinberg: why don't we install things concurrently? *)
|
||||
Fiber.sequential_map entries ~f:(fun entry ->
|
||||
let dst =
|
||||
let paths = Install.Paths.make ~relative:Path.relative ~package ~roots in
|
||||
Install.Entry.relative_installed_path entry ~paths
|
||||
|> interpret_destdir ~destdir
|
||||
in
|
||||
let dir = Path.parent_exn dst in
|
||||
match what with
|
||||
| Uninstall ->
|
||||
Ops.remove_file_if_exists dst;
|
||||
files_deleted_in := Path.Set.add !files_deleted_in dir;
|
||||
Fiber.return entry
|
||||
| Install ->
|
||||
install_entry
|
||||
~ops:(module Ops)
|
||||
~conf
|
||||
~package
|
||||
~dir
|
||||
~create_install_files
|
||||
~dst
|
||||
~verbosity
|
||||
entry)
|
||||
in
|
||||
if create_install_files
|
||||
then (
|
||||
let fn =
|
||||
resolve_package_install
|
||||
workspace
|
||||
~findlib_toolchain:(Context.findlib_toolchain context)
|
||||
package
|
||||
in
|
||||
Install.Entry.gen_install_file entries |> Io.write_file (Path.source fn))))
|
||||
in
|
||||
Path.Set.to_list !files_deleted_in
|
||||
(* This [List.rev] is to ensure we process children directories before
|
||||
their parents *)
|
||||
|> List.rev
|
||||
|> List.iter ~f:(Ops.remove_dir_if_exists ~if_non_empty:Warn)
|
||||
;;
|
||||
|
||||
let make ~what =
|
||||
let doc = Format.asprintf "%a packages defined in the workspace." pp_what what in
|
||||
let name_ = Arg.info [] ~docv:"PACKAGE" in
|
||||
let absolute_path =
|
||||
Arg.conv'
|
||||
( (fun path ->
|
||||
if Filename.is_relative path
|
||||
then Error "the path must be absolute to avoid ambiguity"
|
||||
else Ok path)
|
||||
, Arg.conv_printer Arg.string )
|
||||
in
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ prefix_from_command_line =
|
||||
Arg.(
|
||||
value
|
||||
& opt (some string) None
|
||||
& info
|
||||
[ "prefix" ]
|
||||
~env:(Cmd.Env.info "DUNE_INSTALL_PREFIX")
|
||||
~docv:"PREFIX"
|
||||
~doc:
|
||||
"Directory where files are copied. For instance binaries are copied into \
|
||||
$(i,\\$prefix/bin), library files into $(i,\\$prefix/lib), etc...")
|
||||
and+ destdir =
|
||||
Arg.(
|
||||
value
|
||||
& opt (some string) None
|
||||
& info
|
||||
[ "destdir" ]
|
||||
~env:(Cmd.Env.info "DESTDIR")
|
||||
~docv:"PATH"
|
||||
~doc:"This directory is prepended to all installed paths.")
|
||||
and+ libdir_from_command_line =
|
||||
Arg.(
|
||||
value
|
||||
& opt (some absolute_path) None
|
||||
& info
|
||||
[ "libdir" ]
|
||||
~docv:"PATH"
|
||||
~doc:
|
||||
"Directory where library files are copied, relative to $(b,prefix) or \
|
||||
absolute. If $(b,--prefix) is specified the default is \
|
||||
$(i,\\$prefix/lib). Only absolute path accepted.")
|
||||
and+ mandir_from_command_line =
|
||||
let doc =
|
||||
"Manually override the directory to install man pages. Only absolute path \
|
||||
accepted."
|
||||
in
|
||||
Arg.(value & opt (some absolute_path) None & info [ "mandir" ] ~docv:"PATH" ~doc)
|
||||
and+ docdir_from_command_line =
|
||||
let doc =
|
||||
"Manually override the directory to install documentation files. Only absolute \
|
||||
path accepted."
|
||||
in
|
||||
Arg.(value & opt (some absolute_path) None & info [ "docdir" ] ~docv:"PATH" ~doc)
|
||||
and+ etcdir_from_command_line =
|
||||
let doc =
|
||||
"Manually override the directory to install configuration files. Only absolute \
|
||||
path accepted."
|
||||
in
|
||||
Arg.(value & opt (some absolute_path) None & info [ "etcdir" ] ~docv:"PATH" ~doc)
|
||||
and+ bindir_from_command_line =
|
||||
let doc =
|
||||
"Manually override the directory to install public binaries. Only absolute path \
|
||||
accepted."
|
||||
in
|
||||
Arg.(value & opt (some absolute_path) None & info [ "bindir" ] ~docv:"PATH" ~doc)
|
||||
and+ sbindir_from_command_line =
|
||||
let doc =
|
||||
"Manually override the directory to install files from sbin section. Only \
|
||||
absolute path accepted."
|
||||
in
|
||||
Arg.(value & opt (some absolute_path) None & info [ "sbindir" ] ~docv:"PATH" ~doc)
|
||||
and+ datadir_from_command_line =
|
||||
let doc =
|
||||
"Manually override the directory to install files from share section. Only \
|
||||
absolute path accepted."
|
||||
in
|
||||
Arg.(value & opt (some absolute_path) None & info [ "datadir" ] ~docv:"PATH" ~doc)
|
||||
and+ libexecdir_from_command_line =
|
||||
let doc =
|
||||
"Manually override the directory to install executable library files. Only \
|
||||
absolute path accepted."
|
||||
in
|
||||
Arg.(
|
||||
value & opt (some absolute_path) None & info [ "libexecdir" ] ~docv:"PATH" ~doc)
|
||||
and+ dry_run =
|
||||
Arg.(
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "dry-run" ]
|
||||
~doc:"Only display the file operations that would be performed.")
|
||||
and+ relocatable =
|
||||
Arg.(
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "relocatable" ]
|
||||
~doc:
|
||||
"Make the binaries relocatable (the installation directory can be moved). \
|
||||
The installation directory must be specified with --prefix")
|
||||
and+ create_install_files =
|
||||
Arg.(
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "create-install-files" ]
|
||||
~doc:
|
||||
"Do not directly install, but create install files in the root directory \
|
||||
and create substituted files if needed in destdir (_destdir by default).")
|
||||
and+ pkgs = Arg.(value & pos_all package_name [] name_)
|
||||
and+ context =
|
||||
Arg.(
|
||||
value
|
||||
& opt (some Arg.context_name) None
|
||||
& info
|
||||
[ "context" ]
|
||||
~docv:"CONTEXT"
|
||||
~doc:
|
||||
"Select context to install from. By default, install files from all \
|
||||
defined contexts.")
|
||||
and+ sections = Sections.term in
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
let builder = Common.Builder.disable_log_file builder in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config (fun () ->
|
||||
let from_command_line =
|
||||
{ Install.Roots.lib_root = libdir_from_command_line
|
||||
; etc_root = etcdir_from_command_line
|
||||
; doc_root = docdir_from_command_line
|
||||
; man = mandir_from_command_line
|
||||
; bin = bindir_from_command_line
|
||||
; sbin = sbindir_from_command_line
|
||||
; libexec_root = libexecdir_from_command_line
|
||||
; share_root = datadir_from_command_line
|
||||
}
|
||||
|> Install.Roots.map ~f:(Option.map ~f:Path.of_string)
|
||||
|> Install.Roots.complete
|
||||
in
|
||||
run
|
||||
what
|
||||
context
|
||||
common
|
||||
pkgs
|
||||
sections
|
||||
config
|
||||
~dry_run
|
||||
~destdir
|
||||
~relocatable
|
||||
~create_install_files
|
||||
~prefix_from_command_line
|
||||
~from_command_line)
|
||||
in
|
||||
Cmd.v
|
||||
(Cmd.info
|
||||
(cmd_what what)
|
||||
~doc
|
||||
~man:Manpage.(`S s_synopsis :: (synopsis @ Common.help_secs)))
|
||||
term
|
||||
;;
|
||||
|
||||
let install = make ~what:Install
|
||||
let uninstall = make ~what:Uninstall
|
||||
4
unikernel/duniverse/dune_/bin/install_uninstall.mli
Normal file
4
unikernel/duniverse/dune_/bin/install_uninstall.mli
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
open Import
|
||||
|
||||
val install : unit Cmd.t
|
||||
val uninstall : unit Cmd.t
|
||||
76
unikernel/duniverse/dune_/bin/installed_libraries.ml
Normal file
76
unikernel/duniverse/dune_/bin/installed_libraries.ml
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
open Import
|
||||
|
||||
let doc = "Print out libraries installed on the system."
|
||||
let info = Cmd.info "installed-libraries" ~doc
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ na =
|
||||
Arg.(
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "na"; "not-available" ]
|
||||
~doc:"List libraries that are not available and explain why")
|
||||
in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server
|
||||
~common
|
||||
~config
|
||||
(let run () =
|
||||
let open Memo.O in
|
||||
let* ctxs = Context.DB.all () in
|
||||
let ctx = List.hd ctxs in
|
||||
let* findlib = Findlib.create (Context.name ctx) in
|
||||
let* all_packages = Findlib.all_packages findlib in
|
||||
if na
|
||||
then (
|
||||
let+ broken =
|
||||
Findlib.all_broken_packages findlib
|
||||
>>| List.map ~f:(fun (name, _) ->
|
||||
Lib_name.of_package_name name, "invalid dune file")
|
||||
in
|
||||
let hidden =
|
||||
List.filter_map all_packages ~f:(function
|
||||
| Hidden_library lib ->
|
||||
Some
|
||||
( Dune_package.Lib.info lib |> Dune_rules.Lib_info.name
|
||||
, "unsatisfied 'exists_if'" )
|
||||
| _ -> None)
|
||||
in
|
||||
let all =
|
||||
List.sort (broken @ hidden) ~compare:(fun (a, _) (b, _) ->
|
||||
Lib_name.compare a b)
|
||||
in
|
||||
let longest = String.longest_map all ~f:(fun (n, _) -> Lib_name.to_string n) in
|
||||
let ppf = Format.std_formatter in
|
||||
List.iter all ~f:(fun (n, r) ->
|
||||
Format.fprintf ppf "%-*s -> %s@\n" longest (Lib_name.to_string n) r);
|
||||
Format.pp_print_flush ppf ())
|
||||
else (
|
||||
let pkgs =
|
||||
List.filter all_packages ~f:(function
|
||||
| Dune_package.Entry.Hidden_library _ -> false
|
||||
| _ -> true)
|
||||
in
|
||||
let max_len =
|
||||
String.longest_map pkgs ~f:(fun e ->
|
||||
Lib_name.to_string (Dune_package.Entry.name e))
|
||||
in
|
||||
List.iter pkgs ~f:(fun e ->
|
||||
let ver_string =
|
||||
match Dune_package.Entry.version e with
|
||||
| Some v -> Package_version.to_string v
|
||||
| _ -> "n/a"
|
||||
in
|
||||
Printf.printf
|
||||
"%-*s (version: %s)\n"
|
||||
max_len
|
||||
(Lib_name.to_string (Dune_package.Entry.name e))
|
||||
ver_string);
|
||||
Memo.return ())
|
||||
in
|
||||
fun () -> Memo.run (run ()))
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
3
unikernel/duniverse/dune_/bin/installed_libraries.mli
Normal file
3
unikernel/duniverse/dune_/bin/installed_libraries.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
12
unikernel/duniverse/dune_/bin/internal.ml
Normal file
12
unikernel/duniverse/dune_/bin/internal.ml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
open Import
|
||||
|
||||
let latest_lang_version =
|
||||
Cmd.v
|
||||
(Cmd.info "latest-lang-version")
|
||||
(let+ () = Term.const () in
|
||||
print_endline
|
||||
(Dune_lang.Syntax.greatest_supported_version_exn Stanza.syntax
|
||||
|> Dune_lang.Syntax.Version.to_string))
|
||||
;;
|
||||
|
||||
let group = Cmd.group (Cmd.info "internal") [ Internal_dump.command; latest_lang_version ]
|
||||
3
unikernel/duniverse/dune_/bin/internal.mli
Normal file
3
unikernel/duniverse/dune_/bin/internal.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val group : unit Cmd.t
|
||||
24
unikernel/duniverse/dune_/bin/internal_dump.ml
Normal file
24
unikernel/duniverse/dune_/bin/internal_dump.ml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
open Import
|
||||
module Persistent = Dune_util.Persistent
|
||||
|
||||
let doc = "Dump the contents of a file stored in Dune's persistent database."
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
{|Dump the contents of a file stored in Dune's persistent database in a human readable format.|}
|
||||
; `Blocks Common.help_secs
|
||||
]
|
||||
;;
|
||||
|
||||
let info = Cmd.info "dump" ~doc ~man
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ file = Arg.(required & pos 0 (some Arg.path) None & Arg.info [] ~docv:"FILE") in
|
||||
let _common, _config = Common.init builder in
|
||||
let (Persistent.T ((module D), data)) = Persistent.load_exn (Arg.Path.path file) in
|
||||
Console.print [ Dyn.pp (D.to_dyn data) ]
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
3
unikernel/duniverse/dune_/bin/internal_dump.mli
Normal file
3
unikernel/duniverse/dune_/bin/internal_dump.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
254
unikernel/duniverse/dune_/bin/lock_dev_tool.ml
Normal file
254
unikernel/duniverse/dune_/bin/lock_dev_tool.ml
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
open Dune_config
|
||||
open Import
|
||||
module Lock_dir = Dune_pkg.Lock_dir
|
||||
module Pin = Dune_pkg.Pin
|
||||
|
||||
let is_enabled =
|
||||
lazy
|
||||
(match Config.get Dune_rules.Compile_time.lock_dev_tools with
|
||||
| `Enabled -> true
|
||||
| `Disabled -> false)
|
||||
;;
|
||||
|
||||
(* Returns a version constraint accepting (almost) all versions whose prefix is
|
||||
the given version. This allows alternative distributions of packages to be
|
||||
chosen, such as choosing "ocamlformat.0.26.2+binary" when .ocamlformat
|
||||
contains "version=0.26.2". *)
|
||||
let relaxed_version_constraint_of_version version =
|
||||
let open Dune_lang in
|
||||
let min_version = Package_version.to_string version in
|
||||
(* The goal here is to add a suffix to [min_version] to construct a version
|
||||
number higher than than any version number likely to appear with
|
||||
[min_version] as a prefix. "_" is the highest ascii symbol that can appear
|
||||
in version numbers, excluding "~" which has a special meaning. It's
|
||||
conceivable that one or two consecutive "_" characters may be used in a
|
||||
version, so this appends "___" to [min_version].
|
||||
|
||||
Read more at: https://opam.ocaml.org/doc/Manual.html#Version-ordering
|
||||
*)
|
||||
let max_version = min_version ^ "___MAX_VERSION" in
|
||||
Package_constraint.And
|
||||
[ Package_constraint.Uop
|
||||
(Relop.Gte, Package_constraint.Value.String_literal min_version)
|
||||
; Package_constraint.Uop
|
||||
(Relop.Lte, Package_constraint.Value.String_literal max_version)
|
||||
]
|
||||
;;
|
||||
|
||||
(* The solver satisfies dependencies for local packages, but dev tools
|
||||
are not local packages. As a workaround, create an empty local package
|
||||
which depends on the dev tool package. *)
|
||||
let make_local_package_wrapping_dev_tool ~dev_tool ~dev_tool_version ~extra_dependencies
|
||||
: Dune_pkg.Local_package.t
|
||||
=
|
||||
let dev_tool_pkg_name = Dune_pkg.Dev_tool.package_name dev_tool in
|
||||
let dependency =
|
||||
let open Dune_lang in
|
||||
let open Package_dependency in
|
||||
let constraint_ =
|
||||
Option.map dev_tool_version ~f:relaxed_version_constraint_of_version
|
||||
in
|
||||
{ name = dev_tool_pkg_name; constraint_ }
|
||||
in
|
||||
let local_package_name =
|
||||
Package_name.of_string (Package_name.to_string dev_tool_pkg_name ^ "_dev_tool_wrapper")
|
||||
in
|
||||
{ Dune_pkg.Local_package.name = local_package_name
|
||||
; version = Dune_pkg.Lock_dir.Pkg_info.default_version
|
||||
; dependencies =
|
||||
Dune_pkg.Dependency_formula.of_dependencies (dependency :: extra_dependencies)
|
||||
; conflicts = []
|
||||
; depopts = []
|
||||
; pins = Package_name.Map.empty
|
||||
; conflict_class = []
|
||||
; loc = Loc.none
|
||||
; command_source = Opam_file { build = []; install = [] }
|
||||
}
|
||||
;;
|
||||
|
||||
let solve ~dev_tool ~local_packages =
|
||||
let open Memo.O in
|
||||
let* solver_env_from_current_system =
|
||||
Pkg_common.poll_solver_env_from_current_system ()
|
||||
|> Memo.of_reproducible_fiber
|
||||
>>| Option.some
|
||||
and* workspace =
|
||||
let+ workspace = Workspace.workspace () in
|
||||
match Config.get Dune_rules.Compile_time.bin_dev_tools with
|
||||
| `Enabled ->
|
||||
Workspace.add_repo workspace Dune_pkg.Pkg_workspace.Repository.binary_packages
|
||||
| `Disabled -> workspace
|
||||
in
|
||||
let lock_dir = Lock_dir.dev_tool_lock_dir_path dev_tool in
|
||||
Memo.of_reproducible_fiber
|
||||
@@ Lock.solve
|
||||
workspace
|
||||
~local_packages
|
||||
~project_pins:Pin.DB.empty
|
||||
~solver_env_from_current_system
|
||||
~version_preference:None
|
||||
~lock_dirs:[ lock_dir ]
|
||||
~print_perf_stats:false
|
||||
~portable_lock_dir:false
|
||||
;;
|
||||
|
||||
let compiler_package_name = Package_name.of_string "ocaml"
|
||||
|
||||
(* Some dev tools must be built with the same version of the ocaml
|
||||
compiler as the project. This function returns the version of the
|
||||
"ocaml" package used to compile the project in the default build
|
||||
context.
|
||||
|
||||
TODO: This only makes sure that the version of compiler used to
|
||||
build the dev tool matches the version of the compiler used to
|
||||
build this project. This will fail if the project is built with a
|
||||
custom compiler (e.g. ocaml-variants) since the version of the
|
||||
compiler will be the same between the project and dev tool while
|
||||
they still use different compilers. A more robust solution would be
|
||||
to ensure that the exact compiler package used to build the dev
|
||||
tool matches the package used to build the compiler. *)
|
||||
let locked_ocaml_compiler_version () =
|
||||
let open Memo.O in
|
||||
let context =
|
||||
(* Dev tools are only ever built with the default context. *)
|
||||
Context_name.default
|
||||
in
|
||||
let* result = Dune_rules.Lock_dir.get context
|
||||
and* platform =
|
||||
Pkg_common.poll_solver_env_from_current_system () |> Memo.of_reproducible_fiber
|
||||
in
|
||||
match result with
|
||||
| Error _ ->
|
||||
User_error.raise
|
||||
[ Pp.text "Unable to load the lockdir for the default build context." ]
|
||||
~hints:
|
||||
[ Pp.concat
|
||||
~sep:Pp.space
|
||||
[ Pp.text "Try running"; User_message.command "dune pkg lock" ]
|
||||
]
|
||||
| Ok { packages; _ } ->
|
||||
let packages = Lock_dir.Packages.pkgs_on_platform_by_name packages ~platform in
|
||||
(match Package_name.Map.find packages compiler_package_name with
|
||||
| None ->
|
||||
User_error.raise
|
||||
[ Pp.textf
|
||||
"The lockdir doesn't contain a lockfile for the package %S."
|
||||
(Package_name.to_string compiler_package_name)
|
||||
]
|
||||
~hints:
|
||||
[ Pp.concat
|
||||
~sep:Pp.space
|
||||
[ Pp.textf
|
||||
"Add a dependency on %S to one of the packages in dune-project and \
|
||||
then run"
|
||||
(Package_name.to_string compiler_package_name)
|
||||
; User_message.command "dune pkg lock"
|
||||
]
|
||||
]
|
||||
| Some pkg -> Memo.return pkg.info.version)
|
||||
;;
|
||||
|
||||
(* Returns a dependency constraint on the version of the ocaml
|
||||
compiler in the lockdir associated with the default context. *)
|
||||
let locked_ocaml_compiler_constraint () =
|
||||
let open Dune_lang in
|
||||
let open Memo.O in
|
||||
let+ ocaml_compiler_version = locked_ocaml_compiler_version () in
|
||||
let constraint_ =
|
||||
Some
|
||||
(Package_constraint.Uop
|
||||
(Eq, String_literal (Package_version.to_string ocaml_compiler_version)))
|
||||
in
|
||||
{ Package_dependency.name = compiler_package_name; constraint_ }
|
||||
;;
|
||||
|
||||
let extra_dependencies dev_tool =
|
||||
let open Memo.O in
|
||||
match Dune_pkg.Dev_tool.needs_to_build_with_same_compiler_as_project dev_tool with
|
||||
| false -> Memo.return []
|
||||
| true ->
|
||||
let+ constraint_ = locked_ocaml_compiler_constraint () in
|
||||
[ constraint_ ]
|
||||
;;
|
||||
|
||||
let lockdir_status dev_tool =
|
||||
let open Memo.O in
|
||||
let dev_tool_lock_dir = Lock_dir.dev_tool_lock_dir_path dev_tool in
|
||||
match Lock_dir.read_disk dev_tool_lock_dir with
|
||||
| Error _ -> Memo.return `No_lockdir
|
||||
| Ok { packages; _ } ->
|
||||
(match Dune_pkg.Dev_tool.needs_to_build_with_same_compiler_as_project dev_tool with
|
||||
| false -> Memo.return `Lockdir_ok
|
||||
| true ->
|
||||
let* platform =
|
||||
Pkg_common.poll_solver_env_from_current_system () |> Memo.of_reproducible_fiber
|
||||
in
|
||||
let packages = Lock_dir.Packages.pkgs_on_platform_by_name packages ~platform in
|
||||
(match Package_name.Map.find packages compiler_package_name with
|
||||
| None -> Memo.return `No_compiler_lockfile_in_lockdir
|
||||
| Some { info; _ } ->
|
||||
let+ ocaml_compiler_version = locked_ocaml_compiler_version () in
|
||||
(match Package_version.equal info.version ocaml_compiler_version with
|
||||
| true -> `Lockdir_ok
|
||||
| false ->
|
||||
`Dev_tool_needs_to_be_relocked_because_project_compiler_version_changed
|
||||
(User_message.make
|
||||
[ Pp.textf
|
||||
"The version of the compiler package (%S) in this project's \
|
||||
lockdir has changed to %s (formerly the compiler version was %s). \
|
||||
The dev-tool %S will be re-locked and rebuilt with this version \
|
||||
of the compiler."
|
||||
(Package_name.to_string compiler_package_name)
|
||||
(Package_version.to_string ocaml_compiler_version)
|
||||
(Package_version.to_string info.version)
|
||||
(Dune_pkg.Dev_tool.package_name dev_tool |> Package_name.to_string)
|
||||
]))))
|
||||
;;
|
||||
|
||||
(* [lock_dev_tool_at_version dev_tool version] generates the lockdir for the
|
||||
dev tool [dev_tool]. If [version] is [Some v] then version [v] of the tool
|
||||
will be chosen by the solver. Otherwise the solver is free to choose the
|
||||
appropriate version of the tool to install. *)
|
||||
let lock_dev_tool_at_version dev_tool version =
|
||||
let open Memo.O in
|
||||
let* need_to_solve =
|
||||
lockdir_status dev_tool
|
||||
>>| function
|
||||
| `Lockdir_ok -> false
|
||||
| `No_lockdir -> true
|
||||
| `No_compiler_lockfile_in_lockdir ->
|
||||
Console.print
|
||||
[ Pp.textf
|
||||
"The lockdir for %s lacks a lockfile for %s. Regenerating..."
|
||||
(Dune_pkg.Dev_tool.package_name dev_tool |> Package_name.to_string)
|
||||
(Package_name.to_string compiler_package_name)
|
||||
];
|
||||
true
|
||||
| `Dev_tool_needs_to_be_relocked_because_project_compiler_version_changed message ->
|
||||
Console.print_user_message message;
|
||||
true
|
||||
in
|
||||
if need_to_solve
|
||||
then
|
||||
let* extra_dependencies = extra_dependencies dev_tool in
|
||||
let local_pkg =
|
||||
make_local_package_wrapping_dev_tool
|
||||
~dev_tool
|
||||
~dev_tool_version:version
|
||||
~extra_dependencies
|
||||
in
|
||||
let local_packages = Package_name.Map.singleton local_pkg.name local_pkg in
|
||||
solve ~dev_tool ~local_packages
|
||||
else Memo.return ()
|
||||
;;
|
||||
|
||||
let lock_ocamlformat () =
|
||||
let version = Dune_pkg.Ocamlformat.version_of_current_project's_ocamlformat_config () in
|
||||
lock_dev_tool_at_version Ocamlformat version
|
||||
;;
|
||||
|
||||
let lock_dev_tool dev_tool =
|
||||
match (dev_tool : Dune_pkg.Dev_tool.t) with
|
||||
| Ocamlformat -> lock_ocamlformat ()
|
||||
| other -> lock_dev_tool_at_version other None
|
||||
;;
|
||||
4
unikernel/duniverse/dune_/bin/lock_dev_tool.mli
Normal file
4
unikernel/duniverse/dune_/bin/lock_dev_tool.mli
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
open! Import
|
||||
|
||||
val is_enabled : bool Lazy.t
|
||||
val lock_dev_tool : Dune_pkg.Dev_tool.t -> unit Memo.t
|
||||
117
unikernel/duniverse/dune_/bin/main.ml
Normal file
117
unikernel/duniverse/dune_/bin/main.ml
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
open Import
|
||||
|
||||
let all : _ Cmdliner.Cmd.t list =
|
||||
let terms =
|
||||
Runtest.commands
|
||||
@ [ Installed_libraries.command
|
||||
; External_lib_deps.command
|
||||
; Build.build
|
||||
; Fmt.command
|
||||
; Clean.command
|
||||
; Install_uninstall.install
|
||||
; Install_uninstall.uninstall
|
||||
; Exec.command
|
||||
; Subst.command
|
||||
; Print_rules.command
|
||||
; Utop.command
|
||||
; Promotion.promote
|
||||
; command_alias Printenv.command Printenv.term "printenv"
|
||||
; Help.command
|
||||
; Format_dune_file.command
|
||||
; Upgrade.command
|
||||
; Cache.command
|
||||
; Top.command
|
||||
; Ocaml_merlin.command
|
||||
; Shutdown.command
|
||||
; Diagnostics.command
|
||||
; Monitor.command
|
||||
]
|
||||
in
|
||||
let groups =
|
||||
[ Ocaml_cmd.group
|
||||
; Coq.group
|
||||
; Describe.group
|
||||
; Describe.Show.group
|
||||
; Rpc.group
|
||||
; Internal.group
|
||||
; Init.group
|
||||
; Promotion.group
|
||||
; Pkg.group
|
||||
; Pkg.Alias.group
|
||||
; Tools.group
|
||||
]
|
||||
in
|
||||
terms @ groups
|
||||
;;
|
||||
|
||||
(* Short reminders for the most used and useful commands *)
|
||||
let common_commands_synopsis =
|
||||
Common.command_synopsis
|
||||
[ "build [--watch]"
|
||||
; "runtest [--watch]"
|
||||
; "exec NAME"
|
||||
; "utop [DIR]"
|
||||
; "install"
|
||||
; "init project NAME [PATH] [--libs=l1,l2 --ppx=p1,p2 --inline-tests]"
|
||||
]
|
||||
;;
|
||||
|
||||
let info =
|
||||
let doc = "composable build system for OCaml" in
|
||||
Cmd.info
|
||||
"dune"
|
||||
~doc
|
||||
~envs:Common.envs
|
||||
~version:
|
||||
(match Build_info.V1.version () with
|
||||
| None -> "n/a"
|
||||
| Some v -> Build_info.V1.Version.to_string v)
|
||||
~man:
|
||||
[ `Blocks common_commands_synopsis
|
||||
; `S "DESCRIPTION"
|
||||
; `P
|
||||
{|Dune is a build system designed for OCaml projects only. It
|
||||
focuses on providing the user with a consistent experience and takes
|
||||
care of most of the low-level details of OCaml compilation. All you
|
||||
have to do is provide a description of your project and Dune will
|
||||
do the rest.
|
||||
|}
|
||||
; `P
|
||||
{|The scheme it implements is inspired from the one used inside Jane
|
||||
Street and adapted to the open source world. It has matured over a
|
||||
long time and is used daily by hundreds of developers, which means
|
||||
that it is highly tested and productive.
|
||||
|}
|
||||
; `Blocks Common.help_secs
|
||||
; Common.examples
|
||||
[ "Initialise a new project named `foo'", "dune init project foo"
|
||||
; "Build all targets in the current source tree", "dune build"
|
||||
; "Run the executable named `bar'", "dune exec bar"
|
||||
; "Run all tests in the current source tree", "dune runtest"
|
||||
; "Install all components defined in the project", "dune install"
|
||||
; "Remove all build artefacts", "dune clean"
|
||||
]
|
||||
]
|
||||
;;
|
||||
|
||||
let cmd = Cmd.group info all
|
||||
|
||||
let exit_and_flush code =
|
||||
Console.finish ();
|
||||
exit (Exit_code.code code)
|
||||
;;
|
||||
|
||||
let () =
|
||||
Dune_rules.Colors.setup_err_formatter_colors ();
|
||||
try
|
||||
match Cmd.eval_value cmd ~catch:false with
|
||||
| Ok _ -> exit_and_flush Success
|
||||
| Error _ -> exit_and_flush Error
|
||||
with
|
||||
| Scheduler.Run.Shutdown.E Requested -> exit_and_flush Success
|
||||
| Scheduler.Run.Shutdown.E (Signal _) -> exit_and_flush Signal
|
||||
| exn ->
|
||||
let exn = Exn_with_backtrace.capture exn in
|
||||
Dune_util.Report_error.report exn;
|
||||
exit_and_flush Error
|
||||
;;
|
||||
1
unikernel/duniverse/dune_/bin/main.mli
Normal file
1
unikernel/duniverse/dune_/bin/main.mli
Normal file
|
|
@ -0,0 +1 @@
|
|||
(** Main module *)
|
||||
295
unikernel/duniverse/dune_/bin/monitor.ml
Normal file
295
unikernel/duniverse/dune_/bin/monitor.ml
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
open Import
|
||||
open Fiber.O
|
||||
module Client = Dune_rpc_client.Client
|
||||
module Version_error = Dune_rpc_private.Version_error
|
||||
|
||||
include struct
|
||||
open Dune_rpc
|
||||
module Diagnostic = Diagnostic
|
||||
module Progress = Progress
|
||||
module Job = Job
|
||||
module Sub = Sub
|
||||
module Conv = Conv
|
||||
end
|
||||
|
||||
(** Utility module for generating [Map] modules for [Diagnostic]s and [Job]s which use
|
||||
their [Id] as keys. *)
|
||||
module Id_map (Id : sig
|
||||
type t
|
||||
|
||||
val compare : t -> t -> Ordering.t
|
||||
val sexp : (t, Conv.values) Conv.t
|
||||
end) =
|
||||
struct
|
||||
include Map.Make (struct
|
||||
include Id
|
||||
|
||||
let to_dyn t = Sexp.to_dyn (Conv.to_sexp Id.sexp t)
|
||||
end)
|
||||
end
|
||||
|
||||
module Diagnostic_id_map = Id_map (Diagnostic.Id)
|
||||
module Job_id_map = Id_map (Job.Id)
|
||||
|
||||
module Event = struct
|
||||
(** Events that the render loop will process. *)
|
||||
type t =
|
||||
| Diagnostics of Diagnostic.Event.t list
|
||||
| Jobs of Job.Event.t list
|
||||
| Progress of Progress.t
|
||||
end
|
||||
|
||||
module State : sig
|
||||
(** Internal state of the render loop. *)
|
||||
type t
|
||||
|
||||
(** Initial empty state. *)
|
||||
val init : unit -> t
|
||||
|
||||
module Update : sig
|
||||
(** Incremental updates to the state. Computes increments of the state that
|
||||
will be used for efficient rendering. *)
|
||||
type t
|
||||
end
|
||||
|
||||
val update : t -> Event.t -> Update.t
|
||||
|
||||
(** Given a state update, render the update. *)
|
||||
val render : t -> Update.t -> unit
|
||||
end = struct
|
||||
type t =
|
||||
{ mutable diagnostics : Diagnostic.t Diagnostic_id_map.t
|
||||
; mutable jobs : Job.t Job_id_map.t
|
||||
; mutable progress : Progress.t
|
||||
}
|
||||
|
||||
let init () =
|
||||
{ diagnostics = Diagnostic_id_map.empty; jobs = Job_id_map.empty; progress = Waiting }
|
||||
;;
|
||||
|
||||
let done_status ~complete ~remaining ~failed state =
|
||||
Pp.textf
|
||||
"Done: %d%% (%d/%d, %d left%s) (jobs: %d)"
|
||||
(if complete + remaining = 0 then 0 else complete * 100 / (complete + remaining))
|
||||
complete
|
||||
(complete + remaining)
|
||||
remaining
|
||||
(match failed with
|
||||
| 0 -> ""
|
||||
| failed -> sprintf ", %d failed" failed)
|
||||
(Job_id_map.cardinal state.jobs)
|
||||
;;
|
||||
|
||||
let waiting_for_file_system_changes message =
|
||||
Pp.seq message (Pp.verbatim ", waiting for filesystem changes...")
|
||||
;;
|
||||
|
||||
let restarting_current_build message =
|
||||
Pp.seq message (Pp.verbatim ", restarting current build...")
|
||||
;;
|
||||
|
||||
let had_errors state =
|
||||
match Diagnostic_id_map.cardinal state.diagnostics with
|
||||
| 1 -> Pp.verbatim "Had 1 error"
|
||||
| n -> Pp.textf "Had %d errors" n
|
||||
;;
|
||||
|
||||
let status (state : t) =
|
||||
Console.Status_line.set
|
||||
(Live
|
||||
(fun () ->
|
||||
match (state.progress : Progress.t) with
|
||||
| Waiting -> Pp.verbatim "Initializing..."
|
||||
| In_progress { complete; remaining; failed } ->
|
||||
done_status ~complete ~remaining ~failed state
|
||||
| Interrupted ->
|
||||
Pp.tag User_message.Style.Error (Pp.verbatim "Source files changed")
|
||||
|> restarting_current_build
|
||||
| Success ->
|
||||
Pp.tag User_message.Style.Success (Pp.verbatim "Success")
|
||||
|> waiting_for_file_system_changes
|
||||
| Failed ->
|
||||
Pp.tag User_message.Style.Error (had_errors state)
|
||||
|> waiting_for_file_system_changes))
|
||||
;;
|
||||
|
||||
module Update = struct
|
||||
type t =
|
||||
| Update_status
|
||||
| Add_diagnostics of Diagnostic.t list
|
||||
| Refresh
|
||||
|
||||
let jobs state jobs =
|
||||
let jobs =
|
||||
List.fold_left jobs ~init:state.jobs ~f:(fun acc job_event ->
|
||||
match (job_event : Job.Event.t) with
|
||||
| Start job -> Job_id_map.add_exn acc job.id job
|
||||
| Stop id -> Job_id_map.remove acc id)
|
||||
in
|
||||
state.jobs <- jobs;
|
||||
Update_status
|
||||
;;
|
||||
|
||||
let progress state progress =
|
||||
state.progress <- progress;
|
||||
Update_status
|
||||
;;
|
||||
|
||||
let diagnostics state diagnostics =
|
||||
let mode, diagnostics =
|
||||
List.fold_left
|
||||
diagnostics
|
||||
~init:(`Add_only [], state.diagnostics)
|
||||
~f:(fun (mode, acc) diag_event ->
|
||||
match (diag_event : Diagnostic.Event.t) with
|
||||
| Remove diag -> `Remove, Diagnostic_id_map.remove acc diag.id
|
||||
| Add diag ->
|
||||
( (match mode with
|
||||
| `Add_only diags -> `Add_only (diag :: diags)
|
||||
| `Remove -> `Remove)
|
||||
, Diagnostic_id_map.add_exn acc diag.id diag ))
|
||||
in
|
||||
state.diagnostics <- diagnostics;
|
||||
match mode with
|
||||
| `Add_only update -> Add_diagnostics (List.rev update)
|
||||
| `Remove -> Refresh
|
||||
;;
|
||||
end
|
||||
|
||||
let update state (event : Event.t) =
|
||||
match event with
|
||||
| Jobs jobs -> Update.jobs state jobs
|
||||
| Progress progress -> Update.progress state progress
|
||||
| Diagnostics diagnostics -> Update.diagnostics state diagnostics
|
||||
;;
|
||||
|
||||
let render =
|
||||
let f d = Console.print_user_message (Diagnostic.to_user_message d) in
|
||||
fun (state : t) (update : Update.t) ->
|
||||
(match (update : Update.t) with
|
||||
| Add_diagnostics diags -> List.iter diags ~f
|
||||
| Update_status -> ()
|
||||
| Refresh ->
|
||||
Console.reset ();
|
||||
Diagnostic_id_map.iter state.diagnostics ~f);
|
||||
status state
|
||||
;;
|
||||
end
|
||||
|
||||
(* A generic loop that continuously fetches events from a [sub] that it opens a
|
||||
poll to and writes them to the [event] bus. *)
|
||||
let fetch_loop ~(event : Event.t Fiber_event_bus.t) ~client ~f sub =
|
||||
Client.poll client sub
|
||||
>>= function
|
||||
| Error version_error ->
|
||||
let* () = Fiber_event_bus.close event in
|
||||
User_error.raise [ Pp.verbatim (Version_error.message version_error) ]
|
||||
| Ok poller ->
|
||||
let rec loop () =
|
||||
Fiber.collect_errors (fun () -> Client.Stream.next poller)
|
||||
>>= (function
|
||||
| Ok (Some payload) -> Fiber_event_bus.push event (f payload)
|
||||
| Error _ | Ok None -> Fiber_event_bus.close event >>> Fiber.return `Closed)
|
||||
>>= function
|
||||
| `Closed -> Fiber.return ()
|
||||
| `Ok -> loop ()
|
||||
in
|
||||
loop ()
|
||||
;;
|
||||
|
||||
(* Main render loop *)
|
||||
let render_loop ~(event : Event.t Fiber_event_bus.t) =
|
||||
Console.reset ();
|
||||
let state = State.init () in
|
||||
let rec loop () =
|
||||
Fiber_event_bus.pop event
|
||||
>>= function
|
||||
| `Closed ->
|
||||
Console.print_user_message
|
||||
(User_error.make [ Pp.textf "Lost connection to server." ]);
|
||||
Fiber.return ()
|
||||
| `Next event ->
|
||||
let update = State.update state event in
|
||||
(* CR-someday alizter: If performance of rendering here on every loop is bad we can
|
||||
instead batch updates. It should be very simple to write a [State.Update.union]
|
||||
function that can combine incremental updates to be done at once. *)
|
||||
State.render state update;
|
||||
loop ()
|
||||
in
|
||||
loop ()
|
||||
;;
|
||||
|
||||
let monitor ~quit_on_disconnect () =
|
||||
Fiber.repeat_while ~init:1 ~f:(fun i ->
|
||||
match Dune_rpc_impl.Where.get () with
|
||||
| Some where ->
|
||||
let* connect = Client.Connection.connect_exn where in
|
||||
let+ () =
|
||||
Dune_rpc_impl.Client.client
|
||||
connect
|
||||
(Dune_rpc.Initialize.Request.create
|
||||
~id:(Dune_rpc.Id.make (Sexp.Atom "monitor_cmd")))
|
||||
~f:(fun client ->
|
||||
let event = Fiber_event_bus.create () in
|
||||
let module Sub = Dune_rpc_private.Public.Sub in
|
||||
Fiber.all_concurrently_unit
|
||||
[ render_loop ~event
|
||||
; fetch_loop ~event ~client ~f:(fun x -> Event.Jobs x) Sub.running_jobs
|
||||
; fetch_loop ~event ~client ~f:(fun x -> Event.Progress x) Sub.progress
|
||||
; fetch_loop ~event ~client ~f:(fun x -> Event.Diagnostics x) Sub.diagnostic
|
||||
])
|
||||
in
|
||||
Some i
|
||||
| None when quit_on_disconnect ->
|
||||
User_error.raise [ Pp.text "RPC server not running." ]
|
||||
| None ->
|
||||
Console.Status_line.set
|
||||
(Console.Status_line.Live
|
||||
(fun () -> Pp.verbatim ("Waiting for RPC server" ^ String.make (i mod 4) '.')));
|
||||
let+ () = Scheduler.sleep ~seconds:0.3 in
|
||||
Some (i + 1))
|
||||
;;
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
"$(b,dune monitor) connects to an RPC server running in the current workspace and \
|
||||
displays the build progress and diagnostics. If no server is running or it was \
|
||||
disconnected, it will continuously try to reconnect."
|
||||
]
|
||||
;;
|
||||
|
||||
let command =
|
||||
let info =
|
||||
let doc = "Connect to a Dune RPC server and monitor it." in
|
||||
Cmd.info "monitor" ~doc ~man
|
||||
and term =
|
||||
let open Import in
|
||||
let+ builder = Common.Builder.term
|
||||
and+ quit_on_disconnect =
|
||||
Arg.(
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "quit-on-disconnect" ]
|
||||
~doc:"Quit if the connection to the server is lost.")
|
||||
in
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
let builder = Common.Builder.disable_log_file builder in
|
||||
let common, config = Common.init builder in
|
||||
let stats = Common.stats common in
|
||||
let config =
|
||||
Dune_config.for_scheduler
|
||||
config
|
||||
stats
|
||||
~print_ctrl_c_warning:true
|
||||
~watch_exclusions:[]
|
||||
in
|
||||
Scheduler.Run.go
|
||||
config
|
||||
~on_event:(fun _ _ -> ())
|
||||
~file_watcher:No_watcher
|
||||
(monitor ~quit_on_disconnect)
|
||||
in
|
||||
Cmd.v info term
|
||||
;;
|
||||
1
unikernel/duniverse/dune_/bin/monitor.mli
Normal file
1
unikernel/duniverse/dune_/bin/monitor.mli
Normal file
|
|
@ -0,0 +1 @@
|
|||
val command : unit Cmdliner.Cmd.t
|
||||
68
unikernel/duniverse/dune_/bin/ocaml/doc.ml
Normal file
68
unikernel/duniverse/dune_/bin/ocaml/doc.ml
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
open Import
|
||||
module Main = Import.Main
|
||||
|
||||
let doc = "Build and view the documentation of an OCaml project"
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
{|$(b,dune ocaml doc) builds and then opens the documentation of an OCaml project in the users default browser.|}
|
||||
; `Blocks Common.help_secs
|
||||
]
|
||||
;;
|
||||
|
||||
let info = Cmd.info "doc" ~doc ~man
|
||||
|
||||
let lock_odoc_if_dev_tool_enabled () =
|
||||
match Lazy.force Lock_dev_tool.is_enabled with
|
||||
| false -> Action_builder.return ()
|
||||
| true -> Action_builder.of_memo (Lock_dev_tool.lock_dev_tool Odoc)
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term in
|
||||
let common, config = Common.init builder in
|
||||
let request (setup : Main.build_system) =
|
||||
let dir = Path.(relative root) (Common.prefix_target common ".") in
|
||||
let open Action_builder.O in
|
||||
let* () = lock_odoc_if_dev_tool_enabled () in
|
||||
let+ () =
|
||||
Alias.in_dir ~name:Dune_rules.Alias.doc ~recursive:true ~contexts:setup.contexts dir
|
||||
|> Alias.request
|
||||
in
|
||||
let relative_toplevel_index_path =
|
||||
let toplevel_index_path =
|
||||
let is_default ctx = ctx |> Context.name |> Dune_engine.Context_name.is_default in
|
||||
let doc_ctx = List.find_exn setup.contexts ~f:is_default in
|
||||
Dune_rules.Odoc.Paths.toplevel_index doc_ctx
|
||||
in
|
||||
Path.(toplevel_index_path |> build |> to_string_maybe_quoted)
|
||||
in
|
||||
Console.print
|
||||
[ Pp.textf "Docs built. Index can be found here: %s" relative_toplevel_index_path ];
|
||||
match
|
||||
let open Option.O in
|
||||
let* cmd_name, args =
|
||||
match Platform.OS.value with
|
||||
| Darwin -> Some ("open", [])
|
||||
| Other | FreeBSD | NetBSD | OpenBSD | Haiku | Linux -> Some ("xdg-open", [])
|
||||
| Windows -> None
|
||||
in
|
||||
let+ open_command =
|
||||
let path = Env_path.path Env.initial in
|
||||
Bin.which ~path cmd_name
|
||||
in
|
||||
open_command, args @ [ relative_toplevel_index_path ]
|
||||
with
|
||||
| Some (cmd, args) ->
|
||||
Proc.restore_cwd_and_execve (Path.to_absolute_filename cmd) args ~env:Env.initial
|
||||
| None ->
|
||||
User_warning.emit
|
||||
[ Pp.text
|
||||
"No browser could be found, you will have to open the documentation yourself."
|
||||
]
|
||||
in
|
||||
Build.run_build_command ~common ~config ~request
|
||||
;;
|
||||
|
||||
let cmd = Cmd.v info term
|
||||
3
unikernel/duniverse/dune_/bin/ocaml/doc.mli
Normal file
3
unikernel/duniverse/dune_/bin/ocaml/doc.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open! Import
|
||||
|
||||
val cmd : unit Cmd.t
|
||||
16
unikernel/duniverse/dune_/bin/ocaml/ocaml_cmd.ml
Normal file
16
unikernel/duniverse/dune_/bin/ocaml/ocaml_cmd.ml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
open Import
|
||||
|
||||
let info = Cmd.info "ocaml" ~doc:"Command group related to OCaml."
|
||||
|
||||
let group =
|
||||
Cmdliner.Cmd.group
|
||||
info
|
||||
[ Utop.command
|
||||
; Ocaml_merlin.command
|
||||
; Ocaml_merlin.Dump_dot_merlin.command
|
||||
; Top.command
|
||||
; Top.module_command
|
||||
; Ocaml_merlin.group
|
||||
; Doc.cmd
|
||||
]
|
||||
;;
|
||||
3
unikernel/duniverse/dune_/bin/ocaml/ocaml_cmd.mli
Normal file
3
unikernel/duniverse/dune_/bin/ocaml/ocaml_cmd.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val group : unit Cmd.t
|
||||
317
unikernel/duniverse/dune_/bin/ocaml/ocaml_merlin.ml
Normal file
317
unikernel/duniverse/dune_/bin/ocaml/ocaml_merlin.ml
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
open Import
|
||||
|
||||
module Selected_context = struct
|
||||
let arg =
|
||||
let ctx_name_conv =
|
||||
let parse ctx_name =
|
||||
match Context_name.of_string_opt ctx_name with
|
||||
| None -> Error (`Msg (Printf.sprintf "Invalid context name %S" ctx_name))
|
||||
| Some ctx_name -> Ok ctx_name
|
||||
in
|
||||
let print ppf t = Stdlib.Format.fprintf ppf "%s" (Context_name.to_string t) in
|
||||
Arg.conv ~docv:"context" (parse, print)
|
||||
in
|
||||
Arg.(
|
||||
value
|
||||
& opt ctx_name_conv Context_name.default
|
||||
& info
|
||||
[ "context" ]
|
||||
~docv:"CONTEXT"
|
||||
~doc:"Select the Dune build context that will be used to return information")
|
||||
;;
|
||||
end
|
||||
|
||||
module Server : sig
|
||||
val dump : selected_context:Context_name.t -> string -> unit Fiber.t
|
||||
val dump_dot_merlin : selected_context:Context_name.t -> string -> unit Fiber.t
|
||||
|
||||
(** Once started the server will wait for commands on stdin, read the
|
||||
requested merlin dot file and return its content on stdout. The server
|
||||
will halt when receiving EOF of a bad csexp. *)
|
||||
val start : selected_context:Context_name.t -> unit -> unit Fiber.t
|
||||
end = struct
|
||||
open Fiber.O
|
||||
|
||||
module Merlin_conf = struct
|
||||
type t = Sexp.t
|
||||
|
||||
let make_error msg = Sexp.(List [ List [ Atom "ERROR"; Atom msg ] ])
|
||||
|
||||
let to_stdout (t : t) =
|
||||
Csexp.to_channel stdout t;
|
||||
flush stdout
|
||||
;;
|
||||
end
|
||||
|
||||
module Commands = struct
|
||||
type t =
|
||||
| File of string
|
||||
| Halt
|
||||
| Unknown of string
|
||||
|
||||
let read_input in_channel =
|
||||
match Csexp.input_opt in_channel with
|
||||
| Ok None -> Halt
|
||||
| Ok (Some sexp) ->
|
||||
let open Sexp in
|
||||
(match sexp with
|
||||
| Atom "Halt" -> Halt
|
||||
| List [ Atom "File"; Atom path ] -> File path
|
||||
| sexp ->
|
||||
let msg = Printf.sprintf "Bad input: %s" (Sexp.to_string sexp) in
|
||||
Unknown msg)
|
||||
| Error err ->
|
||||
Format.eprintf "Bad input: %s@." err;
|
||||
Halt
|
||||
;;
|
||||
end
|
||||
|
||||
(* [make_relative_to_root p] will check that [Path.root] is a prefix of the
|
||||
absolute path [p] and remove it if that is the case. Under Windows and
|
||||
Cygwin environment both paths are lowarcased before the comparison *)
|
||||
let make_relative_to_root p =
|
||||
let p = Path.to_absolute_filename p in
|
||||
let prefix = Path.(to_absolute_filename root) in
|
||||
(if Sys.win32 || Sys.cygwin then String.Caseless.drop_prefix else String.drop_prefix)
|
||||
~prefix
|
||||
p
|
||||
(* After dropping the prefix we need to remove the leading path separator *)
|
||||
|> Option.map ~f:(fun s -> String.drop s 1)
|
||||
;;
|
||||
|
||||
(* Given a path [p] relative to the workspace root, [get_merlin_files_paths p]
|
||||
navigates to the [_build] directory and reaches this path from the correct
|
||||
context. Then it returns the list of available Merlin configurations for
|
||||
this directory. *)
|
||||
let get_merlin_files_paths dir =
|
||||
let merlin_path =
|
||||
Path.Build.relative dir Dune_rules.Merlin_ident.merlin_folder_name
|
||||
in
|
||||
Path.build merlin_path
|
||||
|> Path.readdir_unsorted
|
||||
|> Result.value ~default:[]
|
||||
|> List.sort ~compare:String.compare
|
||||
|> List.map ~f:(fun f -> Path.Build.relative merlin_path f |> Path.build)
|
||||
;;
|
||||
|
||||
module Merlin = Dune_rules.Merlin
|
||||
|
||||
let load_merlin_file file =
|
||||
(* We search for an appropriate merlin configuration in the current
|
||||
directory and its parents *)
|
||||
let rec find_closest path =
|
||||
match
|
||||
get_merlin_files_paths path
|
||||
|> List.find_map ~f:(fun file_path ->
|
||||
(* FIXME we are racing against the build system writing these
|
||||
files here *)
|
||||
match Merlin.Processed.load_file file_path with
|
||||
| Error msg -> Some (Merlin_conf.make_error msg)
|
||||
| Ok config -> Merlin.Processed.get config ~file)
|
||||
with
|
||||
| Some p -> Some p
|
||||
| None ->
|
||||
(match Path.Build.parent path with
|
||||
| None -> None
|
||||
| Some dir -> find_closest dir)
|
||||
in
|
||||
match find_closest (Path.Build.parent_exn file) with
|
||||
| Some x -> x
|
||||
| None ->
|
||||
Path.Build.drop_build_context_exn file
|
||||
|> Path.Source.to_string_maybe_quoted
|
||||
|> Printf.sprintf "No config found for file %s. Try calling 'dune build'."
|
||||
|> Merlin_conf.make_error
|
||||
;;
|
||||
|
||||
(* [to_local p] makes path [p] relative to the project's root. [p] can be: -
|
||||
An absolute path - A path relative to [Path.initial_cwd] *)
|
||||
let to_local file_path =
|
||||
let error msg = Error msg in
|
||||
(* This ensure the path is absolute. If not it is prefixed with
|
||||
[Path.initial_cwd] *)
|
||||
let abs_file_path = Path.of_filename_relative_to_initial_cwd file_path in
|
||||
(* Then we make the path relative to [Path.root] (and not
|
||||
[Path.initial_cwd]) *)
|
||||
match make_relative_to_root abs_file_path with
|
||||
| Some path ->
|
||||
(try
|
||||
let path = Path.of_string path in
|
||||
(* If dune ocaml-merlin is called from within the build dir we must
|
||||
remove the build context *)
|
||||
Ok (Path.drop_optional_build_context path |> Path.local_part)
|
||||
with
|
||||
| User_error.E mess -> User_message.to_string mess |> error)
|
||||
| None ->
|
||||
Printf.sprintf
|
||||
"Path %s is not in dune workspace (%s)."
|
||||
(String.maybe_quoted file_path)
|
||||
(String.maybe_quoted @@ Path.(to_absolute_filename Path.root))
|
||||
|> error
|
||||
;;
|
||||
|
||||
let to_local ~selected_context file =
|
||||
match to_local file with
|
||||
| Error s -> Fiber.return (Error s)
|
||||
| Ok file ->
|
||||
(match Dune_engine.Context_name.is_default selected_context with
|
||||
| false ->
|
||||
Fiber.return
|
||||
(Ok (Path.Build.append_local (Context_name.build_dir selected_context) file))
|
||||
| true ->
|
||||
let+ workspace = Memo.run (Workspace.workspace ()) in
|
||||
(match workspace.merlin_context with
|
||||
| None -> Error "no merlin context configured"
|
||||
| Some context ->
|
||||
Ok (Path.Build.append_local (Context_name.build_dir context) file)))
|
||||
;;
|
||||
|
||||
let print_merlin_conf ~selected_context file =
|
||||
to_local ~selected_context file
|
||||
>>| (function
|
||||
| Error s -> Merlin_conf.make_error s
|
||||
| Ok file -> load_merlin_file file)
|
||||
>>| Merlin_conf.to_stdout
|
||||
;;
|
||||
|
||||
let dump ~selected_context s =
|
||||
to_local ~selected_context s
|
||||
>>| function
|
||||
| Error mess -> Printf.eprintf "%s\n%!" mess
|
||||
| Ok path -> get_merlin_files_paths path |> List.iter ~f:Merlin.Processed.print_file
|
||||
;;
|
||||
|
||||
let dump_dot_merlin ~selected_context s =
|
||||
to_local ~selected_context s
|
||||
>>| function
|
||||
| Error mess -> Printf.eprintf "%s\n%!" mess
|
||||
| Ok path ->
|
||||
let files = get_merlin_files_paths path in
|
||||
Merlin.Processed.print_generic_dot_merlin files
|
||||
;;
|
||||
|
||||
let start ~selected_context () =
|
||||
let open Fiber.O in
|
||||
let rec main () =
|
||||
match Commands.read_input stdin with
|
||||
| Halt -> Fiber.return ()
|
||||
| File path ->
|
||||
let* () = print_merlin_conf ~selected_context path in
|
||||
main ()
|
||||
| Unknown msg ->
|
||||
Merlin_conf.to_stdout (Merlin_conf.make_error msg);
|
||||
main ()
|
||||
in
|
||||
main ()
|
||||
;;
|
||||
end
|
||||
|
||||
module Dump_config = struct
|
||||
let info =
|
||||
Cmd.info
|
||||
~doc:
|
||||
"Print the entire content of the merlin configuration for the given folder in a \
|
||||
user friendly form. This is for testing and debugging purposes only and should \
|
||||
not be considered as a stable output."
|
||||
"dump-config"
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ dir = Arg.(value & pos 0 dir "" & info [] ~docv:"PATH")
|
||||
and+ selected_context = Selected_context.arg in
|
||||
let common, config =
|
||||
let builder =
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
Common.Builder.disable_log_file builder
|
||||
in
|
||||
Common.init builder
|
||||
in
|
||||
Scheduler.go_with_rpc_server ~common ~config (fun () ->
|
||||
Server.dump ~selected_context dir)
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
end
|
||||
|
||||
let doc = "Start a merlin configuration server."
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
{|$(b,dune ocaml-merlin) starts a server that can be queried to get
|
||||
.merlin information. It is meant to be used by Merlin itself and does not
|
||||
provide a user-friendly output.|}
|
||||
; `Blocks Common.help_secs
|
||||
; Common.footer
|
||||
]
|
||||
;;
|
||||
|
||||
let start_session_info name = Cmd.info name ~doc ~man
|
||||
|
||||
let start_session_term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ selected_context = Selected_context.arg in
|
||||
let common, config =
|
||||
let builder =
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
Common.Builder.disable_log_file builder
|
||||
in
|
||||
Common.init builder
|
||||
in
|
||||
Scheduler.go_with_rpc_server ~common ~config (Server.start ~selected_context)
|
||||
;;
|
||||
|
||||
let command = Cmd.v (start_session_info "ocaml-merlin") start_session_term
|
||||
|
||||
module Dump_dot_merlin = struct
|
||||
let doc = "Print Merlin configuration"
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
{|$(b,dune ocaml dump-dot-merlin) will attempt to read previously
|
||||
generated configuration in a source folder, merge them and print
|
||||
it to the standard output in Merlin configuration syntax. The
|
||||
output of this command should always be checked and adapted to
|
||||
the project needs afterward.|}
|
||||
; Common.footer
|
||||
]
|
||||
;;
|
||||
|
||||
let info = Cmd.info "dump-dot-merlin" ~doc ~man
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ path =
|
||||
Arg.(
|
||||
value
|
||||
& pos 0 (some string) None
|
||||
& info
|
||||
[]
|
||||
~docv:"PATH"
|
||||
~doc:
|
||||
"The path to the folder of which the configuration should be printed. \
|
||||
Defaults to the current directory.")
|
||||
and+ selected_context = Selected_context.arg in
|
||||
let common, config =
|
||||
let builder =
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
Common.Builder.disable_log_file builder
|
||||
in
|
||||
Common.init builder
|
||||
in
|
||||
Scheduler.go_with_rpc_server ~common ~config (fun () ->
|
||||
match path with
|
||||
| Some s -> Server.dump_dot_merlin ~selected_context s
|
||||
| None -> Server.dump_dot_merlin ~selected_context ".")
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
end
|
||||
|
||||
let group =
|
||||
Cmdliner.Cmd.group
|
||||
(Cmd.info "merlin" ~doc:"Command group related to merlin")
|
||||
[ Dump_config.command; Cmd.v (start_session_info "start-session") start_session_term ]
|
||||
;;
|
||||
9
unikernel/duniverse/dune_/bin/ocaml/ocaml_merlin.mli
Normal file
9
unikernel/duniverse/dune_/bin/ocaml/ocaml_merlin.mli
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
|
||||
module Dump_dot_merlin : sig
|
||||
val command : unit Cmd.t
|
||||
end
|
||||
|
||||
val group : unit Cmd.t
|
||||
248
unikernel/duniverse/dune_/bin/ocaml/top.ml
Normal file
248
unikernel/duniverse/dune_/bin/ocaml/top.ml
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
open Import
|
||||
|
||||
let doc =
|
||||
"Print a list of toplevel directives for including directories and loading cma files."
|
||||
;;
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
{|Print a list of toplevel directives for including directories and loading cma files.|}
|
||||
; `P
|
||||
{|The output of $(b,dune top) should be evaluated in a toplevel
|
||||
to make a library available there.|}
|
||||
; `Blocks Common.help_secs
|
||||
]
|
||||
;;
|
||||
|
||||
let info = Cmd.info "top" ~doc ~man
|
||||
|
||||
let link_deps sctx link =
|
||||
let open Memo.O in
|
||||
let* lib_config =
|
||||
let+ ocaml = Super_context.context sctx |> Context.ocaml in
|
||||
ocaml.lib_config
|
||||
in
|
||||
Memo.parallel_map link ~f:(fun t ->
|
||||
Dune_rules.Lib_flags.link_deps sctx t Dune_rules.Link_mode.Byte lib_config)
|
||||
>>| List.concat
|
||||
;;
|
||||
|
||||
let files_to_load_of_requires sctx requires =
|
||||
let open Memo.O in
|
||||
let* files = link_deps sctx requires in
|
||||
let+ () = Memo.parallel_iter files ~f:Build_system.build_file in
|
||||
List.filter files ~f:(fun p ->
|
||||
let ext = Path.extension p in
|
||||
ext = Ocaml.Mode.compiled_lib_ext Byte || ext = Ocaml.Cm_kind.ext Cmo)
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ dir = Arg.(value & pos 0 string "" & Arg.info [] ~docv:"DIR")
|
||||
and+ ctx_name = Common.context_arg ~doc:{|Select context where to build/run utop.|} in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config (fun () ->
|
||||
let open Fiber.O in
|
||||
let* setup = Import.Main.setup () in
|
||||
build_exn (fun () ->
|
||||
let open Memo.O in
|
||||
let* setup = setup in
|
||||
let sctx =
|
||||
Dune_engine.Context_name.Map.find setup.scontexts ctx_name |> Option.value_exn
|
||||
in
|
||||
let context = Super_context.context sctx in
|
||||
let* libs =
|
||||
let dir =
|
||||
let build_dir = Context.build_dir context in
|
||||
Path.Build.relative build_dir (Common.prefix_target common dir)
|
||||
in
|
||||
let* db =
|
||||
let+ scope = Dune_rules.Scope.DB.find_by_dir dir in
|
||||
Dune_rules.Scope.libs scope
|
||||
in
|
||||
(* TODO why don't we read ppx as well?*)
|
||||
Dune_rules.Utop.libs_under_dir sctx ~db ~dir:(Path.build dir)
|
||||
in
|
||||
let* requires =
|
||||
Dune_rules.Resolve.Memo.read_memo (Dune_rules.Lib.closure ~linking:true libs)
|
||||
in
|
||||
let* lib_config =
|
||||
let+ ocaml = Context.ocaml context in
|
||||
ocaml.lib_config
|
||||
in
|
||||
let include_paths =
|
||||
Dune_rules.Lib_flags.L.toplevel_include_paths requires lib_config
|
||||
in
|
||||
let+ files_to_load = files_to_load_of_requires sctx requires in
|
||||
Dune_rules.Toplevel.print_toplevel_init_file
|
||||
{ include_paths; files_to_load; uses = []; pp = None; ppx = None; code = [] }))
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
|
||||
module Module = struct
|
||||
let doc = "Print a list of toplevel directives for loading a module into the topevel."
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P doc
|
||||
; `P
|
||||
"The module's source is evaluated in the toplevel without being sealed by the \
|
||||
mli."
|
||||
; `P
|
||||
{|The output of $(b,dune top) should be evaluated in a toplevel
|
||||
to make the module available there.|}
|
||||
; `Blocks Common.help_secs
|
||||
]
|
||||
;;
|
||||
|
||||
let info = Cmd.info "top-module" ~doc ~man
|
||||
|
||||
let module_directives sctx mod_ =
|
||||
let ctx = Super_context.context sctx in
|
||||
let src = Path.Build.append_source (Context.build_dir ctx) mod_ in
|
||||
let dir = Path.Build.parent_exn src in
|
||||
let filename = Path.Build.basename src in
|
||||
if Filename.extension filename = ""
|
||||
then User_error.raise [ Pp.text "file is missing an extension" ];
|
||||
let open Memo.O in
|
||||
let module_name =
|
||||
let name = Filename.remove_extension filename in
|
||||
Dune_rules.Module_name.of_string_user_error (Loc.none, name) |> User_error.ok_exn
|
||||
in
|
||||
let* expander = Super_context.expander sctx ~dir in
|
||||
let* top_module_info = Dune_rules.Top_module.find_module sctx mod_ in
|
||||
match top_module_info with
|
||||
| None -> User_error.raise [ Pp.text "no module found" ]
|
||||
| Some (_, _, _, Melange _) ->
|
||||
User_error.raise [ Pp.text "Modules belonging to `melange.emit' are not supported" ]
|
||||
| Some (module_, cctx, merlin, _) ->
|
||||
let module Compilation_context = Dune_rules.Compilation_context in
|
||||
let module Obj_dir = Dune_rules.Obj_dir in
|
||||
let module Top_module = Dune_rules.Top_module in
|
||||
let* requires =
|
||||
let* requires = Compilation_context.requires_link cctx in
|
||||
Dune_rules.Resolve.read_memo requires
|
||||
in
|
||||
let private_obj_dir = Top_module.private_obj_dir ctx mod_ in
|
||||
let include_paths =
|
||||
let libs =
|
||||
let lib_config = (Compilation_context.ocaml cctx).lib_config in
|
||||
Dune_rules.Lib_flags.L.toplevel_include_paths requires lib_config
|
||||
in
|
||||
Path.Set.add libs (Path.build (Obj_dir.byte_dir private_obj_dir))
|
||||
in
|
||||
let files_to_load () =
|
||||
let+ libs, modules =
|
||||
Memo.fork_and_join
|
||||
(fun () -> files_to_load_of_requires sctx requires)
|
||||
(fun () ->
|
||||
let cmis () =
|
||||
let glob =
|
||||
Dune_engine.File_selector.of_glob
|
||||
~dir:(Path.build (Obj_dir.byte_dir private_obj_dir))
|
||||
(Dune_lang.Glob.of_string_exn Loc.none "*.cmi")
|
||||
in
|
||||
let* files = Build_system.eval_pred glob in
|
||||
Memo.parallel_iter
|
||||
(Filename_set.to_list files)
|
||||
~f:Build_system.build_file
|
||||
in
|
||||
let cmos () =
|
||||
let obj_dir = Compilation_context.obj_dir cctx in
|
||||
let dep_graph = (Compilation_context.dep_graphs cctx).impl in
|
||||
let* modules =
|
||||
let graph =
|
||||
Dune_rules.Dep_graph.top_closed_implementations dep_graph [ module_ ]
|
||||
in
|
||||
let+ modules, _ = Action_builder.evaluate_and_collect_facts graph in
|
||||
modules
|
||||
in
|
||||
let cmos =
|
||||
let module Module = Dune_rules.Module in
|
||||
let module Module_name = Dune_rules.Module_name in
|
||||
let module_obj_name = Module.obj_name module_ in
|
||||
List.filter_map modules ~f:(fun m ->
|
||||
let obj_dir =
|
||||
if Module_name.Unique.equal module_obj_name (Module.obj_name m)
|
||||
then private_obj_dir
|
||||
else obj_dir
|
||||
in
|
||||
Obj_dir.Module.cm_file obj_dir m ~kind:(Ocaml Cmo)
|
||||
|> Option.map ~f:Path.build)
|
||||
in
|
||||
let+ (_ : Dep.Facts.t) =
|
||||
Build_system.build_deps (Dep.Set.of_files cmos)
|
||||
in
|
||||
cmos
|
||||
in
|
||||
Memo.fork_and_join_unit cmis cmos)
|
||||
in
|
||||
libs @ modules
|
||||
in
|
||||
let pps () =
|
||||
let module Merlin = Dune_rules.Merlin in
|
||||
let pps = Merlin.pp_config merlin ctx ~expander in
|
||||
let+ pps, _ = Action_builder.evaluate_and_collect_facts pps in
|
||||
let pp = Dune_rules.Module_name.Per_item.get pps module_name in
|
||||
match pp with
|
||||
| None -> None, None
|
||||
| Some pp_flags ->
|
||||
let args = Merlin.Processed.pp_args pp_flags in
|
||||
(match Merlin.Processed.pp_kind pp_flags with
|
||||
| Pp -> Some args, None
|
||||
| Ppx -> None, Some args)
|
||||
in
|
||||
let+ (pp, ppx), files_to_load = Memo.fork_and_join pps files_to_load in
|
||||
let code =
|
||||
let modules = Dune_rules.Compilation_context.modules cctx in
|
||||
let opens_ = Dune_rules.Modules.With_vlib.local_open modules module_ in
|
||||
List.map opens_ ~f:(fun name ->
|
||||
sprintf "open %s" (Dune_rules.Module_name.to_string name))
|
||||
in
|
||||
{ Dune_rules.Toplevel.files_to_load; pp; ppx; include_paths; uses = []; code }
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ module_path =
|
||||
Arg.(
|
||||
required
|
||||
& pos 0 (some string) None
|
||||
& Arg.info [] ~docv:"MODULE" ~doc:"Path to an OCaml module.")
|
||||
and+ ctx_name = Common.context_arg ~doc:{|Select context where to build/run utop.|} in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config (fun () ->
|
||||
let open Fiber.O in
|
||||
let* setup = Import.Main.setup () in
|
||||
build_exn (fun () ->
|
||||
let open Memo.O in
|
||||
let* setup = setup in
|
||||
let sctx =
|
||||
Dune_engine.Context_name.Map.find setup.scontexts ctx_name |> Option.value_exn
|
||||
in
|
||||
let+ directives =
|
||||
let module_path =
|
||||
if Filename.is_relative module_path
|
||||
then Path.Local.of_string module_path
|
||||
else (
|
||||
let root =
|
||||
(Common.root common).dir
|
||||
|> Path.of_string
|
||||
|> Path.to_absolute_filename
|
||||
|> Path.of_string
|
||||
in
|
||||
match Path.drop_prefix ~prefix:root (Path.of_string module_path) with
|
||||
| Some module_path -> module_path
|
||||
| None ->
|
||||
User_error.raise
|
||||
[ Pp.text "Module path not a descendent of workspace root." ])
|
||||
in
|
||||
module_directives sctx (Path.Source.of_local module_path)
|
||||
in
|
||||
Dune_rules.Toplevel.print_toplevel_init_file directives))
|
||||
;;
|
||||
end
|
||||
|
||||
let module_command = Cmd.v Module.info Module.term
|
||||
4
unikernel/duniverse/dune_/bin/ocaml/top.mli
Normal file
4
unikernel/duniverse/dune_/bin/ocaml/top.mli
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
val module_command : unit Cmd.t
|
||||
107
unikernel/duniverse/dune_/bin/ocaml/utop.ml
Normal file
107
unikernel/duniverse/dune_/bin/ocaml/utop.ml
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
open Import
|
||||
module Utop = Dune_rules.Utop
|
||||
|
||||
let doc = "Load library in utop."
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P {|$(b,dune utop DIR) build and run utop toplevel with libraries defined in DIR|}
|
||||
; `Blocks Common.help_secs
|
||||
]
|
||||
;;
|
||||
|
||||
let info = Cmd.info "utop" ~doc ~man
|
||||
|
||||
let lock_utop_if_dev_tool_enabled () =
|
||||
match Lazy.force Lock_dev_tool.is_enabled with
|
||||
| false -> Memo.return ()
|
||||
| true -> Lock_dev_tool.lock_dev_tool Utop
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ dir = Arg.(value & pos 0 string "" & Arg.info [] ~docv:"DIR")
|
||||
and+ ctx_name = Common.context_arg ~doc:{|Select context where to build/run utop.|}
|
||||
and+ args = Arg.(value & pos_right 0 string [] (Arg.info [] ~docv:"ARGS")) in
|
||||
let common, config = Common.init builder in
|
||||
let dir = Common.prefix_target common dir in
|
||||
if not (Path.is_directory (Path.of_string dir))
|
||||
then User_error.raise [ Pp.textf "cannot find directory: %s" (String.maybe_quoted dir) ];
|
||||
let env, utop_path =
|
||||
Scheduler.go_with_rpc_server ~common ~config (fun () ->
|
||||
let open Fiber.O in
|
||||
let* setup = Import.Main.setup () in
|
||||
build_exn (fun () ->
|
||||
let open Memo.O in
|
||||
let* setup = setup in
|
||||
let context = Import.Main.find_context_exn setup ~name:ctx_name in
|
||||
let utop_target_path filename =
|
||||
Path.build
|
||||
(Path.Build.relative
|
||||
(Context.build_dir context)
|
||||
(Filename.concat dir filename))
|
||||
in
|
||||
let utop_exe = utop_target_path Utop.utop_exe in
|
||||
let utop_findlib_conf = utop_target_path Utop.utop_findlib_conf in
|
||||
let* () =
|
||||
(* Calling [Build_system.file_exists] has the side effect of checking
|
||||
and memoizing whether or not the utop dev tool lockdir exists.
|
||||
thus if we generate the lockdir any later than this point, dune
|
||||
will not observe the fact that it now exists. *)
|
||||
lock_utop_if_dev_tool_enabled ()
|
||||
in
|
||||
Build_system.file_exists utop_exe
|
||||
>>= function
|
||||
| false ->
|
||||
User_error.raise
|
||||
[ Pp.textf "no library is defined in %s" (String.maybe_quoted dir) ]
|
||||
| true ->
|
||||
let* () = Build_system.build_file utop_exe in
|
||||
let* utop_dev_tool_lock_dir_exists =
|
||||
Memo.Lazy.force Utop.utop_dev_tool_lock_dir_exists
|
||||
in
|
||||
let* () =
|
||||
if utop_dev_tool_lock_dir_exists
|
||||
then
|
||||
(* Generate the custom findlib.conf file needed when utop is run
|
||||
as a dev tool. *)
|
||||
Build_system.build_file utop_findlib_conf
|
||||
else Memo.return ()
|
||||
in
|
||||
let sctx = Import.Main.find_scontext_exn setup ~name:ctx_name in
|
||||
let* requires =
|
||||
let dir = Path.Build.relative (Context.build_dir context) dir in
|
||||
Utop.requires_under_dir sctx ~dir
|
||||
in
|
||||
let+ requires = Resolve.read_memo requires
|
||||
and+ lib_config =
|
||||
let+ ocaml = Context.ocaml context in
|
||||
ocaml.lib_config
|
||||
and+ env = Super_context.context_env sctx in
|
||||
let env =
|
||||
Dune_rules.Lib_flags.L.toplevel_ld_paths requires lib_config
|
||||
|> Path.Set.fold
|
||||
~f:(fun dir env ->
|
||||
Env_path.cons ~var:Ocaml.Env.caml_ld_library_path env ~dir)
|
||||
~init:env
|
||||
in
|
||||
let env =
|
||||
if utop_dev_tool_lock_dir_exists
|
||||
then
|
||||
(* If there's a utop lockdir then dune will have built utop as a
|
||||
dev tool. In order for it to run correctly dune needed to
|
||||
generate a custom findlib.conf that contains the locations of
|
||||
all of utop's dependencies within the project's _build
|
||||
directory. Setting this environment variable causes the custom
|
||||
findlib.conf file to be used instead of the default
|
||||
findlib.conf. *)
|
||||
Env.add env ~var:"OCAMLFIND_CONF" ~value:(Path.to_string utop_findlib_conf)
|
||||
else env
|
||||
in
|
||||
env, Path.to_string utop_exe))
|
||||
in
|
||||
Hooks.End_of_build.run ();
|
||||
restore_cwd_and_execve (Common.root common) utop_path args env
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
1
unikernel/duniverse/dune_/bin/ocaml/utop.mli
Normal file
1
unikernel/duniverse/dune_/bin/ocaml/utop.mli
Normal file
|
|
@ -0,0 +1 @@
|
|||
val command : unit Cmdliner.Cmd.t
|
||||
380
unikernel/duniverse/dune_/bin/pkg/lock.ml
Normal file
380
unikernel/duniverse/dune_/bin/pkg/lock.ml
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
open Dune_config
|
||||
open Import
|
||||
open Pkg_common
|
||||
module Package_version = Dune_pkg.Package_version
|
||||
module Opam_repo = Dune_pkg.Opam_repo
|
||||
module Lock_dir = Dune_pkg.Lock_dir
|
||||
module Pin_stanza = Dune_lang.Pin_stanza
|
||||
module Pin = Dune_pkg.Pin
|
||||
|
||||
module Progress_indicator = struct
|
||||
module Per_lockdir = struct
|
||||
module State = struct
|
||||
module Repository = Dune_pkg.Pkg_workspace.Repository
|
||||
|
||||
type t =
|
||||
| Updating_repos of Repository.Name.t list
|
||||
| Solving
|
||||
|
||||
let pp = function
|
||||
| Updating_repos repo_names ->
|
||||
Pp.textf
|
||||
"Updating package repos %s..."
|
||||
(List.map repo_names ~f:(fun repo_name ->
|
||||
Repository.Name.to_string repo_name |> String.quoted)
|
||||
|> String.enumerate_and)
|
||||
| Solving -> Pp.text "Solving..."
|
||||
;;
|
||||
end
|
||||
|
||||
type t =
|
||||
{ lockdir_path : Path.Source.t
|
||||
; state : State.t option ref
|
||||
}
|
||||
|
||||
let create lockdir_path = { lockdir_path; state = ref None }
|
||||
end
|
||||
|
||||
(* The progress indicator for the entire lock operation, which may
|
||||
involve generating multiple lockdirs *)
|
||||
type t = Per_lockdir.t list
|
||||
|
||||
let pp (t : t) =
|
||||
(* Only display the first non-done lockdir state, since the status
|
||||
line can only consist of a single line. *)
|
||||
List.find_map t ~f:(fun { Per_lockdir.lockdir_path; state } ->
|
||||
Option.map !state ~f:(fun state ->
|
||||
Pp.concat
|
||||
[ Pp.textf "Locking %s: " (Path.Source.to_string_maybe_quoted lockdir_path)
|
||||
; Per_lockdir.State.pp state
|
||||
]))
|
||||
|> Option.value ~default:Pp.nop
|
||||
;;
|
||||
|
||||
let add_overlay (t : t) = Console.Status_line.add_overlay (Live (fun () -> pp t))
|
||||
end
|
||||
|
||||
let project_and_package_pins project =
|
||||
let dir = Dune_project.root project in
|
||||
let pins = Dune_project.pins project in
|
||||
let packages = Dune_project.packages project in
|
||||
Pin.DB.add_opam_pins (Pin.DB.of_stanza ~dir pins) packages
|
||||
;;
|
||||
|
||||
(* For recursive pins, we must traverse the pinned sources. The [project_pins]
|
||||
are the initial pins that we have in our project. *)
|
||||
let resolve_project_pins project_pins =
|
||||
let scan_project ~read ~files =
|
||||
let read file = Memo.of_reproducible_fiber (read file) in
|
||||
let open Memo.O in
|
||||
(* Opam files may never contain recursive pins, so don't both reading them *)
|
||||
Dune_project.gen_load
|
||||
~read
|
||||
~files
|
||||
~dir:Path.Source.root
|
||||
~infer_from_opam_files:false
|
||||
~load_opam_file_with_contents:Dune_pkg.Opam_file.load_opam_file_with_contents
|
||||
>>| Option.map ~f:(fun project ->
|
||||
let packages = Dune_project.packages project in
|
||||
let pins = project_and_package_pins project in
|
||||
pins, packages)
|
||||
|> Memo.run
|
||||
in
|
||||
Pin.resolve project_pins ~scan_project
|
||||
;;
|
||||
|
||||
let solve_multiple_platforms
|
||||
base_solver_env
|
||||
version_preference
|
||||
repos
|
||||
~pins
|
||||
~local_packages
|
||||
~constraints
|
||||
~selected_depopts
|
||||
~solve_for_platforms
|
||||
~portable_lock_dir
|
||||
=
|
||||
let open Fiber.O in
|
||||
let solve_for_env env =
|
||||
Dune_pkg.Opam_solver.solve_lock_dir
|
||||
env
|
||||
version_preference
|
||||
repos
|
||||
~pins
|
||||
~local_packages
|
||||
~constraints
|
||||
~selected_depopts
|
||||
~portable_lock_dir
|
||||
in
|
||||
let portable_solver_env =
|
||||
Dune_pkg.Solver_env.unset_multi
|
||||
base_solver_env
|
||||
Dune_lang.Package_variable_name.platform_specific
|
||||
in
|
||||
let+ results =
|
||||
Fiber.parallel_map solve_for_platforms ~f:(fun platform_env ->
|
||||
let solver_env = Dune_pkg.Solver_env.extend portable_solver_env platform_env in
|
||||
solve_for_env solver_env)
|
||||
in
|
||||
let solver_results, errors =
|
||||
List.partition_map results ~f:(function
|
||||
| Ok result -> Left result
|
||||
| Error (`Diagnostic_message message) -> Right message)
|
||||
in
|
||||
match solver_results, errors with
|
||||
| [], [] -> Code_error.raise "Solver did not run for any platforms." []
|
||||
| [], errors -> `All_error errors
|
||||
| x :: xs, errors ->
|
||||
let merged_solver_result =
|
||||
List.fold_left xs ~init:x ~f:Dune_pkg.Opam_solver.Solver_result.merge
|
||||
in
|
||||
if List.is_empty errors
|
||||
then `All_ok merged_solver_result
|
||||
else `Partial (merged_solver_result, errors)
|
||||
;;
|
||||
|
||||
let solve_lock_dir
|
||||
workspace
|
||||
~local_packages
|
||||
~project_pins
|
||||
~print_perf_stats
|
||||
~portable_lock_dir
|
||||
version_preference
|
||||
solver_env_from_current_system
|
||||
lock_dir_path
|
||||
progress_state
|
||||
=
|
||||
let open Fiber.O in
|
||||
let lock_dir = Workspace.find_lock_dir workspace lock_dir_path in
|
||||
let project_pins, solve_for_platforms =
|
||||
match lock_dir with
|
||||
| None -> project_pins, Dune_pkg.Solver_env.popular_platform_envs
|
||||
| Some lock_dir ->
|
||||
let workspace =
|
||||
Pin.DB.Workspace.of_stanza workspace.pins
|
||||
|> Pin.DB.Workspace.extract ~names:lock_dir.pins
|
||||
in
|
||||
Pin.DB.combine_exn workspace project_pins, lock_dir.solve_for_platforms
|
||||
in
|
||||
let solver_env_from_context =
|
||||
Option.bind lock_dir ~f:(fun lock_dir -> lock_dir.solver_env)
|
||||
in
|
||||
let solver_env =
|
||||
solver_env
|
||||
~solver_env_from_context
|
||||
~solver_env_from_current_system
|
||||
~unset_solver_vars_from_context:
|
||||
(unset_solver_vars_of_workspace workspace ~lock_dir_path)
|
||||
in
|
||||
let solve_for_platforms =
|
||||
match portable_lock_dir with
|
||||
| true ->
|
||||
(match solver_env_from_context with
|
||||
| Some solver_env_from_context ->
|
||||
List.map solve_for_platforms ~f:(fun platform_env ->
|
||||
Dune_pkg.Solver_env.extend solver_env_from_context platform_env)
|
||||
| None -> solve_for_platforms)
|
||||
| false -> [ solver_env ]
|
||||
in
|
||||
let time_start = Unix.gettimeofday () in
|
||||
let* repos =
|
||||
let repo_map = repositories_of_workspace workspace in
|
||||
let repo_names =
|
||||
Dune_pkg.Pkg_workspace.Repository.Name.Map.keys repo_map
|
||||
|> List.sort ~compare:Dune_pkg.Pkg_workspace.Repository.Name.compare
|
||||
in
|
||||
progress_state
|
||||
:= Some (Progress_indicator.Per_lockdir.State.Updating_repos repo_names);
|
||||
get_repos repo_map ~repositories:(repositories_of_lock_dir workspace ~lock_dir_path)
|
||||
in
|
||||
let* pins = resolve_project_pins project_pins in
|
||||
let time_solve_start = Unix.gettimeofday () in
|
||||
progress_state := Some Progress_indicator.Per_lockdir.State.Solving;
|
||||
let* result =
|
||||
solve_multiple_platforms
|
||||
solver_env
|
||||
(Pkg_common.Version_preference.choose
|
||||
~from_arg:version_preference
|
||||
~from_context:
|
||||
(Option.bind lock_dir ~f:(fun lock_dir -> lock_dir.version_preference)))
|
||||
repos
|
||||
~pins
|
||||
~local_packages:
|
||||
(Package_name.Map.map local_packages ~f:Dune_pkg.Local_package.for_solver)
|
||||
~constraints:(constraints_of_workspace workspace ~lock_dir_path)
|
||||
~selected_depopts:(depopts_of_workspace workspace ~lock_dir_path)
|
||||
~solve_for_platforms
|
||||
~portable_lock_dir
|
||||
in
|
||||
let solver_result =
|
||||
match result with
|
||||
| `All_error messages -> Error messages
|
||||
| `All_ok solver_result -> Ok (solver_result, [])
|
||||
| `Partial (solver_result, errors) ->
|
||||
Log.info errors;
|
||||
Ok
|
||||
( solver_result
|
||||
, [ Pp.nop
|
||||
; Pp.text
|
||||
"No solution was found for some platforms. See the log or run with \
|
||||
--verbose for more details."
|
||||
|> Pp.tag User_message.Style.Warning
|
||||
] )
|
||||
in
|
||||
match solver_result with
|
||||
| Error messages -> Fiber.return (Error (lock_dir_path, messages))
|
||||
| Ok (solver_result, maybe_unsolved_platforms_message) ->
|
||||
let { Dune_pkg.Opam_solver.Solver_result.lock_dir
|
||||
; files
|
||||
; pinned_packages
|
||||
; num_expanded_packages
|
||||
}
|
||||
=
|
||||
solver_result
|
||||
in
|
||||
let time_end = Unix.gettimeofday () in
|
||||
let maybe_perf_stats =
|
||||
if print_perf_stats
|
||||
then
|
||||
[ Pp.nop
|
||||
; Pp.textf "Expanded packages: %d" num_expanded_packages
|
||||
; Pp.textf "Updated repos in: %.2fs" (time_solve_start -. time_start)
|
||||
; Pp.textf "Solved dependencies in: %.2fs" (time_end -. time_solve_start)
|
||||
]
|
||||
else []
|
||||
in
|
||||
let summary_message =
|
||||
User_message.make
|
||||
((Pp.tag
|
||||
User_message.Style.Success
|
||||
(Pp.textf
|
||||
"Solution for %s:"
|
||||
(Path.Source.to_string_maybe_quoted lock_dir_path))
|
||||
:: (match Lock_dir.Packages.to_pkg_list lock_dir.packages with
|
||||
| [] ->
|
||||
Pp.tag User_message.Style.Warning @@ Pp.text "(no dependencies to lock)"
|
||||
| packages -> pp_packages packages)
|
||||
:: maybe_perf_stats)
|
||||
@ maybe_unsolved_platforms_message)
|
||||
in
|
||||
progress_state := None;
|
||||
let+ lock_dir = Lock_dir.compute_missing_checksums ~pinned_packages lock_dir in
|
||||
Ok
|
||||
( Lock_dir.Write_disk.prepare ~portable_lock_dir ~lock_dir_path ~files lock_dir
|
||||
, summary_message )
|
||||
;;
|
||||
|
||||
let solve
|
||||
workspace
|
||||
~local_packages
|
||||
~project_pins
|
||||
~solver_env_from_current_system
|
||||
~version_preference
|
||||
~lock_dirs
|
||||
~print_perf_stats
|
||||
~portable_lock_dir
|
||||
=
|
||||
let open Fiber.O in
|
||||
(* a list of thunks that will perform all the file IO side
|
||||
effects after performing validation so that if materializing any
|
||||
lockdir would fail then no side effect takes place. *)
|
||||
(let+ errors, solutions =
|
||||
let progress_indicator =
|
||||
List.map lock_dirs ~f:Progress_indicator.Per_lockdir.create
|
||||
in
|
||||
let overlay = Progress_indicator.add_overlay progress_indicator in
|
||||
let+ result =
|
||||
Fiber.finalize
|
||||
~finally:(fun () ->
|
||||
Console.Status_line.remove_overlay overlay;
|
||||
Fiber.return ())
|
||||
(fun () ->
|
||||
Fiber.parallel_map progress_indicator ~f:(fun { lockdir_path; state } ->
|
||||
solve_lock_dir
|
||||
workspace
|
||||
~local_packages
|
||||
~project_pins
|
||||
~print_perf_stats
|
||||
~portable_lock_dir
|
||||
version_preference
|
||||
solver_env_from_current_system
|
||||
lockdir_path
|
||||
state))
|
||||
in
|
||||
List.partition_map result ~f:Result.to_either
|
||||
in
|
||||
match errors with
|
||||
| [] -> Ok solutions
|
||||
| _ -> Error errors)
|
||||
>>| function
|
||||
| Error errors ->
|
||||
User_error.raise
|
||||
([ Pp.text "Unable to solve dependencies for the following lock directories:" ]
|
||||
@ List.concat_map errors ~f:(fun (path, messages) ->
|
||||
[ Pp.textf "Lock directory %s:" (Path.Source.to_string_maybe_quoted path)
|
||||
; Pp.hovbox (Pp.concat ~sep:Pp.newline messages)
|
||||
]))
|
||||
| Ok write_disks_with_summaries ->
|
||||
let write_disk_list, summary_messages = List.split write_disks_with_summaries in
|
||||
List.iter summary_messages ~f:Console.print_user_message;
|
||||
(* All the file IO side effects happen here: *)
|
||||
List.iter write_disk_list ~f:Lock_dir.Write_disk.commit
|
||||
;;
|
||||
|
||||
let project_pins =
|
||||
let open Memo.O in
|
||||
Dune_rules.Dune_load.projects ()
|
||||
>>| List.fold_left ~init:Pin.DB.empty ~f:(fun acc project ->
|
||||
let pins = project_and_package_pins project in
|
||||
Pin.DB.combine_exn acc pins)
|
||||
;;
|
||||
|
||||
let lock ~version_preference ~lock_dirs_arg ~print_perf_stats ~portable_lock_dir =
|
||||
let open Fiber.O in
|
||||
let* solver_env_from_current_system =
|
||||
poll_solver_env_from_current_system () >>| Option.some
|
||||
and* workspace, local_packages, project_pins =
|
||||
Memo.run
|
||||
@@
|
||||
let open Memo.O in
|
||||
let+ workspace = Workspace.workspace ()
|
||||
and+ local_packages = find_local_packages
|
||||
and+ project_pins = project_pins in
|
||||
workspace, local_packages, project_pins
|
||||
in
|
||||
let lock_dirs =
|
||||
Pkg_common.Lock_dirs_arg.lock_dirs_of_workspace lock_dirs_arg workspace
|
||||
in
|
||||
solve
|
||||
workspace
|
||||
~local_packages
|
||||
~project_pins
|
||||
~solver_env_from_current_system
|
||||
~version_preference
|
||||
~lock_dirs
|
||||
~print_perf_stats
|
||||
~portable_lock_dir
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ version_preference = Version_preference.term
|
||||
and+ lock_dirs_arg = Pkg_common.Lock_dirs_arg.term
|
||||
and+ print_perf_stats = Arg.(value & flag & info [ "print-perf-stats" ]) in
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config (fun () ->
|
||||
let portable_lock_dir =
|
||||
match Config.get Dune_rules.Compile_time.portable_lock_dir with
|
||||
| `Enabled -> true
|
||||
| `Disabled -> false
|
||||
in
|
||||
lock ~version_preference ~lock_dirs_arg ~print_perf_stats ~portable_lock_dir)
|
||||
;;
|
||||
|
||||
let info =
|
||||
let doc = "Create a lockfile" in
|
||||
Cmd.info "lock" ~doc
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
15
unikernel/duniverse/dune_/bin/pkg/lock.mli
Normal file
15
unikernel/duniverse/dune_/bin/pkg/lock.mli
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
open Import
|
||||
|
||||
val solve
|
||||
: Workspace.t
|
||||
-> local_packages:Dune_pkg.Local_package.t Package_name.Map.t
|
||||
-> project_pins:Dune_pkg.Pin.DB.t
|
||||
-> solver_env_from_current_system:Dune_pkg.Solver_env.t option
|
||||
-> version_preference:Dune_pkg.Version_preference.t option
|
||||
-> lock_dirs:Path.Source.t list
|
||||
-> print_perf_stats:bool
|
||||
-> portable_lock_dir:bool
|
||||
-> unit Fiber.t
|
||||
|
||||
(** Command to create lock directory *)
|
||||
val command : unit Cmd.t
|
||||
103
unikernel/duniverse/dune_/bin/pkg/outdated.ml
Normal file
103
unikernel/duniverse/dune_/bin/pkg/outdated.ml
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
open Import
|
||||
open Pkg_common
|
||||
|
||||
let find_outdated_packages ~transitive ~lock_dirs_arg () =
|
||||
let open Fiber.O in
|
||||
let+ pps, not_founds =
|
||||
let* workspace = Memo.run (Workspace.workspace ()) in
|
||||
Pkg_common.Lock_dirs_arg.lock_dirs_of_workspace lock_dirs_arg workspace
|
||||
|> Fiber.parallel_map ~f:(fun lock_dir_path ->
|
||||
(* updating makes sense when checking for outdated packages *)
|
||||
let* repos =
|
||||
get_repos
|
||||
(repositories_of_workspace workspace)
|
||||
~repositories:(repositories_of_lock_dir workspace ~lock_dir_path)
|
||||
and+ local_packages = Memo.run find_local_packages
|
||||
and+ platform = solver_env_from_system_and_context ~lock_dir_path in
|
||||
let lock_dir = Dune_pkg.Lock_dir.read_disk_exn lock_dir_path in
|
||||
let packages =
|
||||
Dune_pkg.Lock_dir.Packages.pkgs_on_platform_by_name lock_dir.packages ~platform
|
||||
in
|
||||
let+ results = Dune_pkg.Outdated.find ~repos ~local_packages packages in
|
||||
( Dune_pkg.Outdated.pp ~transitive ~lock_dir_path results
|
||||
, ( Dune_pkg.Outdated.packages_that_were_not_found results
|
||||
|> Package_name.Set.of_list
|
||||
|> Package_name.Set.to_list
|
||||
, lock_dir_path
|
||||
, repos ) ))
|
||||
>>| List.split
|
||||
in
|
||||
(match pps with
|
||||
| [ _ ] -> Console.print pps
|
||||
| _ -> Console.print [ Pp.enumerate ~f:Fun.id pps ]);
|
||||
let error_messages =
|
||||
List.filter_map not_founds ~f:(function
|
||||
| [], _, _ -> None
|
||||
| packages, lock_dir_path, repos ->
|
||||
Pp.concat
|
||||
~sep:Pp.space
|
||||
[ Pp.textf
|
||||
"When checking %s, the following packages:"
|
||||
(Path.Source.to_string_maybe_quoted lock_dir_path)
|
||||
|> Pp.hovbox
|
||||
; Pp.concat
|
||||
~sep:Pp.space
|
||||
[ Pp.enumerate packages ~f:(fun name ->
|
||||
Dune_lang.Package_name.to_string name |> Pp.verbatim)
|
||||
; Pp.text "were not found in the following opam repositories:" |> Pp.hovbox
|
||||
; Pp.enumerate repos ~f:(fun repo ->
|
||||
(* CR-rgrinberg: why are we outputting [Dyn.t] in error
|
||||
messages? *)
|
||||
Dune_pkg.Opam_repo.serializable repo
|
||||
|> Dyn.option Dune_pkg.Opam_repo.Serializable.to_dyn
|
||||
|> Dyn.pp)
|
||||
]
|
||||
|> Pp.vbox
|
||||
]
|
||||
|> Pp.hovbox
|
||||
|> Option.some)
|
||||
in
|
||||
match error_messages with
|
||||
| [] -> ()
|
||||
| error_messages ->
|
||||
User_error.raise (Pp.text "Some packages could not be found." :: error_messages)
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ transitive =
|
||||
Arg.(
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "transitive" ]
|
||||
~doc:"Check for outdated packages in transitive dependencies")
|
||||
and+ lock_dirs_arg = Pkg_common.Lock_dirs_arg.term in
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config
|
||||
@@ find_outdated_packages ~transitive ~lock_dirs_arg
|
||||
;;
|
||||
|
||||
let info =
|
||||
let doc = "Check for outdated packages" in
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
"List packages in from lock directory that have newer versions available. By \
|
||||
default, only direct dependencies are checked. The $(b,--transitive) flag can \
|
||||
be used to check transitive dependencies as well."
|
||||
; `P "For example:"
|
||||
; `Pre " \\$ dune pkg outdated"
|
||||
; `Noblank
|
||||
; `Pre " 1/2 packages in dune.lock are outdated."
|
||||
; `Noblank
|
||||
; `Pre " - ocaml 4.14.1 < 5.1.0"
|
||||
; `Noblank
|
||||
; `Pre " - dune 3.7.1 < 3.11.0"
|
||||
]
|
||||
in
|
||||
Cmd.info "outdated" ~doc ~man
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
4
unikernel/duniverse/dune_/bin/pkg/outdated.mli
Normal file
4
unikernel/duniverse/dune_/bin/pkg/outdated.mli
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
open Import
|
||||
|
||||
(** Command to print outdated packages *)
|
||||
val command : unit Cmd.t
|
||||
28
unikernel/duniverse/dune_/bin/pkg/pkg.ml
Normal file
28
unikernel/duniverse/dune_/bin/pkg/pkg.ml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
open Import
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P {|Commands for OCaml package management|}
|
||||
; `Blocks Common.help_secs
|
||||
]
|
||||
;;
|
||||
|
||||
let subcommands =
|
||||
[ Lock.command
|
||||
; Print_solver_env.command
|
||||
; Outdated.command
|
||||
; Validate_lock_dir.command
|
||||
; Pkg_enabled.command
|
||||
]
|
||||
;;
|
||||
|
||||
let info name =
|
||||
let doc = "Experimental package management" in
|
||||
Cmd.info name ~doc ~man
|
||||
;;
|
||||
|
||||
let group = Cmd.group (info "pkg") subcommands
|
||||
|
||||
module Alias = struct
|
||||
let group = Cmd.group (info "package") subcommands
|
||||
end
|
||||
7
unikernel/duniverse/dune_/bin/pkg/pkg.mli
Normal file
7
unikernel/duniverse/dune_/bin/pkg/pkg.mli
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
open Import
|
||||
|
||||
val group : unit Cmd.t
|
||||
|
||||
module Alias : sig
|
||||
val group : unit Cmd.t
|
||||
end
|
||||
228
unikernel/duniverse/dune_/bin/pkg/pkg_common.ml
Normal file
228
unikernel/duniverse/dune_/bin/pkg/pkg_common.ml
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
open Import
|
||||
module Lock_dir = Dune_pkg.Lock_dir
|
||||
module Solver_env = Dune_pkg.Solver_env
|
||||
module Package_variable_name = Dune_lang.Package_variable_name
|
||||
module Variable_value = Dune_pkg.Variable_value
|
||||
|
||||
let solver_env
|
||||
~solver_env_from_current_system
|
||||
~solver_env_from_context
|
||||
~unset_solver_vars_from_context
|
||||
=
|
||||
let solver_env =
|
||||
[ solver_env_from_current_system; solver_env_from_context ]
|
||||
|> List.filter_opt
|
||||
|> List.fold_left ~init:Solver_env.with_defaults ~f:Solver_env.extend
|
||||
in
|
||||
match unset_solver_vars_from_context with
|
||||
| None -> solver_env
|
||||
| Some unset_solver_vars -> Solver_env.unset_multi solver_env unset_solver_vars
|
||||
;;
|
||||
|
||||
let poll_solver_env_from_current_system () =
|
||||
Dune_pkg.Sys_poll.make ~path:(Env_path.path Stdune.Env.initial)
|
||||
|> Dune_pkg.Sys_poll.solver_env_from_current_system
|
||||
;;
|
||||
|
||||
let get_lock_dir_from_context ~lock_dir_path =
|
||||
Memo.run
|
||||
@@
|
||||
let open Memo.O in
|
||||
let+ workspace = Workspace.workspace () in
|
||||
Workspace.find_lock_dir workspace lock_dir_path
|
||||
;;
|
||||
|
||||
let get_solver_env_from_context ~lock_dir_path =
|
||||
let open Fiber.O in
|
||||
let+ lock_dir = get_lock_dir_from_context ~lock_dir_path in
|
||||
Option.bind lock_dir ~f:(fun lock_dir -> lock_dir.solver_env)
|
||||
;;
|
||||
|
||||
let get_unset_solver_vars_from_context ~lock_dir_path =
|
||||
let open Fiber.O in
|
||||
let+ lock_dir = get_lock_dir_from_context ~lock_dir_path in
|
||||
Option.bind lock_dir ~f:(fun lock_dir -> lock_dir.unset_solver_vars)
|
||||
;;
|
||||
|
||||
let solver_env_from_system_and_context ~lock_dir_path =
|
||||
let open Fiber.O in
|
||||
let+ solver_env_from_current_system =
|
||||
poll_solver_env_from_current_system () >>| Option.some
|
||||
and+ solver_env_from_context = get_solver_env_from_context ~lock_dir_path
|
||||
and+ unset_solver_vars_from_context =
|
||||
get_unset_solver_vars_from_context ~lock_dir_path
|
||||
in
|
||||
solver_env
|
||||
~solver_env_from_current_system
|
||||
~solver_env_from_context
|
||||
~unset_solver_vars_from_context
|
||||
;;
|
||||
|
||||
module Version_preference = struct
|
||||
include Dune_pkg.Version_preference
|
||||
|
||||
let term =
|
||||
let all_strings = List.map all_by_string ~f:fst in
|
||||
let doc =
|
||||
sprintf
|
||||
"Whether to prefer the newest compatible version of a package or the oldest \
|
||||
compatible version of packages while solving dependencies. This overrides any \
|
||||
setting in the current workspace. The default is %s."
|
||||
(to_string default)
|
||||
in
|
||||
let docv = String.concat ~sep:"|" all_strings |> sprintf "(%s)" in
|
||||
Arg.(
|
||||
value
|
||||
& opt (some (enum all_by_string)) None
|
||||
& info [ "version-preference" ] ~doc ~docv)
|
||||
;;
|
||||
|
||||
let choose ~from_arg ~from_context =
|
||||
match from_arg, from_context with
|
||||
| Some from_arg, _ -> from_arg
|
||||
| None, Some from_context -> from_context
|
||||
| None, None -> default
|
||||
;;
|
||||
end
|
||||
|
||||
let repositories_of_workspace (workspace : Workspace.t) =
|
||||
List.map workspace.repos ~f:(fun repo ->
|
||||
Dune_pkg.Pkg_workspace.Repository.name repo, repo)
|
||||
|> Dune_pkg.Pkg_workspace.Repository.Name.Map.of_list_exn
|
||||
;;
|
||||
|
||||
let constraints_of_workspace (workspace : Workspace.t) ~lock_dir_path =
|
||||
match Workspace.find_lock_dir workspace lock_dir_path with
|
||||
| None -> []
|
||||
| Some lock_dir -> lock_dir.constraints
|
||||
;;
|
||||
|
||||
let depopts_of_workspace (workspace : Workspace.t) ~lock_dir_path =
|
||||
match Workspace.find_lock_dir workspace lock_dir_path with
|
||||
| None -> []
|
||||
| Some lock_dir -> lock_dir.depopts |> List.map ~f:snd
|
||||
;;
|
||||
|
||||
let repositories_of_lock_dir workspace ~lock_dir_path =
|
||||
match Workspace.find_lock_dir workspace lock_dir_path with
|
||||
| Some lock_dir -> lock_dir.repositories
|
||||
| None ->
|
||||
List.map workspace.repos ~f:(fun repo ->
|
||||
let name = Dune_pkg.Pkg_workspace.Repository.name repo in
|
||||
let loc = Loc.none in
|
||||
loc, name)
|
||||
;;
|
||||
|
||||
let unset_solver_vars_of_workspace workspace ~lock_dir_path =
|
||||
let open Option.O in
|
||||
let* lock_dir = Workspace.find_lock_dir workspace lock_dir_path in
|
||||
lock_dir.unset_solver_vars
|
||||
;;
|
||||
|
||||
let get_repos repos ~repositories =
|
||||
let module Repository = Dune_pkg.Pkg_workspace.Repository in
|
||||
repositories
|
||||
|> Fiber.parallel_map ~f:(fun (loc, name) ->
|
||||
match Repository.Name.Map.find repos name with
|
||||
| None ->
|
||||
User_error.raise
|
||||
~loc
|
||||
[ Pp.textf "Repository '%s' is not a known repository"
|
||||
@@ Repository.Name.to_string name
|
||||
]
|
||||
| Some repo ->
|
||||
let loc, opam_url = Repository.opam_url repo in
|
||||
let module Opam_repo = Dune_pkg.Opam_repo in
|
||||
(match Dune_pkg.OpamUrl.classify opam_url loc with
|
||||
| `Git -> Opam_repo.of_git_repo loc opam_url
|
||||
| `Path path -> Fiber.return @@ Opam_repo.of_opam_repo_dir_path loc path
|
||||
| `Archive ->
|
||||
User_error.raise
|
||||
~loc
|
||||
[ Pp.textf "Repositories stored in archives (%s) are currently unsupported"
|
||||
@@ OpamUrl.to_string opam_url
|
||||
]))
|
||||
;;
|
||||
|
||||
let find_local_packages =
|
||||
let open Memo.O in
|
||||
Dune_rules.Dune_load.packages ()
|
||||
>>| Package.Name.Map.map ~f:Dune_pkg.Local_package.of_package
|
||||
;;
|
||||
|
||||
let pp_package { Lock_dir.Pkg.info = { Lock_dir.Pkg_info.name; version; avoid; _ }; _ } =
|
||||
let warn =
|
||||
if avoid
|
||||
then Pp.tag User_message.Style.Warning (Pp.text " (this version should be avoided)")
|
||||
else Pp.nop
|
||||
in
|
||||
let open Pp.O in
|
||||
Pp.verbatim
|
||||
(Package_name.to_string name ^ "." ^ Dune_pkg.Package_version.to_string version)
|
||||
++ warn
|
||||
;;
|
||||
|
||||
let pp_packages packages = Pp.enumerate packages ~f:pp_package
|
||||
|
||||
module Lock_dirs_arg = struct
|
||||
type t =
|
||||
| All
|
||||
| Selected of Path.Source.t list
|
||||
|
||||
let all = All
|
||||
|
||||
let term =
|
||||
Common.one_of
|
||||
(let+ arg =
|
||||
Arg.(
|
||||
value
|
||||
& pos_all string []
|
||||
& info
|
||||
[]
|
||||
~docv:"LOCKDIRS"
|
||||
~doc:
|
||||
"Lock directories to check for outdated packages. Defaults to dune.lock.")
|
||||
in
|
||||
Selected (List.map arg ~f:Path.Source.of_string))
|
||||
(let+ _all =
|
||||
Arg.(
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "all" ]
|
||||
~doc:"Check all lock directories in the workspace for outdated packages.")
|
||||
in
|
||||
All)
|
||||
;;
|
||||
|
||||
let lock_dirs_of_workspace t (workspace : Workspace.t) =
|
||||
let workspace_lock_dirs =
|
||||
Lock_dir.default_path
|
||||
:: List.map workspace.lock_dirs ~f:(fun (lock_dir : Workspace.Lock_dir.t) ->
|
||||
lock_dir.path)
|
||||
|> Path.Source.Set.of_list
|
||||
|> Path.Source.Set.to_list
|
||||
in
|
||||
match t with
|
||||
| All -> workspace_lock_dirs
|
||||
| Selected [] -> [ Lock_dir.default_path ]
|
||||
| Selected chosen_lock_dirs ->
|
||||
let workspace_lock_dirs_set = Path.Source.Set.of_list workspace_lock_dirs in
|
||||
let chosen_lock_dirs_set = Path.Source.Set.of_list chosen_lock_dirs in
|
||||
if Path.Source.Set.is_subset chosen_lock_dirs_set ~of_:workspace_lock_dirs_set
|
||||
then chosen_lock_dirs
|
||||
else (
|
||||
let unknown_lock_dirs =
|
||||
Path.Source.Set.diff chosen_lock_dirs_set workspace_lock_dirs_set
|
||||
|> Path.Source.Set.to_list
|
||||
in
|
||||
let f x = Path.pp (Path.source x) in
|
||||
User_error.raise
|
||||
[ Pp.text
|
||||
"The following directories are not lock directories in this workspace:"
|
||||
; Pp.enumerate unknown_lock_dirs ~f
|
||||
; Pp.text "This workspace contains the following lock directories:"
|
||||
; Pp.enumerate workspace_lock_dirs ~f
|
||||
])
|
||||
;;
|
||||
end
|
||||
92
unikernel/duniverse/dune_/bin/pkg/pkg_common.mli
Normal file
92
unikernel/duniverse/dune_/bin/pkg/pkg_common.mli
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
open Import
|
||||
|
||||
(** Create a [Dune_pkg.Solver_env.t] by combining variables taken from the
|
||||
current system and variables taken from the current context, with priority
|
||||
being given to the latter. Some variables are initialized to default values
|
||||
(which can be overridden by the arguments to this function):
|
||||
- "with-doc" is set to "false"
|
||||
- "opam-version" is set to the version of opam vendored in dune *)
|
||||
val solver_env
|
||||
: solver_env_from_current_system:Dune_pkg.Solver_env.t option
|
||||
-> solver_env_from_context:Dune_pkg.Solver_env.t option
|
||||
-> unset_solver_vars_from_context:Dune_lang.Package_variable_name.Set.t option
|
||||
-> Dune_pkg.Solver_env.t
|
||||
|
||||
val poll_solver_env_from_current_system : unit -> Dune_pkg.Solver_env.t Fiber.t
|
||||
|
||||
val solver_env_from_system_and_context
|
||||
: lock_dir_path:Path.Source.t
|
||||
-> Dune_pkg.Solver_env.t Fiber.t
|
||||
|
||||
module Version_preference : sig
|
||||
type t := Dune_pkg.Version_preference.t
|
||||
|
||||
val term : Dune_pkg.Version_preference.t option Term.t
|
||||
val choose : from_arg:t option -> from_context:t option -> t
|
||||
end
|
||||
|
||||
val unset_solver_vars_of_workspace
|
||||
: Workspace.t
|
||||
-> lock_dir_path:Path.Source.t
|
||||
-> Dune_lang.Package_variable_name.Set.t option
|
||||
|
||||
val repositories_of_workspace
|
||||
: Workspace.t
|
||||
-> Dune_pkg.Pkg_workspace.Repository.t Dune_pkg.Pkg_workspace.Repository.Name.Map.t
|
||||
|
||||
val repositories_of_lock_dir
|
||||
: Workspace.t
|
||||
-> lock_dir_path:Path.Source.t
|
||||
-> (Loc.t * Dune_pkg.Pkg_workspace.Repository.Name.t) list
|
||||
|
||||
val constraints_of_workspace
|
||||
: Workspace.t
|
||||
-> lock_dir_path:Path.Source.t
|
||||
-> Dune_lang.Package_dependency.t list
|
||||
|
||||
val depopts_of_workspace
|
||||
: Workspace.t
|
||||
-> lock_dir_path:Path.Source.t
|
||||
-> Package_name.t list
|
||||
|
||||
val get_repos
|
||||
: Dune_pkg.Pkg_workspace.Repository.t Dune_pkg.Pkg_workspace.Repository.Name.Map.t
|
||||
-> repositories:(Loc.t * Dune_pkg.Pkg_workspace.Repository.Name.t) list
|
||||
-> Dune_pkg.Opam_repo.t list Fiber.t
|
||||
|
||||
val find_local_packages : Dune_pkg.Local_package.t Package_name.Map.t Memo.t
|
||||
|
||||
module Lock_dirs_arg : sig
|
||||
(** [Lock_dirs_arg.t] is the type of lock directory arguments. This can be
|
||||
created with [Lock_dirs_arg.term] and used with
|
||||
[Lock_dirs_arg.lock_dirs_of_workspace]. *)
|
||||
type t
|
||||
|
||||
(** Select all lockdirs *)
|
||||
val all : t
|
||||
|
||||
(** [Lock_dirs_arg.term] is a command-line argument that can be used to
|
||||
specify the lock directories to consider. This can then be passed to
|
||||
[Lock_dirs_arg.lock_dirs_of_workspace].
|
||||
|
||||
There are two mutually exclusive cases:
|
||||
- The user passed a list of lick directories as positional
|
||||
arguments.contents
|
||||
- The user passed the ["--all"] flag, in which case all lock directories
|
||||
of the workspace are considered. *)
|
||||
val term : t Term.t
|
||||
|
||||
(** [Lock_dirs_arg.lock_dirs_of_workspace t workspace] returns the list of
|
||||
lock directories that should be considered for various operations.
|
||||
|
||||
The [workspace] argument is used to determine the list of all lock lock
|
||||
directories.
|
||||
|
||||
A user error is raised if the list of positional arguments used when
|
||||
creating [t] is not a subset of the lock directories of the workspace. *)
|
||||
val lock_dirs_of_workspace : t -> Workspace.t -> Path.Source.t list
|
||||
end
|
||||
|
||||
(** [pp_packages lock_dir] returns a list of pretty-printed packages occurring in
|
||||
[lock_dir]. *)
|
||||
val pp_packages : Dune_pkg.Lock_dir.Pkg.t list -> User_message.Style.t Pp.t
|
||||
36
unikernel/duniverse/dune_/bin/pkg/pkg_enabled.ml
Normal file
36
unikernel/duniverse/dune_/bin/pkg/pkg_enabled.ml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
open Import
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config (fun () ->
|
||||
Memo.run
|
||||
@@
|
||||
let open Memo.O in
|
||||
let+ workspace = Workspace.workspace () in
|
||||
let lock_dir_paths =
|
||||
Pkg_common.Lock_dirs_arg.lock_dirs_of_workspace
|
||||
Pkg_common.Lock_dirs_arg.all
|
||||
workspace
|
||||
in
|
||||
let any_lockdir_exists =
|
||||
List.exists lock_dir_paths ~f:(fun lock_dir_path ->
|
||||
Path.exists (Path.source lock_dir_path))
|
||||
in
|
||||
(* CR-Leonidas-from-XIV: change this logic when we stop detecting lock
|
||||
directories in the source tree *)
|
||||
let enabled = any_lockdir_exists || workspace.config.pkg_enabled in
|
||||
match enabled with
|
||||
| true -> ()
|
||||
| false -> exit 1)
|
||||
;;
|
||||
|
||||
let info =
|
||||
let doc =
|
||||
"Check if the project indicates that dune's package management features should be \
|
||||
enabled. Exits with 0 if package management is enabled and 1 otherwise."
|
||||
in
|
||||
Cmd.info "enabled" ~doc
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
3
unikernel/duniverse/dune_/bin/pkg/pkg_enabled.mli
Normal file
3
unikernel/duniverse/dune_/bin/pkg/pkg_enabled.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
open Import
|
||||
|
||||
val command : unit Cmd.t
|
||||
56
unikernel/duniverse/dune_/bin/pkg/print_solver_env.ml
Normal file
56
unikernel/duniverse/dune_/bin/pkg/print_solver_env.ml
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
open Import
|
||||
open Pkg_common
|
||||
|
||||
let print_solver_env_for_lock_dir workspace ~solver_env_from_current_system lock_dir_path =
|
||||
let solver_env_from_context =
|
||||
Option.bind (Workspace.find_lock_dir workspace lock_dir_path) ~f:(fun lock_dir ->
|
||||
lock_dir.solver_env)
|
||||
in
|
||||
let solver_env =
|
||||
solver_env
|
||||
~solver_env_from_current_system
|
||||
~solver_env_from_context
|
||||
~unset_solver_vars_from_context:
|
||||
(Pkg_common.unset_solver_vars_of_workspace workspace ~lock_dir_path)
|
||||
in
|
||||
Console.print
|
||||
[ Pp.textf
|
||||
"Solver environment for lock directory %s:"
|
||||
(Path.Source.to_string_maybe_quoted lock_dir_path)
|
||||
; Dune_pkg.Solver_env.pp solver_env
|
||||
]
|
||||
;;
|
||||
|
||||
let print_solver_env ~lock_dirs_arg =
|
||||
let open Fiber.O in
|
||||
let+ workspace = Memo.run (Workspace.workspace ())
|
||||
and+ solver_env_from_current_system =
|
||||
Dune_pkg.Sys_poll.make ~path:(Env_path.path Stdune.Env.initial)
|
||||
|> Dune_pkg.Sys_poll.solver_env_from_current_system
|
||||
>>| Option.some
|
||||
in
|
||||
let lock_dirs = Lock_dirs_arg.lock_dirs_of_workspace lock_dirs_arg workspace in
|
||||
List.iter
|
||||
lock_dirs
|
||||
~f:(print_solver_env_for_lock_dir workspace ~solver_env_from_current_system)
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ lock_dirs_arg = Lock_dirs_arg.term in
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config (fun () -> print_solver_env ~lock_dirs_arg)
|
||||
;;
|
||||
|
||||
let info =
|
||||
let doc =
|
||||
"Print a description of the environment that would be used to solve dependencies and \
|
||||
then exit without attempting to solve the dependencies or generate the lockfile. \
|
||||
Intended to be used to debug situations where no solution can be found to a \
|
||||
project's dependencies."
|
||||
in
|
||||
Cmd.info "print-solver-env" ~doc
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
4
unikernel/duniverse/dune_/bin/pkg/print_solver_env.mli
Normal file
4
unikernel/duniverse/dune_/bin/pkg/print_solver_env.mli
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
open Import
|
||||
|
||||
(** Command to print solver environment *)
|
||||
val command : unit Cmd.t
|
||||
88
unikernel/duniverse/dune_/bin/pkg/validate_lock_dir.ml
Normal file
88
unikernel/duniverse/dune_/bin/pkg/validate_lock_dir.ml
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
open! Import
|
||||
open Pkg_common
|
||||
module Package_universe = Dune_pkg.Package_universe
|
||||
module Lock_dir = Dune_pkg.Lock_dir
|
||||
module Opam_repo = Dune_pkg.Opam_repo
|
||||
module Package_version = Dune_pkg.Package_version
|
||||
module Opam_solver = Dune_pkg.Opam_solver
|
||||
|
||||
let info =
|
||||
let doc = "Validate that a lockdir contains a solution for local packages" in
|
||||
let man = [ `S "DESCRIPTION"; `P doc ] in
|
||||
Cmd.info "validate-lockdir" ~doc ~man
|
||||
;;
|
||||
|
||||
(* CR-someday alizter: The logic here is a little more complicated than it needs
|
||||
to be and can be simplified. *)
|
||||
|
||||
let enumerate_lock_dirs_by_path ~lock_dirs () =
|
||||
let open Memo.O in
|
||||
let+ per_contexts =
|
||||
Workspace.workspace () >>| Pkg_common.Lock_dirs_arg.lock_dirs_of_workspace lock_dirs
|
||||
in
|
||||
List.filter_map per_contexts ~f:(fun lock_dir_path ->
|
||||
if Path.exists (Path.source lock_dir_path)
|
||||
then (
|
||||
try Some (Ok (lock_dir_path, Lock_dir.read_disk_exn lock_dir_path)) with
|
||||
| User_error.E e -> Some (Error (lock_dir_path, `Parse_error e)))
|
||||
else None)
|
||||
;;
|
||||
|
||||
let validate_lock_dirs ~lock_dirs () =
|
||||
let open Fiber.O in
|
||||
let* lock_dirs_by_path, local_packages =
|
||||
Memo.both (enumerate_lock_dirs_by_path ~lock_dirs ()) Pkg_common.find_local_packages
|
||||
|> Memo.run
|
||||
in
|
||||
if List.is_empty lock_dirs_by_path
|
||||
then
|
||||
let+ () = Fiber.return () in
|
||||
Console.print [ Pp.text "No lockdirs to validate." ]
|
||||
else
|
||||
let+ universes =
|
||||
Fiber.parallel_map lock_dirs_by_path ~f:(function
|
||||
| Error e -> Fiber.return (Some e)
|
||||
| Ok (lock_dir_path, lock_dir) ->
|
||||
let+ platform = solver_env_from_system_and_context ~lock_dir_path in
|
||||
(match Package_universe.create ~platform local_packages lock_dir with
|
||||
| Ok _ -> None
|
||||
| Error e -> Some (lock_dir_path, `Lock_dir_out_of_sync e)))
|
||||
>>| List.filter_opt
|
||||
in
|
||||
match universes with
|
||||
| [] -> ()
|
||||
| errors_by_path ->
|
||||
List.iter errors_by_path ~f:(fun (path, error) ->
|
||||
match error with
|
||||
| `Parse_error error ->
|
||||
User_message.prerr
|
||||
(User_message.make
|
||||
[ Pp.textf
|
||||
"Failed to parse lockdir %s:"
|
||||
(Path.Source.to_string_maybe_quoted path)
|
||||
; User_message.pp error
|
||||
])
|
||||
| `Lock_dir_out_of_sync error ->
|
||||
User_message.prerr
|
||||
(User_message.make
|
||||
[ Pp.textf
|
||||
"Lockdir %s does not contain a solution for local packages:"
|
||||
(Path.Source.to_string path)
|
||||
]);
|
||||
User_message.prerr error);
|
||||
User_error.raise
|
||||
[ Pp.text "Some lockdirs do not contain solutions for local packages:"
|
||||
; Pp.enumerate errors_by_path ~f:(fun (path, _) ->
|
||||
Pp.text (Path.Source.to_string path))
|
||||
]
|
||||
;;
|
||||
|
||||
let term =
|
||||
let+ builder = Common.Builder.term
|
||||
and+ lock_dirs = Pkg_common.Lock_dirs_arg.term in
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
let common, config = Common.init builder in
|
||||
Scheduler.go_with_rpc_server ~common ~config @@ validate_lock_dirs ~lock_dirs
|
||||
;;
|
||||
|
||||
let command = Cmd.v info term
|
||||
4
unikernel/duniverse/dune_/bin/pkg/validate_lock_dir.mli
Normal file
4
unikernel/duniverse/dune_/bin/pkg/validate_lock_dir.mli
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
open Import
|
||||
|
||||
(** Command to check if local packages and lockdir agree *)
|
||||
val command : unit Cmd.t
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue