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

windows: opam root redirection when path contains spaces #5457

Merged
merged 6 commits into from
Jun 10, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions master_changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ users)
* Enhance the Git menu by warning if the user appears to need to restart the shell to pick up PATH changes [#5963 @dra27]
* Include Git for Windows installations in the list of possibilities where the user instructed Git-for-Windows setup not to update PATH [#5963 @dra27]
* [BUG] Fail if `--git-location` points to a directory not containing git [#6000 @dra27]
* Redirect the opam root to `C:\opamroot-xxx` when the opam root contains spaces on Windows [#5457 @rjbou @dra27]

## Config report

Expand Down Expand Up @@ -177,6 +178,8 @@ users)
* Expose `OpamSolution.print_depext_msg` [#5994 @dra27]
* Extracted `OpamSolution.install_sys_packages` from `OpamSolution.install_depexts` [#5994 @dra27]
* `OpamInitDefaults.required_packages_for_cygwin`: no longer includes git; as the need to add that is computed in `OpamClient` [#6000 @dra27]
* `OpamClientConfig.opam_init`: add `original_root_dir` argument that contains the original roo directory before redirection [#5457 @rjbou]
* `OpamClientConfig.opam_init`: add `root_from` argument that contains the origin of used root[#5457 @dra27]

## opam-repository
* `OpamDownload.download_command`: separate output from stdout and stderr [#5984 @kit-ty-kate]
Expand All @@ -185,13 +188,16 @@ users)
* `OpamEnv.cygwin_non_shadowed_programs`: exposes the list of executables (not including git) which should always come from Cygwin [#6000 @dra27]
* `opamSysInteract.Cygwin.install`: de-label `packages` argument [#6000 @dra27]
* `OpamSysInteract.Cygwin.check_install` renamed to `analyse_install` which now also returns whether the installation found was MSYS2 or Cygwin [#6000 @dra27]
* `OpamStateConfig.r`, `OpamStateConfig.init`: add `original_root_dir` field to config record and argument that contains the original root directory before redirection [#5457 @rjbou]
* `OpamStateConfig.r`, `OpamStateConfig.init`: add `root_from` field to config record and argument that contains the origin of used root[#5457 @dra27]

## opam-solver

## opam-format
* `OpamPath`: remove `OpamPath.Switch.last_env` function in favor to `OpamPath.last_env` as the files are no more stored in switch directory [#5962 @moyodiallo - fix #5823]
* `OpamFilter.map_up`: correct handling of FDefined [#5983 @dra27]
* `OpamFilter.fold_down_left`: correct handling of FDefined and FUndef [#5983 @dra27]
* `OpamPath`: add `redirected` the file name of redirected opam root [#5457 @rjbou]

## opam-core
* `OpamStd.String`: add `split_quoted` that preserves quoted separator [#5935 @dra27]
Expand All @@ -208,3 +214,5 @@ users)
* `OpamStubs.enumRegistry`: on Windows, retrieves all the values of a given type from a registry key, with their names [#6000 @dra27]
* `OpamCompat`: add `Seq.find_map` from OCaml 4.14 [#6000 @dra27]
* `OpamStd.Sys.{get_windows_executable_variant,get_cygwin_variant,is_cygwin_variant}`: renamed `~cygbin` to `?search_in_path` with a change in semantics so that it acts as though the directory was simply the first entry in PATH [#6000 @dra27]
* `OpamConsole.Symbols`: add `collision` symbol [#5457 @dra27]
* `OpamSystem`: add `mk_unique_dir` that returns an unique directory name as `mk_temp_dir` but not in temporary directory [#5457 @dra27]
1 change: 1 addition & 0 deletions src/client/opamArg.ml
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ let apply_global_options cli o =
(* ?solver_preferences_best_effort_prefix: *)
(* - state options - *)
?root_dir:o.opt_root
?original_root_dir:o.opt_root
?current_switch:(o.opt_switch >>| OpamSwitch.of_string)
?switch_from:(o.opt_switch >>| fun _ -> `Command_line)
(* ?jobs: int *)
Expand Down
4 changes: 2 additions & 2 deletions src/client/opamCliMain.ml
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ let check_and_run_external_commands () =
let yes = if yes then Some (Some true) else None in
OpamCoreConfig.init ?yes ?confirm_level ();
OpamFormatConfig.init ();
let root_dir = OpamStateConfig.opamroot () in
let root_from, root_dir = OpamStateConfig.opamroot () in
let has_init, root_upgraded =
match OpamStateConfig.load_defaults ~lock_kind:`Lock_read root_dir with
| None -> (false, false)
Expand Down Expand Up @@ -210,7 +210,7 @@ let check_and_run_external_commands () =
env_update_resolved "PATH" PlusEq
(OpamFilename.Dir.to_string plugins_bin)
] in
OpamStateConfig.init ~root_dir ();
OpamStateConfig.init ~root_from ~root_dir ();
match OpamStateConfig.get_switch_opt () with
| None -> env_array (OpamEnv.get_pure ~updates ())
| Some sw ->
Expand Down
142 changes: 137 additions & 5 deletions src/client/opamClient.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1645,6 +1645,114 @@ let reinit ?(init_config=OpamInitDefaults.init_config()) ~interactive
in
OpamRepositoryState.drop rt

let has_space s = OpamStd.String.contains_char s ' '

let default_redirect_root = OpamFilename.Dir.of_string "C:\\opamroot"

let setup_redirection target =
let {contents = {OpamStateConfig.original_root_dir = root; _}} =
OpamStateConfig.r
in
let target =
match target with
| Some target -> target
| None ->
OpamFilename.mkdir default_redirect_root;
let readme = OpamFilename.Op.(default_redirect_root // "ReadMe.txt") in
if not (OpamFilename.exists readme) then
OpamFilename.write readme
"This directory is used to contain redirected opam roots.\n\n\
The contents may be shared with other users on this system.";
OpamSystem.mk_unique_dir ~dir:(OpamFilename.Dir.to_string default_redirect_root) ()
in
let root_dir = OpamFilename.Dir.of_string target in
OpamFilename.write (OpamPath.redirected root) target;
OpamStateConfig.update ~root_dir ();
root_dir

let get_redirected_root () =
let {contents = {OpamStateConfig.original_root_dir = root; root_from; _}} =
OpamStateConfig.r
in
let r = OpamConsole.colorise `bold (OpamFilename.Dir.to_string root) in
let collision =
let collision = OpamConsole.utf8_symbol OpamConsole.Symbols.collision "" in
if collision = "" then
""
else
" " ^ collision
in
let options = [
`Redirect, Printf.sprintf
"Redirect files to a directory in %s"
(OpamConsole.colorise `bold (OpamFilename.Dir.to_string default_redirect_root));
`Ask, "Redirect files to an alternate directory";
`Endure, Printf.sprintf
"Do not redirect anything and stick with %s%s" r collision;
`Quit, "Abort initialisation"
] in
let default, explanation =
match root_from with
| `Command_line ->
(* The user has been explicit with --root; nemo salvet modo... *)
`Endure,
"You have specified a root directory for opam containing a space."
| `Env ->
(* The user has perhaps carelessly set an environment variable *)
`Redirect,
"Your OPAMROOT environment variable contains a space."
| `Default ->
(* The user has fallen victim to the defaults of Windows Setup and has a
space in their user name *)
`Redirect,
Printf.sprintf
"By default, opam would store its data in:\n\
%s\n\
however, this directory contains a space." r
in
let rec ask () =
let check r =
if Filename.is_relative r then begin
OpamConsole.msg
"That path is relative!\n\
Please enter an absolute path without spaces.\n";
ask ()
end else if has_space r then begin
OpamConsole.msg
"That path contains contains a space!\n\
Please enter an absolute path without spaces.\n";
ask ()
end else
Some (Some r)
in
OpamStd.Option.replace check (OpamConsole.read "Root directory for opam: ")
in
let rec menu () =
match OpamConsole.menu "Where should opam store files?" ~default ~options
~no:default with
| `Redirect ->
Some None
| `Endure ->
None
| `Ask ->
let r = ask () in
if r = None then
menu ()
else
r
| `Quit ->
OpamStd.Sys.exit_because `Aborted
in
OpamConsole.header_msg "opam root file store";
OpamConsole.msg
"\n\
%s\n\
\n\
Many parts of the OCaml ecosystem do not presently work correctly\n\
when installed to directories containing spaces. You have been warned!%s\n\
\n" explanation collision;
Option.map setup_redirection (menu ())

let init
~init_config ~interactive
?repo ?(bypass_checks=false)
Expand All @@ -1654,10 +1762,34 @@ let init
shell =
log "INIT %a"
(slog @@ OpamStd.Option.to_string OpamRepositoryBackend.to_string) repo;
let original_root = OpamStateConfig.(!r.original_root_dir) in
let root_empty =
not (OpamFilename.exists_dir original_root)
|| OpamFilename.dir_is_empty original_root in
let root = OpamStateConfig.(!r.root_dir) in
let root, remove_root =
let ignore_non_fatal f x =
try f x
with e -> OpamStd.Exn.fatal e
in
let new_root =
if root_empty &&
Sys.win32 &&
has_space (OpamFilename.Dir.to_string root) then
get_redirected_root ()
else
None
in
match new_root with
| None ->
root, (fun () -> ignore_non_fatal OpamFilename.rmdir root)
| Some root ->
root, (fun () ->
ignore_non_fatal OpamFilename.rmdir root;
ignore_non_fatal OpamFilename.rmdir original_root
)
in
let config_f = OpamPath.config root in
let root_empty =
not (OpamFilename.exists_dir root) || OpamFilename.dir_is_empty root in

let gt, rt, default_compiler =
if OpamFile.exists config_f then (
Expand All @@ -1671,7 +1803,7 @@ let init
) else (
if not root_empty then (
OpamConsole.warning "%s exists and is not empty"
(OpamFilename.Dir.to_string root);
(OpamFilename.Dir.to_string original_root);
if not (OpamConsole.confirm "Proceed?") then
OpamStd.Sys.exit_because `Aborted);
try
Expand Down Expand Up @@ -1743,7 +1875,7 @@ let init
in
if failed <> [] then
(if root_empty then
(try OpamFilename.rmdir root with _ -> ());
remove_root ();
OpamConsole.error_and_exit `Sync_error
"Initial download of repository failed.");
let default_compiler =
Expand Down Expand Up @@ -1778,7 +1910,7 @@ let init
OpamStd.Exn.finalise e @@ fun () ->
if not (OpamConsole.debug ()) && root_empty then begin
OpamSystem.release_all_locks ();
OpamFilename.rmdir root
remove_root ()
end)
in
OpamEnv.setup root ~interactive
Expand Down
4 changes: 2 additions & 2 deletions src/client/opamClientConfig.ml
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ let opam_init ?root_dir ?strict ?solver =
let open OpamStd.Option.Op in

(* (i) get root dir *)
let root = OpamStateConfig.opamroot ?root_dir () in
let root_from, root = OpamStateConfig.opamroot ?root_dir () in

(* (ii) load conf file and set defaults *)
(* the init for OpamFormat is done in advance since (a) it has an effect on
Expand Down Expand Up @@ -261,5 +261,5 @@ let opam_init ?root_dir ?strict ?solver =
OpamCoreConfig.initk ?log_dir |>
OpamRepositoryConfig.initk |>
OpamSolverConfig.initk ?solver |>
OpamStateConfig.initk ~root_dir:root |>
OpamStateConfig.initk ~root_dir:root ~root_from |>
initk
1 change: 1 addition & 0 deletions src/client/opamClientConfig.mli
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ val opam_init:
?assume_depexts:bool ->
?cli:OpamCLIVersion.t ->
?scrubbed_environment_variables:string list ->
?original_root_dir:OpamTypes.dirname ->
?current_switch:OpamSwitch.t ->
?switch_from:OpamStateTypes.provenance ->
?jobs:int Lazy.t ->
Expand Down
2 changes: 1 addition & 1 deletion src/client/opamCommands.ml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ let global_options cli =
switch_to_updated_self
OpamStd.Option.Op.(options.debug_level ++
OpamCoreConfig.E.debug () +! 0 |> abs > 0)
(OpamStateConfig.opamroot ?root_dir:options.opt_root ());
(snd (OpamStateConfig.opamroot ?root_dir:options.opt_root ()));
let root_is_ok =
OpamStd.Option.default false (OpamClientConfig.E.rootisok ())
in
Expand Down
1 change: 1 addition & 0 deletions src/core/opamConsole.ml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ module Symbols = struct
let downwards_double_arrow = Uchar.of_int 0x21d3
let black_down_pointing_triangle = Uchar.of_int 0x25bc
let downwards_black_arrow = Uchar.of_int 0x2b07
let collision = Uchar.of_int 0x1f4a5
end

type win32_glyph_checker = {
Expand Down
1 change: 1 addition & 0 deletions src/core/opamConsole.mli
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ module Symbols : sig
val downwards_double_arrow : Uchar.t
val downwards_black_arrow : Uchar.t
val black_down_pointing_triangle : Uchar.t
val collision : Uchar.t
end

val utf8_symbol:
Expand Down
7 changes: 7 additions & 0 deletions src/core/opamSystem.ml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ let rec mk_temp_dir ?(prefix="opam") () =
else
real_path s

let rec mk_unique_dir ~dir ?(prefix="opam") () =
let s = dir / Printf.sprintf "%s-%06x" prefix (Random.int 0xFFFFFF) in
if Sys.file_exists s then
mk_unique_dir ~dir ~prefix ()
else
real_path s

let safe_mkdir dir =
try
log "mkdir %s" dir;
Expand Down
4 changes: 4 additions & 0 deletions src/core/opamSystem.mli
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ val verbose_for_base_commands: unit -> bool
(if [prefix] is not set), pid, and random number. *)
val mk_temp_dir: ?prefix:string -> unit -> string

(** Returns a directory name, in the [~dir], composed by {i opam}
(if [prefix] is not set), and a random number. *)
val mk_unique_dir: dir:string -> ?prefix:string -> unit -> string

(** [copy_file src dst] copies [src] to [dst]. Remove [dst] before the copy
if it is a link. *)
val copy_file: string -> string -> unit
Expand Down
2 changes: 2 additions & 0 deletions src/format/opamPath.ml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ let ( /- ) dir f = OpamFile.make (dir // f)

let config t = t /- "config"

let redirected t = t // "redirected-opamroot"

let init_config_files () =
List.map OpamFile.make [
OpamFilename.Dir.of_string (OpamStd.Sys.etc ()) // "opamrc";
Expand Down
3 changes: 3 additions & 0 deletions src/format/opamPath.mli
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ val lock: t -> filename
(** Main configuration file: {i $opam/config} *)
val config: t -> OpamFile.Config.t OpamFile.t

(** Redirection file for opam root: {i $opam/redirected-opamroot} *)
val redirected: t -> OpamFilename.t

(** The list of configuration files location used by default ({i /etc/opamrc}
and {i ~/.opamrc}). More general (lower priority) first. *)
val init_config_files: unit -> OpamFile.InitConfig.t OpamFile.t list
Expand Down
Loading
Loading