Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

init: detect local cygwin installation #5544

Merged
merged 16 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 18 additions & 1 deletion master_changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ users)
* Surround and add a comment describing the role of the lines added to the ~/.profile or equivalent [#5456 @kit-ty-kate]
* Don't require cc on Windows [#5541 @dra27]
* Generate init and variables for Windows [#5541 @dra27]
* On Windows, ask for pre-existent Cygwin installation, check it, and configure opam with it [#5544 @dra27 @rjbou]

## Config report
* [BUG] Don't fail is no switch is set [#5198 @rjbou]
Expand All @@ -74,6 +75,7 @@ users)
* ◈ Rename --with-tools` to `--with-dev-setup` [#5214 @rjbou - fix #4959]
* Use the default criteria during reinstall/upgrade when requesting at least one non-installed package [#5228 @kit-ty-kate]
* Show the reason for installing packages when using opam reinstall [#5229 @kit-ty-kate]
* When defined, add cygwin binary path to build environment [#5543 @rjbou]

## Remove
*
Expand Down Expand Up @@ -201,6 +203,7 @@ users)

## External dependencies
* Depexts support Cygwin on Windows [#5542 @rjbou]
* Default location of setup.exe is now `<opamroot>/.cygwin/setup-x86_64.exe` [#5544 @rjbou]
* Support MSYS2 on Windows for depexts [#5348 @jonahbeckford #5433 @rjbou]
* Set `DEBIAN_FRONTEND=noninteractive` for unsafe-yes confirmation level [#4735 @dra27 - partially fix #4731] [2.1.0~rc2 #4739]
* Fix depext alpine tagged repositories handling [#4763 @rjbou] [2.1.0~rc2 #4758]
Expand Down Expand Up @@ -597,6 +600,8 @@ users)
* `OpamListCommand`: add `swhid` in `info` printable fields and its handling in `details_printer`
* ✘ `OpamListCommand.apply_selector`, `string_of_selector`: change column name base to invariant, and the content is invariant formula installed dependencies [#5208 @rjbou]
* `OpamSwitchCommand.install_compiler`: fill empty switch synopsis with invariant formula instead of compiler package name [#5208 @rjbou]
* `OpamArg.opam_init`: retrieve cygwin binary path from config (low level reading) to add it to opamCoreConfig.r.cygbin [#5543 @rjbou]
* `OpamAction`: when defined, add cygwin binary path to build environment [#5543 @rjbou]

## opam-repository
* `OpamRepositoryConfig`: add in config record `repo_tarring` field and as an argument to config functions, and a new constructor `REPOSITORYTARRING` in `E` environment module and its access function [#5015 @rjbou]
Expand Down Expand Up @@ -631,7 +636,6 @@ users)
* `OpamUpdate`: change `repository` output to update function option, to not write cache and new repo config if nothing changed in `repositories` [#5146 @rjbou]
* Add `OpamPinned.version_opt` [#5325 @kit-ty-kate]
* `OpamUpdate.download_package_source`: add SWH fallback when archive remain not found [#4859 @rjbou]

* Add optional argument `?env:(variable_contents option Lazy.t * string) OpamVariable.Map.t` to `OpamSysPoll` and `OpamSysInteract` functions. It is used to get syspolling variables from the environment first. [#4892 @rjbou]
* `OpamSwitchState`: move and reimplement `opam-solver` `dependencies` and `reverse_dependencies` [#5337 @rjbou]
* `OpamEnv`: add `env_expansion` [#5352 @dra27]
Expand All @@ -646,6 +650,11 @@ users)
* `OpamSwitchState`: add `compiler_packages` that returns set of installed compilers, with their dependencies including only build & depopt [#5480 @rjbou]
* `OpamEnv`: generalise splitting of environment variables [#5541 @dra27]
* `OpamEnv`: add handling of `SH_pwsh` and `SH_cmd` in shell lists [#5541 @dra27]
* `OpamSysInteract.Cygwin`: add `cygbin_opt` to retrieve cygwin binary path from config file [#5543 @rjbou]
* `OpamGlobalState.load`: Retrieve cygwin binary path from config to add it to opamCoreConfig.r.cygbin [#5543 @rjbou]
* `OpamSysInteract.Cygwin`: add `check_install` to check that a given path is a cygwin installation, regarding presence of `cygcheck.exe` [#5544 @rjbou @dra27]
* `OpamSysInteract.Cygwin`: add `check_setup` to check, copy or download a cygwin setup.exe [#5544 @rjbou]


## opam-solver
* `OpamCudf`: Change type of `conflict_case.Conflict_cycle` (`string list list` to `Cudf.package action list list`) and `cycle_conflict`, `string_of_explanations`, `conflict_explanations_raw` types accordingly [#4039 @gasche]
Expand Down Expand Up @@ -741,3 +750,11 @@ users)
* `OpamStd.Sys`: add `SH_pwsh`, `SH_win_cmd` and `SH_win_powershell` to `shell` type [#4816 @jonahbeckford]
* unify powershell variant: `SH_win_powershell` and `SH_pwsh` to `SH_pwsh of powershell_host` [#5203 @dra27]
* change `SH_win_cmd` into `SH_cmd` [#5541 @dra27]
* `OpamCoreConfig`: add `cygbin`, the cygwin install binary path [#5543 @rjbou]
* `OpamStd.Env`: add `cyg_env` that returns the environment with PATH containing cygwin binary path [#5543 @rjbou]
* `OpamCompat`: add `Filename.quote_command` [#5543 @rjbou]
* `OpamStd.Sys.get_windows_executable`: Add `cygbin` argument to pass cygwin binary path [#5543 @rjbou]
* `OpamStd.Sys.is_cygwin_variant`: returns a boolean [#5543 @rjbou]
* `OpamStd.Sys`: add `is_cygwin_cygcheck` anf `get_cygwin_variant` [#5543 @rjbou]
* `OpamProcess`: add `default_env` to retrieve environment, if cygwin is set, adds cygwin binary path to environment ; and use it instead of `Unix.environment` [#5543 @rjbou]
* `OpamProcess.apply_cygpath`: fix empty output [#5543 @rjbou]
9 changes: 8 additions & 1 deletion src/client/opamAction.ml
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,12 @@ let compilation_env t opam =
let build_env =
List.map (OpamEnv.env_expansion ~opam t) (OpamFile.OPAM.build_env opam)
in
let cygwin_env =
match OpamSysInteract.Cygwin.cygbin_opt t.switch_global.config with
| Some cygbin ->
[ "PATH", EqPlus, OpamFilename.Dir.to_string cygbin, Some "Cygwin path" ]
| None -> []
in
let scrub = OpamClientConfig.(!r.scrubbed_environment_variables) in
OpamEnv.get_full ~scrub ~set_opamroot:true ~set_opamswitch:true
~force_path:true t ~updates:([
Expand All @@ -538,7 +544,8 @@ let compilation_env t opam =
Some "build environment definition";
"OPAMCLI", Eq, "2.0", Some "opam CLI version";
] @
build_env)
build_env
@ cygwin_env)

let installed_opam_opt st nv =
OpamStd.Option.Op.(
Expand Down
33 changes: 31 additions & 2 deletions src/client/opamArg.ml
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ let apply_global_options cli o =
?confirm_level:o.confirm_level
?yes
?safe_mode:(flag o.safe_mode)
(* ?cygbin:string option *)
(* ?lock_retries:int *)
(* ?log_dir:OpamTypes.dirname *)
(* ?keep_log_dir:bool *)
Expand Down Expand Up @@ -570,8 +571,36 @@ let apply_global_options cli o =
OpamJson.append "opam-version" (`String OpamVersion.(to_string (full ())));
OpamJson.append "command-line"
(`A (List.map (fun s -> `String s) (Array.to_list Sys.argv)))
)

);
(* We need to retrieve very early cygwin root path to set up 'cygbin' config
field. It is retrieved from config file, and we use a low level reading of
that file instead of OpamStateConfig.safe_load to avoid multiple error
messages displayed if an error is detected in the config file. If there is
an error, or best effort notification, it will be highlighted after
anyway. *)
try
let opamfile =
OpamPath.config OpamStateConfig.(!r.root_dir)
|> OpamFile.to_string
|> OpamParser.FullPos.file
in
List.iter OpamParserTypes.FullPos.(function
| { pelem = Variable ({ pelem = "sys-pkg-manager-cmd"; _},
{pelem = List { pelem = elements; _}; _}); _} ->
let rec aux last elements =
match last, elements with
| _, [] -> ()
| Some { pelem = String "cygwin"; _},
{ pelem = String cygcheck; _}::_ ->
let cygbin = Filename.dirname cygcheck in
OpamCoreConfig.update ~cygbin ()
| _, element::elements -> aux (Some element) elements
in
aux None elements
| _ -> ())
opamfile.file_contents
with
| Sys_error _ | Not_found -> ()

(** Build options *)

Expand Down
177 changes: 177 additions & 0 deletions src/client/opamClient.ml
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,181 @@ let init_checks ?(hard_fail_exn=true) init_config =
if hard_fail && hard_fail_exn then OpamStd.Sys.exit_because `Configuration_error
else not (soft_fail || hard_fail)

let windows_checks config =
let vars = OpamFile.Config.global_variables config in
let env =
List.map (fun (v, c, s) -> v, (lazy (Some c), s)) vars
|> OpamVariable.Map.of_list
in
let success cygcheck =
let config =
let os_distribution = OpamVariable.of_string "os-distribution" in
let update vars =
OpamFile.Config.with_global_variables
((os_distribution, S "cygwin", "Set by opam init")::vars)
config
in
match OpamStd.List.pick (fun (v,_,_) ->
OpamVariable.equal v os_distribution)
vars with
| Some (_, S "cygwin", _), _ -> config
| None, vars -> update vars
| Some (_, vc, _), vars ->
OpamConsole.warning
"'os-distribution' already set to another value %s"
(OpamVariable.string_of_variable_contents vc);
if OpamConsole.confirm ~default:false "Override?" then
(OpamConsole.msg
"You can revert this setting using \
'opam var --global os-distribution=%s'"
(OpamVariable.string_of_variable_contents vc);
update vars)
else
OpamStd.Sys.exit_because `Aborted
in
OpamFile.Config.with_sys_pkg_manager_cmd
(OpamStd.String.Map.add "cygwin" cygcheck
(OpamFile.Config.sys_pkg_manager_cmd config))
config
in
let get_cygwin = function
| Some cygcheck
when OpamFilename.exists cygcheck
&& OpamStd.Sys.is_cygwin_cygcheck
~cygbin:(Some OpamFilename.(Dir.to_string (dirname cygcheck))) ->
(* Should display the name using the converted path of /, not the bin dir *)
OpamConsole.note "Using Cygwin installation at %s for depexts"
(OpamFilename.(Dir.to_string (dirname cygcheck)));
success cygcheck
| Some _ | None ->
let rec menu () =
let enter_paths () =
let prompt_setup () =
let options = [
`download, "Let opam downloads it";
`manual, "Manually enter its location on disk";
`abort, "Abort initialisation";
]
in
OpamConsole.menu
"Opam needs Cygwin setup executable 'setup-x86_64.exe'"
~default:`download ~no:`download ~options
in
let rec enter_setup () =
match prompt_setup () with
| `abort -> OpamStd.Sys.exit_because `Aborted
| `download -> None
| `manual ->
match OpamConsole.read "Enter path of Cygwin setup executable:" with
| None -> None
| Some setup ->
let setup = OpamFilename.of_string setup in
if OpamFilename.exists setup then Some setup else
(OpamConsole.msg "Cygwin setup executable doesn't exist at %s\n"
(OpamFilename.to_string setup);
enter_setup ())
in
(* Check for default cygwin installation path *)
let default =
match OpamSysInteract.Cygwin.(check_install default_cygroot) with
| Ok cygcheck ->
let prompt_cygroot () =
let options = [
`manual,
"Manually enter prefix of an existing Cygwin installation \
(e.g. C:\\cygwin64)";
`default,
(Printf.sprintf "Use default Cygwin installation at %s"
OpamSysInteract.Cygwin.default_cygroot);
`abort, "Abort initialisation";
] in
OpamConsole.menu "Opam needs pre-existent Cygwin installation"
~default:`default ~no:`default ~options
in
(match prompt_cygroot () with
| `abort -> OpamStd.Sys.exit_because `Aborted
| `manual -> None
| `default -> Some cygcheck)
| Error _ -> None
in
(* Otherwise, ask for prefix *)
let cygcheck =
match default with
| Some cygcheck -> Some cygcheck
| None ->
match OpamConsole.read
"Enter the prefix of an existing Cygwin installation \
(e.g. C:\\cygwin64)" with
| None -> None
| Some entry ->
let cygcheck =
OpamSysInteract.Cygwin.check_install entry
in
match cygcheck with
| Ok cygcheck -> Some cygcheck
| Error msg -> OpamConsole.error "%s" msg; None
in
(* And finally ask for setup.exe *)
match cygcheck with
| Some cygcheck ->
OpamSysInteract.Cygwin.check_setup (enter_setup ());
Some (success cygcheck)
| None -> None
in
match enter_paths () with
| Some config -> config
| None -> menu ()
in
OpamConsole.header_msg "Unix support infrastructure";
OpamConsole.msg
"\n\
opam and the OCaml ecosystem in general depend on various Unix tools \
in order to operate correctly. At present, this requires \
a pre-existing Cygwin installation.\n\n";
menu ()
in
let config =
if OpamSysPoll.os env = Some "win32" then
match OpamSysPoll.os_distribution env with
| Some "win32" ->
(* If there's a "cygwin" entry in sys-pkg-manager-cmd, but os-distribution
hasn't (yet) been set to "cygwin", then that'll be done here.
Otherwise, the user must either allow opam to install Cygwin or must
provide the path to it.

Note that a depext solution is _mandatory_ on Windows for now, because
there are commands opam requires which are only provided using it
(patch, etc.). MSYS2 avoids this by requiring os-distribution to be
set. *)
get_cygwin
(OpamSysInteract.Cygwin.cygcheck_opt config)
| Some "cygwin" ->
(* We check that current install is good *)
(match OpamSysInteract.Cygwin.cygroot_opt config with
| Some cygroot ->
(match OpamSysInteract.Cygwin.check_install
(OpamFilename.Dir.to_string cygroot) with
| Ok cygcheck ->
OpamSysInteract.Cygwin.check_setup None;
success cygcheck
| Error err -> OpamConsole.error "%s" err; get_cygwin None)
| None ->
(* Cygwin is detected from environment (path), we check the install
in that case and stores it in config *)
OpamSystem.resolve_command "cygcheck"
|> OpamStd.Option.map OpamFilename.of_string
|> get_cygwin
)
| _ -> config
else
config
in
OpamCoreConfig.update
?cygbin:OpamStd.Option.Op.(
OpamSysInteract.Cygwin.cygbin_opt config
>>| OpamFilename.Dir.to_string) ();
config

let update_with_init_config ?(overwrite=false) config init_config =
let module I = OpamFile.InitConfig in
let module C = OpamFile.Config in
Expand Down Expand Up @@ -670,6 +845,7 @@ let reinit ?(init_config=OpamInitDefaults.init_config()) ~interactive
config shell =
let root = OpamStateConfig.(!r.root_dir) in
let config = update_with_init_config config init_config in
let config = windows_checks config in
let _all_ok =
if bypass_checks then false else
init_checks ~hard_fail_exn:false init_config
Expand Down Expand Up @@ -745,6 +921,7 @@ let init
init_config |>
OpamFile.Config.with_repositories (List.map fst repos)
in
let config = windows_checks config in

let dontswitch =
if bypass_checks then false else
Expand Down
1 change: 1 addition & 0 deletions src/client/opamClientConfig.mli
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,5 @@ val opam_init:
?errlog_length:int ->
?merged_output:bool ->
?precise_tracking:bool ->
?cygbin:string ->
unit -> unit