Skip to content

Commit

Permalink
Merge pull request #5636 from rjbou/setenv-slash
Browse files Browse the repository at this point in the history
Path rewriting for `setenv:` and `build-env:`
  • Loading branch information
kit-ty-kate authored Nov 13, 2023
2 parents 05915bc + ec03971 commit 0670b46
Show file tree
Hide file tree
Showing 21 changed files with 2,498 additions and 241 deletions.
67 changes: 65 additions & 2 deletions doc/pages/Manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -639,8 +639,7 @@ Some fields define updates to environment variables in the form:

The allowed update operators `update-op` denote how the string is applied to the
environment variable. Prepend and append operators assume list of elements
separated by an OS-specific character (`;` on Windows, `:` on Cygwin or any
other system).
separated by an OS-specific character.
- `=` override (or set if undefined)
- `+=` or `:=` prepend. They differ when the variable is unset of empty, where `:=` adds a trailing separator.
- `=+` or `=:` append. They differ when the variable is unset of empty, where `=:` adds a leading separator.
Expand All @@ -652,6 +651,59 @@ other system).

`FOO += ""`, `FOO := ""`, etc. are all ignored - i.e. opam never adds empty segments to an existing variable.

#### Environment update portability
<a id="env-update-rewrite"></a>

Some fields define an environment update portability specification. In the opam
file it is the [`x-env-path-rewrite:`](#opamfield-x-env-path-rewrite) field, of the
form:

```BNF
<environment-rewrites> ::= { <environment-rewrite> }*
<environment-rewrite> ::= <ident> <bool>
| <ident> <separator-formula> <path-format-formula>

<separator-formula> ::= <separator-formula> "|" <separator-formula>
| ( <separator-formula> )
| <separator> { { <distrib-formula> }* }
<path-format-formula> ::= <path-format-formula> "|" <path-format-formula>
| ( <path-format-formula> )
| <path-format> { { <distrib-formula> }* }

<separator> ::= (") ":" (") | (") ";" (")
<path-format> ::= (") "host" (") | (") "host-quoted" (") | (") "target" (") | (") "target-quoted" (")

<distrib-formula> ::= <distrib-formula> <logop> <distrib-formula>
| "!" <distrib-formula>
| "(" <distrib-formula> ")"
<logop> ::= "&" | "|"
```

The `<separator>` defines the path separator to use for the variable.
The `<path-format>` defines the way to handle variables path formatting:
- host: use the *host* interpretation of PATHs (i.e. convert via `cygpath` on
Windows).
- host-quoted: use the *host* interpretation of entries and double-quote any
entries which include the separator character.
- target: use the *target* interpretation of entries (i.e. rewrite slashes to
backslashes on Windows).
- target-quoted: use the *target* interpretation of entries and double-quote
any entries which include the separator character.

If a variable is not mentioned in `x-env-path-rewrite`, the separator is assumed to be `;` on Windows and `:` on all other systems; no slash or quoting transformations are performed. There are two special default cases:
* `PKG_CONFIG_PATH` uses `:` separator and is `target-quoted`
* `PATH` on Windows uses `;` separator and is `target-quoted`

For example, on Windows:
- `[FOO false]`: `FOO` won't be translated nor rewritten, and default separator is used if needed
- `[FOO true]`: `FOO` is rewritten using defaults `;` and `target` with slash rewriting
- `FOO = "a:/path/to"` -> FOO=a:\path\to
- `[FOO ":" "target-quoted"]: `FOO` will be appended using `:` separator, if the added path contains '/' they are transformed into `\`, and if the added path contains a `:`, the added path will be quoted
- `FOO += "a/path/to"` -> FOO=a\path\to:R:\previous\path
- `FOO += "a:path/to"` -> FOO="a:path\to":R:\previous\path
- `[FOO ":" "host"]: `FOO` will be appended using `:`, and its path will be translated according to the host translator, i.e. `cygpath <path>`:
- `FOO += "A:\path\to"` -> FOO=/cygdrive/a/path/to:/previous/path

### URLs

URLs are provided as strings. They can refer to:
Expand Down Expand Up @@ -1124,6 +1176,9 @@ files.
- `OPAMCLI=2.0` (since opam 2.1)
- `TMP` and `TMPDIR` are set by the sandbox script (bubblewrap), but should not be relied on since the sandbox is not used on all platforms and can be disabled by the user.

See [`x-env-path-rewrite:`](#opamfield-x-env-path-rewrite)
for path portability of environment variables on Windows.

- <a id="opamsection-extra-sources">`extra-source <string> "{" <url-file> "}"`</a>:
allows the definition of extra files that need downloading into the source
tree before the package can be patched (if necessary) and built. The format is
Expand All @@ -1132,6 +1187,9 @@ files.
`<string>` indicates the name the file should be saved to, and is relative to
the root of the source tree.

See [`x-env-path-rewrite:`](#opamfield-x-env-path-rewrite)
for path portability of environment variables on Windows.

- <a id="opamfield-extra-files">`extra-files: [ [ <string> <checksum> ] ... ]`</a>:
optionally lists the files below `files/` with their checksums. Used
internally for integrity verifications.
Expand All @@ -1152,6 +1210,11 @@ files.
has changed. Even then, this won't unpin any packages that would have
been removed from `pin-depends:`.

- <a id="opamfield-x-env-path-rewrite">`x-env-path-rewrite: [ <environment-update-rewrite> ... ]`</a>:
a specific extra field, used to specify rewrite rules for environment variables
defined in [`setenv:`](#opamfield-setenv) and [`build-env`](#opamfield-build-env).
See [`environment update portability`](#env-update-rewrite) for syntax.

- <a id="opamfield-extra-fields">`x-*: <value>`</a>:
extra fields prefixed with `x-` can be defined for use by external tools. opam
will ignore them except for some search operations.
Expand Down
29 changes: 28 additions & 1 deletion master_changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ users)
* [BUG] On install driven by `.install` file, track intermediate directories too, in order to have them suppressed at package removal [#5691 @rjbou - fix #5688]
* [BUG] With `--assume-built`, resolve variables in depends filter according switch & global environment, not only depends predefined variables [#570 @rjbou - fix #5698]
* [BUG] Handle undefined variables defaults to false in dependencies formula resolution for assume built [#5701 rjbou]
* Reinstall if `x-env-path-rewrite` is updated [#5636 @rjbou]

## Remove

Expand Down Expand Up @@ -64,8 +65,13 @@ users)

## Clean

## Env
* When computing environment variables updates, use rewriting rules (defined in opam file, or default per variable) to split, join, quote, etc. [#5636 @rjbou - fix #5602 #4690 #2927]

## Opamfile
* Update populating extra-files fields log [#5640 @rjbou]
* Fix `x-locked` type error message [#5636 @rjbou]
* Add `x-env-path-rewrite` extensions field to permit specification of rewriting rules for variables defined in `setenv` and `build-env`: no rewrite; separator and path format formulae [#5636 @rjbou - fix #5602 #4690 #2927]

## External dependencies

Expand Down Expand Up @@ -104,6 +110,7 @@ users)
## Shell

## Internal
* `environment` file now stores environmnet variable rewriting rules [#5636 @rjbou]

## Internal: Windows
* Fix sporadic crash and segfault in shell detection (seen in native containers) [#5714 @dra27]
Expand All @@ -123,6 +130,7 @@ users)
* dot-install: add a test for removal of non specified in .install empty directories [#5701 @rjbou]
* Add test in assume-built for depends with switch variable filters [#5700 @rjbou]
* Add undefined variable handling in assume built test [#5701 @rjbou]
* Add `env.unix` & `env.win32` to test environment variables rewriting rules [#5636 @rjbou]

### Engine
* With real path resolved for all opam temp dir, remove `/private` from mac temp dir regexp [#5654 @rjbou]
Expand All @@ -138,6 +146,7 @@ users)
## Doc
* Fix typos in readme [#5706 @MisterDA]
* Fix formatting in the Manual [#5708 @kit-ty-kate]
* Add `x-env-path-rewriting` documentation [#5636 @rjbou]

## Security fixes

Expand All @@ -151,13 +160,31 @@ users)

## opam-solver

## opam-format
## opam-format
* `OpamFilter`: add `expand_interpolations_in_file_full` which allows setting the output file along with the input file [#5629 @rgrinberg]
* `OpamFilter`: expose `string_interp_regex` which allows clients to identify variable interpolations in strings [#5633 @gridbugs]
* `OpamTypes.env_update`: change from tuple to a record [#5636 @rjbou]
* `OpamTypesBase`: add `env_update`, `env_update_resolved`, and `env_update_unresolved` builders [#5636 @rjbou]
* `OpamTypes.env_update`: add a `rewrite` field, that contains environment variable rewriting rules (formula to resolved, or already resolved, or no rewriting) [#5636 @rjbou]
* `OpamPp.fallback`: add name concatenation and printing fallback too [#5636 @rjbou]
* `OpamFormat`: add `formula_items` to permit definition of formulae pp not only of the type `package-formula` [#5636 @rjbou]
* `OpamTypesBase`: add to_string function for `path_format` & `separator` [#5636 @rjbou]
* `OpamFormat.V`: add `path_format` & `separator` value parser printer [#5636 @rjbou]
* `OpamFile.OPAM`: add handling of `x-env-path-rewrite` extensions field, that specifies rewrite rules [#5636 @rjbou]
* `OpamFile.Environment`: add parsing-printing of rewriting rules, keeping backward compatibility [#5636 @rjbou]
* `OpamFile.OPAM`: `effective_part` keeps `x-env-path-rewrite`, affects also `effectively_equal` [#5636 @rjbou]
* `OpamTypesBase`: add `env_update_resolved` and `env_update_unresolved` builders [#5636 @rjbou]
* `OpamPp.fallback`: add name concatenation and printing fallback too [#5636 @rjbou]
* `OpamFormat`: add `formula_items` to permit definition of formulae pp not only of the type `package-formula` [#5636 @rjbou]
* `OpamTypesBase`: add to_string function for `path_format` & `separator` [#5636 @rjbou]
* `OpamFormat.V`: add `path_format` & `separator` value parser printer [#5636 @rjbou]
* `OpamFile.OPAM`: add handling of `x-env-path-rewrite` extensions field, that specifies rewrite rules [#5636 @rjbou]
* `OpamFile.Environment`: add parsing-printing of rewriting rules, keeping backward compatibility [#5636 @rjbou]
* `OpamFile.OPAM`: `effective_part` keeps `x-env-path-rewrite`, affects also `effectively_equal` [#5636 @rjbou]

## opam-core
* `OpamSystem.mk_temp_dir`: resolve real path with `OpamSystem.real_path` before returning it [#5654 @rjbou]
* `OpamSystem.resolve_command`: in command resolution path, check that the file is not a directory and that it is a regular file [#5606 @rjbou - fix #5585 #5597 #5650 #5626]
* `OpamStd.Config.env_level`: fix level parsing, it was inverted (eg, "no" gives level 1, and "yes" level 0) [#5686 @smorimoto]
* `OpamStd.Sys.chop_exe_suffix`: removes `.exe` from the end of a path, if present
* `OpamSystem.get_cygpath_path_transform`: add labeled argument to specify if path is a pathlist [#5636 @rjbou]
61 changes: 46 additions & 15 deletions src/client/opamAction.ml
Original file line number Diff line number Diff line change
Expand Up @@ -522,30 +522,61 @@ let prepare_package_source st nv dir =
let compilation_env t opam =
let open OpamParserTypes in
let build_env =
List.map (OpamEnv.env_expansion ~opam t) (OpamFile.OPAM.build_env opam)
List.map
(fun env ->
OpamEnv.resolve_separator_and_format
(OpamEnv.env_expansion ~opam t env))
(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" ]
[ OpamTypesBase.env_update_resolved "PATH" EqPlus
(OpamFilename.Dir.to_string cygbin)
~comment:"Cygwin path"
]
| None -> []
in
let shell_sanitization = "shell env sanitization" in
let build_env_def = "build environment definition" in
let cdpath =
OpamTypesBase.env_update_resolved "CDPATH" Eq ""
~comment:shell_sanitization
in
let makeflags =
OpamTypesBase.env_update_resolved "MAKEFLAGS" Eq ""
~comment:shell_sanitization
in
let makelevel =
OpamTypesBase.env_update_resolved "MAKELEVEL" Eq ""
~comment:"make env sanitization"
in
let pkg_name =
OpamTypesBase.env_update_resolved "OPAM_PACKAGE_NAME" Eq
(OpamPackage.Name.to_string (OpamFile.OPAM.name opam))
~comment:build_env_def
in
let pkg_version =
OpamTypesBase.env_update_resolved "OPAM_PACKAGE_VERSION" Eq
(OpamPackage.Version.to_string (OpamFile.OPAM.version opam))
~comment:build_env_def
in
let cli =
OpamTypesBase.env_update_resolved "OPAMCLI" Eq "2.0"
~comment:"opam CLI version"
in
let scrub = OpamClientConfig.(!r.scrubbed_environment_variables) in
OpamEnv.get_full ~scrub ~set_opamroot:true ~set_opamswitch:true
~force_path:true t ~updates:([
"CDPATH", Eq, "", Some "shell env sanitization";
"MAKEFLAGS", Eq, "", Some "make env sanitization";
"MAKELEVEL", Eq, "", Some "make env sanitization";
"OPAM_PACKAGE_NAME", Eq,
OpamPackage.Name.to_string (OpamFile.OPAM.name opam),
Some "build environment definition";
"OPAM_PACKAGE_VERSION", Eq,
OpamPackage.Version.to_string (OpamFile.OPAM.version opam),
Some "build environment definition";
"OPAMCLI", Eq, "2.0", Some "opam CLI version";
] @
build_env
@ cygwin_env)
cdpath;
makeflags;
makelevel;
pkg_name;
pkg_version;
cli
] @
build_env
@ cygwin_env)

let installed_opam_opt st nv =
OpamStd.Option.Op.(
Expand Down
14 changes: 9 additions & 5 deletions src/client/opamAdminRepoUpgrade.ml
Original file line number Diff line number Diff line change
Expand Up @@ -366,12 +366,16 @@ let do_upgrade repo_root =
None
] |>
O.with_maintainer [ "[email protected]" ] |>
O.with_build_env ["CAML_LD_LIBRARY_PATH", Eq, "", None] |>
O.with_env [
"CAML_LD_LIBRARY_PATH", Eq, "%{_:stubsdir}%", None;
"CAML_LD_LIBRARY_PATH", PlusEq, "%{lib}%/stublibs", None;
"OCAML_TOPLEVEL_PATH", Eq, "%{toplevel}%", None;
O.with_build_env [
OpamTypesBase.env_update_unresolved "CAML_LD_LIBRARY_PATH" Eq ""
] |>
O.with_env [
OpamTypesBase.env_update_unresolved "CAML_LD_LIBRARY_PATH" Eq
"%{_:stubsdir}%";
OpamTypesBase.env_update_unresolved "CAML_LD_LIBRARY_PATH" PlusEq
"%{lib}%/stublibs";
OpamTypesBase.env_update_unresolved "OCAML_TOPLEVEL_PATH" Eq
"%{toplevel}%" ] |>
(* leave the Compiler flag to the implementations (since the user
needs to select one)
O.with_flags [Pkgflag_Compiler] |> *)
Expand Down
8 changes: 4 additions & 4 deletions src/client/opamCliMain.ml
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,10 @@ let check_and_run_external_commands () =
in
let env =
if has_init then
let updates =
["PATH", OpamParserTypes.PlusEq,
OpamFilename.Dir.to_string plugins_bin, None]
in
let updates = [
env_update_resolved "PATH" PlusEq
(OpamFilename.Dir.to_string plugins_bin)
] in
OpamStateConfig.init ~root_dir ();
match OpamStateConfig.get_switch_opt () with
| None -> env_array (OpamEnv.get_pure ~updates ())
Expand Down
38 changes: 28 additions & 10 deletions src/client/opamConfigCommand.ml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ let list t ns =
OpamConsole.print_table stdout ~sep:" "

let possibly_unix_path_env_value k v =
if k = "PATH" then (Lazy.force OpamSystem.get_cygpath_path_transform) v
if k = "PATH" then
(Lazy.force OpamSystem.get_cygpath_path_transform) ~pathlist:true v
else v

let rec print_env = function
Expand Down Expand Up @@ -209,11 +210,14 @@ let load_and_verify_env ~set_opamroot ~set_opamswitch ~force_path
gt.root switch
in
let environment_opam_switch_prefix =
List.find_opt (function
| "OPAM_SWITCH_PREFIX", OpamParserTypes.Eq, _, _ -> true
| _ -> false)
OpamStd.List.find_map_opt (function
| OpamTypes.{ envu_var = "OPAM_SWITCH_PREFIX";
envu_op = OpamParserTypes.Eq;
envu_value; _} ->
Some envu_value
| _ -> None)
upd
|> OpamStd.Option.map_default (fun (_, _, v, _) -> v) ""
|> OpamStd.Option.default ""
in
let actual_opam_switch_prefix =
OpamFilename.Dir.to_string (OpamPath.Switch.root gt.root switch)
Expand Down Expand Up @@ -272,14 +276,17 @@ let ensure_env_aux ?(base=[]) ?(set_opamroot=false) ?(set_opamswitch=false)
gt switch env_file
end
in
let open OpamTypes in
let updates =
List.filter (function ("OPAM_LAST_ENV", _, _, _) -> false | _ -> true)
List.filter (fun upd ->
not (String.equal upd.envu_var "OPAM_LAST_ENV"))
updates
in
let last_env_file = write_last_env_file gt switch updates in
let updates =
OpamStd.Option.map_default (fun target ->
("OPAM_LAST_ENV", OpamParserTypes.Eq, OpamFilename.to_string target, None)
(env_update_resolved "OPAM_LAST_ENV" Eq
(OpamFilename.to_string target))
::updates)
updates last_env_file
in
Expand Down Expand Up @@ -638,10 +645,21 @@ let switch_allowed_fields, switch_allowed_sections =
("setenv", Modifiable (
(fun nc c -> { c with env = nc.env @ c.env }),
(fun nc c ->
let open OpamTypes in
let env =
List.filter (fun (vr,op,vl,_) ->
None = OpamStd.List.find_opt (fun (vr',op',vl',_) ->
vr = vr' && op = op' && vl = vl') nc.env) c.env
List.filter
(fun { envu_var = var; envu_op = op;
envu_value = value; envu_comment = _;
envu_rewrite = _ } ->
None =
OpamStd.List.find_opt
(fun { envu_var = var'; envu_op = op';
envu_value = value'; envu_comment = _;
envu_rewrite = _ } ->
String.equal var var'
&& (op : OpamParserTypes.env_update_op) = op'
&& String.equal value value')
nc.env) c.env
in
{ c with env })),
fun t -> { t with env = empty.env });
Expand Down
10 changes: 6 additions & 4 deletions src/core/opamSystem.ml
Original file line number Diff line number Diff line change
Expand Up @@ -548,10 +548,12 @@ let get_cygpath_function =
let f = Lazy.from_val (fun x -> x) in
fun ~command:_ -> f
let apply_cygpath_path_transform path =
let apply_cygpath_path_transform ~pathlist path =
let args = if pathlist then [ "--path" ] else [] in
let r =
OpamProcess.run
(OpamProcess.command ~name:(temp_file "command") ~verbose:false "cygpath" ["--path"; "--"; path])
(OpamProcess.command ~name:(temp_file "command")
~verbose:false "cygpath" (args @ ["--"; path]))
in
OpamProcess.cleanup ~force:true r;
if OpamProcess.is_success r then
Expand All @@ -566,9 +568,9 @@ let get_cygpath_path_transform =
lazy (
match resolve_command "cygpath" with
| Some _ -> apply_cygpath_path_transform
| None -> fun x -> x)
| None -> fun ~pathlist:_ x -> x)
else
Lazy.from_val (fun x -> x)
Lazy.from_val (fun ~pathlist:_ x -> x)
let runs = ref []
let print_stats () =
Expand Down
Loading

0 comments on commit 0670b46

Please sign in to comment.