There is a certain category of mix task that I think we can offer better tooling and automatic discovery for. This type of task is rarely parameterized, normally namespaced as a subtask, and generally serves as a setup/teardown step for a library at the filesystem level. I think of these as 'project lifecycle hooks'. Some examples include:
- new: create source files locally
- get: retrieve files across the network
- compile: create necessary code artifacts within the build folder
- build: create artifacts not necessary for the execution of the project or library's other tasks
- clean: remove artifacts that this library owns from the above 3 steps
- install: place files in a location such that they auto-enable some given functionality
- uninstall: remove files from such locations
Many libraries offer these sorts of task under their namespaces. The way these setup/teardown tasks work, I often find myself wishing that I could run all of a certain type at once as a sort of common 'phase' when interacting with a project. For example:
- before starting a parallelized CI run I want to `compile` everything I can into a cached build folder
- after passing a test run I want to produce all artifacts like project documentation, escripts, hex packages, test and documentation coverage reports, and the like
- after thinking I've fixed a warning emitted somewhere during compile time I want to force `clean` everything out so I can see a comprehensive list of remaining warnings
The `compile` task is a good example of elixir already sort of supporting this today. It's a simple meta-task that runs all `compile.xxx` tasks based on the `:compilers` configuration inside the project mix.exs.
I think we should run with this idea of a meta-task to the other lifecycle tasks. Optionally, I think we should allow for automatic discovery of them (such that configuration within the mix.exs is not required).
The proposal is simple: define a set of task names that should exhibit this behaviour, specify exactly what they should be responsible for as a guide to task implementers, and create meta-tasks for them that will enumerate the subtasks within their namespaces.
Libraries that want to opt-in to this framework can simply start naming their tasks under the appropriate meta-task namespace (ie. `mix build.dialyzer`) instead of their own (`mix dialyzer.build`).
Users that want to add existing tasks to a meta-workflow from libraries that have not opted in can just specify an alias in their mix.exs to place it within the namespace (ie `aliases: [{"clean.dialyzer", "dialyzer.clean"}]`).
Of the examples I listed above, `get` and `new` do not make much sense to run in bulk, as in order to discover other potential `get` tasks you need a compile phase after `deps.get`; and `new` is normally parameterized and rarely idempotent. `install` and `uninstall` also are generally specific one-off commands.
However, bulk compile, build, and clean tasks correspond elegantly to CI cache, on-success, and cache-cleaning phases respectively. If a common Elixir build script was defined in terms of these batch meta-tasks, it would be invaluable for projects that add or remove tooling, since their setup documentation and CI builds would keep working without modification.
Such meta-task tooling could be a starting point for identifying and unifying other common project lifecycle tasks across the ecosystem. I can envision a `report` meta-task separate from `build` that exists to produce human-readable reference files (like docs and test/documentation coverage reports) separate from machine-readable artifacts like PLTs, escripts, and hex packages; or a `push` meta-task that publishes hex packages, updates 3rd party code coverage sites, or triggers webhooks.