CSC207 Software Design
Lectures
Making Make

Where We Are

Can parse a (simple) Makefile to create rules

Real parsers are more complex

But it's a difference of degree, not kind

Can store dependencies in a graph

And then traverse the graph

Remaining steps:

Transform parser output to graph

Update graph nodes according to Make's rules

But First

Best to start with some refactoring

Refactoring an equation means extracting common values and eliminating redundancies

Refactoring code is similar

Two changes:

Allow clients of DirectedGraph to associate arbitrary data with nodes

Like a set of actions

Don't tie the parser to a particular rule class

May find lots of uses for it, each with its own needs

Require them all to implement an interface IRule

Associating Data With Graph Nodes

Two choices:

A second map from nodes to data

But then have to worry about consistency

A map from nodes to objects that store arcs and data

Create an inner class for this

No-one outside the graph class needs to know it exists

class DirectedGraph {

    public DirectedGraph() {
        fNodes = new HashMap();
    }

    public Set getArcs(Object node) {
        return getStorage(node).fArcs;
    }

    public Object getData(Object node) {
        return getStorage(node).fData;
    }

    protected void addEmptyNode(Object node) {
        if (!fNodes.containsKey(node)) {
            fNodes.put(node, new Storage());
        }
    }

    protected Storage getStorage(Object node) {
        return (Storage)fNodes.get(node);
    }

    protected class Storage {
        public Storage() {
            fArcs = new HashSet();
            fData = null;
        }
        Set    fArcs;
        Object fData;
    }

    protected Map       fNodes;
}

Generalizing the Parser

Problem: want a parser to build objects of a user-specified class

Solution 1: make the parser an abstract class

Users must derive from it, and implement a method createRule()

public abstract class DirectedGraphParserBase {

    // Parsing method calls createRule() to make new rules.
    protected void startRule(String line) {
        fCurrentRule = createRule();
        ...fill in rule as before...
    }
    

    // Derived class must override this.
    protected abstract IRule createRule();
}

public class AppRule implements IRule {
    ...whatever the application needs...
}

public class AppDirectedGraphParser extends DirectedGraphParserBase {
    protected IRule createRule() {
        return new AppRule();
    }
}

Building Things With Factories

Solution 2: create a factory

A class whose only job is to create instances of another class

Each application passes its own factory to the parser

We can require that this class implement some interface IRuleFactory

Then client applications can have the parser create whatever rules they want

The Rule and Rule Factory Interfaces


interface IRule {
    String getTarget();
    void setTarget(String target);
    Iterator getPrereqIter();
    void addPrereq(String prereq);
    Iterator getActionIter();
    void addAction(String action);
    String toString();
}
interface IRuleFactory {
    IRule makeRule();
    IRule makeRule(String target, String[] prereqs, String[] actions);
}

Factory-Based Parser

class ParseMakefile {
    public ParseMakefile(IRuleFactory ruleFactory) {
        fRuleFactory = ruleFactory;
    }

    protected void startRule(String line) {
        fCurrentRule = fRuleFactory.makeRule();
        ...build up rule...
    }

    protected IRuleFactory  fRuleFactory;
}

Main Body of Make

class Make {
    public static void main(String[] args) {
        // Create input stream
        // Read rules
        // Create dependency graph
        // Create rules for pre-requisites that aren't targets
        // Mark specified targets as out of date
        // Update
    }
}

Converting Rule List to Graph

public static DirectedGraph rulesToGraph(List ruleList) {
    DirectedGraph graph = new DirectedGraph();
    Iterator ir = ruleList.iterator();
    while (ir.hasNext()) {
        IRule rule = (IRule)ir.next();
        String target = rule.getTarget();
        graph.addNode(target);
        graph.setData(target, rule);
        Iterator ia = rule.getPrereqIter();
        while (ia.hasNext()) {
            graph.addArc(target, ia.next());
        }
    }
    return graph;
}

Filling In the Graph

Makefiles can mention nodes without providing rules for them

Treating this as an error would put a heavy burden on users

Need to act as if a do-nothing rule for y.class had been defined

Creating on the fly would complicate the logic

Solution: fill in the graph before starting update

Often called pre-processing

Filling In

public static void fillInGraph(DirectedGraph graph) {
    Iterator in = graph.getNodes().iterator();
    while (in.hasNext()) {
        Object node = in.next();
        if (graph.getData(node) == null) {
            IRule rule = new MakeRule();
            rule.setTarget((String)node);
            graph.setData(node, rule);
        }
    }
}

The Update Algorithm

A node is to be updated if:

Any pre-requisites have changed

The user said to on the command line

Useful for testing

Never execute a rule more than once

Have each node keep track of whether it has already executed

Cannot execute rule before all pre-requisites have been checked

Bottom-up traversal

Update Implementation

public static boolean update(DirectedGraph graph, Object node) {

    MakeRule rule = (MakeRule)graph.getData(node);
    
    // Check pre-requisites first
    boolean prereqChanged = false;
    Iterator ia = graph.getArcs(node).iterator();
    while (ia.hasNext()) {
        Object prereq = ia.next();
        if (update(graph, prereq)) {
            prereqChanged = true;
        }
    }
    
    // Explicit change, or pre-requisites have changed?
    if (prereqChanged || rule.isChanged()) {
        rule.execute();
        return true;
    }
    
    // No reason to update parent
    return false;
}

Smart Rules

Last step is to build nodes that keep track of:

Whether they have changed

Whether their rules have been executed

Could:

Create a new class that implements IRule

Create a wrapper around SimpleRule

Extend SimpleRule

This is a case where derivation makes sense

A MakeRule really is a SimpleRule, only smarter

Smart Rules

class MakeRule extends SimpleRule {

    public MakeRule() {
        super();
        init();
    }
  
    public void execute() {
        if (!fExecuted) {
            System.out.println("Executing rules for " + this.getTarget());
            Iterator ia = this.getActionIter();
            if (!ia.hasNext()) {
                System.out.println("\t<no rules>");
            }
            else {
                while (ia.hasNext()) {
                    System.out.println("\t" + (String)ia.next());
                }
            }
            fExecuted = true;
        }
    }
  
    protected void init() {
        fChanged = false;
        fExecuted = false;
    }
  
    protected boolean   fChanged;       // Has this target changed?
    protected boolean   fExecuted;      // Have actions been executed?
}

$Id: remake.html,v 1.1.1.1 2004/01/04 05:02:31 reid Exp $