enabled_if (#819)
# Problem The problem this PR is trying to solve is a bit specific to dune, but it's likely that it will be encountered by other projects as well. Currently, dune has a complicated script for generating test definitions that make sure tests run only in environments which support them. For example, some tests may not run on win32. A mechanism to enable tests conditionally would fix these problems. # Proposal Add an `enabled_if` field to aliases to toggle the execution of the alias. This field will be valued by a little EDSL for expressing boolean expressions. Here's an example of the kind of conditions we'd express with it: ``` (alias ((name runtest) (deps (foo.exe)) (action (run ${<})) (enabled_if (and (<> ${os} win32) (>= ${ocaml_version} 4.0.5))))) ``` # Progress This stalled a bit since I'm not sure how to do handle type safety here. Ideally, we only allow numbers and versions to be compared with `>=`, `<`, etc. I guess we really need to annotate which variables are comparable and making sure that we don't compare numbers to strings or versions.
This commit is contained in:
commit
de0ccfaec4
|
@ -9,6 +9,9 @@ next
|
||||||
now allowed in toplevel position in the workspace file, or for individual
|
now allowed in toplevel position in the workspace file, or for individual
|
||||||
contexts. This feature requires `(dune lang 1.1)` (#1038, @rgrinberg)
|
contexts. This feature requires `(dune lang 1.1)` (#1038, @rgrinberg)
|
||||||
|
|
||||||
|
- Add ``enabled_if`` field for aliases and tests. This field controls whether
|
||||||
|
the test will be ran using a boolean expression language. (#819, @rgrinberg)
|
||||||
|
|
||||||
1.0.1 (19/07/2018)
|
1.0.1 (19/07/2018)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
|
@ -569,6 +569,10 @@ The syntax is as follows:
|
||||||
- ``(locks (<lock-names>))`` specify that the action must be run while
|
- ``(locks (<lock-names>))`` specify that the action must be run while
|
||||||
holding the following locks. See the `Locks`_ section for more details.
|
holding the following locks. See the `Locks`_ section for more details.
|
||||||
|
|
||||||
|
- ``(enabled_if <blang expression>)`` specifies the boolean condition that must
|
||||||
|
be true for the tests to run. The condition is specified using the blang_, and
|
||||||
|
the field allows for variables_ to appear in the expressions.
|
||||||
|
|
||||||
The typical use of the ``alias`` stanza is to define tests:
|
The typical use of the ``alias`` stanza is to define tests:
|
||||||
|
|
||||||
.. code:: scheme
|
.. code:: scheme
|
||||||
|
@ -824,6 +828,36 @@ doesn't start by `-`, you can simply quote it: ``("x" y z)``.
|
||||||
Most fields using the ordered set language also support `Variables expansion`_.
|
Most fields using the ordered set language also support `Variables expansion`_.
|
||||||
Variables are expanded after the set language is interpreted.
|
Variables are expanded after the set language is interpreted.
|
||||||
|
|
||||||
|
.. _blang:
|
||||||
|
|
||||||
|
Boolean Language
|
||||||
|
----------------
|
||||||
|
|
||||||
|
The boolean language allows the user to define simple boolean expressions that
|
||||||
|
dune can evaluate. Here's a semi formal specification of the language:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
op := '=' | '<' | '>' | '<>' | '>=' | '<='
|
||||||
|
|
||||||
|
expr := (and <expr>+)
|
||||||
|
| (or <expr>+)
|
||||||
|
| (<op> <template> <template>)
|
||||||
|
| <template>
|
||||||
|
|
||||||
|
After an expression is evaluated, it must be exactly the string ``true`` or
|
||||||
|
``false`` to be considered as a boolean. Any other value will be treated as an
|
||||||
|
error.
|
||||||
|
|
||||||
|
Here's a simple example of a condition that expresses running on OSX and having
|
||||||
|
an flambda compiler with the help of variable expansion:
|
||||||
|
|
||||||
|
.. code:: scheme
|
||||||
|
|
||||||
|
(and %{ocamlc-config:flambda} (= %{ocamlc-config:system} macosx))
|
||||||
|
|
||||||
|
.. _variables:
|
||||||
|
|
||||||
Variables expansion
|
Variables expansion
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
open! Stdune
|
||||||
|
|
||||||
|
module Op = struct
|
||||||
|
type t =
|
||||||
|
| Eq
|
||||||
|
| Gt
|
||||||
|
| Gte
|
||||||
|
| Lte
|
||||||
|
| Lt
|
||||||
|
| Neq
|
||||||
|
|
||||||
|
let eval t (x : Ordering.t) =
|
||||||
|
match t, x with
|
||||||
|
| (Eq | Gte | Lte) , Eq
|
||||||
|
| (Lt | Lte) , Lt
|
||||||
|
| (Gt | Gte) , Gt -> true
|
||||||
|
| _, _ -> false
|
||||||
|
end
|
||||||
|
|
||||||
|
type 'a t =
|
||||||
|
| Expr of 'a
|
||||||
|
| And of 'a t list
|
||||||
|
| Or of 'a t list
|
||||||
|
| Compare of Op.t * 'a * 'a
|
||||||
|
|
||||||
|
type 'a expander =
|
||||||
|
{ f : 'value. mode:'value String_with_vars.Mode.t
|
||||||
|
-> 'a
|
||||||
|
-> Loc.t * 'value
|
||||||
|
}
|
||||||
|
|
||||||
|
let rec eval_bool t ~dir ~(f : 'a expander) =
|
||||||
|
match t with
|
||||||
|
| Expr a ->
|
||||||
|
begin match f.f ~mode:Single a with
|
||||||
|
| _, String "true" -> true
|
||||||
|
| _, String "false" -> false
|
||||||
|
| loc, _ -> Loc.fail loc "This value must be either true or false"
|
||||||
|
end
|
||||||
|
| And xs -> List.for_all ~f:(eval_bool ~f ~dir) xs
|
||||||
|
| Or xs -> List.exists ~f:(eval_bool ~f ~dir) xs
|
||||||
|
| Compare (op, x, y) ->
|
||||||
|
let ((_, x), (_, y)) = (f.f ~mode:Many x, f.f ~mode:Many y) in
|
||||||
|
Value.L.compare_vals ~dir x y
|
||||||
|
|> Op.eval op
|
|
@ -0,0 +1,25 @@
|
||||||
|
open Stdune
|
||||||
|
|
||||||
|
module Op : sig
|
||||||
|
type t =
|
||||||
|
| Eq
|
||||||
|
| Gt
|
||||||
|
| Gte
|
||||||
|
| Lte
|
||||||
|
| Lt
|
||||||
|
| Neq
|
||||||
|
end
|
||||||
|
|
||||||
|
type 'a t =
|
||||||
|
| Expr of 'a
|
||||||
|
| And of 'a t list
|
||||||
|
| Or of 'a t list
|
||||||
|
| Compare of Op.t * 'a * 'a
|
||||||
|
|
||||||
|
type 'a expander =
|
||||||
|
{ f : 'value. mode:'value String_with_vars.Mode.t
|
||||||
|
-> 'a
|
||||||
|
-> Loc.t * 'value
|
||||||
|
}
|
||||||
|
|
||||||
|
val eval_bool : 'a t -> dir:Path.t -> f:'a expander -> bool
|
|
@ -590,6 +590,7 @@ module Gen(P : Install_rules.Params) = struct
|
||||||
; package = t.package
|
; package = t.package
|
||||||
; deps = t.deps
|
; deps = t.deps
|
||||||
; action = None
|
; action = None
|
||||||
|
; enabled_if = t.enabled_if
|
||||||
} in
|
} in
|
||||||
match test_kind (loc, s) with
|
match test_kind (loc, s) with
|
||||||
| `Regular ->
|
| `Regular ->
|
||||||
|
|
|
@ -316,7 +316,7 @@ module Dep_conf = struct
|
||||||
; "universe" , return Universe
|
; "universe" , return Universe
|
||||||
; "files_recursively_in",
|
; "files_recursively_in",
|
||||||
(let%map () =
|
(let%map () =
|
||||||
Syntax.renamed_in Stanza.syntax (1, 0) ~to_:"source_tree"
|
Syntax.renamed_in Stanza.syntax (1, 0) ~to_:"source_tree"
|
||||||
and x = sw in
|
and x = sw in
|
||||||
Source_tree x)
|
Source_tree x)
|
||||||
; "source_tree",
|
; "source_tree",
|
||||||
|
@ -378,6 +378,45 @@ module Preprocess = struct
|
||||||
| _ -> []
|
| _ -> []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Blang = struct
|
||||||
|
type 'a t = 'a Blang.t
|
||||||
|
|
||||||
|
open Blang
|
||||||
|
let ops =
|
||||||
|
[ "=", Op.Eq
|
||||||
|
; ">=", Gte
|
||||||
|
; "<=", Lt
|
||||||
|
; ">", Gt
|
||||||
|
; "<", Lt
|
||||||
|
; "<>", Neq
|
||||||
|
]
|
||||||
|
|
||||||
|
let t =
|
||||||
|
let ops =
|
||||||
|
List.map ops ~f:(fun (name, op) ->
|
||||||
|
( name
|
||||||
|
, (let%map x = String_with_vars.t
|
||||||
|
and y = String_with_vars.t
|
||||||
|
in
|
||||||
|
Compare (op, x, y))))
|
||||||
|
in
|
||||||
|
let t =
|
||||||
|
fix begin fun (t : String_with_vars.t Blang.t Sexp.Of_sexp.t) ->
|
||||||
|
if_list
|
||||||
|
~then_:(
|
||||||
|
[ "or", repeat t >>| (fun x -> Or x)
|
||||||
|
; "and", repeat t >>| (fun x -> And x)
|
||||||
|
] @ ops
|
||||||
|
|> sum)
|
||||||
|
~else_:(String_with_vars.t >>| fun v -> Expr v)
|
||||||
|
end
|
||||||
|
in
|
||||||
|
let%map () = Syntax.since Stanza.syntax (1, 1)
|
||||||
|
and t = t
|
||||||
|
in
|
||||||
|
t
|
||||||
|
end
|
||||||
|
|
||||||
module Per_module = struct
|
module Per_module = struct
|
||||||
include Per_item.Make(Module.Name)
|
include Per_item.Make(Module.Name)
|
||||||
|
|
||||||
|
@ -1336,15 +1375,15 @@ module Rule = struct
|
||||||
let dune_syntax =
|
let dune_syntax =
|
||||||
peek_exn >>= function
|
peek_exn >>= function
|
||||||
| List (_, Atom (loc, A s) :: _) -> begin
|
| List (_, Atom (loc, A s) :: _) -> begin
|
||||||
match String.Map.find atom_table s with
|
match String.Map.find atom_table s with
|
||||||
| None ->
|
| None ->
|
||||||
of_sexp_errorf loc ~hint:{ on = s
|
of_sexp_errorf loc ~hint:{ on = s
|
||||||
; candidates = String.Map.keys atom_table
|
; candidates = String.Map.keys atom_table
|
||||||
}
|
}
|
||||||
"Unknown action or rule field."
|
"Unknown action or rule field."
|
||||||
| Some Field -> fields long_form
|
| Some Field -> fields long_form
|
||||||
| Some Action -> short_form
|
| Some Action -> short_form
|
||||||
end
|
end
|
||||||
| sexp ->
|
| sexp ->
|
||||||
of_sexp_errorf (Sexp.Ast.loc sexp)
|
of_sexp_errorf (Sexp.Ast.loc sexp)
|
||||||
"S-expression of the form (<atom> ...) expected"
|
"S-expression of the form (<atom> ...) expected"
|
||||||
|
@ -1500,6 +1539,7 @@ module Alias_conf = struct
|
||||||
; action : (Loc.t * Action.Unexpanded.t) option
|
; action : (Loc.t * Action.Unexpanded.t) option
|
||||||
; locks : String_with_vars.t list
|
; locks : String_with_vars.t list
|
||||||
; package : Package.t option
|
; package : Package.t option
|
||||||
|
; enabled_if : String_with_vars.t Blang.t option
|
||||||
}
|
}
|
||||||
|
|
||||||
let alias_name =
|
let alias_name =
|
||||||
|
@ -1516,21 +1556,24 @@ module Alias_conf = struct
|
||||||
and action = field_o "action" (located Action.Unexpanded.t)
|
and action = field_o "action" (located Action.Unexpanded.t)
|
||||||
and locks = field "locks" (list String_with_vars.t) ~default:[]
|
and locks = field "locks" (list String_with_vars.t) ~default:[]
|
||||||
and deps = field "deps" (Bindings.t Dep_conf.t) ~default:Bindings.empty
|
and deps = field "deps" (Bindings.t Dep_conf.t) ~default:Bindings.empty
|
||||||
|
and enabled_if = field_o "enabled_if" Blang.t
|
||||||
in
|
in
|
||||||
{ name
|
{ name
|
||||||
; deps
|
; deps
|
||||||
; action
|
; action
|
||||||
; package
|
; package
|
||||||
; locks
|
; locks
|
||||||
|
; enabled_if
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
module Tests = struct
|
module Tests = struct
|
||||||
type t =
|
type t =
|
||||||
{ exes : Executables.t
|
{ exes : Executables.t
|
||||||
; locks : String_with_vars.t list
|
; locks : String_with_vars.t list
|
||||||
; package : Package.t option
|
; package : Package.t option
|
||||||
; deps : Dep_conf.t Bindings.t
|
; deps : Dep_conf.t Bindings.t
|
||||||
|
; enabled_if : String_with_vars.t Blang.t option
|
||||||
}
|
}
|
||||||
|
|
||||||
let gen_parse names =
|
let gen_parse names =
|
||||||
|
@ -1543,6 +1586,7 @@ module Tests = struct
|
||||||
and modes = field "modes" Executables.Link_mode.Set.t
|
and modes = field "modes" Executables.Link_mode.Set.t
|
||||||
~default:Executables.Link_mode.Set.default
|
~default:Executables.Link_mode.Set.default
|
||||||
and deps = field "deps" (Bindings.t Dep_conf.t) ~default:Bindings.empty
|
and deps = field "deps" (Bindings.t Dep_conf.t) ~default:Bindings.empty
|
||||||
|
and enabled_if = field_o "enabled_if" Blang.t
|
||||||
in
|
in
|
||||||
{ exes =
|
{ exes =
|
||||||
{ Executables.
|
{ Executables.
|
||||||
|
@ -1555,6 +1599,7 @@ module Tests = struct
|
||||||
; locks
|
; locks
|
||||||
; package
|
; package
|
||||||
; deps
|
; deps
|
||||||
|
; enabled_if
|
||||||
})
|
})
|
||||||
|
|
||||||
let multi = gen_parse (field "names" (list (located string)))
|
let multi = gen_parse (field "names" (list (located string)))
|
||||||
|
|
|
@ -335,6 +335,7 @@ module Alias_conf : sig
|
||||||
; action : (Loc.t * Action.Unexpanded.t) option
|
; action : (Loc.t * Action.Unexpanded.t) option
|
||||||
; locks : String_with_vars.t list
|
; locks : String_with_vars.t list
|
||||||
; package : Package.t option
|
; package : Package.t option
|
||||||
|
; enabled_if : String_with_vars.t Blang.t option
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -355,10 +356,11 @@ end
|
||||||
|
|
||||||
module Tests : sig
|
module Tests : sig
|
||||||
type t =
|
type t =
|
||||||
{ exes : Executables.t
|
{ exes : Executables.t
|
||||||
; locks : String_with_vars.t list
|
; locks : String_with_vars.t list
|
||||||
; package : Package.t option
|
; package : Package.t option
|
||||||
; deps : Dep_conf.t Bindings.t
|
; deps : Dep_conf.t Bindings.t
|
||||||
|
; enabled_if : String_with_vars.t Blang.t option
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -70,32 +70,45 @@ let add_alias sctx ~dir ~name ~stamp ?(locks=[]) build =
|
||||||
SC.add_alias_action sctx alias ~locks ~stamp build
|
SC.add_alias_action sctx alias ~locks ~stamp build
|
||||||
|
|
||||||
let alias sctx ~dir ~scope (alias_conf : Alias_conf.t) =
|
let alias sctx ~dir ~scope (alias_conf : Alias_conf.t) =
|
||||||
let stamp =
|
let enabled =
|
||||||
let module S = Sexp.To_sexp in
|
match alias_conf.enabled_if with
|
||||||
Sexp.List
|
| None -> true
|
||||||
[ Sexp.unsafe_atom_of_string "user-alias"
|
| Some blang ->
|
||||||
; Jbuild.Bindings.sexp_of_t Jbuild.Dep_conf.sexp_of_t alias_conf.deps
|
let f : String_with_vars.t Blang.expander =
|
||||||
; S.option Action.Unexpanded.sexp_of_t
|
{ f = fun ~mode sw ->
|
||||||
(Option.map alias_conf.action ~f:snd)
|
( String_with_vars.loc sw
|
||||||
]
|
, Super_context.expand_vars sctx ~scope ~mode ~dir sw
|
||||||
|
)
|
||||||
|
} in
|
||||||
|
Blang.eval_bool blang ~dir ~f
|
||||||
in
|
in
|
||||||
add_alias sctx
|
if enabled then
|
||||||
~dir
|
let stamp =
|
||||||
~name:alias_conf.name
|
let module S = Sexp.To_sexp in
|
||||||
~stamp
|
Sexp.List
|
||||||
~locks:(interpret_locks sctx ~dir ~scope alias_conf.locks)
|
[ Sexp.unsafe_atom_of_string "user-alias"
|
||||||
(SC.Deps.interpret_named sctx ~scope ~dir alias_conf.deps
|
; Jbuild.Bindings.sexp_of_t Jbuild.Dep_conf.sexp_of_t alias_conf.deps
|
||||||
>>>
|
; S.option Action.Unexpanded.sexp_of_t
|
||||||
match alias_conf.action with
|
(Option.map alias_conf.action ~f:snd)
|
||||||
| None -> Build.progn []
|
]
|
||||||
| Some (loc, action) ->
|
in
|
||||||
SC.Action.run
|
add_alias sctx
|
||||||
sctx
|
~dir
|
||||||
action
|
~name:alias_conf.name
|
||||||
~loc
|
~stamp
|
||||||
~dir
|
~locks:(interpret_locks sctx ~dir ~scope alias_conf.locks)
|
||||||
~dep_kind:Required
|
(SC.Deps.interpret_named sctx ~scope ~dir alias_conf.deps
|
||||||
~bindings:(Pform.Map.of_bindings alias_conf.deps)
|
>>>
|
||||||
~targets:Alias
|
match alias_conf.action with
|
||||||
~targets_dir:dir
|
| None -> Build.progn []
|
||||||
~scope)
|
| Some (loc, action) ->
|
||||||
|
SC.Action.run
|
||||||
|
sctx
|
||||||
|
action
|
||||||
|
~loc
|
||||||
|
~dir
|
||||||
|
~dep_kind:Required
|
||||||
|
~bindings:(Pform.Map.of_bindings alias_conf.deps)
|
||||||
|
~targets:Alias
|
||||||
|
~targets_dir:dir
|
||||||
|
~scope)
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
type t = bool
|
||||||
|
|
||||||
|
let compare x y =
|
||||||
|
match x, y with
|
||||||
|
| true, true
|
||||||
|
| false, false -> Ordering.Eq
|
||||||
|
| true, false -> Gt
|
||||||
|
| false, true -> Lt
|
||||||
|
|
||||||
|
let to_string = string_of_bool
|
|
@ -0,0 +1,5 @@
|
||||||
|
type t = bool
|
||||||
|
|
||||||
|
val compare : t -> t -> Ordering.t
|
||||||
|
|
||||||
|
val to_string : t -> string
|
|
@ -446,7 +446,6 @@ module Of_sexp = struct
|
||||||
String.Map.iteri map ~f:(Hashtbl.add tbl);
|
String.Map.iteri map ~f:(Hashtbl.add tbl);
|
||||||
tbl
|
tbl
|
||||||
|
|
||||||
|
|
||||||
let find_cstr cstrs loc name ctx values =
|
let find_cstr cstrs loc name ctx values =
|
||||||
match List.assoc cstrs name with
|
match List.assoc cstrs name with
|
||||||
| Some t ->
|
| Some t ->
|
||||||
|
|
|
@ -18,6 +18,7 @@ module Set = Set
|
||||||
module Staged = Staged
|
module Staged = Staged
|
||||||
module String = String
|
module String = String
|
||||||
module Char = Char
|
module Char = Char
|
||||||
|
module Bool = Bool
|
||||||
module Sexp = Sexp
|
module Sexp = Sexp
|
||||||
module Path = Path
|
module Path = Path
|
||||||
module Fmt = Fmt
|
module Fmt = Fmt
|
||||||
|
|
|
@ -79,6 +79,14 @@ val dump_env : t -> dir:Path.t -> (unit, Sexp.t list) Build.t
|
||||||
val find_scope_by_dir : t -> Path.t -> Scope.t
|
val find_scope_by_dir : t -> Path.t -> Scope.t
|
||||||
val find_scope_by_name : t -> Dune_project.Name.t -> Scope.t
|
val find_scope_by_name : t -> Dune_project.Name.t -> Scope.t
|
||||||
|
|
||||||
|
val expand_vars
|
||||||
|
: t
|
||||||
|
-> mode:'a String_with_vars.Mode.t
|
||||||
|
-> scope:Scope.t
|
||||||
|
-> dir:Path.t -> ?bindings:Pform.Map.t
|
||||||
|
-> String_with_vars.t
|
||||||
|
-> 'a
|
||||||
|
|
||||||
val expand_vars_string
|
val expand_vars_string
|
||||||
: t
|
: t
|
||||||
-> scope:Scope.t
|
-> scope:Scope.t
|
||||||
|
|
23
src/value.ml
23
src/value.ml
|
@ -1,10 +1,17 @@
|
||||||
open Stdune
|
open Import
|
||||||
|
|
||||||
type t =
|
type t =
|
||||||
| String of string
|
| String of string
|
||||||
| Dir of Path.t
|
| Dir of Path.t
|
||||||
| Path of Path.t
|
| Path of Path.t
|
||||||
|
|
||||||
|
let sexp_of_t =
|
||||||
|
let open Sexp.To_sexp in
|
||||||
|
function
|
||||||
|
| String s -> (pair string string) ("string", s)
|
||||||
|
| Path p -> (pair string Path.sexp_of_t) ("path", p)
|
||||||
|
| Dir p -> (pair string Path.sexp_of_t) ("dir", p)
|
||||||
|
|
||||||
let string_of_path ~dir p = Path.reach ~from:dir p
|
let string_of_path ~dir p = Path.reach ~from:dir p
|
||||||
|
|
||||||
let to_string t ~dir =
|
let to_string t ~dir =
|
||||||
|
@ -13,6 +20,17 @@ let to_string t ~dir =
|
||||||
| Dir p
|
| Dir p
|
||||||
| Path p -> string_of_path ~dir p
|
| Path p -> string_of_path ~dir p
|
||||||
|
|
||||||
|
let compare_vals ~dir x y =
|
||||||
|
match x, y with
|
||||||
|
| String x, String y ->
|
||||||
|
String.compare x y
|
||||||
|
| (Path x | Dir x), (Path y | Dir y) ->
|
||||||
|
Path.compare x y
|
||||||
|
| String x, (Path _ | Dir _) ->
|
||||||
|
String.compare x (to_string ~dir y)
|
||||||
|
| (Path _ | Dir _), String y ->
|
||||||
|
String.compare (to_string ~dir x) y
|
||||||
|
|
||||||
let to_path ?error_loc t ~dir =
|
let to_path ?error_loc t ~dir =
|
||||||
match t with
|
match t with
|
||||||
| String s -> Path.relative ?error_loc dir s
|
| String s -> Path.relative ?error_loc dir s
|
||||||
|
@ -22,6 +40,9 @@ let to_path ?error_loc t ~dir =
|
||||||
module L = struct
|
module L = struct
|
||||||
let to_strings t ~dir = List.map t ~f:(to_string ~dir)
|
let to_strings t ~dir = List.map t ~f:(to_string ~dir)
|
||||||
|
|
||||||
|
let compare_vals ~dir =
|
||||||
|
List.compare ~compare:(compare_vals ~dir)
|
||||||
|
|
||||||
let concat ts ~dir =
|
let concat ts ~dir =
|
||||||
List.map ~f:(to_string ~dir) ts
|
List.map ~f:(to_string ~dir) ts
|
||||||
|> String.concat ~sep:" "
|
|> String.concat ~sep:" "
|
||||||
|
|
|
@ -5,6 +5,8 @@ type t =
|
||||||
| Dir of Path.t
|
| Dir of Path.t
|
||||||
| Path of Path.t
|
| Path of Path.t
|
||||||
|
|
||||||
|
val sexp_of_t : t Sexp.To_sexp.t
|
||||||
|
|
||||||
val to_string : t -> dir:Path.t -> string
|
val to_string : t -> dir:Path.t -> string
|
||||||
|
|
||||||
val to_path : ?error_loc:Loc.t -> t -> dir:Path.t -> Path.t
|
val to_path : ?error_loc:Loc.t -> t -> dir:Path.t -> Path.t
|
||||||
|
@ -12,6 +14,16 @@ val to_path : ?error_loc:Loc.t -> t -> dir:Path.t -> Path.t
|
||||||
module L : sig
|
module L : sig
|
||||||
val strings : string list -> t list
|
val strings : string list -> t list
|
||||||
|
|
||||||
|
(** [compare_vals ~dir a b] is a more efficient version of:
|
||||||
|
|
||||||
|
{[
|
||||||
|
List.compare ~compare:String.compare
|
||||||
|
(to_string ~dir a)
|
||||||
|
(to_string ~dir b)
|
||||||
|
]}
|
||||||
|
*)
|
||||||
|
val compare_vals : dir:Path.t -> t list -> t list -> Ordering.t
|
||||||
|
|
||||||
val paths : Path.t list -> t list
|
val paths : Path.t list -> t list
|
||||||
|
|
||||||
val deps_only : t list -> Path.t list
|
val deps_only : t list -> Path.t list
|
||||||
|
|
Loading…
Reference in New Issue