Predicates always have the form ‘is-foo’ (like ‘*foo’ for tactics).
let websitedir = "%HOME/d/websites/people.redhat.com/goals"
-# XXX Should actually use the *url tactic here.
+# XXX Should actually use the is-url predicate here.
goal maintainer-upload = : distcheck {
[ -d %websitedir ]
cp %tarfile %websitedir/files
Goals is an experimental tool which aims to generalize make beyond
these limitations. It works for any resource, not just files:
- *url("https://example.com/uploads/software.tar.gz";) : "software.tar.gz" {
+ is-url("https://example.com/uploads/software.tar.gz";) : "software.tar.gz" {
scp %< example.com:/web/uploads/
}
-(You can write your own "tactics" like the *url in the example above).
+(You can write your own "predicates" like is-url in the example above).
Goal recipes can be named, and can be called either by name or by
-automatically finding a matching tactic:
+automatically finding a matching predicate:
goal rebuild (pkg) = "%pkg.o" : "%pkg.c" { %CC %CFLAGS -c %< -o %@ }
cd %HOME/fedora
echo '['
for f in perl*; do
- test -f $f/$f.spec && echo ' *built-in-koji("'$f'"),'
+ test -f $f/$f.spec && echo ' is-koji-built("'$f'"),'
done
echo ']'
}
goal rebuild (name) =
- *built-in-koji ("%name") : build-requires (name) {
+ is-koji-built ("%name") : build-requires (name) {
cd %HOME/fedora/%name
rpmdev-bumpspec %name.spec
fedpkg commit -c
# source BuildRequires
}
- tactic *built-in-koji (name) = {
+ predicate is-koji-built (name) = {
# shell commands to check if latest NVR of %name from the spec
# file matches the latest successful build in Koji
}
might allow goals to be called with labelled parameters.
Deleting target files if goals is interrupted, but only if the
-timestamp changes (what about non-*files?). Also: atomic code. This
-will delete the target if the code doesn't run to completion. (make
-doesn't do this, but probably it should).
+timestamp changes (what about things which are not is-files?).
+Also: atomic code. This will delete the target if the code
+doesn't run to completion. (make doesn't do this, but
+probably it should).
Conditional sections (same as "ifeq" etc in make).
For example:
- wrap ("*file", ["bar.c", "foo.c"]) ⇒ [*file("bar.c"), *file("foo.c")]
+ wrap ("is-file", ["bar.c", "foo.c"]) ⇒ [is-file("bar.c"), is-file("foo.c")]
Each element in C<list> is wrapped into a call to C<wrapper(element)>.
-There are two common uses for this: either to add explicit tactics
-(such as C<*file>) to a plain list of strings as in the example above;
+There are two common uses for this: either to add explicit predicates
+(such as C<is-file>) to a plain list of strings as in the example above;
or to turn a list of strings into a list of goal or function calls.
=head1 SEE ALSO
type env = expr Env.t
and pattern =
- | PTactic of loc * id * substs list
+ | PPred of loc * id * substs list
and expr =
| EGoalDefn of loc * goal
| EFuncDefn of loc * func
- | ETacticDefn of loc * tactic
+ | EPredDefn of loc * pred
| ECall of loc * id * expr list
- | ETacticCtor of loc * id * expr list
+ | EPredCtor of loc * id * expr list
| EVar of loc * id
| EList of loc * expr list
| ESubsts of loc * substs
| CString of string
and goal = param_decl list * pattern list * expr list * code option
and func = param_decl list * returning * bool * code
-and tactic = param_decl list * code
+and pred = param_decl list * code
and param_decl = id
and id = string
and code = substs * bool
string_loc loc name in
func
-let gettactic env loc name =
- assert (name.[0] = '*');
+let getpred env loc name =
+ assert (String.length name >= 3 && String.sub name 0 3 = "is-");
let expr =
try Env.find name env
with Not_found ->
- failwithf "%a: tactic ‘%s’ not found" string_loc loc name in
- let tactic =
+ failwithf "%a: predicate ‘%s’ not found" string_loc loc name in
+ let pred =
match expr with
- | ETacticDefn (loc, tactic) -> tactic
+ | EPredDefn (loc, pred) -> pred
| _ ->
- failwithf "%a: tried to call ‘%s’ which is not a tactic"
+ failwithf "%a: tried to call ‘%s’ which is not a predicate"
string_loc loc name in
- tactic
+ pred
module Substs = struct
type t = {
match expr with
| EGoalDefn (loc, goal) -> string_goal () (Some name, goal) ^ "\n"
| EFuncDefn (loc, func) -> string_func () (Some name, func) ^ "\n"
- | ETacticDefn (loc, tactic) -> string_tactic () (Some name, tactic) ^ "\n"
+ | EPredDefn (loc, pred) -> string_pred () (Some name, pred) ^ "\n"
| expr -> sprintf "let %s = %a\n" name string_expr expr;
and print_def fp name expr = output_string fp (string_def () (name, expr))
(String.concat ", " (List.map (string_param_decl ()) param_decls))
(if quiet then "@" else "")
-and string_tactic () (name, (param_decls, (code, quiet))) =
- sprintf "tactic%s (%s) = %s{ ... }"
+and string_pred () (name, (param_decls, (code, quiet))) =
+ sprintf "predicate%s (%s) = %s{ ... }"
(match name with None -> "" | Some name -> " " ^ name)
(String.concat ", " (List.map (string_param_decl ()) param_decls))
(if quiet then "@" else "")
and string_param_decl () name = name
and string_pattern () = function
- | PTactic (loc, name, params) ->
+ | PPred (loc, name, params) ->
sprintf "%s (%s)" name (String.concat ", "
(List.map (string_substs ()) params))
and string_expr () = function
| EGoalDefn (loc, goal) -> string_goal () (None, goal)
| EFuncDefn (loc, func) -> string_func () (None, func)
- | ETacticDefn (loc, goal) -> string_tactic () (None, goal)
+ | EPredDefn (loc, goal) -> string_pred () (None, goal)
| ECall (loc, name, params) ->
sprintf "%s (%s)"
name (String.concat ", " (List.map (string_expr ()) params))
- | ETacticCtor (loc, name, params) ->
+ | EPredCtor (loc, name, params) ->
sprintf "%s (%s)"
name (String.concat ", " (List.map (string_expr ()) params))
| EVar (loc, var) -> var
variable or goal name -> expression. *)
type env = expr Env.t
and pattern =
- | PTactic of loc * id * substs list
- (** match tactic such as *file ("filename") *)
+ | PPred of loc * id * substs list
+ (** match predicate such as is-file ("filename") *)
and expr =
| EGoalDefn of loc * goal
(** goal (params) = patterns : exprs code *)
| EFuncDefn of loc * func
(** function (params) = code *)
- | ETacticDefn of loc * tactic
- (** tactic (params) = code *)
+ | EPredDefn of loc * pred
+ (** predicate (params) = code *)
| ECall of loc * id * expr list
(** call goal (params) or function (params) *)
- | ETacticCtor of loc * id * expr list
- (** constructor *tactic (params) *)
+ | EPredCtor of loc * id * expr list
+ (** constructor is-predicate (params) *)
| EVar of loc * id
(** variable, or goal call with no parameters *)
| EList of loc * expr list
| CString of string
and goal = param_decl list * pattern list * expr list * code option
and func = param_decl list * returning * bool * code
-and tactic = param_decl list * code
-and param_decl = id (** goal/func/tactic parameter. *)
+and pred = param_decl list * code
+and param_decl = id (** goal/func/pred parameter. *)
and id = string
and code = substs * bool (** code + quiet flag *)
and returning = RetExpr | RetStrings | RetString
(** Look up a function in the environment. Raise [Failure _] if not
found or if the named variable is not a function. *)
-val gettactic : env -> loc -> id -> tactic
-(** Look up a tactic in the environment. Raise [Failure _] if not
- found or if the named variable is not a tactic. *)
+val getpred : env -> loc -> id -> pred
+(** Look up a predicate in the environment. Raise [Failure _] if not
+ found or if the named variable is not a predicate. *)
(** This is used for incrementally building Ast.substs in the parser. *)
module Substs : sig
let string_of_node = function
| Goal (_, _, _, _, _, _, debug_goal) -> debug_goal
- | Exists (_, _, _, debug_tactic) -> debug_tactic
+ | Exists (_, _, _, debug_pred) -> debug_pred
let compare_nodes n1 n2 =
match n1, n2 with
List.fold_left (fun dag root -> add_target dag ?parent env root) dag roots
and add_target dag ?parent env = function
- | Ast.EGoalDefn _ | Ast.EFuncDefn _ | Ast.ETacticDefn _ -> assert false
+ | Ast.EGoalDefn _ | Ast.EFuncDefn _ | Ast.EPredDefn _ -> assert false
(* Call a goal or function. *)
| Ast.ECall (loc, name, args) ->
Ast.string_loc loc name
)
- (* Call a tactic. *)
- | Ast.ETacticCtor (loc, name, args) ->
- (* All parameters of tactics must be simple constant expressions
+ (* Call a predicate. *)
+ | Ast.EPredCtor (loc, name, args) ->
+ (* All parameters of predicates must be simple constant expressions
* (strings, in future booleans, numbers, etc).
*)
let args = List.map (Eval.to_constant env) args in
- add_tactic dag ?parent env loc name args
+ add_pred dag ?parent env loc name args
(* If this is a goal then it's the same as calling goal(). If not
* then look up the variable and substitute it.
| Ast.EList (loc, exprs) ->
add_targets dag ?parent env exprs
- (* A string (with or without substitutions) implies *file(filename). *)
+ (* A string (with or without substitutions) implies is-file(filename). *)
| Ast.ESubsts (loc, str) ->
let str = Eval.substitute env loc str in
- add_tactic dag ?parent env loc "*file" [Ast.CString str]
+ add_pred dag ?parent env loc "is-file" [Ast.CString str]
| Ast.EConstant (loc, c) ->
- add_tactic dag ?parent env loc "*file" [c]
+ add_pred dag ?parent env loc "is-file" [c]
(* Add a goal by name. *)
and add_goal dag ?parent env loc name args
(* Add all dependencies. *)
add_targets dag ~parent:node env (deps @ extra_deps)
-(* Find the goal which matches the given tactic and add it.
+(* Find the goal which matches the given predicate and add it.
* cargs is a list of parameters (all constants).
*)
-and add_tactic dag ?parent env loc tactic cargs =
- (* This is used to print the tactic in debug and error messages only. *)
- let debug_tactic =
+and add_pred dag ?parent env loc pred cargs =
+ (* This is used to print the predicate in debug and error messages only. *)
+ let debug_pred =
Ast.string_expr ()
- (Ast.ETacticCtor (loc, tactic,
+ (Ast.EPredCtor (loc, pred,
List.map (fun c -> Ast.EConstant (loc, c)) cargs)) in
- Cmdline.debug "%a: adding tactic %s" Ast.string_loc loc debug_tactic;
+ Cmdline.debug "%a: adding predicate %s" Ast.string_loc loc debug_pred;
(* Find all goals in the environment. Returns a list of (name, goal). *)
let goals =
(List.map (fun (name, ((_, patterns, _, _) as goal)) ->
List.map (fun pattern -> (pattern, name, goal)) patterns) goals) in
- (* Find any patterns (ie. tactics) which match the one we
+ (* Find any patterns (ie. predicates) which match the one we
* are searching for. This returns a binding for the goal args,
* so we end up with a list of (pattern, name, goal, args).
*)
let patterns : (Ast.pattern * Ast.id * Ast.goal * Ast.expr list) list =
filter_map (
fun (pattern, name, ((params, _, _, _) as goal)) ->
- match matching_pattern env loc tactic cargs pattern params with
+ match matching_pattern env loc pred cargs pattern params with
| None -> None
| Some args -> Some (pattern, name, goal, args)
) patterns in
match patterns with
| [] ->
(* There's no matching goal, but we don't need one if
- * the tactic doesn't need to be rebuilt. So create a
- * special Exists node which will be used to run the tactic
+ * the predicate doesn't need to be rebuilt. So create a
+ * special Exists node which will be used to run the predicate
* to see if the target needs to be rebuilt, and only fail
* if it does need a rebuild.
*)
let targs = List.map (function Ast.CString s -> [Ast.SString s]) cargs in
- let p = Ast.PTactic (loc, tactic, targs) in
- let _, dag = add_node dag ?parent (Exists (env, loc, p, debug_tactic)) in
+ let p = Ast.PPred (loc, pred, targs) in
+ let _, dag = add_node dag ?parent (Exists (env, loc, p, debug_pred)) in
dag
| [_, name, goal, args] ->
(g, extra_deps)
| _ :: _ ->
- failwithf "%a: multiple goals found which match tactic %s, but more than one of these goals have {code} sections which is not allowed"
- Ast.string_loc loc debug_tactic in
+ failwithf "%a: multiple goals found which match predicate %s, but more than one of these goals have {code} sections which is not allowed"
+ Ast.string_loc loc debug_pred in
add_goal dag ?parent env loc name args goal extra_deps
-(* Test if pattern matches *tactic(cargs). If it does
+(* Test if pattern matches is-predicate(cargs). If it does
* then we return Some args where args is the arguments that must
* be passed to the matching goal. The params parameter is
* the names of the parameters of that goal.
*)
-and matching_pattern env loc tactic cargs pattern params =
+and matching_pattern env loc pred cargs pattern params =
match pattern with
- | Ast.PTactic (loc, ttactic, targs)
- when tactic <> ttactic ||
+ | Ast.PPred (loc, tpred, targs)
+ when pred <> tpred ||
List.length cargs <> List.length targs ->
- None (* Can't possibly match if tactic name or #args is different. *)
- | Ast.PTactic (loc, ttactic, targs) ->
+ None (* Can't possibly match if predicate name or #args is different. *)
+ | Ast.PPred (loc, tpred, targs) ->
(* Do the args match with a possible params binding? *)
try Some (matching_params env loc params targs cargs)
with Not_found -> None
Jobs.Job (node, fun () ->
state.goal_runner env loc name args goal
extra_deps debug_goal)
- | Exists (env, loc, p, debug_tactic) ->
+ | Exists (env, loc, p, debug_pred) ->
Jobs.Job (node, fun () ->
- state.exists_runner env loc p debug_tactic)
+ state.exists_runner env loc p debug_pred)
)
| node :: nodes when node_failed state node ->
(* Mark it as failed also, and drop it from the list of jobs. *)
(** Run a single goal. *)
type exists_runner = Ast.env -> Ast.loc -> Ast.pattern -> string -> unit
-(** Run an existence tactic. *)
+(** Run an existence predicate. *)
val new_state : t -> goal_runner -> exists_runner -> state
(** Create a new state object from the DAG.
[goal_runner] is a function for running single goals.
- [exists_runner] is a function for running existence tactics.
+ [exists_runner] is a function for running existence predicates.
See the {!Run} module. *)
type node
Ast.string_loc loc name
)
- | ETacticCtor (loc, name, _) ->
- failwithf "%a: cannot use tactic ‘%s’ in constant expression"
+ | EPredCtor (loc, name, _) ->
+ failwithf "%a: cannot use predicate ‘%s’ in constant expression"
Ast.string_loc loc name
| EGoalDefn (loc, _) ->
failwithf "%a: cannot use function in constant expression"
Ast.string_loc loc
- | ETacticDefn (loc, _) ->
- failwithf "%a: cannot use tactic in constant expression"
+ | EPredDefn (loc, _) ->
+ failwithf "%a: cannot use predicate in constant expression"
Ast.string_loc loc
and substitute env loc substs =
Ast.string_loc loc name
)
- (* Tactics expand to the first parameter. *)
- | ETacticCtor (loc, _, []) -> Filename.quote ""
- | ETacticCtor (loc, _, (arg :: _)) -> expr_to_shell_string env arg
+ (* Predicates expand to the first parameter. *)
+ | EPredCtor (loc, _, []) -> Filename.quote ""
+ | EPredCtor (loc, _, (arg :: _)) -> expr_to_shell_string env arg
| EGoalDefn (loc, _) ->
failwithf "%a: cannot use goal in shell expansion"
failwithf "%a: cannot use function in shell expansion"
Ast.string_loc loc
- | ETacticDefn (loc, _) ->
- failwithf "%a: cannot use tactic in shell expansion"
+ | EPredDefn (loc, _) ->
+ failwithf "%a: cannot use predicate in shell expansion"
Ast.string_loc loc
and run_code env loc code =
| EList (loc, exprs) ->
Ast.EList (loc, List.map (evaluate_goal_arg env) exprs)
- | ETacticCtor (loc, name, exprs) ->
- Ast.ETacticCtor (loc, name, List.map (evaluate_goal_arg env) exprs)
+ | EPredCtor (loc, name, exprs) ->
+ Ast.EPredCtor (loc, name, List.map (evaluate_goal_arg env) exprs)
| ECall (loc, name, args) ->
let expr = Ast.getvar env loc name in
| EConstant _
| EGoalDefn _
| EFuncDefn _
- | ETacticDefn _ as e -> e
+ | EPredDefn _ as e -> e
(* Functions are only called from goal args or when substituting
* into a shell script or constant expression (this may change if we
| "{" { read_code false (Ast.Substs.create ()) (ref 1) lexbuf }
| "@{" { read_code true (Ast.Substs.create ()) (ref 1) lexbuf }
| "goal" { GOAL }
- | "tactic"
- { TACTIC_KEYWORD }
+ | "predicate"
+ { PREDICATE }
| "function"
{ FUNCTION }
| "pure" { PURE }
{ STRING_KEYWORD }
| "strings"
{ STRINGS }
- | "*" id { (* NB: The initial '*' is part of the name. *)
- TACTIC (Lexing.lexeme lexbuf) }
+ | "is-" id
+ { (* NB: The initial 'is-' is part of the name. *)
+ PRED (Lexing.lexeme lexbuf) }
| id { ID (Lexing.lexeme lexbuf) }
| _ { raise (SyntaxError ("unexpected character: " ^
Lexing.lexeme lexbuf)) }
%token <Ast.substs> STRING
%token STRING_KEYWORD
%token STRINGS
-%token <string> TACTIC
-%token TACTIC_KEYWORD
+%token <string> PRED
+%token PREDICATE
(* Start nonterminals. *)
%start <Ast.expr Ast.Env.t> file
{
$3, Ast.EFuncDefn ($loc, ($4, $5, $1 <> None, $7))
}
- | TACTIC_KEYWORD TACTIC params_decl EQUALS CODE
+ | PREDICATE PRED params_decl EQUALS CODE
{
- $2, Ast.ETacticDefn ($loc, ($3, $5))
+ $2, Ast.EPredDefn ($loc, ($3, $5))
}
| LET ID EQUALS expr { $2, $4 }
;
| separated_list(COMMA, pattern) { $1 }
;
pattern:
- | STRING { Ast.PTactic ($loc, "*file", [$1]) }
- | TACTIC pattern_params { Ast.PTactic ($loc, $1, $2) }
+ | STRING { Ast.PPred ($loc, "is-file", [$1]) }
+ | PRED pattern_params { Ast.PPred ($loc, $1, $2) }
;
pattern_params:
| LEFT_PAREN separated_list(COMMA, pattern_param) RIGHT_PAREN { $2 }
expr:
| ID params { Ast.ECall ($loc, $1, $2) }
| ID { Ast.EVar ($loc, $1) }
- | TACTIC params { Ast.ETacticCtor ($loc, $1, $2) }
+ | PRED params { Ast.EPredCtor ($loc, $1, $2) }
| STRING { Ast.ESubsts ($loc, $1) }
| LEFT_ARRAY barelist RIGHT_ARRAY { Ast.EList ($loc, $2) }
;
(* Add some standard variables to the environment. *)
let expr_of_substs s = Ast.ESubsts (Ast.noloc, s) in
let expr_of_pattern = function
- | Ast.PTactic (loc, tactic, targs) ->
- Ast.ETacticCtor (loc, tactic, List.map expr_of_substs targs)
+ | Ast.PPred (loc, pred, targs) ->
+ Ast.EPredCtor (loc, pred, List.map expr_of_substs targs)
in
let pexprs = List.map expr_of_pattern patterns in
let env = Ast.Env.add "@" (Ast.EList (Ast.noloc, pexprs)) env in
Ast.string_loc loc Ast.string_pattern pattern;
match pattern with
- | Ast.PTactic (loc, tactic, targs) ->
- (* Look up the tactic. *)
- let params, code = Ast.gettactic env loc tactic in
+ | Ast.PPred (loc, pred, targs) ->
+ (* Look up the predicate. *)
+ let params, code = Ast.getpred env loc pred in
(* Resolve the targs down to constants. Since needs_rebuild
* should be called with env containing the goal params, this
- * should substitute any parameters in the tactic arguments.
+ * should substitute any parameters in the predicate arguments.
*)
let targs = List.map (Eval.substitute env loc) targs in
let targs =
Ast.EConstant (Ast.noloc, Ast.CString targ)) targs in
(* Create a new environment binding parameter names
- * to tactic args.
+ * to predicate args.
*)
let env =
let params =
try List.combine params targs
with Invalid_argument _ ->
- failwithf "%a: calling tactic ‘%s’ with wrong number of arguments"
- Ast.string_loc loc tactic in
+ failwithf "%a: calling predicate ‘%s’ with wrong number of arguments"
+ Ast.string_loc loc pred in
List.fold_left (fun env (k, v) -> Ast.Env.add k v env) env params in
(* Add some standard variables to the environment. *)
if r = 99 (* means "needs rebuild" *) then true
else if r = 0 (* means "doesn't need rebuild" *) then false
else
- failwithf "%a: tactic ‘%s’ failed with exit code %d"
- Ast.string_loc loc tactic r
+ failwithf "%a: predicate ‘%s’ failed with exit code %d"
+ Ast.string_loc loc pred r
-and exists_runner env loc p debug_tactic =
- Cmdline.debug "%a: running implicit existence rule for tactic %s"
- Ast.string_loc loc debug_tactic;
+and exists_runner env loc p debug_pred =
+ Cmdline.debug "%a: running implicit existence rule for predicate %s"
+ Ast.string_loc loc debug_pred;
if needs_rebuild env loc [] [] p then
failwithf "%a: don't know how to build ‘%s’"
- Ast.string_loc loc debug_tactic
+ Ast.string_loc loc debug_pred
(** Run a single goal. *)
val exists_runner : Deps.exists_runner
-(** Run the implicit existence tactic.
+(** Run the implicit existence predicate.
- This is used when we find a tactic like *foo(...) but there
+ This is used when we find a predicate like is-foo(...) but there
is no matching goal. We run (in this callback) the associated
- tactic code. As long as it runs successfully, not returning 99 or
- any error value, then we're OK - the tactic doesn't need rebuilding
+ predicate code. As long as it runs successfully, not returning 99 or
+ any error value, then we're OK - the predicate doesn't need rebuilding
so the dependency is satisfied. However if it returns 99 (needs
rebuild) or an error then we have to exit with an error. *)
# Check if the source package has been built in Koji.
-tactic *koji-built (pkg) = {
+predicate is-koji-built (pkg) = {
cd %fedora-dir/%pkg/%fedora-branch
koji=%koji
specfile=%pkg.spec
# Rebuild a Fedora package. This rebuilds any dependencies first.
goal fedora-rebuild (pkg) =
-*koji-built ("%pkg") : wrap ("*koji-built", fedora-source-dependencies (pkg)) {
+is-koji-built ("%pkg") : wrap ("is-koji-built",
+ fedora-source-dependencies (pkg)) {
cd %fedora-dir/%pkg/%fedora-branch
fedpkg=%fedpkg
koji=%koji
# This file is included first and automatically in all Goalfiles
# (unless you use --no-prelude). It contains standard functions and
-# tactics.
+# predicates.
-# The only tactic that ‘make’ has.
-tactic *file (filename) = @{
+# The only predicate that ‘make’ has.
+predicate is-file (filename) = @{
# Rebuild if the target file doesn't exist at all.
test -f %filename || exit 99
done
}
-# This is a simpler tactic than the above since it will
+# This is a simpler predicate than the above since it will
# rebuild if the file is missing, but not if it is older.
-tactic *exists (filename) = @{
+predicate is-file-exists (filename) = @{
test -f %filename || exit 99
}
exit 1
}
-# Wrap list of strings in a call or tactic.
+# Wrap list of strings in a call or predicate.
pure function wrap (wrapper, xs) = @{
echo '['
for x in %xs; do
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-# Tactics for dealing with URLs. These require the curl command
+# Predicates for dealing with URLs. These require the curl command
# line tool.
-# XXX tactic *url
+# XXX predicate is-url
# Check only if a URL exists without considering its age.
-tactic *url_exists (url) = {
+predicate is-url-exists (url) = {
curl --output /dev/null --silent --head --fail %url || exit 99
}