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.
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. |
| 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. |
| 6 | Preflight | The pipeline checks the machine: the Flutter SDK for every app, then what the modules need. 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 ask what only you can decide, such as the start screen when several routes can start the app. |
| 8 | Rendering | The render hooks of the roles and their providers run. The pipeline fills the sockets, adds the imports and renders the templates in memory. |
| 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. |
| 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, and exits with its exit code. |
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.
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, and the state of the machine. Then it stops.
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
smf_contractsdefines the module model: modules, roles, sockets, contributions and the built-in roles.package:smf_contracts/core.dartis the model without concrete roles.smf_pipelineruns the stages. It imports onlycore.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_cliis thesmfcommand: 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 onsmf_pipelineonly in its tests.