From 5643f06f23cc9c7385a8284f2427c2edb565e3ef Mon Sep 17 00:00:00 2001 From: Rudi Grinberg Date: Wed, 31 Dec 2025 17:43:15 +0000 Subject: [PATCH 1/2] dev Signed-off-by: Rudi Grinberg From 5b19d616ae75fb2c86d286ea8346f0ba7d2ce618 Mon Sep 17 00:00:00 2001 From: Simon Spies Date: Tue, 20 Jan 2026 10:18:00 +0000 Subject: [PATCH 2/2] support for using oxcaml 0.1 Signed-off-by: Simon Spies fully use extensions in workspace files Signed-off-by: Simon Spies Signed-off-by: Rudi Grinberg fix tests Signed-off-by: Simon Spies Signed-off-by: Rudi Grinberg --- doc/changes/added/13395.md | 2 + src/dune_lang/dune_project.ml | 87 +++++++++++-------- src/dune_lang/dune_project.mli | 20 +++++ src/dune_sexp/syntax.ml | 4 +- src/source/workspace.ml | 21 ++++- .../using-and-dune-lang.t/run.t | 4 +- .../fs-mode-dependent/fsmd-exe.t/run.t | 4 +- .../test-cases/github1529.t/run.t | 2 +- .../test-cases/mdx-stanza/using-mdx.t/run.t | 4 +- .../test-cases/menhir/flags-in-workspace.t | 10 +-- .../oxcaml/library-field-parameters.t | 4 +- .../test-cases/oxcaml/library_parameter.t | 4 +- 12 files changed, 107 insertions(+), 59 deletions(-) create mode 100644 doc/changes/added/13395.md diff --git a/doc/changes/added/13395.md b/doc/changes/added/13395.md new file mode 100644 index 00000000000..634d70f7128 --- /dev/null +++ b/doc/changes/added/13395.md @@ -0,0 +1,2 @@ +- Allow enabling extensions with `(using ..)` in `dune-workspace` files + (#13395, @spiessimon) diff --git a/src/dune_lang/dune_project.ml b/src/dune_lang/dune_project.ml index 8a7a3cd674e..b6c0d69ef6c 100644 --- a/src/dune_lang/dune_project.ml +++ b/src/dune_lang/dune_project.ml @@ -222,6 +222,8 @@ include Versioned_file.Make (struct type t = Stanza.Parser.t list end) +let workspace_instance ~syntax ~version = { Lang.Instance.syntax; version; data = [] } + let default_dune_language_version = ref (Syntax.greatest_supported_version_exn Stanza.syntax) ;; @@ -321,29 +323,54 @@ module Extension = struct | Deleted_in _ -> acc | Extension e -> Not_selected e :: acc)) ;; + + let explicit_extensions_map explicit_extensions = + match + String.Map.of_list + (List.map explicit_extensions ~f:(fun (e : instance) -> + let syntax = + let (Packed e) = e.extension in + e.syntax + in + Syntax.name syntax, e)) + with + | Error (name, _, ext) -> + User_error.raise + ~loc:ext.loc + [ Pp.textf "Extension %S specified for the second time." name ] + | Ok map -> map + ;; + + let parse_extensions ~lang_version = + let open Decoder in + let* explicit_extensions = + multi_field + "using" + (let+ loc = loc + and+ name = located string + and+ ver = located Syntax.Version.decode + and+ parse_args = capture in + (* We don't parse the arguments quite yet as we want to set the + version of extensions before parsing them. *) + instantiate ~dune_lang_ver:lang_version ~loc ~parse_args name ver) + in + String_with_vars.set_decoding_env + (let extensions = + List.map explicit_extensions ~f:(fun (instance : instance) -> + let (Packed { syntax; _ }) = instance.extension in + syntax, instance.version) + in + Pform.Env.initial ~stanza:lang_version ~extensions) + @@ + let explicit_extensions_map = explicit_extensions_map explicit_extensions in + return (explicit_extensions_map, explicit_extensions) + ;; end module Melange_syntax = struct let name = "melange" end -let explicit_extensions_map explicit_extensions = - match - String.Map.of_list - (List.map explicit_extensions ~f:(fun (e : Extension.instance) -> - let syntax = - let (Packed e) = e.extension in - e.syntax - in - Syntax.name syntax, e)) - with - | Error (name, _, ext) -> - User_error.raise - ~loc:ext.loc - [ Pp.textf "Extension %S specified for the second time." name ] - | Ok map -> map -;; - let make_parsing_context ~(lang : Lang.Instance.t) extensions = let init = let init = Univ_map.singleton (Syntax.key lang.syntax) (Active lang.version) in @@ -382,6 +409,7 @@ let make_parsing_context ~(lang : Lang.Instance.t) extensions = ;; let interpret_lang_and_extensions ~(lang : Lang.Instance.t) ~explicit_extensions = + let explicit_extensions = Extension.explicit_extensions_map explicit_extensions in let extensions = Extension.automatic ~explicitly_selected:explicit_extensions in let parsing_context = make_parsing_context ~lang extensions in let extension_args, extension_stanzas = @@ -520,7 +548,7 @@ let infer ~dir info packages = let lang = get_dune_lang () in let name = default_name ~dir ~packages in let parsing_context, stanza_parser, extension_args = - interpret_lang_and_extensions ~lang ~explicit_extensions:String.Map.empty + interpret_lang_and_extensions ~lang ~explicit_extensions:[] in let implicit_transitive_deps = Implicit_transitive_deps.Stanza.default ~lang in let wrapped_executables = wrapped_executables_default ~lang in @@ -885,26 +913,9 @@ let parse ~dir ~(lang : Lang.Instance.t) ~file = let open Decoder in fields @@ - let* explicit_extensions = - multi_field - "using" - (let+ loc = loc - and+ name = located string - and+ ver = located Syntax.Version.decode - and+ parse_args = capture in - (* We don't parse the arguments quite yet as we want to set the - version of extensions before parsing them. *) - Extension.instantiate ~dune_lang_ver:lang.version ~loc ~parse_args name ver) + let* explicit_extensions_map, explicit_extensions = + Extension.parse_extensions ~lang_version:lang.version in - String_with_vars.set_decoding_env - (let extensions = - List.map explicit_extensions ~f:(fun (instance : Extension.instance) -> - let (Extension.Packed { syntax; _ }) = instance.extension in - syntax, instance.version) - in - Pform.Env.initial ~stanza:lang.version ~extensions) - @@ - let explicit_extensions = explicit_extensions_map explicit_extensions in let parsing_context, stanza_parser, extension_args = interpret_lang_and_extensions ~lang ~explicit_extensions in @@ -1040,7 +1051,7 @@ let parse ~dir ~(lang : Lang.Instance.t) ~file = let root = dir in let dialects = let dialects = - match String.Map.find explicit_extensions Melange_syntax.name with + match String.Map.find explicit_extensions_map Melange_syntax.name with | Some extension -> (extension.loc, Dialect.rescript) :: dialects | None -> dialects in diff --git a/src/dune_lang/dune_project.mli b/src/dune_lang/dune_project.mli index 4fdaa083a72..af6fd369050 100644 --- a/src/dune_lang/dune_project.mli +++ b/src/dune_lang/dune_project.mli @@ -50,8 +50,14 @@ module Lang : sig what stanzas the user can write in [dune] files. *) val register : Syntax.t -> Stanza.Parser.t list -> unit + + module Instance : sig + type t + end end +val workspace_instance : syntax:Syntax.t -> version:Syntax.Version.t -> Lang.Instance.t + module Extension : sig type 'a t @@ -76,8 +82,22 @@ module Extension : sig (** Register experimental extensions that were deleted *) val register_deleted : name:string -> deleted_in:Syntax.Version.t -> unit + + (** An instantiated extension with captured arguments *) + type instance + + val parse_extensions + : lang_version:Syntax.Version.t + -> (instance String.Map.t * instance list) Decoder.fields_parser end +(** Interpret extensions and build parsing context. Returns the parsing context, + stanza parser, and extension arguments. *) +val interpret_lang_and_extensions + : lang:Lang.Instance.t + -> explicit_extensions:Extension.instance list + -> Univ_map.t * Stanza.t list Decoder.t * Univ_map.t + (** Create an anonymous project at the given directory Optional arguments: diff --git a/src/dune_sexp/syntax.ml b/src/dune_sexp/syntax.ml index 06fa3983f4e..11421cbfc61 100644 --- a/src/dune_sexp/syntax.ml +++ b/src/dune_sexp/syntax.ml @@ -319,8 +319,8 @@ module Error = struct User_error.raise ~loc ([ Pp.textf - "%s is available only when %s is enabled in the dune-project file. You must \ - enable it using (using %s %s) in your dune-project file." + "%s is available only when %s is enabled in the dune-project or workspace \ + file. You must enable it using (using %s %s) in the file." what t.name t.name diff --git a/src/source/workspace.ml b/src/source/workspace.ml index 5d95a1c78b0..ce6ad196247 100644 --- a/src/source/workspace.ml +++ b/src/source/workspace.ml @@ -794,6 +794,8 @@ let find_lock_dir t path = let add_repo t repo = { t with repos = repo :: t.repos } +(* Why do we even have a separate versioned file here if we're using the same + syntax as the dune project? *) include Dune_lang.Versioned_file.Make (struct type t = unit end) @@ -979,7 +981,7 @@ let check_lock_dirs_no_dupes lock_dirs = ] ;; -let step1 clflags = +let step1 ~(lang : Lang.Instance.t) clflags = let { Clflags.x ; profile = cl_profile ; instrument_with = cl_instrument_with @@ -1009,6 +1011,19 @@ let step1 clflags = let+ x = field in lazy (Option.value cl ~default:(Lazy.force x)) in + (* This is all dodgy but we get away with it because we don't really allow + extensions to inject their own stanza into the workspace. *) + let* parsing_context, _stanza_parser, _extension_args = + let+ _, explicit_extensions = + Dune_project.Extension.parse_extensions ~lang_version:lang.version + in + let lang = + Dune_project.workspace_instance ~syntax:lang.syntax ~version:lang.version + in + Dune_lang.Dune_project.interpret_lang_and_extensions ~lang ~explicit_extensions + in + Dune_lang.Decoder.set_many parsing_context + @@ let* () = Dune_lang.Versioned_file.no_more_lang and+ env = env_field_lazy and+ profile = @@ -1111,7 +1126,7 @@ let step1 clflags = { Step1.t; config } ;; -let step1 clflags = fields (step1 clflags) +let step1 ~lang clflags = fields (step1 ~lang clflags) let default clflags = let { Clflags.x @@ -1157,7 +1172,7 @@ let load_step1 clflags p = parse_contents lb ~f:(fun lang -> String_with_vars.set_decoding_env (Pform.Env.initial ~stanza:lang.version ~extensions:[]) - (step1 clflags))) + (step1 ~lang clflags))) ;; let filename = "dune-workspace" diff --git a/test/blackbox-tests/test-cases/extensions-versioning/using-and-dune-lang.t/run.t b/test/blackbox-tests/test-cases/extensions-versioning/using-and-dune-lang.t/run.t index 5852e91d562..53e5a5989da 100644 --- a/test/blackbox-tests/test-cases/extensions-versioning/using-and-dune-lang.t/run.t +++ b/test/blackbox-tests/test-cases/extensions-versioning/using-and-dune-lang.t/run.t @@ -4,8 +4,8 @@ at the current language version: File "dune", line 1, characters 0-5: 1 | (mdx) ^^^^^ - Error: 'mdx' is available only when mdx is enabled in the dune-project file. - You must enable it using (using mdx ..) in your dune-project file. + Error: 'mdx' is available only when mdx is enabled in the dune-project or + workspace file. You must enable it using (using mdx ..) in the file. Note however that the currently selected version of dune (1.0) does not support this plugin. The first version of this plugin is 0.1 and was introduced in dune 2.4. diff --git a/test/blackbox-tests/test-cases/foreign-stubs/fs-mode-dependent/fsmd-exe.t/run.t b/test/blackbox-tests/test-cases/foreign-stubs/fs-mode-dependent/fsmd-exe.t/run.t index 3272931d38c..c087b08162b 100644 --- a/test/blackbox-tests/test-cases/foreign-stubs/fs-mode-dependent/fsmd-exe.t/run.t +++ b/test/blackbox-tests/test-cases/foreign-stubs/fs-mode-dependent/fsmd-exe.t/run.t @@ -10,8 +10,8 @@ Without the toggle, we get an error message for using the new mode subfield 11 | (mode native) ^^^^^^^^^^^^^ Error: 'mode' is available only when mode_specific_stubs is enabled in the - dune-project file. You must enable it using (using mode_specific_stubs 0.1) - in your dune-project file. + dune-project or workspace file. You must enable it using (using + mode_specific_stubs 0.1) in the file. But the toggle only exists in Dune 3.5 $ cat >dune-project <) in your dune-project file. + or workspace file. You must enable it using (using menhir ) in the file. [1] diff --git a/test/blackbox-tests/test-cases/mdx-stanza/using-mdx.t/run.t b/test/blackbox-tests/test-cases/mdx-stanza/using-mdx.t/run.t index d4776e35441..ee58488c399 100644 --- a/test/blackbox-tests/test-cases/mdx-stanza/using-mdx.t/run.t +++ b/test/blackbox-tests/test-cases/mdx-stanza/using-mdx.t/run.t @@ -7,6 +7,6 @@ dune-project File "dune", line 1, characters 0-5: 1 | (mdx) ^^^^^ - Error: 'mdx' is available only when mdx is enabled in the dune-project file. - You must enable it using (using mdx 0.1) in your dune-project file. + Error: 'mdx' is available only when mdx is enabled in the dune-project or + workspace file. You must enable it using (using mdx 0.1) in the file. [1] diff --git a/test/blackbox-tests/test-cases/menhir/flags-in-workspace.t b/test/blackbox-tests/test-cases/menhir/flags-in-workspace.t index 70c77a4db8a..00f64d58a67 100644 --- a/test/blackbox-tests/test-cases/menhir/flags-in-workspace.t +++ b/test/blackbox-tests/test-cases/menhir/flags-in-workspace.t @@ -13,9 +13,9 @@ See #9024. > EOF $ dune build 2>&1 | head -n 5 - Internal error, please report upstream including the contents of _build/log. - Description: - ("Syntax identifier is unset", - { name = "menhir" - ; supported_versions = + File "dune-workspace", line 3, characters 7-29: + 3 | (test (menhir_flags --table))) + ^^^^^^^^^^^^^^^^^^^^^^ + Error: 'menhir_flags' is available only when menhir is enabled in the + dune-project or workspace file. You must enable it using (using menhir 2.1) [1] diff --git a/test/blackbox-tests/test-cases/oxcaml/library-field-parameters.t b/test/blackbox-tests/test-cases/oxcaml/library-field-parameters.t index 5c900a3a02c..18a296c193e 100644 --- a/test/blackbox-tests/test-cases/oxcaml/library-field-parameters.t +++ b/test/blackbox-tests/test-cases/oxcaml/library-field-parameters.t @@ -21,8 +21,8 @@ It should fail because the syntax extension wasn't enabled in `dune-project`: 1 | (library (name lib) (parameters a)) ^^^^^^^^^^^^^^ Error: 'parameters' is available only when oxcaml is enabled in the - dune-project file. You must enable it using (using oxcaml 0.1) in your - dune-project file. + dune-project or workspace file. You must enable it using (using oxcaml 0.1) + in the file. Note however that oxcaml is experimental and might change without notice in the future. [1] diff --git a/test/blackbox-tests/test-cases/oxcaml/library_parameter.t b/test/blackbox-tests/test-cases/oxcaml/library_parameter.t index 4313041fc03..7cf3ad30fb2 100644 --- a/test/blackbox-tests/test-cases/oxcaml/library_parameter.t +++ b/test/blackbox-tests/test-cases/oxcaml/library_parameter.t @@ -25,8 +25,8 @@ The test build should fails because the oxcaml extension is not available. 2 | (public_name param.intf) 3 | (name param_intf)) Error: 'library_parameter' is available only when oxcaml is enabled in the - dune-project file. You must enable it using (using oxcaml 0.1) in your - dune-project file. + dune-project or workspace file. You must enable it using (using oxcaml 0.1) + in the file. Note however that oxcaml is experimental and might change without notice in the future. [1]