123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220(* Most of this content is a modification of:
https://github.com/diskuv/dkml-component-ocamlcompiler/blob/d01c4f78ee1a794dab53b27f7fcb999f0b956f98/src/api/staging-ocamlrun/staging_ocamlrun_api.ml *)openBosopenAstringtypecli_opts={ci:bool}letis_not_definednameenv=matchString.Map.findnameenvwith|None->true|Some""->true|Some_->falseletabi_will_popup_terminalhost_abi=Dkml_install_api.Context.Abi_v2.is_windowshost_abi(** [wait_for_user_confirmation_if_popup_terminal] asks the user to press "y"
if and only if all the following are true: the terminal is a tty, the
--ci option was not used, and the terminal is on a
platform that will pop open a new terminal when running the setup (today
only Windows ABIs are popup terminal platforms).
Typically this should be used before exiting the program so that the user
has a chance to see the final messages, especially success or failure.
*)letwait_for_user_confirmation_if_popup_terminal?info_ci{ci}install_directionhost_abi=match(ci,abi_will_popup_terminalhost_abi,Unix.isattyUnix.stdin)with|false,true,true->ifinstall_direction=Dkml_install_runner.Path_eval.Global_context.Install&&(info_ci=None||info_ci=Sometrue)then(prerr_newline();prerr_endline"[INFO] Use --ci at beginning of command line arguments to skip the \
confirmation question in future installations.");(* Sigh. Would just like to wait for a single character "y" rather
than "y" + ENTER. However no easy OCaml interface to that, and more
importantly we don't have any discard-prior-keyboard-events API
on Windows like Unix.tcflush that can ensure the user has had a
chance to see the final messages (as opposed to accidentally
pressing a key early in the install process, and having that
keystroke be interpreted at the end of the installer as confirmation
that they have read the final messages). *)letrechelper()=letinstaller_what=matchinstall_directionwith|Dkml_install_runner.Path_eval.Global_context.Install->"installer"|Uninstall->"uninstaller"inprerr_newline();prerr_endline(Fmt.str{|Press "y" and ENTER to exit the %s.|}installer_what);matchread_line()with|"y"->(* 7zip sfx needs to delete the possibly large temporary
directory it uninstalled, so give user some feedback. *)prerr_endline"Exiting ...";()|_->helper()inhelper()|_->()(** [get_and_remove_path env] finds the first of the ["PATH"] or the ["Path"] environment variable (the latter
is present sometimes on Windows), and removes the same two environment variables from [env]. *)letget_and_remove_pathenv=(*
TODO: STOP DUPLICATING THIS CODE! The canonical source is
dkml-component-ocamlrun's staging_ocamlrun_api.ml
*)letold_path_as_list=matchString.Map.find_opt"PATH"envwith|Somevwhenv!=""->[v]|_->(matchString.Map.find_opt"Path"envwith|Somevwhenv!=""->[v]|_->[])inletnew_env=String.Map.remove"PATH"envinletnew_env=String.Map.remove"Path"new_envin(old_path_as_list,new_env)(** [spawn_ocamlrun] sets the environment variables needed for
ocamlrun.exe. *)letspawn_ocamlrun~ocamlrun_exe~install_direction~target_abi~lib_ocaml~cli_optscmd=(*
TODO: STOP DUPLICATING THIS CODE! The canonical source is
dkml-component-ocamlrun's staging_ocamlrun_api.ml
*)letnew_cmd=Cmd.(v(Fpath.to_stringocamlrun_exe)%%cmd)inLogs.info(funm->m"Running bytecode with: %a"Cmd.ppnew_cmd);let(let*)=Result.bindinletsequence=let*new_env=OS.Env.current()inletold_path_as_list,new_env=get_and_remove_pathnew_envin(* Definitely enable stacktraces *)letnew_env=ifis_not_defined"OCAMLRUNPARAM"new_envthenString.Map.add"OCAMLRUNPARAM""b"new_envelsenew_envin(* Handle dynamic loading *)letnew_env=String.Map.add"OCAMLLIB"(Fpath.to_stringlib_ocaml)new_envin(* Handle the early loading of dllunix by ocamlrun *)letstublibs=Fpath.(lib_ocaml/"stublibs")inletnew_env=matchtarget_abiwith|_whenDkml_install_api.Context.Abi_v2.is_windowstarget_abi->(* Add lib/ocaml/stublibs to PATH for Win32
to locate the dllunix.dll *)letpath_sep=ifSys.win32then";"else":"inletnew_path_entries=[Fpath.(to_stringstublibs)]@old_path_as_listinletnew_path=String.concat~sep:path_sepnew_path_entriesinString.Map.add"PATH"new_pathnew_env|_whenDkml_install_api.Context.Abi_v2.is_darwintarget_abi->(* Add lib/ocaml/stublibs to DYLD_FALLBACK_LIBRARY_PATH for macOS
to locate the dllunix.so *)String.Map.add"DYLD_FALLBACK_LIBRARY_PATH"Fpath.(to_stringstublibs)new_env|_whenDkml_install_api.Context.Abi_v2.is_linuxtarget_abi||Dkml_install_api.Context.Abi_v2.is_androidtarget_abi->(* Add lib/ocaml/stublibs to LD_LIBRARY_PATH for Linux and Android
to locate the dllunix.so *)String.Map.add"LD_LIBRARY_PATH"Fpath.(to_stringstublibs)new_env|_->new_envinOS.Cmd.run_status~env:new_envnew_cmdinletwait?info_ci()=wait_for_user_confirmation_if_popup_terminal?info_cicli_optsinstall_directiontarget_abiinmatchsequencewith|Ok(`Exited0)->ifLogs.level()=SomeLogs.DebugthenLogs.info(funl->l"The command %a ran successfully"Cmd.ppcmd)elseLogs.info(funl->l"The command %a ran successfully"Fmt.(optionstring)(Cmd.line_toolcmd));wait()|Ok(`Exitedc)->(* An exit code from one of the predefined exit codes already has
the root cause printed. Don't obscure the console by printing
more errors. *)letconforming_exitcode=List.mapDkml_install_api.Forward_progress.Exit_code.to_int_exitcodeDkml_install_api.Forward_progress.Exit_code.values|>List.memcinifnotconforming_exitcodethenLogs.err(funl->l"The command %a exited with status %d"Cmd.ppcmdc);wait~info_ci:false();exit2|Ok(`Signaledc)->Logs.err(funl->l"The command %a terminated from a signal %d"Cmd.ppcmdc);wait~info_ci:false();(* https://stackoverflow.com/questions/1101957/are-there-any-standard-exit-status-codes-in-linux/1535733#1535733 *)exit(128+c)|Errorrmsg->Logs.err(funl->l"The command %a could not be run due to: %a"Cmd.ppcmdRresult.R.pp_msgrmsg);wait~info_ci:false();exit3letentry~install_direction~target_abi=(* Default logging *)let(_:Dkml_install_api.Log_config.t)=Dkml_install_runner.Cmdliner_runner.setup_logNoneNonein(* Get args, if any.
If there are no arguments, supply defaults so that there is console
logging. *)letcli_opts,argl=letrechelpercli_opts'argl'=match(Sys.win32,argl')with|true,[]->(* Windows does not have a TERM environment variable for auto-detection,
but color always works in Command Prompt or PowerShell *)(cli_opts',["-v";"--color=always"])|false,[]->(cli_opts',["-v"])|_,"--ci"::rest->helper{ci=true}rest|_->(cli_opts',argl')inhelper{ci=false}(List.tl(Array.to_listSys.argv))inletargs=Cmd.of_listarglin(* Find ocamlrun and ocaml lib. *)letarchive_dir=Dkml_install_runner.Error_handling.continue_or_exit@@Dkml_install_runner.Cmdliner_runner.enduser_archive_dir()inletocamlrun_dir=Fpath.(archive_dir/"sg"/"staging-ocamlrun"/Dkml_install_api.Context.Abi_v2.to_canonical_stringtarget_abi)inletocamlrun_exe=Fpath.(ocamlrun_dir/"bin"/"ocamlrun.exe")inletlib_ocaml=Fpath.(ocamlrun_dir/"lib"/"ocaml")in(* Run the packager bytecode with any arguments it needs *)letsetup_bc=Fpath.(archive_dir/"bin"/"dkml-package.bc")inspawn_ocamlrun~ocamlrun_exe~install_direction~target_abi~lib_ocaml~cli_optsCmd.(v(Fpath.to_stringsetup_bc)%%args)