Skip to main content

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​

#StageWhat happens
1SelectionThe 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.
2IdentityThe pipeline derives the package name and the platform identifiers of the app from the name and the organization.
3ResolutionThe 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.
4CollectionThe 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.
5ValidationThe 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.
6PreflightThe 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.
7ChoicesRoles ask what only you can decide, such as the start screen when several routes can start the app.
8RenderingThe render hooks of the roles and their providers run. The pipeline fills the sockets, adds the imports and renders the templates in memory.
9Post-generationIn 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.
10MovingThe app moves to its directory without the files where Flutter records the temporary path, and flutter pub get writes those again there.
11Exitsmf 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_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 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 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.