# The generation pipeline

`smf create` runs eleven stages. The first seven decide what the app will be, and nothing of it is written yet. The rest render it, finish it in a temporary directory and move it into place, so a run that fails or is cancelled halfway leaves no half-made app in your project.

```mermaid
flowchart TD
    select["1 · Selection"] --> identity["2 · Identity"]
    identity --> resolve["3 · Resolution"]
    resolve --> collect["4 · Collection"]
    collect --> validate["5 · Validation"]
    validate --> preflight["6 · Preflight"]
    preflight -- "with --explain" --> report["Print the report and stop"]
    preflight --> choose["7 · Choices"]
    choose --> render["8 · Rendering in memory"]
    render --> postgen["9 · Post-generation in a temporary directory"]
    postgen --> move["10 · Moving into place"]
    move --> done["11 · Report and exit code"]
    validate -. "a module cannot work: leave it out" .-> resolve
    preflight -. "a module cannot work: leave it out" .-> resolve
```

## The stages

| # | Stage | What happens |
| --- | --- | --- |
| 1 | Selection | The app name, the directory, the organization and the modules come from the options or from questions. The pipeline also decides here what to do with an existing directory, before anything else happens. |
| 2 | Identity | The pipeline derives the package name and the platform identifiers of the app from the name and the organization, and gives every app the platforms Android and iOS. |
| 3 | Resolution | The pipeline adds the modules that the chosen ones depend on, and a provider for every role that the modules require and every role that every app has exactly one of, such as the app entry: the only provider, or the one you pick. Each module with variants gets its variant for the provider in the app. This repeats until nothing changes. |
| 4 | Collection | The pipeline collects the contributions of every module, of its variant and of the template of every role in the app, and records who contributed each. |
| 5 | Validation | The pipeline checks the contributions against the rules of the module model, the kinds, the checks of the roles and their providers, and the tags in the templates. It merges `pubspec.yaml`, orders the contributions of every socket, and renders them once to catch merge conflicts early. It also plans the [steps by hand](https://doc.saymyframe.com/extending/infrastructure-module.md#steps-by-hand) of the app, each once and in order, and asks the front end for the link to their guide. |
| 6 | Preflight | The pipeline checks the machine: the Flutter SDK for every app, then what the modules need, each module after the modules it depends on. It compares the versions of the SDK with the constraints of the app. In a terminal, SMF offers to set up what is missing. |
| 7 | Choices | Roles decide what the modules leave open. They ask what only you can decide, such as the start screen when several routes can start the app, or who may use the app without an account. Or they take it from an option, as the localization role takes the languages of the app from `--locales`. A role whose data depends on what another role chose makes its choice after that role. Then the contributions with conditions on what the roles chose apply only where those hold, such as the fallback start screen, which only an app that no route starts gets. |
| 8 | Rendering | The render hooks of the roles and their providers run. The pipeline fills the sockets, adds the imports, renders the templates in memory and takes the files that the hooks generate. |
| 9 | Post-generation | In a temporary directory: `flutter pub get`, code generation once if a module asks for it, the steps of the modules, `dart fix` for the imports, the full `dart fix`, and `dart format`. A run in a terminal asks before a step that the app is complete without, and before a step with a notice. |
| 10 | Moving | The app moves to its directory without the files where Flutter records the temporary path, and `flutter pub get` writes those again there. |
| 11 | Exit | `smf` reports what the app is without and which steps are left for later, then how many steps by hand the app has, with the link to their guide, and exits with its [exit code](https://doc.saymyframe.com/guides/smf-create.md#exit-codes). |

## Lenient and strict

In stages 3 to 6, a problem that one module causes, such as a feature whose tab does not fit into the tab bar, leaves that module out by default. The pipeline warns, removes the module and runs again from stage 3. A problem that no single module causes stops the run, and with `--strict` every problem stops it.

A check can also find something about the app that keeps every module in it, such as routes of which none can start the app. That is a warning about what the app is made of, with a code of the role that reports it, such as `router.no_start`. By default the pipeline prints it and goes on. With `--strict` it stops: on a warning of stage 5 before the machine is checked, and on a warning of stage 7 before the app is rendered. A warning about the machine never stops it. See [lenient and strict](https://doc.saymyframe.com/guides/smf-create.md#lenient-and-strict).

The pipeline leaves a module out only if an app can still be made without it. Every role that each app has exactly one of, such as the app entry, must keep a provider that is not left out. That provider must not depend on a module that is left out, and must not require a role whose providers are all left out. So the problem of the only provider of the app entry, such as a Flutter SDK that is too old for it, stops the run too, before anything is installed.

## `--explain`

`--explain` runs stages 1 to 5 and only the checks of stage 6, without installing anything. It prints the app, the modules and why each is there, the roles, the order of the contributions, the dependencies, the commands that would run after generation, the steps by hand, and the state of the machine. Then it stops. It makes no choices, so it takes every contribution with a condition on a choice as one that applies, and shows no warning of a choice.

## The temporary directory

The pipeline renders the app in memory and writes it to a temporary directory, where the commands of stage 9 run. The app moves to its directory only when they are done. If a command fails, the app stays in the temporary directory, and the error names it. When you cancel the run, the pipeline deletes the temporary directory.

Some files hold the path of the app on this machine, such as `ios/Flutter/Generated.xcconfig`, `.dart_tool/` and `build/`. The pipeline does not move them, and `flutter pub get` in the app's directory writes them again with the right path. For the same reason, a step of a module must not write the absolute path of its working directory into the app.

## The packages

```mermaid
flowchart LR
    cli["smf_flutter_cli<br/>the smf command"] --> pipeline["smf_pipeline<br/>the stages and the contract harness"]
    cli --> modules["module packages"]
    pipeline --> contracts["smf_contracts<br/>the module model"]
    modules --> contracts
    modules -. "in tests" .-> pipeline
```

- [`smf_contracts`](https://pub.dev/packages/smf_contracts) defines the module model: modules, roles, sockets, contributions and the built-in roles. `package:smf_contracts/core.dart` is the model without concrete roles.
- [`smf_pipeline`](https://pub.dev/packages/smf_pipeline) runs the stages. It imports only `core.dart`, so it knows no concrete role and no module, and everything specific comes from the modules and the roles themselves. It also has the contract harness, which the tests of modules use.
- [`smf_flutter_cli`](https://pub.dev/packages/smf_flutter_cli) is the `smf` command: the list of modules it offers, and the terminal, the files and the processes of the machine it runs on.
- Every module is a package of its own. It depends on `smf_contracts`, and on `smf_pipeline` only in its tests.
