fix target_specific_var
diff --git a/eval.go b/eval.go
index beef685..a31235a 100644
--- a/eval.go
+++ b/eval.go
@@ -15,7 +15,6 @@
 	outVars  *VarTab
 	outRules []*Rule
 	vars     *VarTab
-	curRule  *Rule
 
 	funcs map[string]Func
 
@@ -141,13 +140,18 @@
 }
 
 func (ev *Evaluator) evalAssign(ast *AssignAST) {
+	lhs, rhs := ev.evalAssignAST(ast)
+	Log("ASSIGN: %s=%q (flavor:%q)", lhs, rhs, rhs.Flavor())
+	ev.outVars.Assign(lhs, rhs)
+}
+
+func (ev *Evaluator) evalAssignAST(ast *AssignAST) (string, Var) {
 	ev.filename = ast.filename
 	ev.lineno = ast.lineno
 
 	lhs := ev.evalExpr(ast.lhs)
 	rhs := ast.evalRHS(ev, lhs)
-	Log("ASSIGN: %s=%q (flavor:%q)", lhs, rhs, rhs.Flavor())
-	ev.outVars.Assign(lhs, rhs)
+	return lhs, rhs
 }
 
 func (ev *Evaluator) evalMaybeRule(ast *MaybeRuleAST) {
@@ -164,27 +168,35 @@
 		return
 	}
 
-	ev.curRule = &Rule{
+	rule := &Rule{
 		filename:  ast.filename,
 		lineno:    ast.lineno,
 		cmdLineno: ast.cmdLineno,
 	}
-	if err := ev.curRule.parse(line); err != "" {
-		Error(ast.filename, ast.lineno, err)
+	assign, err := rule.parse(line)
+	if err != nil {
+		Error(ast.filename, ast.lineno, err.Error())
 	}
-	Log("rule %q => outputs:%q, inputs:%q", line, ev.curRule.outputs, ev.curRule.inputs)
+	Log("rule %q => outputs:%q, inputs:%q", line, rule.outputs, rule.inputs)
 	// It seems rules with no outputs are siliently ignored.
-	if len(ev.curRule.outputs) == 0 && len(ev.curRule.outputPatterns) == 0 {
-		ev.curRule = nil
+	if len(rule.outputs) == 0 && len(rule.outputPatterns) == 0 {
 		return
 	}
 
 	// TODO: Pretty print.
 	//Log("RULE: %s=%s (%d commands)", lhs, rhs, len(cmds))
 
-	ev.curRule.cmds = ast.cmds
-	ev.outRules = append(ev.outRules, ev.curRule)
-	ev.curRule = nil
+	if assign != nil {
+		if len(ast.cmds) > 0 {
+			Error(ast.filename, ast.lineno, "*** commands commence before first target.  Stop.")
+		}
+		rule.vars = NewVarTab(nil)
+		lhs, rhs := ev.evalAssignAST(assign)
+		rule.vars.Assign(lhs, rhs)
+	} else {
+		rule.cmds = ast.cmds
+	}
+	ev.outRules = append(ev.outRules, rule)
 }
 
 func (ev *Evaluator) LookupVar(name string) Var {
diff --git a/exec.go b/exec.go
index 15c1e5f..4fed885 100644
--- a/exec.go
+++ b/exec.go
@@ -152,6 +152,12 @@
 		}
 		return outputTs, fmt.Errorf("no rule to make target %q", output)
 	}
+	if rule.vars != nil {
+		vars = NewVarTab(vars)
+		for k, v := range rule.vars.m {
+			vars.Assign(k, v)
+		}
+	}
 
 	latest := int64(-1)
 	var actualInputs []string
@@ -260,6 +266,20 @@
 		isSuffixRule := ex.populateSuffixRule(rule, output)
 
 		if oldRule, present := ex.rules[output]; present {
+			if oldRule.vars != nil || rule.vars != nil {
+				oldRule.isDoubleColon = rule.isDoubleColon
+				switch {
+				case rule.vars == nil && oldRule.vars != nil:
+					rule.vars = oldRule.vars
+				case rule.vars != nil && oldRule.vars == nil:
+				case rule.vars != nil && oldRule.vars != nil:
+					// parent would be the same vars?
+					for k, v := range rule.vars.m {
+						oldRule.vars.m[k] = v
+					}
+					rule.vars = oldRule.vars
+				}
+			}
 			if oldRule.isDoubleColon != rule.isDoubleColon {
 				Error(rule.filename, rule.lineno, "*** target file %q has both : and :: entries.", output)
 			}
diff --git a/rule_parser.go b/rule_parser.go
index c7ee1bd..729f2d3 100644
--- a/rule_parser.go
+++ b/rule_parser.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"errors"
 	"strings"
 )
 
@@ -11,6 +12,7 @@
 	outputPatterns  []string
 	isDoubleColon   bool
 	isSuffixRule    bool
+	vars            *VarTab
 	cmds            []string
 	filename        string
 	lineno          int
@@ -37,10 +39,38 @@
 	}
 }
 
-func (r *Rule) parse(line string) string {
+func (r *Rule) parseVar(s string) *AssignAST {
+	eq := strings.IndexByte(s, '=')
+	if eq <= 0 {
+		return nil
+	}
+	assign := &AssignAST{
+		rhs: strings.TrimLeft(s[eq+1:], " \t"),
+	}
+	assign.filename = r.filename
+	assign.lineno = r.lineno
+	// TODO(ukai): support override, export.
+	switch s[eq-1 : eq] {
+	case ":=":
+		assign.lhs = strings.TrimSpace(s[:eq-1])
+		assign.op = ":="
+	case "+=":
+		assign.lhs = strings.TrimSpace(s[:eq-1])
+		assign.op = "+="
+	case "?=":
+		assign.lhs = strings.TrimSpace(s[:eq-1])
+		assign.op = "?="
+	default:
+		assign.lhs = strings.TrimSpace(s[:eq])
+		assign.op = "="
+	}
+	return assign
+}
+
+func (r *Rule) parse(line string) (*AssignAST, error) {
 	index := strings.IndexByte(line, ':')
 	if index < 0 {
-		return "*** missing separator."
+		return nil, errors.New("*** missing separator.")
 	}
 
 	first := line[:index]
@@ -48,7 +78,7 @@
 	isFirstPattern := isPatternRule(first)
 	if isFirstPattern {
 		if len(outputs) > 1 {
-			return "*** mixed implicit and normal rules: deprecated syntax"
+			return nil, errors.New("*** mixed implicit and normal rules: deprecated syntax")
 		}
 		r.outputPatterns = outputs
 	} else {
@@ -62,15 +92,18 @@
 	}
 
 	rest := line[index:]
+	if assign := r.parseVar(rest); assign != nil {
+		return assign, nil
+	}
 	index = strings.IndexByte(rest, ':')
 	if index < 0 {
 		r.parseInputs(rest)
-		return ""
+		return nil, nil
 	}
 
 	// %.x: %.y: %.z
 	if isFirstPattern {
-		return "*** mixed implicit and normal rules: deprecated syntax"
+		return nil, errors.New("*** mixed implicit and normal rules: deprecated syntax")
 	}
 
 	second := rest[:index]
@@ -79,15 +112,15 @@
 	r.outputs = outputs
 	r.outputPatterns = splitSpaces(second)
 	if len(r.outputPatterns) == 0 {
-		return "*** missing target pattern."
+		return nil, errors.New("*** missing target pattern.")
 	}
 	if len(r.outputPatterns) > 1 {
-		return "*** multiple target patterns."
+		return nil, errors.New("*** multiple target patterns.")
 	}
 	if !isPatternRule(r.outputPatterns[0]) {
-		return "*** target pattern contains no '%'."
+		return nil, errors.New("*** target pattern contains no '%'.")
 	}
 	r.parseInputs(third)
 
-	return ""
+	return nil, nil
 }
diff --git a/rule_parser_test.go b/rule_parser_test.go
index f44c589..dc66d0b 100644
--- a/rule_parser_test.go
+++ b/rule_parser_test.go
@@ -7,29 +7,30 @@
 
 func TestRuleParser(t *testing.T) {
 	for _, tc := range []struct {
-		in   string
-		want Rule
-		err  string
-	} {
+		in     string
+		want   Rule
+		assign *AssignAST
+		err    string
+	}{
 		{
-			in:   "foo: bar",
+			in: "foo: bar",
 			want: Rule{
 				outputs: []string{"foo"},
 				inputs:  []string{"bar"},
 			},
 		},
 		{
-			in:   "foo: bar baz",
+			in: "foo: bar baz",
 			want: Rule{
 				outputs: []string{"foo"},
 				inputs:  []string{"bar", "baz"},
 			},
 		},
 		{
-			in:   "foo:: bar",
+			in: "foo:: bar",
 			want: Rule{
-				outputs: []string{"foo"},
-				inputs:  []string{"bar"},
+				outputs:       []string{"foo"},
+				inputs:        []string{"bar"},
 				isDoubleColon: true,
 			},
 		},
@@ -38,7 +39,7 @@
 			err: "*** missing separator.",
 		},
 		{
-			in:  "%.o: %.c",
+			in: "%.o: %.c",
 			want: Rule{
 				outputPatterns: []string{"%.o"},
 				inputs:         []string{"%.c"},
@@ -49,7 +50,7 @@
 			err: "*** mixed implicit and normal rules: deprecated syntax",
 		},
 		{
-			in:  "foo.o: %.o: %.c %.h",
+			in: "foo.o: %.o: %.c %.h",
 			want: Rule{
 				outputs:        []string{"foo.o"},
 				outputPatterns: []string{"%.o"},
@@ -73,13 +74,24 @@
 			err: "*** target pattern contains no '%'.",
 		},
 		{
-			in:  "foo: bar | baz",
+			in: "foo: bar | baz",
 			want: Rule{
 				outputs:         []string{"foo"},
 				inputs:          []string{"bar"},
 				orderOnlyInputs: []string{"baz"},
 			},
 		},
+		{
+			in: "foo: CFLAGS = -g",
+			want: Rule{
+				outputs: []string{"foo"},
+			},
+			assign: &AssignAST{
+				lhs: "CFLAGS",
+				rhs: "-g",
+				op:  "=",
+			},
+		},
 		/* TODO
 		{
 			in:  "foo.o: %.c: %.c",
@@ -88,12 +100,36 @@
 		*/
 	} {
 		got := &Rule{}
-		err := got.parse(tc.in)
-		if err != tc.err {
-			t.Errorf(`r.parse(%q)=%s, want %s`, tc.in, err, tc.err)
+		assign, err := got.parse(tc.in)
+		if tc.err != "" {
+			if err == nil {
+				t.Errorf(`r.parse(%q)=_, <nil>, want _, %q`, tc.in, tc.err)
+				continue
+			}
+			if got, want := err.Error(), tc.err; got != want {
+				t.Errorf(`r.parse(%q)=_, %s, want %s`, tc.in, got, want)
+			}
+			continue
 		}
-		if err == "" && !reflect.DeepEqual(*got, tc.want) {
+		if err != nil {
+			t.Errorf(`r.parse(%q)=_, %v; want nil error`, tc.in, err)
+			continue
+		}
+		if !reflect.DeepEqual(*got, tc.want) {
 			t.Errorf(`r.parse(%q); r=%q, want %q`, tc.in, *got, tc.want)
 		}
+		if tc.assign != nil {
+			if assign == nil {
+				t.Errorf(`r.parse(%q)=<nil>; want=%v`, tc.in, tc.assign)
+				continue
+			}
+			if got, want := assign, tc.assign; !reflect.DeepEqual(got, want) {
+				t.Errorf(`r.parse(%q)=%v; want=%v`, got, want)
+			}
+			continue
+		}
+		if assign != nil {
+			t.Errorf(`r.parse(%q)=%v; want=<nil>`, tc.in, assign)
+		}
 	}
 }
diff --git a/test/target_specific_var.mk b/test/target_specific_var.mk
index 1696344..54b3fd0 100644
--- a/test/target_specific_var.mk
+++ b/test/target_specific_var.mk
@@ -1,4 +1,3 @@
-# TODO
 # https://www.gnu.org/software/make/manual/html_node/Target_002dspecific.html
 
 CFLAGS = -O