diff --git a/src/dune_lang/action.mli b/src/dune_lang/action.mli index 548c7438e0c..40a664b3114 100644 --- a/src/dune_lang/action.mli +++ b/src/dune_lang/action.mli @@ -54,7 +54,7 @@ end module File_perm : sig (** File mode, for when creating files. We only allow what Dune takes into - account when memoizing commands. *) + account when memoizing commands. *) type t = File_perm.t = | Normal @@ -95,8 +95,8 @@ type t = | Chdir of String_with_vars.t * t | Setenv of String_with_vars.t * String_with_vars.t * t (* It's not possible to use a build String_with_vars.t here since jbuild - supports redirecting to /dev/null. In [dune] files this is replaced with - %{null} *) + supports redirecting to /dev/null. In [dune] files this is replaced with + %{null} *) | Redirect_out of Outputs.t * String_with_vars.t * File_perm.t * t | Redirect_in of Inputs.t * String_with_vars.t * t | Ignore of Outputs.t * t diff --git a/src/dune_lang/pform.ml b/src/dune_lang/pform.ml index 53f26e52b39..29261626dd3 100644 --- a/src/dune_lang/pform.ml +++ b/src/dune_lang/pform.ml @@ -55,6 +55,7 @@ module Var = struct | Arch | Sys_ocaml_version | Section_dir of Section.t + | Target of string let compare = Poly.compare @@ -75,6 +76,7 @@ module Var = struct | Sys_ocaml_version -> variant "Sys_ocaml_version" [] | Section_dir section -> variant "Section_dir" [ string (Section.to_string section) ] + | Target target -> variant "Target" [ string target ] ;; let encode_to_latest_dune_lang_version = function @@ -91,6 +93,7 @@ module Var = struct | Arch -> "arch" | Sys_ocaml_version -> "sys_ocaml_version" | Section_dir section -> Section.to_string section + | Target target -> target ;; end @@ -102,7 +105,7 @@ module Var = struct | First_dep | Deps | Targets - | Target + | Target of string | Cc | Cxx | Ccomp_type @@ -141,9 +144,16 @@ module Var = struct | Toolchain | Pkg of Pkg.t - let compare : t -> t -> Ordering.t = Poly.compare + let compare (a : t) (b : t) : Ordering.t = + match a, b with + | Target a, Target b -> String.compare a b + | Target _, _ -> Lt + | _, Target _ -> Gt + | a, b -> Poly.compare a b + ;; let to_dyn = function + | Target name -> Dyn.Variant ("Target", [ String name ]) | User_var v -> Dyn.Variant ("User_var", [ String v ]) | t -> let open Dyn in @@ -155,7 +165,7 @@ module Var = struct | First_dep -> variant "First_dep" [] | Deps -> variant "Deps" [] | Targets -> variant "Targets" [] - | Target -> variant "Target" [] + | Target _ -> assert false (* Handled above *) | Cc -> variant "Cc" [] | Cxx -> variant "Cxx" [] | Ccomp_type -> variant "Ccomp_type" [] @@ -465,7 +475,7 @@ let encode_to_latest_dune_lang_version t = | First_dep -> None | Deps -> Some "deps" | Targets -> Some "targets" - | Target -> Some "target" + | Target _ -> Some "target" | Cc -> Some "cc" | Cxx -> Some "cxx" | Ccomp_type -> Some "ccomp_type" @@ -654,7 +664,8 @@ module Env = struct in let other : (string * Var.t With_versioning_info.t) list = [ "targets", since ~version:(1, 0) Var.Targets - ; "target", since ~version:(1, 11) Var.Target + ; "target", since ~version:(1, 11) (Var.Target "") + (* or some appropriate default value *) ; "deps", since ~version:(1, 0) Var.Deps ; "project_root", since ~version:(1, 0) Var.Project_root ; ( "<" @@ -816,3 +827,36 @@ module Env = struct ~f:(fun _ _ _ -> assert false) ;; end + +let parse_target_var s = + match String.split_on_char ~sep:':' s with + | [ "target"; name ] -> Some (Var (Var.Target name)) + | [ "targets" ] -> Some (Var Var.Targets) + | _ -> None +;; + +let parse_pkg_target_var s = + match String.split_on_char ~sep:':' s with + | [ "pkg"; pkg_name; "target"; target_name ] -> + Some (Var.Pkg.Target (pkg_name ^ ":" ^ target_name)) + | [ "pkg"; pkg_name; section ] -> + (match Var.Pkg.Section.of_string section with + | Some section -> Some (Var.Pkg.Section_dir section) + | None -> Some (Var.Pkg.Target (pkg_name ^ ":" ^ section))) + | [ "pkg"; var ] -> + (match var with + | "switch" -> Some Var.Pkg.Switch + | "os" -> Some Var.Pkg.Os + | "os_version" -> Some Var.Pkg.Os_version + | "os_distribution" -> Some Var.Pkg.Os_distribution + | "os_family" -> Some Var.Pkg.Os_family + | "build" -> Some Var.Pkg.Build + | "prefix" -> Some Var.Pkg.Prefix + | "user" -> Some Var.Pkg.User + | "group" -> Some Var.Pkg.Group + | "jobs" -> Some Var.Pkg.Jobs + | "arch" -> Some Var.Pkg.Arch + | "sys_ocaml_version" -> Some Var.Pkg.Sys_ocaml_version + | target -> Some (Var.Pkg.Target target)) + | _ -> None +;; diff --git a/src/dune_lang/pform.mli b/src/dune_lang/pform.mli index 1233d4c3476..717f3fa9c7e 100644 --- a/src/dune_lang/pform.mli +++ b/src/dune_lang/pform.mli @@ -34,6 +34,7 @@ module Var : sig | Arch | Sys_ocaml_version | Section_dir of Section.t + | Target of string val compare : t -> t -> Ordering.t val to_dyn : t -> Dyn.t @@ -47,7 +48,7 @@ module Var : sig | First_dep | Deps | Targets - | Target + | Target of string | Cc | Cxx | Ccomp_type @@ -207,3 +208,6 @@ module Env : sig val to_stamp : t -> stamp val to_dyn : t -> Dyn.t end + +val parse_target_var : string -> t option +val parse_pkg_target_var : string -> Var.Pkg.t option diff --git a/src/dune_lang/string_with_vars.mli b/src/dune_lang/string_with_vars.mli index e18ed38aa23..0ff38f54b48 100644 --- a/src/dune_lang/string_with_vars.mli +++ b/src/dune_lang/string_with_vars.mli @@ -33,7 +33,7 @@ val add_user_vars_to_decoding_env -> ('a, 'k) Decoder.parser (** [t] generated by the OCaml code. The first argument should be [__POS__]. - [quoted] says whether the string is quoted ([false] by default). *) + [quoted] says whether the string is quoted ([false] by default). *) val virt_pform : ?quoted:bool -> string * int * int * int -> Pform.t -> t val virt_text : string * int * int * int -> string -> t @@ -57,12 +57,12 @@ val pform_only : t -> Pform.t option module Mode : sig (** How many values expansion of a template must produce. - - The caller always knows which of the contexts below it requires, therefore - it can specify this to the expansion functions. This allows us to return a - precise result type from the expansion, and do some validation to make - sure we aren't expanding into multiple values in cases where it's not - allowed. *) + + The caller always knows which of the contexts below it requires, therefore + it can specify this to the expansion functions. This allows us to return a + precise result type from the expansion, and do some validation to make + sure we aren't expanding into multiple values in cases where it's not + allowed. *) type (_, _) t = | Single : (Value.Deferred_concat.t, Value.t) t (** Expansion must produce a single value *) @@ -104,8 +104,8 @@ module type Expander = sig type 'a app (** [expand ~f] attempts to expand all percent forms in a template. If [f] - returns [None] for any variable (no substitution was found), then this - function will raise. *) + returns [None] for any variable (no substitution was found), then this + function will raise. *) val expand : t -> mode:(_, 'value) Mode.t @@ -114,7 +114,7 @@ module type Expander = sig -> 'value app (** Behaves the same as [expand] except the pform expander [f] returns a - result and errors are propagated *) + result and errors are propagated *) val expand_result : t -> mode:(_, 'value) Mode.t @@ -129,7 +129,7 @@ module type Expander = sig -> ('deferred_concat, 'error) result app (** [expand_as_much_as_possible] expands all variables for which [f] returns - [None] and left other unexpanded. *) + [None] and left other unexpanded. *) val expand_as_much_as_possible : t -> dir:Path.t diff --git a/src/dune_lang/targets_spec.ml b/src/dune_lang/targets_spec.ml index 9e20f650a98..1b2b107013b 100644 --- a/src/dune_lang/targets_spec.ml +++ b/src/dune_lang/targets_spec.ml @@ -32,10 +32,17 @@ module Kind = struct | Directory end +module Named_target = struct + type 'path t = + | Anonymous of 'path * Kind.t + | Named of string * ('path * Kind.t) +end + module Static = struct type 'path t = { targets : ('path * Kind.t) list ; multiplicity : Multiplicity.t + ; named_targets : (string * 'path) list } end @@ -45,51 +52,176 @@ type 'a t = let decode_target ~allow_directory_targets = let open Dune_sexp.Decoder in - let file = - let+ file = String_with_vars.decode in - file, Kind.File + let base_decode = + let file = + let+ file = String_with_vars.decode in + file, Kind.File + in + let dir = + let+ dir = sum ~force_parens:true [ "dir", String_with_vars.decode ] in + if not allow_directory_targets + then + User_error.raise + ~loc:(String_with_vars.loc dir) + [ Pp.text "Directory targets require the 'directory-targets' extension" ]; + dir, Kind.Directory + in + file <|> dir in - let dir = - let+ dir = sum ~force_parens:true [ "dir", String_with_vars.decode ] in - if not allow_directory_targets - then - User_error.raise - ~loc:(String_with_vars.loc dir) - [ Pp.text "Directory targets require the 'directory-targets' extension" ]; - dir, Kind.Directory + let named = + enter (pair string base_decode) + >>| fun (name, (path, kind)) -> Named_target.Named (name, (path, kind)) + in + let anonymous = + base_decode >>| fun (path, kind) -> Named_target.Anonymous (path, kind) in - file <|> dir + anonymous <|> named +;; + +(* Remove any concrete type assumptions *) +let extract_named_targets targets_named = + let named_targets = + List.filter_map targets_named ~f:(function + | Named_target.Anonymous _ -> None + | Named_target.Named (name, (path, _)) -> Some (name, path)) + in + List.fold_left named_targets ~init:String.Map.empty ~f:(fun acc (name, path) -> + match String.Map.add acc name path with + | Ok map -> map + | Error _ -> User_error.raise [ Pp.textf "Duplicate named target: %s" name ]) ;; let decode_static ~allow_directory_targets = let open Dune_sexp.Decoder in let+ syntax_version = Dune_sexp.Syntax.get_exn Stanza.syntax - and+ targets = repeat (decode_target ~allow_directory_targets) in + and+ targets_named = repeat (decode_target ~allow_directory_targets) in + (* Check syntax version constraints *) if syntax_version < (1, 3) then - List.iter targets ~f:(fun (target, (_ : Kind.t)) -> - if String_with_vars.has_pforms target - then + List.iter targets_named ~f:(function + | Named_target.Anonymous (target, _) when String_with_vars.has_pforms target -> Dune_sexp.Syntax.Error.since (String_with_vars.loc target) Stanza.syntax (1, 3) - ~what:"Using variables in the targets field"); - Static { targets; multiplicity = Multiple } + ~what:"Using variables in the targets field" + | Named_target.Named (_, (target, _)) when String_with_vars.has_pforms target -> + Dune_sexp.Syntax.Error.since + (String_with_vars.loc target) + Stanza.syntax + (1, 3) + ~what:"Using variables in named targets" + | _ -> ()); + (* Convert from Named_target.t list to (path * Kind.t) list *) + let targets = + List.map targets_named ~f:(function + | Named_target.Anonymous (path, kind) -> path, kind + | Named_target.Named (_, (path, kind)) -> path, kind) + in + (* Extract named targets for variable expansion *) + let named_targets = + List.filter_map targets_named ~f:(function + | Named_target.Named (name, (path, _)) -> Some (name, path) + | Named_target.Anonymous _ -> None) + in + Static { targets; multiplicity = Multiple; named_targets } ;; let decode_one_static ~allow_directory_targets = let open Dune_sexp.Decoder in let+ () = Dune_sexp.Syntax.since Stanza.syntax (1, 11) - and+ target = decode_target ~allow_directory_targets in - Static { targets = [ target ]; multiplicity = One } + and+ target_named = decode_target ~allow_directory_targets in + let target = + match target_named with + | Named_target.Anonymous (path, kind) -> path, kind + | Named_target.Named (_, (path, kind)) -> path, kind + in + let named_targets = + match target_named with + | Named_target.Anonymous _ -> [] + | Named_target.Named (name, (path, _)) -> [ name, path ] + in + Static { targets = [ target ]; multiplicity = One; named_targets } +;; + +let decode ~allow_directory_targets:_ string_decoder = + let open Dune_sexp.Decoder in + let+ path = string_decoder in + let kind = Kind.File in + Static { targets = [ path, kind ]; multiplicity = One; named_targets = [] } +;; + +let field_one ~allow_directory_targets name decode = + let open Dune_sexp.Decoder in + field + name + (decode + >>= fun name_value -> + decode_one_static ~allow_directory_targets + >>= fun static_spec -> + let targets = + match static_spec with + | Static { targets; _ } -> targets + | _ -> [] (* or handle other cases appropriately *) + in + match static_spec with + | Static { targets = [ (path, kind) ]; multiplicity; named_targets } -> + let new_target = path, kind in + let new_named_targets = [ name, path ] in + return + (Static + { targets = [ new_target ] + ; multiplicity + ; named_targets = new_named_targets @ named_targets + }) + | _ -> + User_error.raise + ~loc:(String_with_vars.loc name_value) + [ Pp.textf "Expected exactly one target but got %d" (List.length targets) ]) +;; + +let field_many ~allow_directory_targets name decode = + let open Dune_sexp.Decoder in + field + name + (decode + >>= fun name_value -> + decode_static ~allow_directory_targets + >>= function + | Static static_record -> + (* Maintain the same type parameter throughout *) + let new_named_targets = [ name, name_value ] in + return + (Static + { static_record with + named_targets = new_named_targets @ static_record.named_targets + }) + | Infer -> + User_error.raise + ~loc:(Loc.of_pos __POS__) + [ Pp.text "Cannot infer targets in this context" ]) ;; -let field ~allow_directory_targets = +let field ~allow_directory_targets name decode = let open Dune_sexp.Decoder in - fields_mutually_exclusive - ~default:Infer - [ "target", decode_one_static ~allow_directory_targets - ; "targets", decode_static ~allow_directory_targets - ] + match name with + | "target" -> field_one ~allow_directory_targets name decode + | "targets" -> field_many ~allow_directory_targets name decode + | _ -> + field + name + (decode + >>= fun name_value -> + match String_with_vars.text_only name_value with + | Some name_str -> + return + (Static + { targets = [ name_value, Kind.File ] + ; multiplicity = One + ; named_targets = [ name_str, name_value ] + }) + | None -> + User_error.raise + ~loc:(String_with_vars.loc name_value) + [ Pp.text "Expected a static string without variables" ]) ;; diff --git a/src/dune_lang/targets_spec.mli b/src/dune_lang/targets_spec.mli index c454586a2a7..a25cb195d93 100644 --- a/src/dune_lang/targets_spec.mli +++ b/src/dune_lang/targets_spec.mli @@ -16,10 +16,17 @@ module Kind : sig | Directory end +module Named_target : sig + type 'path t = + | Anonymous of 'path * Kind.t + | Named of string * ('path * Kind.t) +end + module Static : sig type 'path t = { targets : ('path * Kind.t) list ; multiplicity : Multiplicity.t + ; named_targets : (string * 'path) list } end @@ -33,4 +40,23 @@ type 'a t = (** [target] or [targets] field with the correct multiplicity. *) val field : allow_directory_targets:bool + -> string + -> (String_with_vars.t, Dune_sexp.Decoder.values) Dune_sexp.Decoder.parser + -> String_with_vars.t t Dune_sexp.Decoder.fields_parser + +val field_many + : allow_directory_targets:bool + -> string + -> String_with_vars.t Dune_sexp.Decoder.t -> String_with_vars.t t Dune_sexp.Decoder.fields_parser + +val decode_target + : allow_directory_targets:bool + -> String_with_vars.t Named_target.t Dune_sexp.Decoder.t + +val extract_named_targets : 'path Named_target.t list -> 'path String.Map.t + +val decode + : allow_directory_targets:bool + -> String_with_vars.t Dune_sexp.Decoder.t + -> String_with_vars.t t Dune_sexp.Decoder.t diff --git a/src/dune_rules/action_unexpanded.ml b/src/dune_rules/action_unexpanded.ml index d0a742cfe4d..c67316e1ca2 100644 --- a/src/dune_rules/action_unexpanded.ml +++ b/src/dune_rules/action_unexpanded.ml @@ -628,14 +628,14 @@ let expand let expander = match (targets_written_by_user : _ Targets_spec.t) with | Infer -> expander - | Static { targets; multiplicity } -> + | Static { targets; multiplicity; _ } -> Expander.add_bindings_full expander ~bindings: (Pform.Map.singleton (Var (match multiplicity with - | One -> Target + | One -> Target "target" | Multiple -> Targets)) (Expander.Deps.Without (Memo.return @@ -651,7 +651,7 @@ let expand let targets = match (targets_written_by_user : _ Targets_spec.t) with | Infer -> targets - | Static { targets = targets_written_by_user; multiplicity = _ } -> + | Static { targets = targets_written_by_user; multiplicity = _; named_targets = _ } -> let files, dirs = List.partition_map targets_written_by_user ~f:(fun (path, kind) -> validate_target_dir ~targets_dir ~loc targets path; @@ -671,6 +671,15 @@ let expand Action_builder.with_targets ~targets build ;; +let expand_target ~expander name = + let open Expander in + let open Stdune in + match String.Map.find (Expander.named_targets expander) name with + | Some target -> Ok target + | None -> Error (sprintf "Unknown target %%{target:%s}" name) +[@@warning "-32-33"] +;; + (* We re-export [Dune_lang.Action] in the end to avoid polluting the inferred types in this module with all the various t's *) include Dune_lang.Action diff --git a/src/dune_rules/expander.ml b/src/dune_rules/expander.ml index 265aeedd8d0..4dec5a03284 100644 --- a/src/dune_rules/expander.ml +++ b/src/dune_rules/expander.ml @@ -1,6 +1,8 @@ open Import open Action_builder.O open Expander0 +open Targets_spec +module Pform = Dune_lang.Pform (* Use only this one *) module Expanding_what = struct type t = @@ -59,8 +61,66 @@ type t = ; context : Context.t ; expanding_what : Expanding_what.t ; project : Dune_project.t + ; named_targets : Path.Build.t String.Map.t } +let named_targets t = t.named_targets + +let add_named_target t ~name ~target:(_, target_str) ~kind = + let open Targets_spec.Named_target in + (* Convert String_with_vars to string with proper error handling *) + let target_path = + match String_with_vars.text_only target_str with + | Some s -> Path.Build.of_string s + | None -> + User_error.raise + ~loc:(String_with_vars.loc target_str) + [ Pp.textf + "Complex target expressions not supported for %s. Only plain strings are \ + allowed." + name + ] + in + (* Update the expander's named_targets map and return the updated expander *) + let updated_expander = + { t with named_targets = String.Map.set t.named_targets name target_path } + in + (* Return both the updated expander and the named target specification *) + updated_expander, Named (name, (target_str, kind)) +;; + +let add_named_targets t targets ~kind = + (* Process all targets and collect the updated expander *) + List.fold_left + targets + ~init:(t, []) (* Start with the original expander and empty list *) + ~f:(fun (acc_t, acc_targets) (name, target) -> + let t', named_target = add_named_target acc_t ~name ~target ~kind in + t', named_target :: acc_targets) +;; + +let setup_rule ~expander ~targets ~action = + let expander' = + match targets with + | Targets_spec.Static { named_targets; _ } -> + let expander', _ = + add_named_targets expander named_targets ~kind:Targets_spec.Kind.File + in + expander' + | Infer -> expander + in + expander', action +;; + +let[@warning "-32"] expand_target_var t ~source name = + match String.Map.find t.named_targets name with + | Some path -> Value.String (Path.Build.to_string path) + | None -> + User_error.raise + ~loc:(Dune_lang.Template.Pform.loc source) + [ Pp.textf "Undefined target variable: %%{target:%s}" name ] +;; + let artifacts t = t.artifacts_host let dir t = t.dir let project t = t.project @@ -206,6 +266,15 @@ let expand_artifact ~source t artifact arg = Value.Path fn)) ;; +let expand_target_var t ~source name = + match String.Map.find t.named_targets name with + | Some path -> Value.String (Path.Build.to_string path) + | None -> + User_error.raise + ~loc:(Dune_lang.Template.Pform.loc source) + [ Pp.textf "Undefined target variable: %%{target:%s}" name ] +;; + let foreign_flags = Fdecl.create Dyn.opaque let cc t = @@ -265,7 +334,7 @@ let[@inline never] invalid_use_of_target_variable "You cannot use %s with inferred rules." (Dune_lang.Template.Pform.describe source) ] - | Static { targets = _; multiplicity } -> + | Static { targets = _; multiplicity; _ } -> assert (multiplicity <> var_multiplicity); Targets_spec.Multiplicity.check_variable_matches_field ~loc:source.loc @@ -476,18 +545,34 @@ let expand_pform_var (context : Context.t) ~dir ~source (var : Pform.Var.t) = | Impl_files | Intf_files | Test - | Corrected_suffix -> - (* These would be part of [bindings] *) - isn't_allowed_in_this_position ~source - | First_dep -> - (* This case is for %{<} which was only allowed inside jbuild files *) - assert false - | Target -> + | Corrected_suffix -> isn't_allowed_in_this_position ~source + | First_dep -> assert false + | Target name -> Need_full_expander - (fun t -> invalid_use_of_target_variable t ~source ~var_multiplicity:One) + (fun t -> + match String.Map.find t.named_targets name with + | Some path -> Without (Memo.return [ Value.Path (Path.build path) ]) + | None -> + User_error.raise + ~loc:(Dune_lang.Template.Pform.loc source) + [ Pp.textf "Unknown target variable %s" name ]) | Targets -> Need_full_expander - (fun t -> invalid_use_of_target_variable t ~source ~var_multiplicity:Multiple) + (fun t -> + match t.expanding_what with + | User_action targets_spec -> + (match targets_spec with + | Infer -> Without (Memo.return []) + | Static { targets; _ } -> + Without + (Memo.map + (Memo.return targets) + ~f: + (List.map ~f:(fun (path, _) -> + Value.String (Path.Build.to_string path))))) + | Nothing_special -> Without (Memo.return []) + | Deps_like_field -> Without (Memo.return []) + | User_action_without_targets _ -> Without (Memo.return [])) | Profile -> Context.profile context |> Profile.to_string |> string |> Memo.return |> static | Workspace_root -> @@ -734,24 +819,35 @@ let describe_source ~source = (Loc.to_file_colon_line source.loc) ;; -let expand_pform t ~source pform = - Action_builder.push_stack_frame - (fun () -> - match +let expand_pform t ~source = function + | Pform.Var (Pform.Var.Target name) -> + Action_builder.push_stack_frame + (fun () -> + match String.Map.find t.named_targets name with + | Some path -> Action_builder.return [ Value.Path (Path.build path) ] + | None -> + User_error.raise + ~loc:(Dune_lang.Template.Pform.loc source) + [ Pp.textf "Unknown target variable %%{%s}" name ]) + ~human_readable_description:(fun () -> describe_source ~source) + | other_pform -> + Action_builder.push_stack_frame + (fun () -> match expand_pform_gen ~context:t.context ~bindings:t.bindings ~dir:t.dir ~source - pform + other_pform with - | Direct v -> v - | Need_full_expander f -> f t - with - | With x -> x - | Without x -> Action_builder.of_memo x) - ~human_readable_description:(fun () -> describe_source ~source) + | Direct (Deps.With x) -> x (* Already Action_builder.t *) + | Direct (Deps.Without x) -> Action_builder.of_memo x + | Need_full_expander f -> + (match f t with + | Deps.With x -> x + | Deps.Without x -> Action_builder.of_memo x)) + ~human_readable_description:(fun () -> describe_source ~source) ;; let expand t ~mode template = @@ -791,6 +887,7 @@ let make_root ; context ; expanding_what = Nothing_special ; project + ; named_targets = String.Map.empty } ;; @@ -855,25 +952,64 @@ module With_deps_if_necessary = struct module E = String_with_vars.Make_expander (Deps) let expand_pform t ~source pform : _ Deps.t = - match - match - expand_pform_gen ~context:t.context ~bindings:t.bindings ~dir:t.dir ~source pform - with - | Direct v -> v - | Need_full_expander f -> f t - with - | Without t -> - Without - (Memo.push_stack_frame - (fun () -> t) - ~human_readable_description:(fun () -> describe_source ~source)) - | With t -> - With - (Action_builder.push_stack_frame - (fun () -> t) - ~human_readable_description:(fun () -> describe_source ~source)) + let combine_values vs = + match vs with + | [ x ] -> x (* Single value - return as-is *) + | [] -> Value.String "" (* Empty list - return empty string *) + | xs -> + (* For multiple values, ensure they're all strings and concatenate *) + if + List.for_all xs ~f:(function + | Value.String _ -> true + | _ -> false) + then + Value.String + (String.concat + ~sep:" " + (List.map xs ~f:(function + | Value.String s -> s + | _ -> assert false (* can't happen due to check above *)))) + else + (* If not all strings, fail with descriptive error *) + User_error.raise + [ Pp.textf + "Cannot combine non-string values: %s" + (String.concat + ~sep:", " + (List.map xs ~f:(fun v -> Value.to_string v ~dir:(Path.build t.dir)))) + ] + in + match pform with + | Pform.Var (Pform.Var.Target name) -> + (* Handle target variable expansion - wrap single value in a list *) + let value = expand_target_var t ~source name in + Without (Memo.return [ value ]) + | other_pform -> + (match + match + expand_pform_gen + ~context:t.context + ~bindings:t.bindings + ~dir:t.dir + ~source + other_pform + with + | Direct v -> v + | Need_full_expander f -> f t + with + | Without t -> + Without + (Memo.push_stack_frame + (fun () -> Memo.map t ~f:(fun vs -> [ combine_values vs ])) + ~human_readable_description:(fun () -> describe_source ~source)) + | With t -> + With + (Action_builder.push_stack_frame + (fun () -> Action_builder.map t ~f:(fun vs -> [ combine_values vs ])) + ~human_readable_description:(fun () -> describe_source ~source))) ;; + (* Rest of the module remains unchanged *) let expand t ~mode sw = E.expand ~dir:(Path.build t.dir) ~mode sw ~f:(expand_pform t) let expand_path t sw = diff --git a/src/dune_rules/expander.mli b/src/dune_rules/expander.mli index e7424e458ae..8480229bf8c 100644 --- a/src/dune_rules/expander.mli +++ b/src/dune_rules/expander.mli @@ -77,6 +77,7 @@ val expand_path : t -> String_with_vars.t -> Path.t Action_builder.t val expand_str : t -> String_with_vars.t -> string Action_builder.t val expand_pform : t -> Value.t list Action_builder.t String_with_vars.expander val expand_str_partial : t -> String_with_vars.t -> String_with_vars.t Action_builder.t +val named_targets : t -> Path.Build.t String.Map.t module No_deps : sig (** Same as [expand_xxx] but disallow percent forms that introduce action @@ -125,3 +126,34 @@ val foreign_flags val lookup_artifacts : (dir:Path.Build.t -> Artifacts_obj.t Memo.t) Fdecl.t val to_expander0 : t -> Expander0.t + +(* In expander.mli *) +val add_named_target + : t + -> name:string + -> target:string * String_with_vars.t + -> kind:Targets_spec.Kind.t + -> t * String_with_vars.t Targets_spec.Named_target.t + +(* In expander.mli *) +val add_named_targets + : t + -> (string * (string * String_with_vars.t)) list + -> kind:Targets_spec.Kind.t + -> t * String_with_vars.t Targets_spec.Named_target.t list + +(* In expander.mli *) +val expand_target_var : t -> source:Dune_lang.Template.Pform.t -> string -> Value.t + +(* In src/dune_rules/expander.mli *) +val invalid_use_of_target_variable + : t + -> source:Dune_lang.Template.Pform.t + -> var_multiplicity:Import.Targets_spec.Multiplicity.t + -> 'a + +val setup_rule + : expander:t + -> targets:(string * String_with_vars.t) Targets_spec.t + -> action:'a + -> t * 'a diff --git a/src/dune_rules/gen_rules.ml b/src/dune_rules/gen_rules.ml index 61cfcf08919..fe6288bb516 100644 --- a/src/dune_rules/gen_rules.ml +++ b/src/dune_rules/gen_rules.ml @@ -1,7 +1,34 @@ open Import open Memo.O +open Expander (* or however you typically import modules *) module Gen_rules = Build_config.Gen_rules +let prepare_rule ~expander rule = + let { Rule_conf.targets; action; _ } = rule in + let string_targets = + match targets with + | Infer -> Targets_spec.Infer + | Static { targets; multiplicity; named_targets } -> + Targets_spec.Static + { targets = + List.map targets ~f:(fun (sw, kind) -> + match String_with_vars.text_only sw with + | Some s -> s, kind (* Changed from (s, (sw, kind)) to (s, kind) *) + | None -> failwith "Cannot expand variables in target name") + ; multiplicity + ; named_targets = + List.map named_targets ~f:(fun (name, sw) -> + match String_with_vars.text_only sw with + | Some s -> name, s + | None -> + failwith + (Printf.sprintf "Cannot expand variables in named target '%s'" name)) + } + in + let expander', action = Expander.setup_rule ~expander ~targets:string_targets ~action in + expander', action +;; + let install_stanza_rules ~ctx_dir ~expander (install_conf : Install_conf.t) = let action = (* XXX we're evaluating these stanzas here and [Install_rules]. Seems a bit @@ -30,13 +57,6 @@ let install_stanza_rules ~ctx_dir ~expander (install_conf : Install_conf.t) = ;; module For_stanza : sig - type ('merlin, 'cctx, 'js, 'source_dirs) t = - { merlin : 'merlin - ; cctx : 'cctx - ; js : 'js - ; source_dirs : 'source_dirs - } - val of_stanzas : Stanza.t list -> cctxs:(Loc.t * Compilation_context.t) list @@ -118,7 +138,11 @@ end = struct in if_available_buildable ~loc:lib.buildable.loc - (fun () -> Lib_rules.rules lib ~sctx ~dir ~scope ~dir_contents ~expander) + (fun () -> + let* rule = Lib_rules.rules lib ~sctx ~dir ~scope ~dir_contents ~expander in + let rule, expander' = prepare_rule ~expander rule in + Super_context.add_rule sctx ~dir rule + >>| fun () -> with_cctx_merlin ~loc:lib.buildable.loc (rule.cctx, rule.merlin)) enabled_if | Foreign_library.T lib -> Expander.eval_blang expander lib.enabled_if @@ -128,26 +152,35 @@ end = struct | Executables.T exes -> Expander.eval_blang expander exes.enabled_if >>= if_available (fun () -> - let+ () = + let* () = Memo.Option.iter exes.install_conf ~f:(install_stanza_rules ~expander ~ctx_dir) - and+ cctx_merlin = + in + let* cctx_merlin = Exe_rules.rules exes ~sctx ~dir ~scope ~expander ~dir_contents in - { (with_cctx_merlin ~loc:exes.buildable.loc cctx_merlin) with - js = - Some - (Nonempty_list.to_list exes.names - |> List.concat_map ~f:(fun (_, exe) -> - List.map Js_of_ocaml.Mode.all ~f:(fun mode -> - Path.Build.relative dir (exe ^ Js_of_ocaml.Ext.exe ~mode)))) - }) + let rule = + { (with_cctx_merlin ~loc:exes.buildable.loc cctx_merlin) with + js = + Some + (Nonempty_list.to_list exes.names + |> List.concat_map ~f:(fun (_, exe) -> + List.map Js_of_ocaml.Mode.all ~f:(fun mode -> + Path.Build.relative dir (exe ^ Js_of_ocaml.Ext.exe ~mode)))) + } + in + let rule, _ = prepare_rule ~expander rule in + Memo.return rule) | Alias_conf.T alias -> let+ () = Simple_rules.alias sctx alias ~dir ~expander in empty_none | Tests.T tests -> Expander.eval_blang expander tests.exes.enabled_if >>= if_available_buildable ~loc:tests.exes.buildable.loc (fun () -> - Test_rules.rules tests ~sctx ~dir ~scope ~expander ~dir_contents) + let* rule = Test_rules.rules tests ~sctx ~dir ~scope ~expander ~dir_contents in + let rule, expander' = prepare_rule ~expander rule in + Super_context.add_rule sctx ~dir rule + >>| fun () -> + with_cctx_merlin ~loc:tests.exes.buildable.loc (rule.cctx, rule.merlin)) | Copy_files.T { files = glob; _ } -> let+ source_dirs = let+ src_glob = Expander.No_deps.expand_str expander glob in @@ -179,8 +212,63 @@ end = struct | Melange_stanzas.Emit.T mel -> Expander.eval_blang expander mel.enabled_if >>= if_available_buildable ~loc:mel.loc (fun () -> - Melange_rules.setup_emit_cmj_rules ~dir_contents ~dir ~scope ~sctx ~expander mel) - | _ -> Memo.return empty_none + let* rule = + Melange_rules.setup_emit_cmj_rules ~dir_contents ~dir ~scope ~sctx ~expander mel + in + let rule, _ = prepare_rule ~expander rule in + Super_context.add_rule sctx ~dir rule + >>| fun () -> with_cctx_merlin ~loc:mel.loc (rule.cctx, rule.merlin)) + | Menhir_stanza.T m -> + Expander.eval_blang expander m.enabled_if + >>= (function + | false -> Memo.return empty_none + | true -> + let* ml_sources = Dir_contents.ocaml dir_contents in + let base_path = + match Ml_sources.include_subdirs ml_sources with + | Include Unqualified | No -> [] + | Include Qualified -> + Path.Local.descendant + (Path.Build.local ctx_dir) + ~of_:(Path.Build.local (Dir_contents.dir dir_contents)) + |> Option.value_exn + |> Path.Local.explode + |> List.map ~f:Module_name.of_string + in + Menhir_rules.module_names m + |> Memo.List.find_map ~f:(fun name -> + let path = base_path @ [ name ] in + Ml_sources.find_origin ml_sources ~libs:(Scope.libs scope) path + >>| function + | None -> None + | Some origin -> + List.find_map cctxs ~f:(fun (loc, cctx) -> + Option.some_if (Loc.equal loc (Ml_sources.Origin.loc origin)) cctx)) + >>= (function + | Some cctx -> + let* rule = Menhir_rules.gen_rules cctx m ~dir:ctx_dir in + let rule, _ = prepare_rule ~expander rule in + Super_context.add_rule sctx ~dir:ctx_dir rule >>| fun () -> empty_none + | None -> + let file_targets = + Menhir_stanza.targets m |> List.map ~f:(Path.Build.relative ctx_dir) + in + let rule = + Action_builder.fail + { fail = + (fun () -> + User_error.raise + ~loc:m.loc + [ Pp.text + "I can't determine what library/executable the files \ + produced by this stanza are part of." + ]) + } + |> Action_builder.with_file_targets ~file_targets + in + let rule, _ = prepare_rule ~expander rule in + Super_context.add_rule sctx ~dir:ctx_dir rule >>| fun () -> empty_none + | _ -> Memo.return empty_none)) ;; let of_stanzas stanzas ~cctxs ~sctx ~src_dir ~ctx_dir ~scope ~dir_contents ~expander = diff --git a/src/dune_rules/pkg_rules.ml b/src/dune_rules/pkg_rules.ml index 471687e9e0f..d889f4dc3de 100644 --- a/src/dune_rules/pkg_rules.ml +++ b/src/dune_rules/pkg_rules.ml @@ -660,6 +660,7 @@ module Action_expander = struct let roots = Paths.install_roots paths in let dir = section_dir_of_root roots section in Memo.return [ Value.Dir dir ] + | Target target_name -> Memo.return [ Value.String target_name ] ;; let expand_pkg_macro ~loc (paths : _ Paths.t) deps macro_invocation = diff --git a/src/dune_rules/simple_rules.ml b/src/dune_rules/simple_rules.ml index 7159b3f1693..002ffb26f5f 100644 --- a/src/dune_rules/simple_rules.ml +++ b/src/dune_rules/simple_rules.ml @@ -87,7 +87,7 @@ let user_rule sctx ?extra_bindings ~dir ~expander (rule : Rule_conf.t) = let* targets = match rule.targets with | Infer -> Memo.return Targets_spec.Infer - | Static { targets; multiplicity } -> + | Static { targets; multiplicity; _ } -> let+ targets = Memo.List.concat_map targets ~f:(fun (target, kind) -> (match multiplicity with @@ -98,7 +98,7 @@ let user_rule sctx ?extra_bindings ~dir ~expander (rule : Rule_conf.t) = let error_loc = String_with_vars.loc target in List.map ~f:(fun value -> check_filename ~kind ~dir ~error_loc value, kind)) in - Targets_spec.Static { multiplicity; targets } + Targets_spec.Static { multiplicity; targets; named_targets = [] } in let expander = match extra_bindings with diff --git a/src/dune_rules/stanzas/rule_conf.ml b/src/dune_rules/stanzas/rule_conf.ml index 9cc9f8944af..9fb7821b9fd 100644 --- a/src/dune_rules/stanzas/rule_conf.ml +++ b/src/dune_rules/stanzas/rule_conf.ml @@ -1,5 +1,6 @@ open Import open Dune_lang.Decoder +module Base_targets_spec = Targets_spec module Mode = struct include Rule.Mode @@ -7,7 +8,7 @@ module Mode = struct end type t = - { targets : String_with_vars.t Targets_spec.t + { targets : String_with_vars.t Dune_lang.Targets_spec.t ; deps : Dep_conf.t Bindings.t ; action : Loc.t * Dune_lang.Action.t ; mode : Rule.Mode.t @@ -93,16 +94,57 @@ let directory_targets_extension = ;; let long_form = - let* deps = field "deps" (Bindings.decode Dep_conf.decode) ~default:Bindings.empty in + let* deps = + let decode_deps = + let open Dune_sexp.Decoder in + peek_exn + >>= function + | List (_, _ :: _ :: _) -> + (* Named bindings case *) + Bindings.decode String_with_vars.decode + >>| fun bindings -> + (* Convert bindings to a single String_with_vars representing all dependencies *) + let string_with_vars_to_string sw = + match String_with_vars.text_only sw with + | Some text -> text + | None -> + let loc = String_with_vars.loc sw in + User_error.raise + ~loc + [ Pp.text "Cannot use variables in dependency specifications" ] + in + let strings = + List.concat_map bindings ~f:(function + | Bindings.Unnamed sw -> [ string_with_vars_to_string sw ] + | Bindings.Named (name, sw_list) -> + List.map sw_list ~f:(fun sw -> + sprintf "%s:%s" name (string_with_vars_to_string sw))) + in + String_with_vars.make_text Loc.none (String.concat ~sep:" " strings) + | _ -> + (* Simple string case *) + String_with_vars.decode + in + Base_targets_spec.field ~allow_directory_targets:true "deps" decode_deps + in let* project = Dune_project.get_exn () in let allow_directory_targets = Dune_project.is_extension_set project directory_targets_extension in String_with_vars.add_user_vars_to_decoding_env - (Bindings.var_names deps) + (match deps with + | Base_targets_spec.Infer -> [] + | Base_targets_spec.Static { targets; _ } -> + targets + |> List.map ~f:fst + |> List.concat_map ~f:(fun sw -> + match String_with_vars.text_only sw with + | Some text -> [ text ] + | None -> [])) (let+ loc = loc and+ action_o = field_o "action" (located Dune_lang.Action.decode_dune_file) - and+ targets = Targets_spec.field ~allow_directory_targets + and+ targets = + Base_targets_spec.field ~allow_directory_targets "targets" String_with_vars.decode and+ locks = Locks.field () and+ () = let+ fallback = @@ -111,10 +153,6 @@ let long_form = (Dune_lang.Syntax.renamed_in Stanza.syntax (1, 0) ~to_:"(mode fallback)") "fallback" in - (* The "fallback" field was only allowed in jbuild file, which we don't - support anymore. So this cannot be [true]. We just keep the parser - to provide a nice error message for people switching from jbuilder - to dune. *) assert (not fallback) and+ mode = Mode.field and+ enabled_if = Enabled_if.decode ~allowed_vars:Any ~since:(Some (1, 4)) () @@ -147,12 +185,40 @@ let long_form = in field_missing ~hints loc "action" in + let targets = + match targets with + | Base_targets_spec.Infer -> Base_targets_spec.Infer + | Base_targets_spec.Static { targets; multiplicity; named_targets = _ } -> + Base_targets_spec.Static { targets; multiplicity; named_targets = [] } + in + let deps : Dep_conf.t Bindings.t = [] in { targets; deps; action; mode; locks; loc; enabled_if; aliases; package }) ;; +[@@@warning "-32"] + +let targets = + let* project = Dune_project.get_exn () in + let allow_directory_targets = + Dune_project.is_extension_set project directory_targets_extension + in + let+ targets = + Base_targets_spec.field + ~allow_directory_targets + "targets" (* Field name *) + String_with_vars.decode (* Value decoder *) + in + match targets with + | Base_targets_spec.Infer -> Base_targets_spec.Infer + | Base_targets_spec.Static { targets; multiplicity; named_targets = _ } -> + Base_targets_spec.Static { targets; multiplicity; named_targets = [] } +;; + let decode = let rec interpret atom = function - | Field -> fields long_form + | Field -> + let* rule = fields long_form in + return rule | Action -> short_form | Since (version, inner) -> let what = Printf.sprintf "'%s' in short-form 'rule'" atom in @@ -172,6 +238,7 @@ let decode = User_error.raise ~loc:(Dune_lang.Ast.loc sexp) [ Pp.textf "S-expression of the form ( ...) expected" ] + >>| fun rule -> rule ;; type lex_or_yacc = @@ -192,16 +259,18 @@ let ocamllex = let ocamlyacc = ocamllex -let ocamllex_to_rule loc { modules; mode; enabled_if } = +let ocamllex_to_rule ~expander loc { modules; mode; enabled_if } = let module S = String_with_vars in List.map modules ~f:(fun name -> let src = name ^ ".mll" in let dst = name ^ ".ml" in + let dst_s = S.make_text loc dst in { targets = - (* CR-someday aalekseyev: want to use [multiplicity = One] here, but - can't because this is might get parsed with old dune syntax where - [multiplicity = One] is not supported. *) - Static { targets = [ S.make_text loc dst, File ]; multiplicity = Multiple } + Static + { targets = [ dst_s, File ] + ; multiplicity = Multiple + ; named_targets = [ "main", dst_s ] + } ; deps = Bindings.singleton (Dep_conf.File (S.virt_text __POS__ src)) ; action = ( loc @@ -211,7 +280,7 @@ let ocamllex_to_rule loc { modules; mode; enabled_if } = (S.virt_text __POS__ "ocamllex") [ S.virt_text __POS__ "-q" ; S.virt_text __POS__ "-o" - ; S.virt_pform __POS__ (Var Targets) + ; S.virt_text __POS__ dst ; S.virt_pform __POS__ (Var Deps) ] ) ) ; mode @@ -223,7 +292,7 @@ let ocamllex_to_rule loc { modules; mode; enabled_if } = }) ;; -let ocamlyacc_to_rule loc { modules; mode; enabled_if } = +let ocamlyacc_to_rule ~expander loc { modules; mode; enabled_if } = let module S = String_with_vars in List.map modules ~f:(fun name -> let src = name ^ ".mly" in @@ -232,8 +301,14 @@ let ocamlyacc_to_rule loc { modules; mode; enabled_if } = { targets = List.map [ name ^ ".ml"; name ^ ".mli" ] - ~f:(fun target -> S.make_text loc target, Targets_spec.Kind.File) + ~f:(fun target -> + let target_s = S.make_text loc target in + target_s, Base_targets_spec.Kind.File) ; multiplicity = Multiple + ; named_targets = + [ "implementation", S.make_text loc (name ^ ".ml") + ; "interface", S.make_text loc (name ^ ".mli") + ] } ; deps = Bindings.singleton (Dep_conf.File (S.virt_text __POS__ src)) ; action = @@ -251,3 +326,33 @@ let ocamlyacc_to_rule loc { modules; mode; enabled_if } = ; package = None }) ;; + +let decode_named_target = + let open Dune_lang.Decoder in + let+ loc = loc + and+ name = string + and+ target = String_with_vars.decode in + loc, (name, target) +;; + +let decode_targets = + let* project = Dune_project.get_exn () in + let _allow_directory_targets = + Dune_project.is_extension_set project directory_targets_extension + in + let open Dune_lang.Decoder in + (* Create the targets parser *) + let targets_parser = + repeat (String_with_vars.decode >>| fun x -> x, Base_targets_spec.Kind.File) + in + (* Create a complete fields parser with both target fields *) + fields + (let+ targets = field "targets" targets_parser ~default:[] + and+ named_targets = + field "named_targets" (repeat decode_named_target) ~default:[] + in + let named_targets = List.map named_targets ~f:(fun (_, pair) -> pair) in + Base_targets_spec.Static { targets; multiplicity = Multiple; named_targets }) +;; + +let process_rule = Rule_processor.process_rule diff --git a/src/dune_rules/stanzas/rule_conf.mli b/src/dune_rules/stanzas/rule_conf.mli index c42f8654e61..6d25794429a 100644 --- a/src/dune_rules/stanzas/rule_conf.mli +++ b/src/dune_rules/stanzas/rule_conf.mli @@ -26,3 +26,6 @@ val ocamlyacc : lex_or_yacc Dune_lang.Decoder.t val ocamllex : lex_or_yacc Dune_lang.Decoder.t val ocamllex_to_rule : Loc.t -> lex_or_yacc -> t list val ocamlyacc_to_rule : Loc.t -> lex_or_yacc -> t list + +val process_rule : expander:Expander.t -> t -> Expander.t * (Loc.t * Dune_lang.Action.t) +[@@deprecated "Use Rule_processor.process_rule instead"] diff --git a/src/dune_rules/stanzas/rule_processor.ml b/src/dune_rules/stanzas/rule_processor.ml new file mode 100644 index 00000000000..ed6699e6670 --- /dev/null +++ b/src/dune_rules/stanzas/rule_processor.ml @@ -0,0 +1,47 @@ +open Import + +let process_rule ~expander rule = + let { Rule_conf.targets; action; _ } = rule in + (* Convert targets to the format expected by setup_rule *) + let string_targets, static_targets = + match targets with + | Infer -> Targets_spec.Infer, Targets_spec.Infer + | Static { targets; multiplicity; named_targets } -> + (* For string_targets (going to setup_rule) *) + let string_sw_targets = + List.map targets ~f:(fun (sw, _kind) -> + match String_with_vars.text_only sw with + | Some s -> s, sw (* This makes (string * String_with_vars.t) *) + | None -> failwith "Cannot expand variables in target name") + in + (* No need to transform named_targets as they're already (string * String_with_vars.t) *) + let string_sw_spec = + Targets_spec.Static + { targets = string_sw_targets + ; multiplicity + ; named_targets (* Keep as is - already (string * String_with_vars.t) *) + } + in + (* For static_targets (to return from function) *) + let processed_targets = + List.map targets ~f:(fun (sw, kind) -> + match String_with_vars.text_only sw with + | Some s -> s, kind + | None -> failwith "Cannot expand variables in target name") + in + let processed_named = + List.map named_targets ~f:(fun (name, sw) -> + match String_with_vars.text_only sw with + | Some s -> name, s + | None -> + failwith (Printf.sprintf "Cannot expand variables in named target '%s'" name)) + in + let processed_spec = + Targets_spec.Static + { targets = processed_targets; multiplicity; named_targets = processed_named } + in + string_sw_spec, processed_spec + in + let expander', action = Expander.setup_rule ~expander ~targets:string_targets ~action in + expander', static_targets, action +;; diff --git a/src/dune_rules/target_conf.ml b/src/dune_rules/target_conf.ml new file mode 100644 index 00000000000..7a2914485b3 --- /dev/null +++ b/src/dune_rules/target_conf.ml @@ -0,0 +1,14 @@ +[@@@warning "-33"] + +open Stdune +open Dune_lang +module Name = String + +type t = String_with_vars.t Targets_spec.Named_target.t Bindings.t + +let decode = + let open Dune_sexp.Decoder in + Bindings.decode (Targets_spec.decode_target ~allow_directory_targets:true) +;; + +let empty = Bindings.empty diff --git a/src/dune_rules/target_conf.mli b/src/dune_rules/target_conf.mli new file mode 100644 index 00000000000..135e493024c --- /dev/null +++ b/src/dune_rules/target_conf.mli @@ -0,0 +1,10 @@ +[@@@warning "-33"] + +open Stdune +open Dune_lang +module Name = String + +type t = String_with_vars.t Targets_spec.Named_target.t Bindings.t + +val decode : t Dune_lang.Decoder.t +val empty : t diff --git a/test/blackbox-tests/test-cases/named-targets/named-targets.t b/test/blackbox-tests/test-cases/named-targets/named-targets.t new file mode 100644 index 00000000000..90b6eb6ef7d --- /dev/null +++ b/test/blackbox-tests/test-cases/named-targets/named-targets.t @@ -0,0 +1,21 @@ +# Basic test for multiple targets with named targets + $ echo '(lang dune 3.8)' > dune-project + $ cat > dune << 'EOF' + > (rule + > (targets (output.txt as primary) secondary.log) + > (deps dune-project) + > (action + > (progn + > (with-stdout-to %{targets:primary} (echo "Primary content")) + > (with-stdout-to %{targets:secondary.log} (echo "Log content")) + > ) + > ) + > ) + > EOF + $ dune build + $ test -f _build/default/output.txt + $ test -f _build/default/secondary.log + $ cat _build/default/output.txt + Primary content + $ cat _build/default/secondary.log + Log content \ No newline at end of file diff --git a/test/dune b/test/dune index c185b6d5d89..2711bdc9589 100644 --- a/test/dune +++ b/test/dune @@ -3,6 +3,10 @@ ;; $ ./_build/default/bin/main.exe build @test/fail-with-background-jobs-running ;; +(include_subdirs no) + +; Disable automatic inclusion + (rule (alias sleep5) (action @@ -157,3 +161,8 @@ (action (run ./incr.exe y %{targets})) (locks m)) + +(rule + (alias runtest) + (action + (diff output.txt output.expected)))