# Makefile Contract

`flow/Makefile` is written in make, a rich programming language behind a
terse syntax: recursive and simple expansion, `=` against `:=` against `?=`,
the origin of a variable, exports to sub-makes, a graph built in one pass and
run in another. ORFS keeps the Makefile as simple as it can, and over time a
handful of conventions have grown up that keep it that way. Most of them were
never written down.

This page writes them down as rules a review can check a change against: a
change to `flow/Makefile`, `flow/scripts/*.mk`, the stage scripts in
`flow/scripts/*.tcl`, `flow/scripts/variables.yaml`, or the `config.mk` of a
platform (`flow/platforms/*/config.mk`) or a design
(`flow/designs/**/config.mk`). It is written for
automated review of AI maintenance of these files: an agent that changes one
of them, or reviews such a change, checks it against every rule here.

The rules are hard stops: a change that breaks one is wrong, whatever else it
fixes. Each rule says what breaks when it is broken. Any place where the
existing code disagrees with a rule is technical debt, whether an expedient
solution or older code written before the rule: debt to fix, not a precedent
to follow.

Some flows run the Makefile one step at a time, each step in a fresh tree that
holds only that step's inputs, and track the dependencies themselves;
[bazel-orfs](https://github.com/The-OpenROAD-Project/bazel-orfs) is one.
Several of the rules exist for them.

## Targets

- **A `do-` target never builds anything.** It runs its one step on the files
  already in the tree. Its prerequisites are checks (`check-kepler-formal`),
  the step's source files and one-time markers (`$(OBJECTS_DIR)/copyright.txt`):
  never an order-only prerequisite, never a file another target writes.
  *Breaks:* a flow that runs each `do-` target alone in a fresh tree rebuilds
  the prerequisite every time.
- **Dependencies live only on the file targets and stage aliases**
  (`$(RESULTS_DIR)/2_1_floorplan.odb`, `synth`, `floorplan`, ...), never on a
  `do-` target. The file target checks the dependencies and calls the `do-`
  target to run the step.
  *Breaks:* a flow that tracks dependencies itself cannot run one step on
  existing results.
- **`open_*`, `gui_*`, `web_*` and `run` never build anything.** `run` writes
  `$(LOG_DIR)/$(RUN_LOG_NAME_STEM).log` and nothing under `RESULTS_DIR`,
  `REPORTS_DIR` or `OBJECTS_DIR`.
  *Breaks:* opening or scripting a stage changes the tree it looks at, and a
  caller that runs several `make run` at once on one tree gets them writing
  the same files.
- **The graph never depends on a variable.** No `ifeq` chooses which steps run
  or which files they write. A step with nothing to do for a design runs
  anyway and copies its `.odb` and `.sdc` through; the choice is made in its
  `.tcl`.
  *Breaks:* a flow that names a step's outputs before running it gets a
  different set for each design.

## Steps

- **A step never writes next to its inputs or into one.** It writes new files
  under names of its own, as `2_floorplan.odb` → `3_place.odb`.
  *Breaks:* a rerun reads what the last run wrote, and a flow that keeps
  inputs read-only cannot run the step.
- **A step never skips an output because the file exists.** "Up to date" is
  the file target's decision.
  *Breaks:* a tree that kept a file from an earlier run and a fresh tree give
  different results.
- **A step never reads a file it does not declare.**
  *Breaks:* it works under `make synth`, where an earlier target happened to
  build the file, and fails when the step runs alone.
- **A step never reads an input it does not use.**
  *Breaks:* every change to that input reruns the step and every step after
  it.
- **A step never leaves configuration for a later step.** A command gets its
  options in its own `log_cmd` line; a setting it needs is set right before
  the call and reset right after. State that crosses steps is in the `.odb` or
  another result file.
  *Breaks:* the later step's log does not show what it ran with, and a
  checkpoint changes when the design did not.
- **A step never draws an unseeded random number.** The seed comes from a
  variable and is recorded.
  *Breaks:* caching a step's result, the QoR check against a baseline, and
  bisecting a regression.
- **A failing step never calls `exit`.** It writes `<step>-failed.odb` and
  calls `error`, as `flow/scripts/global_place.tcl` does.
  *Breaks:* the log and the exit status stop saying where the step failed,
  and there is no checkpoint to inspect.
- **A `PRE_<STEP>_TCL` hook never runs before the design is loaded.** Hooks
  run through `source_step_tcl` in `flow/scripts/util.tcl`, after
  `load_design`.
  *Breaks:* the hook has no design to act on.

## Variables

- **A variable a design sets is never assigned with `=` in the Makefile.** It
  is assigned with `?=`. `RUN_CMD` in `flow/scripts/variables.mk` is the
  exception; it can be set only on the make command line.
  *Breaks:* a flow that passes a design's variables in `DESIGN_CONFIG` as
  `export VAR?=value` lines is overridden.
- **A variable the environment supplies is never exported with
  `export VAR := $(VAR)`.** It is exported bare, `export VAR`, and its default
  is a `?=` fallback to the in-tree install.
  *Breaks:* `:=` rebinds the variable to origin "file", `UNSET_AND_MAKE`
  unsets it before the sub-make, and the fallback fires there with an in-tree
  path that a flow supplying its own tools does not have (the "HERE BE
  DRAGONS" comment in `flow/scripts/variables.mk`).
- **Only the Makefile sets `RESULTS_DIR`, `LOG_DIR`, `REPORTS_DIR` and
  `OBJECTS_DIR`.** It derives them from `WORK_HOME`, which a flow sets. A
  variable a flow adds never reuses one of these names.
  *Breaks:* a flow that relocates the tree through `WORK_HOME` finds results
  where the Makefile did not put them.
- **A variable a design or platform sets, or a stage script reads, is never
  missing from `flow/scripts/variables.yaml`.** Its entry has its default,
  when it has one, and `stages:` when not every stage reads it. A variable
  only the Makefile reads, such as `WORK_HOME`, or that is internal to it,
  such as `DO_STEP_DEPS_*` and `UNSET_AND_MAKE`, may be documented elsewhere
  and need not be listed.
  *Breaks:* a flow that checks variable names against `variables.yaml`
  refuses it, and a flow that passes each stage only its own variables
  passes it nowhere, or everywhere.
- **A variable that has a default is never read through an existence check.**
  It is read as `$::env(VAR)`, never through `env_var_exists_and_non_empty`
  with a fallback. A boolean is `0`/`1` and has a default.
  *Breaks:* a misspelled or unpropagated variable takes the other branch
  quietly, instead of stopping at the line that reads it.
- **A script never overwrites a variable that is set.** It sets one only when
  it is unset.
  *Breaks:* the design's or the user's value is ignored.
- **A platform default is never repeated in a design's `config.mk`.** It is in
  the platform's `config.mk`, assigned with `?=`.
  *Breaks:* a change to the platform default misses the designs that copied
  it, and a design can no longer override it.
- **A variable never has a negative name** (`LEC_CHECK=1`, never
  `DISABLE_LEC_CHECK`), **never duplicates an existing one** (`ADDITIONAL_LIBS`
  for an extra library), **and is never a default-off feature toggle** that no
  CI design sets. A variable with no effect is deleted, not deprecated.
  *Breaks:* double negatives in settings, two variables for one choice, and
  code no CI run exercises.
- **A variable's value is never computed with `$(shell awk ...)` or
  `$(shell sed ...)`.** That logic is in Python with unit tests.
  *Breaks:* a heuristic nobody can test, read, or run on a machine without
  that tool.

## Directories

A design's files are split over four directories so that where a file lands
says what it is: the design, a measurement of it, a log, or scaffolding. One
directory would work for the flow; four make the tree legible. What a file
is decides its directory, not who reads it.

- **`RESULTS_DIR` holds the design, in any format:** netlists, `.odb`,
  `.def`, `.gds`, `.sdc`, routing guides, placement scripts, the checkpoint a
  failed step leaves, and memory inventories.
  *Breaks:* a flow or a user looking for the design finds part of it
  elsewhere.
- **`REPORTS_DIR` holds measurements of the design,** for tools or people to
  read. No step's result depends on it.
  *Breaks:* a flow that hands a step only the results has to carry reports
  along.
- **`LOG_DIR` holds logs,** and each step's metrics `.json` next to its log.
  *Breaks:* `genReport.py` and the metrics tools miss a run's record.
- **`OBJECTS_DIR` holds what is not the design:** files a step derives from
  the platform (`klayout.lyt`), a step's own intermediate files
  (`abc.constr`, `rtlmp/`, `surgery/`), caches, and stopgaps that wait for a
  cleaner implementation.
  *Breaks:* the design is mixed with scaffolding.
