Library
Module
Module type
Parameter
Class
Class type
You are responsible for creating a module of type Component_config
to describe your component. A do-nothing implementation for most of the module is available as Default_component_config
.
Dkml_install_api
will use the Component_config
to create four (4) command line applications.
Each of the command line applications are "subcommands" in the language of the OCaml Cmdliner
package. You will not need to understand Cmdliner to define your own component, although you may visit the Cmdliner documentation if you want more information.
The four (4) command line applications have limited access to the OCaml runtime. The expectation is that all installation logic is embedded in bytecode executables which have the complete set of package dependencies you need to run your logic. Through Component_config
Dkml_install_api
will have given you a ~ctx_t
Cmdliner term that, when evaluated, leads to the context record Context.t
. The context record has the information needed to run your bytecode executables.
On Windows it is recommended security practice to separate functionality that requires administrative privileges from functionality that does not require non-administrative privileges. Dkml_install_api
follows the same recommendations:
"<package>-admin-runner.exe"
that is responsible for the following functionality for all components:Component_config.install_admin_subcommand
Component_config.uninstall_admin_subcommand
"<package>-user-runner.exe"
that is responsible for the following functionality for all components:Component_config.install_user_subcommand
Component_config.uninstall_user_subcommand
module Context : sig ... end
Context
is a module providing a record type for the context.
module type Component_config_defaultable = sig ... end
Component configuration values that can be supplied with defaults.
module type Component_config = sig ... end
Each component must define a configuration module
You should include Default_component_config
in any of your components so that your component can be future-proof against changes in the Component_config
signature.
module Default_component_config : sig ... end
Default values for a subset of the module type Component_config
.
val log_spawn_onerror_exit :
id:string ->
?success_exitcodes:(int -> bool) ->
?conformant_subprocess_exitcodes:bool ->
Bos.Cmd.t ->
unit
log_spawn_onerror_exit ~id ?success_exitcodes ?conformant_subprocess_exitcodes cmd
logs the command cmd
and runs it synchronously, and prints an error on the fatal logger fl ~id
and then exits with a non-zero exit code if the command exits with a non-zero error code.
The environment variable "OCAMLRUNPARAM"
will be set to "b"
so that any OCaml bytecode launched by log_spawn_onerror_exit
will have backtraces. Any exiting environment variable "OCAMLRUNPARAM"
will be kept, however.
Success Exit Codes
By default exit code 0 is determined to be a success, and every other exit code is determined to be a failure. The success_exitcodes
parameter can be specified to change which codes are determined to be successes.
Further exit code process is described in the next section after an exit code is determined to be a failure.
Failed Exit Codes
The exit code used to leave this process depends on conformant_subprocess_exitcodes
.
When conformant_subprocess_exitcodes = true
or conformant_subprocess_exitcodes
is not specified, the exit code will be the same as the spawned process exit code if and only if the exit code belongs to one of Forward_progress.Exit_code
; if the spawned exit code does not belong then the exit code will be Forward_progress.Exit_code.t.Exit_transient_failure
.
When conformant_subprocess_exitcodes = false
the exit code will always be Forward_progress.Exit_code.t.Exit_transient_failure
if the spawned process ends in error.
val uninstall_directory_onerror_exit :
id:string ->
dir:Fpath.t ->
wait_seconds_if_stuck:float ->
unit
uninstall_directory ~id ~dir ~wait_seconds_if_stuck
removes the directory dir
and, if any process is using the files in dir
, will give the wait_seconds_if_stuck
seconds to stop using the program. If the directory cannot be removed then prints an error on the fatal logger fl ~id
and exists with a transient error code.
For Windows machines a file cannot be removed if it is in use. For most *nix machines the file can be removed since the inode lives on. Consequently only on Windows machines will trigger the logic to check if a process is using a file or directory. This behavior may change in the future.
Logging follows the Cmdliner standards.
All dkml_install generated executables can be supplied with the following options:
--color=WHEN (absent=auto) Colorize the output. WHEN must be one of `auto', `always' or `never'. -q, --quiet Be quiet. Takes over -v and --verbosity. -v, --verbose Increase verbosity. Repeatable, but more than twice does not bring more. --verbosity=LEVEL (absent=warning) Be more or less verbose. LEVEL must be one of `quiet', `error', `warning', `info' or `debug'. Takes over -v.
You can use Log_config
to pass the color and verbosity options into your own bytecode executables.
Start by initializing the logger in your own executables with the following setup_log_t
Cmdliner Term:
let setup_log style_renderer level =
Fmt_tty.setup_std_outputs ?style_renderer ();
Logs.set_level level;
Logs.set_reporter (Logs.format_reporter ());
Dkml_install_api.Log_config.create ?log_config_style_renderer:style_renderer
?log_config_level:level ()
let setup_log_t =
Term.(const setup_log $ Fmt_cli.style_renderer () $ Logs_cli.level ())
Finally, with a Log_config.t
you can use Log_config.to_args
to pass the correct command line options into your own executables. For components that are configured to spawn bytecode programs you can locate the Log_config.t
in the Dkml_install_api.Context.t.log_config
(ctx.Dkml_install_api.Context.log_config
) context field. That could look like:
let execute ctx =
let ocamlrun =
ctx.Context.path_eval "%{staging-ocamlrun:share-abi}/bin/ocamlrun"
in
log_spawn_onerror_exit
(* Always use your own unique id; create it with PowerShell on Windows:
[guid]::NewGuid().Guid.Substring(0,8)
or on macOS/Unix:
uuidgen | tr A-Z a-z | cut -c1-8
*)
~id:"9b7e32e0"
Cmd.(
v (Fpath.to_string
(ctx.Context.path_eval "%{staging-ocamlrun:share-abi}/bin/ocamlrun"))
% Fpath.to_string
(ctx.Context.path_eval "%{_:share}%/generic/your_bytecode.bc")
(* Pass --verbosity and --color to your bytecode *)
%% of_list (Array.to_list (Log_config.to_args ctx.Context.log_config)))
let () =
let reg = Component_registry.get () in
Component_registry.add_component reg
(module struct
include Default_component_config
let component_name = "enduser-yourcomponent"
let install_depends_on = [ "staging-ocamlrun" ]
let install_user_subcommand ~component_name:_ ~subcommand_name ~fl ~ctx_t =
let doc = "Install your component" in
Dkml_install_api.Forward_progress.Continue_progress (Cmdliner.Term.(const execute $ ctx_t, info subcommand_name ~doc), fl)
end)
Others can use the Log_config.t
return value from setup_log
when calling Log_config.to_args
.
module Log_config : module type of Log_config
module Forward_progress : sig ... end
Forward_progress
provides common functions to handle graceful and informative exits from the nested chain of subprocesses typical in DKML Install API and many other applications.