Skip to content

Commit 6bab283

Browse files
committed
fix(pkg): substitute absent package variables in string interpolation
Signed-off-by: Ali Caglayan <alizter@gmail.com>
2 parents 3a8b14f + d6ea5ac commit 6bab283

File tree

5 files changed

+143
-31
lines changed

5 files changed

+143
-31
lines changed

src/dune_lang/package_variable_name.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,13 @@ 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+
let absent_package_value t =
52+
if equal t installed then Some "false" else None
53+
;;
54+
5055
let platform_specific =
5156
Set.of_list [ arch; os; os_version; os_distribution; os_family; sys_ocaml_version ]
5257
;;

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 value of a package variable when the package is not in the
39+
solution. Some variables have well-defined values for absent packages
40+
(e.g., "installed" is "false"), while others are undefined and return None. *)
41+
val absent_package_value : t -> string 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: 76 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,42 @@ 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 =
57+
let opam_variable_to_slang ~loc ~packages_in_solution ~for_string_interp packages variable =
5858
let variable_string = OpamVariable.to_string variable in
59+
let variable_name = Package_variable_name.of_string variable_string in
5960
let convert_with_package_name package_name =
6061
match is_valid_package_variable_name variable_string with
6162
| false -> Error (invalid_variable_error ~loc variable)
6263
| 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-
Ok (Slang.pform pform)
64+
(match package_name with
65+
| Some p ->
66+
let pkg_name = Package_name.of_opam_package_name p in
67+
if Package_name.Map.mem packages_in_solution pkg_name
68+
then (
69+
let pform =
70+
Package_variable.to_pform
71+
{ Package_variable.name = variable_name; scope = Package pkg_name }
72+
in
73+
Ok (Slang.pform pform))
74+
else if for_string_interp
75+
then (
76+
let value =
77+
Package_variable_name.absent_package_value variable_name
78+
|> Option.value ~default:""
79+
in
80+
Ok (Slang.text value))
81+
else (
82+
let pform =
83+
Package_variable.to_pform
84+
{ Package_variable.name = variable_name; scope = Package pkg_name }
85+
in
86+
Ok (Slang.pform pform))
87+
| None ->
88+
let pform =
89+
Package_variable.to_pform
90+
{ Package_variable.name = variable_name; scope = Self }
91+
in
92+
Ok (Slang.pform pform))
7393
in
7494
match packages with
7595
| [] ->
@@ -114,12 +134,21 @@ let desugar_special_string_interpolation_syntax
114134
| _ -> fident
115135
;;
116136

117-
let opam_fident_to_slang ~loc fident =
137+
let opam_fident_to_slang ~loc ~packages_in_solution ~for_string_interp fident =
118138
let open Result.O in
119139
let packages, variable, string_converter =
120140
OpamFilter.desugar_fident fident |> desugar_special_string_interpolation_syntax
121141
in
122-
let+ slang = opam_variable_to_slang ~loc packages variable in
142+
(* When there's a string_converter (?then:else syntax), don't substitute
143+
absent package values - let catch_undefined_var handle it at build time. *)
144+
let for_string_interp =
145+
match string_converter with
146+
| Some _ -> false
147+
| None -> for_string_interp
148+
in
149+
let+ slang =
150+
opam_variable_to_slang ~loc ~packages_in_solution ~for_string_interp packages variable
151+
in
123152
match string_converter with
124153
| None -> slang
125154
| Some (then_, else_) ->
@@ -133,11 +162,12 @@ let opam_fident_to_slang ~loc fident =
133162
Slang.if_ condition ~then_:(Slang.text then_) ~else_:(Slang.text else_)
134163
;;
135164

136-
let opam_raw_fident_to_slang ~loc raw_ident =
137-
OpamTypesBase.filter_ident_of_string raw_ident |> opam_fident_to_slang ~loc
165+
let opam_raw_fident_to_slang ~loc ~packages_in_solution ~for_string_interp raw_ident =
166+
OpamTypesBase.filter_ident_of_string raw_ident
167+
|> opam_fident_to_slang ~loc ~packages_in_solution ~for_string_interp
138168
;;
139169

140-
let opam_string_to_slang ~package ~loc opam_string =
170+
let opam_string_to_slang ~packages_in_solution ~package ~loc opam_string =
141171
Re.Seq.split_full OpamFilter.string_interp_regex opam_string
142172
|> Seq.map ~f:(function
143173
| `Text text -> Ok (Slang.text text)
@@ -148,7 +178,7 @@ let opam_string_to_slang ~package ~loc opam_string =
148178
when String.starts_with ~prefix:"%{" interp
149179
&& String.ends_with ~suffix:"}%" interp ->
150180
let ident = String.sub ~pos:2 ~len:(String.length interp - 4) interp in
151-
opam_raw_fident_to_slang ~loc ident
181+
opam_raw_fident_to_slang ~loc ~packages_in_solution ~for_string_interp:true ident
152182
| other ->
153183
Error
154184
(User_error.make
@@ -220,11 +250,13 @@ let resolve_depopts ~resolve depopts =
220250
These two Slang operators are used to emulate Opam's undefined value
221251
semantics.
222252
*)
223-
let filter_to_blang ~package ~loc filter =
253+
let filter_to_blang ~packages_in_solution ~package ~loc filter =
224254
let filter_to_slang (filter : OpamTypes.filter) =
225255
match filter with
226-
| FString s -> opam_string_to_slang ~package ~loc s
227-
| FIdent fident -> opam_fident_to_slang ~loc fident
256+
| FString s -> opam_string_to_slang ~packages_in_solution ~package ~loc s
257+
| FIdent fident ->
258+
(* FIdent in filter context is truthy, so don't substitute absent values *)
259+
opam_fident_to_slang ~loc ~packages_in_solution ~for_string_interp:false fident
228260
| other ->
229261
Code_error.raise
230262
"The opam file parser should only allow identifiers and strings in places where \
@@ -273,6 +305,7 @@ let filter_to_blang ~package ~loc filter =
273305
;;
274306

275307
let opam_commands_to_actions
308+
~packages_in_solution
276309
get_solver_var
277310
loc
278311
package
@@ -293,8 +326,13 @@ let opam_commands_to_actions
293326
let slang =
294327
let+ slang =
295328
match simple_arg with
296-
| CString s -> opam_string_to_slang ~package ~loc s
297-
| CIdent ident -> opam_raw_fident_to_slang ~loc ident
329+
| CString s -> opam_string_to_slang ~packages_in_solution ~package ~loc s
330+
| CIdent ident ->
331+
opam_raw_fident_to_slang
332+
~loc
333+
~packages_in_solution
334+
~for_string_interp:true
335+
ident
298336
in
299337
Slang.simplify slang
300338
in
@@ -304,7 +342,8 @@ let opam_commands_to_actions
304342
| None -> slang
305343
| Some filter ->
306344
let+ filter_blang =
307-
filter_to_blang ~package ~loc filter >>| Slang.simplify_blang
345+
filter_to_blang ~packages_in_solution ~package ~loc filter
346+
>>| Slang.simplify_blang
308347
and+ slang = slang in
309348
let filter_blang_handling_undefined =
310349
(* Wrap the blang filter so that if any undefined
@@ -331,7 +370,8 @@ let opam_commands_to_actions
331370
| None -> Ok action
332371
| Some filter ->
333372
let+ condition =
334-
filter_to_blang ~package ~loc filter >>| Slang.simplify_blang
373+
filter_to_blang ~packages_in_solution ~package ~loc filter
374+
>>| Slang.simplify_blang
335375
in
336376
Action.When (condition, action)
337377
in
@@ -358,14 +398,15 @@ let opam_commands_to_actions
358398
solving. Opam allows depexts to be filtered by arbitrary filter expressions,
359399
which is why the slang dsl is needed as opposed to (say) a map from
360400
distro/version to depext name. *)
361-
let depexts_to_conditional_external_dependencies package depexts =
401+
let depexts_to_conditional_external_dependencies ~packages_in_solution package depexts =
362402
let open Result.O in
363403
List.map depexts ~f:(fun (sys_pkgs, filter) ->
364404
let external_package_names =
365405
OpamSysPkg.Set.to_list_map OpamSysPkg.to_string sys_pkgs
366406
in
367407
let+ condition =
368-
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
369410
in
370411
let enabled_if =
371412
if Slang.Blang.equal condition Slang.Blang.true_
@@ -468,6 +509,7 @@ let opam_package_to_lock_file_pkg
468509
Solver_stats.Updater.expand_variable stats_updater variable_name;
469510
Solver_env.get solver_env variable_name
470511
in
512+
let packages_in_solution = version_by_package_name in
471513
let* build_command =
472514
if Resolved_package.dune_build resolved_package
473515
then Ok (Some Lock_dir.Build_command.Dune)
@@ -491,13 +533,18 @@ let opam_package_to_lock_file_pkg
491533
| None -> Ok action
492534
| Some filter ->
493535
let+ blang =
494-
filter_to_blang ~package:opam_package ~loc:Loc.none filter
536+
filter_to_blang
537+
~packages_in_solution
538+
~package:opam_package
539+
~loc:Loc.none
540+
filter
495541
>>| Slang.simplify_blang
496542
in
497543
Action.When (blang, action))
498544
|> Result.List.all
499545
and+ build_step =
500546
opam_commands_to_actions
547+
~packages_in_solution
501548
get_solver_var
502549
loc
503550
opam_package
@@ -528,6 +575,7 @@ let opam_package_to_lock_file_pkg
528575
if portable_lock_dir
529576
then
530577
depexts_to_conditional_external_dependencies
578+
~packages_in_solution
531579
opam_package
532580
(OpamFile.OPAM.depexts opam_file)
533581
else (
@@ -549,7 +597,7 @@ let opam_package_to_lock_file_pkg
549597
in
550598
let+ install_command =
551599
OpamFile.OPAM.install opam_file
552-
|> opam_commands_to_actions get_solver_var loc opam_package
600+
|> opam_commands_to_actions ~packages_in_solution get_solver_var loc opam_package
553601
>>| make_action
554602
>>| Option.map ~f:(fun action -> lockfile_field_choice (build_env action))
555603
>>| Option.value ~default:Lock_dir.Conditional_choice.empty

test/blackbox-tests/test-cases/pkg/opam-var/absent-pkg-installed.t

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ First, test the variable in string interpolation context (command argument):
1515
Solution for dune.lock:
1616
- string-context.0.0.1
1717

18-
Currently the variable is left as a pform. It should resolve to "false" at
19-
solve time:
18+
The variable resolves to "false" at solve time:
2019

2120
$ cat dune.lock/string-context.0.0.1.pkg
2221
(version 0.0.1)
2322

2423
(build
25-
(all_platforms ((action (run echo %{pkg:not-in-lock:installed})))))
24+
(all_platforms ((action (run echo false)))))
25+
2626

2727

2828

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
Test the "version" variable for packages not in the solution in different
2+
contexts: string interpolation vs truthy/filter context.
3+
4+
$ mkrepo
5+
6+
First, test the variable in string interpolation context (command argument):
7+
8+
$ mkpkg "string-context" <<'EOF'
9+
> build: [
10+
> [ "echo" "version=%{not-in-lock:version}%" ]
11+
> ]
12+
> EOF
13+
14+
$ solve string-context
15+
Solution for dune.lock:
16+
- string-context.0.0.1
17+
18+
The variable resolves to empty string at solve time:
19+
20+
$ cat dune.lock/string-context.0.0.1.pkg
21+
(version 0.0.1)
22+
23+
(build
24+
(all_platforms ((action (run echo "version=")))))
25+
26+
27+
Now test the variable in truthy/filter context (conditional on command):
28+
29+
$ mkpkg "truthy-context" <<'EOF'
30+
> build: [
31+
> [ "echo" "has version" ] {not-in-lock:version}
32+
> [ "echo" "no version" ] {!not-in-lock:version}
33+
> ]
34+
> EOF
35+
36+
$ solve truthy-context
37+
Solution for dune.lock:
38+
- truthy-context.0.0.1
39+
40+
In truthy context, the variable is left for build time evaluation:
41+
42+
$ cat dune.lock/truthy-context.0.0.1.pkg
43+
(version 0.0.1)
44+
45+
(build
46+
(all_platforms
47+
((action
48+
(progn
49+
(when %{pkg:not-in-lock:version} (run echo "has version"))
50+
(when (not %{pkg:not-in-lock:version}) (run echo "no version")))))))
51+
52+
53+

0 commit comments

Comments
 (0)