Add deprecated / obsolete variable support
By calling the custom KATI_deprecated_var / KATI_obsolete_var functions,
variables may be marked as deprecated or obsolete.
When accessed or assigned, deprecated variables will print a warning.
When accessed or assigned, obsolete variables will print an error and
stop.
Variables do not need to be set before calling the functions, and will
persist the deprecation warning through a reassignment. This way we can
easily mark variables that are sometimes passed via the environment as
deprecated.
Change-Id: Id04c974c446f471a18cc173f817760f4a02b9239
diff --git a/eval.cc b/eval.cc
index 7a08be7..ecfcd50 100644
--- a/eval.cc
+++ b/eval.cc
@@ -55,7 +55,9 @@
is_override ? VarOrigin::OVERRIDE : VarOrigin::FILE));
Var* rhs = NULL;
+ Var* prev = LookupVarInCurrentScope(lhs);
bool needs_assign = true;
+
switch (op) {
case AssignOp::COLON_EQ: {
SimpleVar* sv = new SimpleVar(origin);
@@ -67,7 +69,6 @@
rhs = new RecursiveVar(rhs_v, origin, orig_rhs);
break;
case AssignOp::PLUS_EQ: {
- Var* prev = LookupVarInCurrentScope(lhs);
if (!prev->IsDefined()) {
rhs = new RecursiveVar(rhs_v, origin, orig_rhs);
} else if (prev->ReadOnly()) {
@@ -80,7 +81,6 @@
break;
}
case AssignOp::QUESTION_EQ: {
- Var* prev = LookupVarInCurrentScope(lhs);
if (!prev->IsDefined()) {
rhs = new RecursiveVar(rhs_v, origin, orig_rhs);
} else {
@@ -91,6 +91,13 @@
}
}
+ prev->Used(this, lhs);
+ if (prev->Deprecated()) {
+ if (needs_assign) {
+ rhs->SetDeprecated(prev->DeprecatedMessage());
+ }
+ }
+
LOG("Assign: %s=%s", lhs.c_str(), rhs->DebugString().c_str());
if (needs_assign) {
return rhs;
diff --git a/expr.cc b/expr.cc
index 30a25b5..641bf5a 100644
--- a/expr.cc
+++ b/expr.cc
@@ -134,6 +134,7 @@
virtual void Eval(Evaluator* ev, string* s) const override {
Var* v = ev->LookupVar(name_);
+ v->Used(ev, name_);
v->Eval(ev, s);
}
@@ -158,7 +159,9 @@
ev->IncrementEvalDepth();
const string&& name = name_->Eval(ev);
ev->DecrementEvalDepth();
- Var* v = ev->LookupVar(Intern(name));
+ Symbol sym = Intern(name);
+ Var* v = ev->LookupVar(sym);
+ v->Used(ev, sym);
v->Eval(ev, s);
}
@@ -184,10 +187,12 @@
virtual void Eval(Evaluator* ev, string* s) const override {
ev->IncrementEvalDepth();
const string&& name = name_->Eval(ev);
- Var* v = ev->LookupVar(Intern(name));
+ Symbol sym = Intern(name);
+ Var* v = ev->LookupVar(sym);
const string&& pat_str = pat_->Eval(ev);
const string&& subst = subst_->Eval(ev);
ev->DecrementEvalDepth();
+ v->Used(ev, sym);
const string&& value = v->Eval(ev);
WordWriter ww(s);
Pattern pat(pat_str);
diff --git a/func.cc b/func.cc
index 7d50219..cc55f70 100644
--- a/func.cc
+++ b/func.cc
@@ -809,6 +809,66 @@
}
}
+void DeprecatedVarFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+ string vars_str = args[0]->Eval(ev);
+ string msg;
+
+ if (args.size() == 2) {
+ msg = ". " + args[1]->Eval(ev);
+ }
+
+ if (ev->avoid_io()) {
+ ev->Error("*** $(KATI_deprecated_var ...) is not supported in rules.");
+ }
+
+ for (StringPiece var : WordScanner(vars_str)) {
+ Symbol sym = Intern(var);
+ Var* v = ev->LookupVar(sym);
+ if (!v->IsDefined()) {
+ v = new SimpleVar(VarOrigin::FILE);
+ sym.SetGlobalVar(v, false, nullptr);
+ }
+
+ if (v->Deprecated()) {
+ ev->Error(StringPrintf("*** Cannot call KATI_deprecated_var on already deprecated variable: %s.", sym.c_str()));
+ } else if (v->Obsolete()) {
+ ev->Error(StringPrintf("*** Cannot call KATI_deprecated_var on already obsolete variable: %s.", sym.c_str()));
+ }
+
+ v->SetDeprecated(msg);
+ }
+}
+
+void ObsoleteVarFunc(const vector<Value*>&args, Evaluator* ev, string*) {
+ string vars_str = args[0]->Eval(ev);
+ string msg;
+
+ if (args.size() == 2) {
+ msg = ". " + args[1]->Eval(ev);
+ }
+
+ if (ev->avoid_io()) {
+ ev->Error("*** $(KATI_obsolete_var ...) is not supported in rules.");
+ }
+
+ for (StringPiece var : WordScanner(vars_str)) {
+ Symbol sym = Intern(var);
+ Var* v = ev->LookupVar(sym);
+ if (!v->IsDefined()) {
+ v = new SimpleVar(VarOrigin::FILE);
+ sym.SetGlobalVar(v, false, nullptr);
+ }
+
+ if (v->Deprecated()) {
+ ev->Error(StringPrintf("*** Cannot call KATI_obsolete_var on already deprecated variable: %s.", sym.c_str()));
+ } else if (v->Obsolete()) {
+ ev->Error(StringPrintf("*** Cannot call KATI_obsolete_var on already obsolete variable: %s.", sym.c_str()));
+ }
+
+ v->SetObsolete(msg);
+ }
+}
+
FuncInfo g_func_infos[] = {
{ "patsubst", &PatsubstFunc, 3, 3, false, false },
{ "strip", &StripFunc, 1, 1, false, false },
@@ -852,6 +912,10 @@
{ "error", &ErrorFunc, 1, 1, false, false },
{ "file", &FileFunc, 2, 1, false, false },
+
+ /* Kati custom extension functions */
+ { "KATI_deprecated_var", &DeprecatedVarFunc, 2, 1, false, false },
+ { "KATI_obsolete_var", &ObsoleteVarFunc, 2, 1, false, false },
};
unordered_map<StringPiece, FuncInfo*>* g_func_info_map;
diff --git a/testcase/deprecated_var.mk b/testcase/deprecated_var.mk
new file mode 100644
index 0000000..2cacbda
--- /dev/null
+++ b/testcase/deprecated_var.mk
@@ -0,0 +1,70 @@
+# TODO(go): not implemented
+
+
+A := test
+$(KATI_deprecated_var A B C D)
+
+# Writing to an undefined deprecated variable
+B := test
+ifndef KATI
+$(info Makefile:8: B has been deprecated.)
+endif
+
+# Reading from deprecated variables (set before/after/never the deprecation func)
+# Writing to an undefined deprecated variable
+D := $(A)$(B)$(C)
+ifndef KATI
+$(info Makefile:15: A has been deprecated.)
+$(info Makefile:15: B has been deprecated.)
+$(info Makefile:15: C has been deprecated.)
+$(info Makefile:15: D has been deprecated.)
+endif
+
+# Writing to a reset deprecated variable
+D += test
+ifndef KATI
+$(info Makefile:24: D has been deprecated.)
+endif
+
+# Using a custom message
+$(KATI_deprecated_var E,Use X instead)
+E = $(C)
+ifndef KATI
+$(info Makefile:31: E has been deprecated. Use X instead.)
+endif
+
+# Expanding a recursive variable with an embedded deprecated variable
+$(E)
+ifndef KATI
+$(info Makefile:37: E has been deprecated. Use X instead.)
+$(info Makefile:37: C has been deprecated.)
+endif
+
+# All of the previous variable references have been basic SymRefs, now check VarRefs
+F = E
+G := $($(F))
+ifndef KATI
+$(info Makefile:45: E has been deprecated. Use X instead.)
+$(info Makefile:45: C has been deprecated.)
+endif
+
+# And check VarSubst
+G := $(C:%.o=%.c)
+ifndef KATI
+$(info Makefile:52: C has been deprecated.)
+endif
+
+# Deprecated variable used in a rule-specific variable
+test: A := $(E)
+ifndef KATI
+$(info Makefile:58: E has been deprecated. Use X instead.)
+$(info Makefile:58: C has been deprecated.)
+# A hides the global A variable, so is not considered deprecated.
+endif
+
+# Deprecated variable used in a rule
+test:
+ echo $(C)Done
+ifndef KATI
+$(info Makefile:67: C has been deprecated.)
+endif
diff --git a/testcase/err_deprecated_var_already_deprecated.mk b/testcase/err_deprecated_var_already_deprecated.mk
new file mode 100644
index 0000000..3f110cf
--- /dev/null
+++ b/testcase/err_deprecated_var_already_deprecated.mk
@@ -0,0 +1,4 @@
+# TODO(go): not implemented
+
+$(KATI_deprecated_var A)
+$(KATI_deprecated_var A)$(or $(KATI),$(error Cannot call KATI_deprecated_var on already deprecated variable: A))
diff --git a/testcase/err_deprecated_var_already_obsolete.mk b/testcase/err_deprecated_var_already_obsolete.mk
new file mode 100644
index 0000000..3f60202
--- /dev/null
+++ b/testcase/err_deprecated_var_already_obsolete.mk
@@ -0,0 +1,4 @@
+# TODO(go): not implemented
+
+$(KATI_obsolete_var A)
+$(KATI_deprecated_var A)$(or $(KATI),$(error Cannot call KATI_deprecated_var on already obsolete variable: A))
diff --git a/testcase/err_obsolete_var.mk b/testcase/err_obsolete_var.mk
new file mode 100644
index 0000000..fe032bd
--- /dev/null
+++ b/testcase/err_obsolete_var.mk
@@ -0,0 +1,6 @@
+# TODO(go): not implemented
+#
+# We go into a lot more cases in deprecated_var.mk, and hope that if deprecated works, obsolete does too.
+
+$(KATI_obsolete_var A)
+$(A) $(or $(KATI),$(error A is obsolete))
diff --git a/testcase/err_obsolete_var_already_deprecated.mk b/testcase/err_obsolete_var_already_deprecated.mk
new file mode 100644
index 0000000..42cfbfb
--- /dev/null
+++ b/testcase/err_obsolete_var_already_deprecated.mk
@@ -0,0 +1,4 @@
+# TODO(go): not implemented
+
+$(KATI_deprecated_var A)
+$(KATI_obsolete_var A)$(or $(KATI),$(error Cannot call KATI_obsolete_var on already deprecated variable: A))
diff --git a/testcase/err_obsolete_var_already_obsolete.mk b/testcase/err_obsolete_var_already_obsolete.mk
new file mode 100644
index 0000000..e6f9d76
--- /dev/null
+++ b/testcase/err_obsolete_var_already_obsolete.mk
@@ -0,0 +1,4 @@
+# TODO(go): not implemented
+
+$(KATI_obsolete_var A)
+$(KATI_obsolete_var A)$(or $(KATI),$(error Cannot call KATI_obsolete_var on already obsolete variable: A))
diff --git a/testcase/err_obsolete_var_assign.mk b/testcase/err_obsolete_var_assign.mk
new file mode 100644
index 0000000..a6b7397
--- /dev/null
+++ b/testcase/err_obsolete_var_assign.mk
@@ -0,0 +1,4 @@
+# TODO(go): not implemented
+
+$(KATI_obsolete_var A)
+A := $(or $(KATI),$(error A is obsolete))
diff --git a/testcase/err_obsolete_var_msg.mk b/testcase/err_obsolete_var_msg.mk
new file mode 100644
index 0000000..8162b10
--- /dev/null
+++ b/testcase/err_obsolete_var_msg.mk
@@ -0,0 +1,4 @@
+# TODO(go): not implemented
+
+$(KATI_obsolete_var A,Use Y instead)
+$(A) $(or $(KATI),$(error A is obsolete. Use Y instead))
diff --git a/testcase/err_obsolete_var_varref.mk b/testcase/err_obsolete_var_varref.mk
new file mode 100644
index 0000000..a671d56
--- /dev/null
+++ b/testcase/err_obsolete_var_varref.mk
@@ -0,0 +1,5 @@
+# TODO(go): not implemented
+
+$(KATI_obsolete_var A)
+B := A
+$($(B)) $(or $(KATI),$(error A is obsolete))
diff --git a/testcase/err_obsolete_var_varsubst.mk b/testcase/err_obsolete_var_varsubst.mk
new file mode 100644
index 0000000..33c89b5
--- /dev/null
+++ b/testcase/err_obsolete_var_varsubst.mk
@@ -0,0 +1,4 @@
+# TODO(go): not implemented
+
+$(KATI_obsolete_var A)
+$(A:%.o=%.c) $(or $(KATI),$(error A is obsolete))
diff --git a/var.cc b/var.cc
index 8f46ff8..1e6f5bb 100644
--- a/var.cc
+++ b/var.cc
@@ -16,6 +16,7 @@
#include "var.h"
+#include "eval.h"
#include "expr.h"
#include "log.h"
@@ -37,7 +38,7 @@
return "*** broken origin ***";
}
-Var::Var() : readonly_(false) {
+Var::Var() : readonly_(false), message_(), error_(false) {
}
Var::~Var() {
diff --git a/var.h b/var.h
index 75653de..15f461e 100644
--- a/var.h
+++ b/var.h
@@ -15,11 +15,14 @@
#ifndef VAR_H_
#define VAR_H_
+#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
+#include "eval.h"
#include "expr.h"
+#include "log.h"
#include "stmt.h"
#include "string_piece.h"
#include "symtab.h"
@@ -59,11 +62,39 @@
bool ReadOnly() const { return readonly_; }
void SetReadOnly() { readonly_ = true; }
+ bool Deprecated() const { return message_ && !error_; }
+ void SetDeprecated(StringPiece msg) {
+ message_.reset(new string(msg.as_string()));
+ }
+
+ bool Obsolete() const { return error_; }
+ void SetObsolete(StringPiece msg) {
+ message_.reset(new string(msg.as_string()));
+ error_ = true;
+ }
+
+ const string& DeprecatedMessage() const { return *message_; }
+
+ // This variable was used (either written or read from)
+ void Used(Evaluator* ev, const Symbol& sym) const {
+ if (!message_) {
+ return;
+ }
+
+ if (error_) {
+ ev->Error(StringPrintf("*** %s is obsolete%s.", sym.c_str(), message_->c_str()));
+ } else {
+ WARN_LOC(ev->loc(), "%s has been deprecated%s.", sym.c_str(), message_->c_str());
+ }
+ }
+
protected:
Var();
private:
bool readonly_;
+ unique_ptr<string> message_;
+ bool error_;
};
class SimpleVar : public Var {