|
| 1 | +--- |
| 2 | +--- |
| 3 | + |
| 4 | +# Graph lab |
| 5 | + |
| 6 | +Build system targets are often modeled as a directed acyclic graph (DAG). |
| 7 | +Each node in the graph represents a build target and each edge represents a |
| 8 | +dependency on another target. A correct build order is a topological sort: an |
| 9 | +order in which each target is only built after all of its dependencies have |
| 10 | +been built. |
| 11 | + |
| 12 | +We're not going to make a whole build system from scratch, but we will |
| 13 | +implement one of the core components in your preferred programming language: a |
| 14 | +topological sort. |
| 15 | + |
| 16 | +## Graph representation |
| 17 | + |
| 18 | +Write a class that satisfies this interface (it need not be Python; that's just |
| 19 | +the syntax for the interface sketch pseudocode): |
| 20 | + |
| 21 | +```python |
| 22 | +class Target: |
| 23 | + def name(self) -> str: ... |
| 24 | + def recipe(self) -> str: ... |
| 25 | + def dependencies(self) -> List[Target]: ... |
| 26 | +``` |
| 27 | + |
| 28 | +The `name` method should return the name of the target. The `recipe` method |
| 29 | +should return the command executed to build the target. The `dependencies` |
| 30 | +method should return a list of the target's dependencies. |
| 31 | + |
| 32 | +## A small graph |
| 33 | + |
| 34 | +Now, manually create a small graph of targets and dependencies. Try modeling |
| 35 | +the C example from `build.sh`: |
| 36 | + |
| 37 | +``` |
| 38 | +foo: foo.o rng.o |
| 39 | + cc -o foo foo.o rng.o |
| 40 | +
|
| 41 | +foo.o: foo.c rng.h |
| 42 | + cc -c foo.c |
| 43 | +
|
| 44 | +rng.o: rng.c rng.h |
| 45 | + cc -c rng.c |
| 46 | +
|
| 47 | +myls.o: myls.c |
| 48 | + cc -c myls.c |
| 49 | +
|
| 50 | +myls: myls.o |
| 51 | + cc -o myls myls.o |
| 52 | +``` |
| 53 | + |
| 54 | +You may need to change around the order (bottom-up) to get your |
| 55 | +manually-created `Target` objects to make sense. |
| 56 | + |
| 57 | +## Topological sort |
| 58 | + |
| 59 | +Start by writing a function `execute` that, given a target with no |
| 60 | +dependencies, prints the target's recipe. |
| 61 | + |
| 62 | +Then, add another case: if the target has dependencies, execute them first. |
| 63 | + |
| 64 | +Now you have a problem: if targets `A` and `B` both depend on `C`, it will |
| 65 | +execute (print) the recipe for `C` twice. To fix this, add a `set` parameter |
| 66 | +called `visited` to `execute`. Before executing a target, check if its name is |
| 67 | +in the visited set. |
| 68 | + |
| 69 | +## Tests |
| 70 | + |
| 71 | +What are the core properties of your program that you want to test? |
| 72 | + |
| 73 | +Write tests for your topological sort. You can use the example above from |
| 74 | +`build.sh`, but you should also think smaller, and be more targeted. What is |
| 75 | +your specific test case trying to "break" in your implementation? We'll talk |
| 76 | +more about this next module. |
| 77 | + |
| 78 | +## Cycles? |
| 79 | + |
| 80 | +If you have a cycle in your graph, your topological sort will not work. How |
| 81 | +might you detect cycles? |
0 commit comments