Skip to content

Commit ddc6389

Browse files
rjboudra27
andcommitted
Redirect opam on Windows if path contains a space.
It is needed for Cygwin installation, that doesn't handle paths with space. At init, detection and redirection are done, afterwards opam always load redirected opam root. Original root directory is stored in `OpamStateConfig.!r.original_root_dir`. Co-authored-by: David Allsopp <david.allsopp@metastack.com>
1 parent f6baa94 commit ddc6389

File tree

5 files changed

+174
-63
lines changed

5 files changed

+174
-63
lines changed

master_changes.md

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ users)
2929
* Enhance the Git menu by warning if the user appears to need to restart the shell to pick up PATH changes [#5963 @dra27]
3030
* Include Git for Windows installations in the list of possibilities where the user instructed Git-for-Windows setup not to update PATH [#5963 @dra27]
3131
* [BUG] Fail if `--git-location` points to a directory not containing git [#6000 @dra27]
32+
* Redirect the opam root to `C:\opamroot-xxx` when the opam root contains spaces on Windows [#5457 @rjbou @dra27]
3233

3334
## Config report
3435

src/client/opamClient.ml

+137-5
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,114 @@ let reinit ?(init_config=OpamInitDefaults.init_config()) ~interactive
16451645
in
16461646
OpamRepositoryState.drop rt
16471647

1648+
let has_space s = OpamStd.String.contains_char s ' '
1649+
1650+
let default_redirect_root = OpamFilename.Dir.of_string "C:\\opamroot"
1651+
1652+
let setup_redirection target =
1653+
let {contents = {OpamStateConfig.original_root_dir = root; _}} =
1654+
OpamStateConfig.r
1655+
in
1656+
let target =
1657+
match target with
1658+
| Some target -> target
1659+
| None ->
1660+
OpamFilename.mkdir default_redirect_root;
1661+
let readme = OpamFilename.Op.(default_redirect_root // "ReadMe.txt") in
1662+
if not (OpamFilename.exists readme) then
1663+
OpamFilename.write readme
1664+
"This directory is used to contain redirected opam roots.\n\n\
1665+
The contents may be shared with other users on this system.";
1666+
OpamSystem.mk_unique_dir ~dir:(OpamFilename.Dir.to_string default_redirect_root) ()
1667+
in
1668+
let root_dir = OpamFilename.Dir.of_string target in
1669+
OpamFilename.write (OpamPath.redirected root) target;
1670+
OpamStateConfig.update ~root_dir ();
1671+
root_dir
1672+
1673+
let get_redirected_root () =
1674+
let {contents = {OpamStateConfig.original_root_dir = root; root_from; _}} =
1675+
OpamStateConfig.r
1676+
in
1677+
let r = OpamConsole.colorise `bold (OpamFilename.Dir.to_string root) in
1678+
let collision =
1679+
let collision = OpamConsole.utf8_symbol OpamConsole.Symbols.collision "" in
1680+
if collision = "" then
1681+
""
1682+
else
1683+
" " ^ collision
1684+
in
1685+
let options = [
1686+
`Redirect, Printf.sprintf
1687+
"Redirect files to a directory in %s"
1688+
(OpamConsole.colorise `bold (OpamFilename.Dir.to_string default_redirect_root));
1689+
`Ask, "Redirect files to an alternate directory";
1690+
`Endure, Printf.sprintf
1691+
"Do not redirect anything and stick with %s%s" r collision;
1692+
`Quit, "Abort initialisation"
1693+
] in
1694+
let default, explanation =
1695+
match root_from with
1696+
| `Command_line ->
1697+
(* The user has been explicit with --root; nemo salvet modo... *)
1698+
`Endure,
1699+
"You have specified a root directory for opam containing a space."
1700+
| `Env ->
1701+
(* The user has perhaps carelessly set an environment variable *)
1702+
`Redirect,
1703+
"Your OPAMROOT environment variable contains a space."
1704+
| `Default ->
1705+
(* The user has fallen victim to the defaults of Windows Setup and has a
1706+
space in their user name *)
1707+
`Redirect,
1708+
Printf.sprintf
1709+
"By default, opam would store its data in:\n\
1710+
%s\n\
1711+
however, this directory contains a space." r
1712+
in
1713+
let rec ask () =
1714+
let check r =
1715+
if Filename.is_relative r then begin
1716+
OpamConsole.msg
1717+
"That path is relative!\n\
1718+
Please enter an absolute path without spaces.\n";
1719+
ask ()
1720+
end else if has_space r then begin
1721+
OpamConsole.msg
1722+
"That path contains contains a space!\n\
1723+
Please enter an absolute path without spaces.\n";
1724+
ask ()
1725+
end else
1726+
Some (Some r)
1727+
in
1728+
OpamStd.Option.replace check (OpamConsole.read "Root directory for opam: ")
1729+
in
1730+
let rec menu () =
1731+
match OpamConsole.menu "Where should opam store files?" ~default ~options
1732+
~no:default with
1733+
| `Redirect ->
1734+
Some None
1735+
| `Endure ->
1736+
None
1737+
| `Ask ->
1738+
let r = ask () in
1739+
if r = None then
1740+
menu ()
1741+
else
1742+
r
1743+
| `Quit ->
1744+
OpamStd.Sys.exit_because `Aborted
1745+
in
1746+
OpamConsole.header_msg "opam root file store";
1747+
OpamConsole.msg
1748+
"\n\
1749+
%s\n\
1750+
\n\
1751+
Many parts of the OCaml ecosystem do not presently work correctly\n\
1752+
when installed to directories containing spaces. You have been warned!%s\n\
1753+
\n" explanation collision;
1754+
Option.map setup_redirection (menu ())
1755+
16481756
let init
16491757
~init_config ~interactive
16501758
?repo ?(bypass_checks=false)
@@ -1654,10 +1762,34 @@ let init
16541762
shell =
16551763
log "INIT %a"
16561764
(slog @@ OpamStd.Option.to_string OpamRepositoryBackend.to_string) repo;
1765+
let original_root = OpamStateConfig.(!r.original_root_dir) in
1766+
let root_empty =
1767+
not (OpamFilename.exists_dir original_root)
1768+
|| OpamFilename.dir_is_empty original_root in
16571769
let root = OpamStateConfig.(!r.root_dir) in
1770+
let root, remove_root =
1771+
let ignore_non_fatal f x =
1772+
try f x
1773+
with e -> OpamStd.Exn.fatal e
1774+
in
1775+
let new_root =
1776+
if root_empty &&
1777+
Sys.win32 &&
1778+
has_space (OpamFilename.Dir.to_string root) then
1779+
get_redirected_root ()
1780+
else
1781+
None
1782+
in
1783+
match new_root with
1784+
| None ->
1785+
root, (fun () -> ignore_non_fatal OpamFilename.rmdir root)
1786+
| Some root ->
1787+
root, (fun () ->
1788+
ignore_non_fatal OpamFilename.rmdir root;
1789+
ignore_non_fatal OpamFilename.rmdir original_root
1790+
)
1791+
in
16581792
let config_f = OpamPath.config root in
1659-
let root_empty =
1660-
not (OpamFilename.exists_dir root) || OpamFilename.dir_is_empty root in
16611793

16621794
let gt, rt, default_compiler =
16631795
if OpamFile.exists config_f then (
@@ -1671,7 +1803,7 @@ let init
16711803
) else (
16721804
if not root_empty then (
16731805
OpamConsole.warning "%s exists and is not empty"
1674-
(OpamFilename.Dir.to_string root);
1806+
(OpamFilename.Dir.to_string original_root);
16751807
if not (OpamConsole.confirm "Proceed?") then
16761808
OpamStd.Sys.exit_because `Aborted);
16771809
try
@@ -1743,7 +1875,7 @@ let init
17431875
in
17441876
if failed <> [] then
17451877
(if root_empty then
1746-
(try OpamFilename.rmdir root with _ -> ());
1878+
remove_root ();
17471879
OpamConsole.error_and_exit `Sync_error
17481880
"Initial download of repository failed.");
17491881
let default_compiler =
@@ -1778,7 +1910,7 @@ let init
17781910
OpamStd.Exn.finalise e @@ fun () ->
17791911
if not (OpamConsole.debug ()) && root_empty then begin
17801912
OpamSystem.release_all_locks ();
1781-
OpamFilename.rmdir root
1913+
remove_root ()
17821914
end)
17831915
in
17841916
OpamEnv.setup root ~interactive

src/state/opamStateConfig.ml

+10-4
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ type t = {
7171
no_depexts: bool;
7272
}
7373

74+
let win_space_redirection root =
75+
let redirected = OpamPath.redirected root in
76+
if OpamFilename.exists redirected then
77+
OpamFilename.Dir.of_string (OpamFilename.read redirected)
78+
else root
79+
7480
let default_root =
7581
(* On Windows, if a .opam directory is found in %HOME% or %USERPROFILE% then
7682
then we'll use it. Otherwise, we use %LOCALAPPDATA%. *)
@@ -88,7 +94,7 @@ let default_root =
8894
concat_and_resolve local_appdata "opam"
8995

9096
let default = {
91-
root_dir = default_root;
97+
root_dir = win_space_redirection default_root;
9298
original_root_dir = default_root;
9399
root_from = `Default;
94100
current_switch = None;
@@ -185,7 +191,7 @@ let initk k =
185191
| None -> None, None, None
186192
| Some root ->
187193
let root = OpamFilename.Dir.of_string root in
188-
Some root, Some root, Some `Env
194+
Some (win_space_redirection root), Some root, Some `Env
189195
in
190196
let current_switch, switch_from =
191197
match E.switch () with
@@ -219,11 +225,11 @@ let init ?noop:_ = initk (fun () -> ())
219225

220226
let opamroot ?root_dir () =
221227
match root_dir with
222-
| Some root -> `Command_line, root
228+
| Some root -> `Command_line, win_space_redirection root
223229
| None ->
224230
match OpamStd.Env.getopt "OPAMROOT" with
225231
| Some root ->
226-
`Env, OpamFilename.Dir.of_string root
232+
`Env, win_space_redirection (OpamFilename.Dir.of_string root)
227233
| None ->
228234
`Default, default.root_dir
229235

tests/reftests/env.test

-54
Original file line numberDiff line numberDiff line change
@@ -439,60 +439,6 @@ The following actions will be performed:
439439
Done.
440440
### opam exec -- sh -c "eval $(opam env | tr -d '\\r'); opam remove foo; opam env; eval $(opam env | tr -d '\\r'); opam env" | grep "FOO"
441441
FOO=''; export FOO;
442-
### : root and switch with spaces :
443-
### RT="$BASEDIR/root 2"
444-
### SW="switch w spaces"
445-
### OPAMNOENVNOTICE=0
446-
### opam init -na --bare --bypass-check --disable-sandbox --root "$RT" defaut ./REPO | grep -v Cygwin
447-
No configuration file found, using built-in defaults.
448-
449-
<><> Fetching repository information ><><><><><><><><><><><><><><><><><><><><><>
450-
[defaut] Initialised
451-
### opam switch create "./$SW" nv --root "$RT"
452-
453-
<><> Installing new switch packages <><><><><><><><><><><><><><><><><><><><><><>
454-
Switch invariant: ["nv"]
455-
456-
<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><>
457-
-> installed nv.1
458-
Done.
459-
# Run eval $(opam env '--root=${BASEDIR}/root 2' '--switch=${BASEDIR}/switch w spaces') to update the current shell environment
460-
### opam env --root "$RT" --switch "./$SW" | grep "NV_VARS" | ';' -> ':'
461-
NV_VARS3='foo:/yet/another/different/path': export NV_VARS3:
462-
NV_VARS4='': export NV_VARS4:
463-
NV_VARS_5925_1='foo': export NV_VARS_5925_1:
464-
NV_VARS_5925_2='foo': export NV_VARS_5925_2:
465-
NV_VARS_5925_3='foo': export NV_VARS_5925_3:
466-
NV_VARS_5925_4='foo': export NV_VARS_5925_4:
467-
NV_VARS_5925_5='foo:': export NV_VARS_5925_5:
468-
NV_VARS_5925_6='foo:': export NV_VARS_5925_6:
469-
NV_VARS_5925_7=':foo': export NV_VARS_5925_7:
470-
NV_VARS_5925_8=':foo': export NV_VARS_5925_8:
471-
NV_VARS_5926_L_1='b::a': export NV_VARS_5926_L_1:
472-
NV_VARS_5926_L_2='b::a': export NV_VARS_5926_L_2:
473-
NV_VARS_5926_L_3=':a:b': export NV_VARS_5926_L_3:
474-
NV_VARS_5926_L_4=':a:b': export NV_VARS_5926_L_4:
475-
NV_VARS_5926_L_5='b::a': export NV_VARS_5926_L_5:
476-
NV_VARS_5926_L_6='b::a': export NV_VARS_5926_L_6:
477-
NV_VARS_5926_L_7=':a:b': export NV_VARS_5926_L_7:
478-
NV_VARS_5926_L_8=':a:b': export NV_VARS_5926_L_8:
479-
NV_VARS_5926_M_1='b:a1::a2': export NV_VARS_5926_M_1:
480-
NV_VARS_5926_M_2='a1::a2:b': export NV_VARS_5926_M_2:
481-
NV_VARS_5926_M_3='b:a1::a2': export NV_VARS_5926_M_3:
482-
NV_VARS_5926_M_4='a1::a2:b': export NV_VARS_5926_M_4:
483-
NV_VARS_5926_S_1='a:': export NV_VARS_5926_S_1:
484-
NV_VARS_5926_S_2=':a': export NV_VARS_5926_S_2:
485-
NV_VARS_5926_S_3='a:': export NV_VARS_5926_S_3:
486-
NV_VARS_5926_S_4=':a': export NV_VARS_5926_S_4:
487-
NV_VARS_5926_T_1='b:a:': export NV_VARS_5926_T_1:
488-
NV_VARS_5926_T_2='b:a:': export NV_VARS_5926_T_2:
489-
NV_VARS_5926_T_3='a::b': export NV_VARS_5926_T_3:
490-
NV_VARS_5926_T_4='a::b': export NV_VARS_5926_T_4:
491-
NV_VARS_5926_T_5='b:a:': export NV_VARS_5926_T_5:
492-
NV_VARS_5926_T_6='b:a:': export NV_VARS_5926_T_6:
493-
NV_VARS_5926_T_7='a::b': export NV_VARS_5926_T_7:
494-
NV_VARS_5926_T_8='a::b': export NV_VARS_5926_T_8:
495-
### OPAMNOENVNOTICE=1
496442
### : Env hooks :
497443
### <pkg:av.1>
498444
opam-version: "2.0"

tests/reftests/env.unix.test

+26
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,30 @@
11
N0REP0
2+
### : root and switches with spaces :
3+
### <pkg:nv.1>
4+
opam-version: "2.0"
5+
flags: compiler
6+
### RT="$BASEDIR/root 2"
7+
### SW="switch w spaces"
8+
### OPAMNOENVNOTICE=0
9+
### opam init -na --bare --bypass-check --disable-sandbox --root "$RT" defaut ./REPO
10+
No configuration file found, using built-in defaults.
11+
12+
<><> Fetching repository information ><><><><><><><><><><><><><><><><><><><><><>
13+
[defaut] Initialised
14+
### opam switch create "./$SW" nv --root "$RT"
15+
16+
<><> Installing new switch packages <><><><><><><><><><><><><><><><><><><><><><>
17+
Switch invariant: ["nv"]
18+
19+
<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><>
20+
-> installed nv.1
21+
Done.
22+
# Run eval $(opam env '--root=${BASEDIR}/root 2' '--switch=${BASEDIR}/switch w spaces') to update the current shell environment
23+
### opam env --root "$RT" --switch "./$SW" | grep "PREFIX" | ';' -> ':'
24+
OPAM_SWITCH_PREFIX='${BASEDIR}/switch w spaces/_opam': export OPAM_SWITCH_PREFIX:
25+
### opam var root --root "$RT"
26+
${BASEDIR}/root 2
27+
### OPAMNOENVNOTICE=1
228
### : setenv & build env rewriting :
329
### opam switch create rewriting --empty
430
### ::::::::::::::::::

0 commit comments

Comments
 (0)