This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,591 @@
(*
* Copyright (c) 2013-2020 Thomas Gazagnaire <thomas@gazagnaire.org>
* Copyright (c) 2013-2020 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2015-2020 Gabriel Radanne <drupyog@zoho.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)
let setup_log style_renderer level =
Fmt_tty.setup_std_outputs ?style_renderer ();
Logs.set_level level;
Logs.set_reporter (Logs_fmt.reporter ())
open Cmdliner
let common_section = "COMMON OPTIONS"
let configuration_section = "CONFIGURE OPTIONS"
let query_section = "QUERY OPTIONS"
let description_section = "DESCRIBE OPTIONS"
type query_kind =
[ `Name
| `Packages
| `Opam
| `Files
| `Dune of [ `Config | `Build | `Project | `Workspace | `Dist ]
| `Makefile ]
let query_kinds : (string * query_kind) list =
[
("name", `Name);
("packages", `Packages);
("opam", `Opam);
("files", `Files);
("Makefile", `Makefile);
("dune.config", `Dune `Config);
("dune.build", `Dune `Build);
("dune-project", `Dune `Project);
("dune-workspace", `Dune `Workspace);
("dune.dist", `Dune `Dist);
]
let setup ~with_setup =
Term.(
const (if with_setup then setup_log else fun _ _ -> ())
$ Fmt_cli.style_renderer ~docs:common_section ()
$ Logs_cli.level ~docs:common_section ())
let config_file =
let doc =
Arg.info ~docs:configuration_section ~docv:"FILE"
~doc:"The configuration file to use."
[ "f"; "file"; "config-file" ]
in
Term.(const Fpath.v $ Arg.(value & opt string "config.ml" & doc))
let map_default ~default f x = if x == default then None else Some (f x)
let context_file mname =
let doc =
Arg.info ~docs:configuration_section ~docv:"FILE"
~doc:"The context file to use." [ "context-file" ]
in
let default = mname ^ ".context" in
Term.(
const (map_default ~default Fpath.v)
$ Arg.(value & opt string default & doc))
let extra_repos doc_section =
let key =
let parser str =
match Astring.String.cut ~sep:":" str with
| Some (name, repository) -> Ok (name, repository)
| None ->
Rresult.R.error_msgf
"Invalid extra repository argument (expected <name>:<repository>)"
in
let pp ppf (name, repository) = Fmt.pf ppf "%s:%s" name repository in
Arg.conv (parser, pp)
in
let env = Cmd.Env.info "MIRAGE_EXTRA_REPOS" in
let doc =
Arg.info ~docs:doc_section ~docv:"NAME1:URL1,NAME2:URL2,..." ~env
~doc:
"Additional opam-repositories to use when using `opam monorepo lock' \
to gather local sources. Default: \
https://github.com/dune-universe/opam-overlays.git & \
https://github.com/dune-universe/mirage-opam-overlays.git."
[ "extra-repos" ]
in
Arg.(
value
& opt (list key)
[
("opam-overlays", "https://github.com/dune-universe/opam-overlays.git");
( "mirage-overlays",
"https://github.com/dune-universe/mirage-opam-overlays.git" );
]
& doc)
let no_extra_repo doc_section =
let doc =
Arg.info ~docs:doc_section ~doc:"Disable the use of any overlay repository."
[ "no-extra-repo" ]
in
Arg.(value & flag & doc)
let extra_repos doc_section =
let ex = extra_repos doc_section in
let no_ex = no_extra_repo doc_section in
Term.(const (fun ex no_ex -> if no_ex then [] else ex) $ ex $ no_ex)
let dry_run =
let doc =
Arg.info ~docs:configuration_section
~doc:"Display I/O actions instead of executing them." [ "dry-run" ]
in
Arg.(value & flag doc)
(** * Argument specifications *)
(** Argument specification for --depext *)
let depext section =
let depext_doc =
Arg.info ~docs:section [ "depext" ]
~doc:"Enable call to `opam depext' in the project Makefile."
in
let no_depext_doc =
Arg.info ~docs:section [ "no-depext" ]
~doc:"Disable call to `opam depext' in the project Makefile."
in
let eval_opts = [ (true, depext_doc); (false, no_depext_doc) ] in
Arg.(value & vflag true eval_opts)
(** Argument specification for --eval *)
let full_eval =
let eval_doc =
Arg.info ~docs:description_section [ "eval" ]
~doc:
"Fully evaluate the graph before showing it. The default when the \
unikernel has already been configured."
in
let no_eval_doc =
Arg.info ~docs:description_section [ "no-eval" ]
~doc:
"Do not evaluate the graph before showing it. See $(b,--eval). The \
default when the unikernel has not been configured."
in
let eval_opts = [ (Some true, eval_doc); (Some false, no_eval_doc) ] in
Arg.(value & vflag None eval_opts)
(** Argument specification for --dot *)
let dot =
let doc =
Arg.info ~docs:description_section [ "dot" ]
~doc:
"Output a dot description. If no output file is given, it will display \
the dot file using the command given to $(b,--dot-command). Use in \
combination with $(b,--output=-) (short version: $(b,-o-)) to display \
the dot file on stdout."
in
Arg.(value & flag doc)
(** Argument specification for --dot-command=COMMAND *)
let dotcmd =
let doc =
Arg.info ~docs:description_section ~docv:"COMMAND" [ "dot-command" ]
~doc:
"Command used to show a dot file. This command should accept a dot \
file on its standard input."
in
Arg.(value & opt string "xdot" & doc)
(** Argument specification for -o FILE or --output=FILE *)
let output =
let doc =
Arg.info ~docs:configuration_section ~docv:"FILE" [ "o"; "output" ]
~doc:"Name of the output file."
in
Arg.(value & opt (some string) None & doc)
let kind =
let enums = Arg.doc_alts_enum ~quoted:true query_kinds in
let doc =
Arg.info ~docs:configuration_section ~docv:"INFO" []
~doc:(Fmt.str "The information to query. $(docv) must be %s" enums)
in
Arg.(value & pos 0 (enum query_kinds) `Packages & doc)
type 'a args = {
context : 'a;
config_file : Fpath.t;
context_file : Fpath.t option;
output : string option;
dry_run : bool;
}
let default_args =
{
context = ();
config_file = Fpath.v "dummy";
context_file = None;
output = None;
dry_run = false;
}
type 'a configure_args = {
args : 'a args;
depext : bool;
extra_repo : (string * string) list;
}
type 'a build_args = 'a args
type 'a clean_args = 'a args
type 'a help_args = 'a args
type 'a describe_args = {
args : 'a args;
dotcmd : string;
dot : bool;
eval : bool option;
}
type 'a query_args = {
args : 'a args;
kind : query_kind;
depext : bool;
extra_repo : (string * string) list;
}
type 'a action =
| Configure of 'a configure_args
| Query of 'a query_args
| Describe of 'a describe_args
| Clean of 'a clean_args
| Help of 'a help_args
(*
* Pretty-printing
*)
let pp_args pp_a =
let open Fmt.Dump in
record
[
field "context" (fun (t : 'a args) -> t.context) pp_a;
field "config_file" (fun t -> t.config_file) Fpath.pp;
field "output" (fun t -> t.output) (option string);
field "dry_run" (fun t -> t.dry_run) Fmt.bool;
]
let pp_configure pp_a =
let open Fmt.Dump in
record
[
field "args" (fun (t : 'a configure_args) -> t.args) (pp_args pp_a);
field "depext" (fun (t : 'a configure_args) -> t.depext) Fmt.bool;
]
let pp_clean = pp_args
let pp_help = pp_args
let pp_query_kind ppf (q : query_kind) =
let rec aux = function
| [] -> invalid_arg "missing query kind!"
| (a, b) :: t -> if b = q then Fmt.string ppf a else aux t
in
aux query_kinds
let pp_query pp_a =
let open Fmt.Dump in
record
[
field "args" (fun (t : 'a query_args) -> t.args) (pp_args pp_a);
field "kind" (fun t -> t.kind) pp_query_kind;
field "depext" (fun t -> t.depext) Fmt.bool;
]
let pp_describe pp_a =
let open Fmt.Dump in
record
[
field "args" (fun (t : 'a describe_args) -> t.args) (pp_args pp_a);
field "dotcmd" (fun t -> t.dotcmd) string;
field "dot" (fun t -> t.dot) Fmt.bool;
field "eval" (fun t -> t.eval) (option Fmt.bool);
]
let pp_action pp_a ppf = function
| Configure c -> Fmt.pf ppf "@[configure:@ @[<2>%a@]@]" (pp_configure pp_a) c
| Query q -> Fmt.pf ppf "@[query:@ @[<2>%a@]@]" (pp_query pp_a) q
| Describe d -> Fmt.pf ppf "@[describe:@ @[<2>%a@]@]" (pp_describe pp_a) d
| Clean c -> Fmt.pf ppf "@[clean:@ @[<2>%a@]@]" (pp_clean pp_a) c
| Help h -> Fmt.pf ppf "@[help:@ @[<2>%a@]@]" (pp_help pp_a) h
(*
* Subcommand specifications
*)
module Subcommands = struct
type 'a t = { with_setup : bool; mname : string; context : 'a Term.t }
module T = struct
let args { with_setup; context; mname } =
Term.(
const (fun () config_file context_file dry_run output context ->
{ config_file; context_file; dry_run; output; context })
$ setup ~with_setup
$ config_file
$ context_file mname
$ dry_run
$ output
$ context)
end
(** The 'configure' subcommand *)
let configure t =
( Term.(
const (fun args depext extra_repo ->
Configure { args; depext; extra_repo })
$ T.args t
$ depext configuration_section
$ extra_repos configuration_section),
Cmd.info "configure" ~doc:"Configure a $(mname) application."
~man:
[
`S "DESCRIPTION";
`P
"The $(b,configure) command initializes a fresh $(mname) \
application.";
] )
let query t =
( Term.(
const (fun kind args depext extra_repo ->
Query { kind; args; depext; extra_repo })
$ kind
$ T.args t
$ depext query_section
$ extra_repos query_section),
Cmd.info "query" ~doc:"Query information about the $(mname) application."
~man:
[
`S "DESCRIPTION";
`P
"The $(b,query) command queries information about the $(mname) \
application.";
] )
(** The 'describe' subcommand *)
let describe t =
( Term.(
const (fun args eval dotcmd dot -> Describe { args; eval; dotcmd; dot })
$ T.args t
$ full_eval
$ dotcmd
$ dot),
Cmd.info "describe" ~doc:"Describe a $(mname) application."
~man:
[
`S "DESCRIPTION";
`P
"The $(b,describe) command describes the configuration of a \
$(mname) application.";
`P "The dot output contains the following elements:";
`Noblank;
`I
( "If vertices",
"Represented as circles. Branches are dotted, and the default \
branch is in bold." );
`Noblank;
`I
( "Configurables",
"Represented as rectangles. The order of the output arrows is \
the order of the functor arguments." );
`Noblank;
`I ("Data dependencies", "Represented as dashed arrows.");
`Noblank;
`I
( "App vertices",
"Represented as diamonds. The bold arrow is the functor part."
);
] )
(** The 'clean' subcommand *)
let clean t =
let doc = "Clean the files produced by $(mname) for a given application." in
( Term.(const (fun args -> Clean args) $ T.args t),
Cmd.info "clean" ~doc ~man:[ `S "DESCRIPTION"; `P doc ] )
(** The 'help' subcommand *)
let help t =
let topic =
let doc = Arg.info [] ~docv:"TOPIC" ~doc:"The topic to get help on." in
Arg.(value & pos 0 (some string) None & doc)
in
let help man_format cmds topic =
match topic with
| None -> `Help (man_format, None)
| Some topic -> (
let parser =
Arg.conv_parser
(Arg.enum (List.rev_map (fun s -> (s, s)) ("topics" :: cmds)))
in
match parser topic with
| Error (`Msg e) -> `Error (false, e)
| Ok t when t = "topics" ->
List.iter print_endline cmds;
`Ok ()
| Ok t -> `Help (man_format, Some t))
in
( Term.(
const (fun args _ _ _ () -> Help args)
$ T.args t
$ depext configuration_section
$ extra_repos configuration_section
$ full_eval
$ ret (const help $ Arg.man_format $ Term.choice_names $ topic)),
Cmd.info "help" ~doc:"Display help about $(mname) commands."
~man:
[
`S "DESCRIPTION";
`P "Prints help.";
`P "Use `$(mname) help topics' to get the full list of help topics.";
] )
let default ~with_setup ~name ~version =
let usage = `Help (`Plain, None) in
( Term.(ret (const usage) $ setup ~with_setup),
Cmd.info name ~version ~doc:"The $(mname) application builder"
~man:
[
`S "DESCRIPTION";
`P
"The $(mname) application builder. It glues together a set of \
libraries and configuration (e.g. network and storage) into a \
standalone unikernel or UNIX binary.";
`P
"Use $(mname) $(b,help <command>) for more information on a \
specific command.";
] )
end
(*
* Functions for extracting particular flags from the command line.
*)
let peek_full_eval argv =
match Cmd.eval_peek_opts ~argv full_eval with _, Ok (`Ok b) -> b | _ -> None
let peek_output argv =
match Cmd.eval_peek_opts ~argv output with _, Ok (`Ok b) -> b | _ -> None
let peek_args ?(with_setup = false) ~mname argv =
let args =
Subcommands.T.args { with_setup; mname; context = Term.const () }
in
match Cmd.eval_peek_opts ~argv args with
| _, Ok (`Ok b) | Some b, _ -> Some b
| _ -> None
let eval ?(with_setup = true) ?help_ppf ?err_ppf ~name ~version ~configure
~query ~describe ~clean ~help ~mname argv =
let default, info = Subcommands.default ~with_setup ~name ~version in
let args context = { Subcommands.with_setup; mname; context } in
let group =
Cmd.group ~default info
(List.map
(fun (term, info) -> Cmd.v info term)
[
Subcommands.configure (args configure);
Subcommands.describe (args describe);
Subcommands.query (args query);
Subcommands.clean (args clean);
Subcommands.help (args help);
])
in
match Cmd.eval_value ?help:help_ppf ?err:err_ppf ~argv ~catch:false group with
| Ok (#Cmd.eval_ok as v) -> v
| Error (#Cmd.eval_error as e) -> `Error e
let args = function
| Configure { args; _ } -> args
| Clean x | Help x -> x
| Query { args; _ } -> args
| Describe { args; _ } -> args
let choices =
[
("configure", `Configure);
("clean", `Clean);
("query", `Query);
("describe", `Describe);
("help", `Help);
]
let find_choices s =
List.find_all (fun (k, _) -> Astring.String.is_prefix ~affix:s k) choices
let find_kind s =
List.find_all (fun (k, _) -> Astring.String.is_prefix ~affix:s k) query_kinds
let next_pos_arg argv i =
let rec aux i =
if i >= Array.length argv then None
else if argv.(i) = "" then aux (i + 1)
else if argv.(i).[0] = '-' then aux (i + 1)
else Some i
in
aux i
let remove_argv argv i =
let a = Array.sub argv 0 i in
let b = Array.sub argv (i + 1) (Array.length argv - i - 1) in
Array.append a b
let rec find_next_kind argv i =
match next_pos_arg argv i with
| None -> (None, argv)
| Some i -> (
match find_kind argv.(i) with
| [] -> find_next_kind argv (i + 1)
| _ :: _ :: _ as cs ->
Fmt.invalid_arg "ambiguous sub-command: %a\n%!"
Fmt.Dump.(list string)
(List.map fst cs)
| [ (_, k) ] -> (Some k, remove_argv argv i))
let rec find_next_choice argv i =
match next_pos_arg argv i with
| None -> (None, argv)
| Some i -> (
match find_choices argv.(i) with
| [] -> find_next_choice argv (i + 1)
| _ :: _ :: _ as cs ->
Fmt.invalid_arg "ambiguous sub-command: %a\n%!"
Fmt.Dump.(list string)
(List.map fst cs)
| [ (_, a) ] -> (
match a with
| (`Configure | `Clean | `Describe | `Help) as c ->
(Some c, remove_argv argv i)
| `Query ->
let k, argv = find_next_kind argv (i + 1) in
(Some (`Query k), remove_argv argv i)))
let peek_choice argv =
try match find_next_choice argv 1 with Some c, _ -> `Ok c | _ -> `Default
with Invalid_argument _ -> `Error `Parse
type 'a result =
[ `Ok of 'a action
| `Error of 'a args option * [ `Exn | `Parse | `Term ]
| `Version ]
let peek ?(with_setup = false) ~mname argv : unit result =
let niet = Term.const () in
let peek t =
match Cmd.eval_peek_opts ~argv ~version_opt:true (fst t) with
| _, Ok `Version -> `Version
| _, Error e -> `Error (peek_args ~mname argv, e)
| _, Ok `Help -> (
let args = peek_args ~with_setup:false ~mname argv in
match args with
| Some args -> `Ok (Help args)
| _ -> `Error (None, `Parse))
| Some v, _ | _, Ok (`Ok v) -> `Ok v
in
let peek_cmd f =
let args = { Subcommands.with_setup; mname; context = niet } in
peek (f args)
in
match peek_choice argv with
| `Ok `Configure -> peek_cmd Subcommands.configure
| `Ok `Clean -> peek_cmd Subcommands.clean
| `Ok (`Query _) -> peek_cmd Subcommands.query
| `Ok `Describe -> peek_cmd Subcommands.describe
| `Ok `Help -> peek_cmd Subcommands.help
| `Default ->
peek (Subcommands.default ~with_setup ~name:"<name>" ~version:"<version>")
| `Error e -> `Error (peek_args ~mname argv, e)