Skip to content

Commit 2b36474

Browse files
committed
fix(pkg): substitute installed variable at solve time
When a package is absent from the solution, its 'installed' variable is known to have the value "false". We therefore substitute it at solve time. Signed-off-by: Ali Caglayan <alizter@gmail.com>
2 parents 1be7e09 + f9e36c4 commit 2b36474

File tree

6 files changed

+141
-120
lines changed

6 files changed

+141
-120
lines changed

src/dune_lang/package_variable_name.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,14 @@ let name = of_string "name"
4545
let build = of_string "build"
4646
let post = of_string "post"
4747
let dev = of_string "dev"
48+
let installed = of_string "installed"
4849
let one_of t xs = List.mem xs ~equal t
4950

51+
(** Returns the slang value of a variable for an absent package. Returns None
52+
for variables without known values or contexts where substitution shouldn't
53+
occur. *)
54+
let absent_package_value t = if equal t installed then Some (Slang.bool false) else None
55+
5056
let platform_specific =
5157
Set.of_list [ arch; os; os_version; os_distribution; os_family; sys_ocaml_version ]
5258
;;
@@ -69,6 +75,7 @@ let all_known =
6975
; build
7076
; post
7177
; dev
78+
; installed
7279
]
7380
;;
7481

src/dune_lang/package_variable_name.mli

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,14 @@ val version : t
3232
val post : t
3333
val build : t
3434
val dev : t
35+
val installed : t
3536
val one_of : t -> t list -> bool
3637

38+
(** Returns the slang value of a variable for an absent package. Returns None
39+
for variables without known values or contexts where substitution shouldn't
40+
occur. *)
41+
val absent_package_value : t -> Slang.t option
42+
3743
(** The set of variable names whose values are expected to differ depending on
3844
the current platform. *)
3945
val platform_specific : Set.t

src/dune_pkg/lock_pkg.ml

Lines changed: 90 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -54,40 +54,46 @@ let invalid_variable_error ~loc variable =
5454
[ Pp.textf "Variable %S is not supported." (OpamVariable.to_string variable) ]
5555
;;
5656

57-
let opam_variable_to_slang ~loc packages variable =
58-
let variable_string = OpamVariable.to_string variable in
59-
let convert_with_package_name package_name =
60-
match is_valid_package_variable_name variable_string with
61-
| false -> invalid_variable_error ~loc variable
62-
| true ->
63-
let pform =
64-
let name = Package_variable_name.of_string variable_string in
65-
let scope : Package_variable.Scope.t =
66-
match package_name with
67-
| None -> Self
68-
| Some p -> Package (Package_name.of_opam_package_name p)
69-
in
70-
Package_variable.to_pform { Package_variable.name; scope }
71-
in
72-
Slang.pform pform
57+
let opam_variable_to_slang =
58+
let opam_var_to_pform variable_name scope =
59+
Package_variable.to_pform { Package_variable.name = variable_name; scope }
60+
|> Slang.pform
7361
in
74-
match packages with
75-
| [] ->
76-
(match is_valid_global_variable_name variable_string with
77-
| false ->
78-
(* Note that there's no syntactic distinction between global variables
62+
fun ~loc ~packages_in_solution packages variable ->
63+
let variable_string = OpamVariable.to_string variable in
64+
let variable_name = Package_variable_name.of_string variable_string in
65+
let convert_with_package_name package_name =
66+
match is_valid_package_variable_name variable_string with
67+
| false -> invalid_variable_error ~loc variable
68+
| true ->
69+
(match package_name with
70+
| Some p ->
71+
let pkg_name = Package_name.of_opam_package_name p in
72+
let pform = opam_var_to_pform variable_name (Package pkg_name) in
73+
if Package_name.Map.mem packages_in_solution pkg_name
74+
then pform
75+
else
76+
Package_variable_name.absent_package_value variable_name
77+
|> Option.value ~default:pform
78+
| None -> opam_var_to_pform variable_name Self)
79+
in
80+
match packages with
81+
| [] ->
82+
(match is_valid_global_variable_name variable_string with
83+
| false ->
84+
(* Note that there's no syntactic distinction between global variables
7985
and package variables in the current package. This check will prevent
8086
invalid global variable names from being used for package variables in the
8187
current package where the optional qualifier "_:" is omitted. *)
82-
invalid_variable_error ~loc variable
83-
| true ->
84-
(match Pform.Var.of_opam_global_variable_name variable_string with
85-
| Some global_var -> Slang.pform (Pform.Var global_var)
86-
| None -> convert_with_package_name None))
87-
| [ package_name ] -> convert_with_package_name package_name
88-
| many ->
89-
let many = List.map many ~f:convert_with_package_name in
90-
Slang.blang (Blang.And (List.map many ~f:(fun slang -> Blang.Expr slang)))
88+
invalid_variable_error ~loc variable
89+
| true ->
90+
(match Pform.Var.of_opam_global_variable_name variable_string with
91+
| Some global_var -> Slang.pform (Pform.Var global_var)
92+
| None -> convert_with_package_name None))
93+
| [ package_name ] -> convert_with_package_name package_name
94+
| many ->
95+
let many = List.map many ~f:convert_with_package_name in
96+
Slang.blang (Blang.And (List.map many ~f:(fun slang -> Blang.Expr slang)))
9197
;;
9298

9399
(* Handles the special case for packages whose names contain '+' characters
@@ -113,29 +119,38 @@ let desugar_special_string_interpolation_syntax
113119
| _ -> fident
114120
;;
115121

116-
let opam_fident_to_slang ~loc fident =
122+
let opam_fident_to_slang ~loc ~packages_in_solution fident =
117123
let packages, variable, string_converter =
118124
OpamFilter.desugar_fident fident |> desugar_special_string_interpolation_syntax
119125
in
120-
let slang = opam_variable_to_slang ~loc packages variable in
126+
let slang = opam_variable_to_slang ~loc ~packages_in_solution packages variable in
121127
match string_converter with
122128
| None -> slang
123129
| Some (then_, else_) ->
124130
(* The "else" case is also used when evaluating the condition would expand
125131
an undefined variable. The catch_undefined_var operator is used to
126132
convert expressions that throw undefined variable exceptions into false.
127133
*)
128-
let condition =
129-
Blang.Expr (Slang.catch_undefined_var slang ~fallback:(Slang.bool false))
134+
let is_known_false =
135+
match slang with
136+
| Form (_, Blang (Blang.Const false)) -> true
137+
| _ -> false
130138
in
131-
Slang.if_ condition ~then_:(Slang.text then_) ~else_:(Slang.text else_)
139+
if is_known_false
140+
then Slang.text else_
141+
else (
142+
let condition =
143+
Blang.Expr (Slang.catch_undefined_var slang ~fallback:(Slang.bool false))
144+
in
145+
Slang.if_ condition ~then_:(Slang.text then_) ~else_:(Slang.text else_))
132146
;;
133147

134-
let opam_raw_fident_to_slang ~loc raw_ident =
135-
OpamTypesBase.filter_ident_of_string raw_ident |> opam_fident_to_slang ~loc
148+
let opam_raw_fident_to_slang ~loc ~packages_in_solution raw_ident =
149+
OpamTypesBase.filter_ident_of_string raw_ident
150+
|> opam_fident_to_slang ~loc ~packages_in_solution
136151
;;
137152

138-
let opam_string_to_slang ~package ~loc opam_string =
153+
let opam_string_to_slang ~packages_in_solution ~package ~loc opam_string =
139154
Re.Seq.split_full OpamFilter.string_interp_regex opam_string
140155
|> Seq.map ~f:(function
141156
| `Text text -> Slang.text text
@@ -146,7 +161,7 @@ let opam_string_to_slang ~package ~loc opam_string =
146161
when String.starts_with ~prefix:"%{" interp
147162
&& String.ends_with ~suffix:"}%" interp ->
148163
let ident = String.sub ~pos:2 ~len:(String.length interp - 4) interp in
149-
opam_raw_fident_to_slang ~loc ident
164+
opam_raw_fident_to_slang ~loc ~packages_in_solution ident
150165
| other ->
151166
User_error.raise
152167
~loc
@@ -216,11 +231,13 @@ let resolve_depopts ~resolve depopts =
216231
These two Slang operators are used to emulate Opam's undefined value
217232
semantics.
218233
*)
219-
let filter_to_blang ~package ~loc filter =
234+
let filter_to_blang ~packages_in_solution ~package ~loc filter =
220235
let filter_to_slang (filter : OpamTypes.filter) =
221236
match filter with
222-
| FString s -> opam_string_to_slang ~package ~loc s
223-
| FIdent fident -> opam_fident_to_slang ~loc fident
237+
| FString s -> opam_string_to_slang ~packages_in_solution ~package ~loc s
238+
| FIdent fident ->
239+
(* FIdent in filter context is truthy, so don't substitute absent values *)
240+
opam_fident_to_slang ~loc ~packages_in_solution fident
224241
| other ->
225242
Code_error.raise
226243
"The opam file parser should only allow identifiers and strings in places where \
@@ -268,6 +285,7 @@ let filter_to_blang ~package ~loc filter =
268285
;;
269286

270287
let opam_commands_to_actions
288+
~packages_in_solution
271289
get_solver_var
272290
loc
273291
package
@@ -287,8 +305,9 @@ let opam_commands_to_actions
287305
let slang =
288306
let slang =
289307
match simple_arg with
290-
| CString s -> opam_string_to_slang ~package ~loc s
291-
| CIdent ident -> opam_raw_fident_to_slang ~loc ident
308+
| CString s -> opam_string_to_slang ~packages_in_solution ~package ~loc s
309+
| CIdent ident ->
310+
opam_raw_fident_to_slang ~loc ~packages_in_solution ident
292311
in
293312
Slang.simplify slang
294313
in
@@ -298,8 +317,9 @@ let opam_commands_to_actions
298317
| None -> slang
299318
| Some filter ->
300319
let filter_blang =
301-
filter_to_blang ~package ~loc filter |> Slang.simplify_blang
302-
and slang = slang in
320+
filter_to_blang ~packages_in_solution ~package ~loc filter
321+
|> Slang.simplify_blang
322+
in
303323
let filter_blang_handling_undefined =
304324
(* Wrap the blang filter so that if any undefined
305325
variables are expanded while evaluating the filter,
@@ -318,17 +338,18 @@ let opam_commands_to_actions
318338
if List.is_empty terms
319339
then None
320340
else (
321-
let action =
322-
let action = Action.Run terms in
323-
match filter with
324-
| None -> action
325-
| Some filter ->
326-
let condition =
327-
filter_to_blang ~package ~loc filter |> Slang.simplify_blang
328-
in
329-
Action.When (condition, action)
330-
in
331-
Some action))
341+
let action = Action.Run terms in
342+
match filter with
343+
| None -> Some action
344+
| Some filter ->
345+
let condition =
346+
filter_to_blang ~packages_in_solution ~package ~loc filter
347+
|> Slang.simplify_blang
348+
in
349+
(match condition with
350+
| Const true -> Some action
351+
| Const false -> None
352+
| _ -> Some (Action.When (condition, action)))))
332353
;;
333354

334355
(* Standard package variables that are always defined at build time.
@@ -379,12 +400,13 @@ let rec filter_vars_are_defined : OpamTypes.filter -> bool = function
379400
380401
Depexts with filters that reference undefined variables are excluded, as
381402
they would error at build time. *)
382-
let depexts_to_conditional_external_dependencies package depexts =
403+
let depexts_to_conditional_external_dependencies ~packages_in_solution package depexts =
383404
List.filter_map depexts ~f:(fun (sys_pkgs, filter) ->
384405
let open Option.O in
385406
let* () = Option.some_if (filter_vars_are_defined filter) () in
386407
let condition =
387-
filter_to_blang ~package ~loc:Loc.none filter |> Slang.simplify_blang
408+
filter_to_blang ~packages_in_solution ~package ~loc:Loc.none filter
409+
|> Slang.simplify_blang
388410
in
389411
let+ () = Option.some_if (not (Slang.Blang.equal condition Slang.Blang.false_)) () in
390412
let external_package_names =
@@ -489,6 +511,7 @@ let opam_package_to_lock_file_pkg
489511
Solver_stats.Updater.expand_variable stats_updater variable_name;
490512
Solver_env.get solver_env variable_name
491513
in
514+
let packages_in_solution = version_by_package_name in
492515
let build_command =
493516
if Resolved_package.dune_build resolved_package
494517
then Some Lock_dir.Build_command.Dune
@@ -512,12 +535,17 @@ let opam_package_to_lock_file_pkg
512535
| None -> action
513536
| Some filter ->
514537
let blang =
515-
filter_to_blang ~package:opam_package ~loc:Loc.none filter
538+
filter_to_blang
539+
~packages_in_solution
540+
~package:opam_package
541+
~loc:Loc.none
542+
filter
516543
|> Slang.simplify_blang
517544
in
518545
Action.When (blang, action))
519546
and build_step =
520547
opam_commands_to_actions
548+
~packages_in_solution
521549
get_solver_var
522550
loc
523551
opam_package
@@ -548,6 +576,7 @@ let opam_package_to_lock_file_pkg
548576
if portable_lock_dir
549577
then
550578
depexts_to_conditional_external_dependencies
579+
~packages_in_solution
551580
opam_package
552581
(OpamFile.OPAM.depexts opam_file)
553582
else (
@@ -566,7 +595,7 @@ let opam_package_to_lock_file_pkg
566595
in
567596
let install_command =
568597
OpamFile.OPAM.install opam_file
569-
|> opam_commands_to_actions get_solver_var loc opam_package
598+
|> opam_commands_to_actions ~packages_in_solution get_solver_var loc opam_package
570599
|> make_action
571600
|> Option.map ~f:(fun action -> lockfile_field_choice (build_env action))
572601
|> Option.value ~default:Lock_dir.Conditional_choice.empty

0 commit comments

Comments
 (0)