- π¨π¦Canada mandclu
I'd be interested in helping with this, but I'm not very familiar with how Features works under the hood. Could a maintainer point me in the right direction on where to start?
- π¨π¦Canada mandclu
Poking around in the code a little, it seems like the logical place to start would be in the addPackageFiles() method of the FeaturesManager class. I can see some potential approaches to making these changes:
1. Overwrite the current method and assume that recipes are the way forward (probably only acceptable if this would be the basis of a new major version)
2. Put code into a submodule that would decorate the current FeaturesManager class
3. Add some kind of global configuration option to export recipes instead of standard modules
4. Add a per-feature option to export a recipe instead of a standard moduleDo the maintainers (or anyone else) have a preference on the best path forward?
- π¨π¦Canada nedjo
@mandclu If you happen to still be interested and available to start on this, I have some availability in the next few weeks to provide review and comments. While I'm not a current maintainer, I am a past one and have a good familiarity with the code.
I'll start by commenting on some of what may be needed.
At a high level, we have the advantage that we already model features fairly generically as a set of "packages", see the
Package
object and its properties. Currently, to generate feature modules, we parse these packages into modules. Our task here is to enable a second target format: recipes.Packages are associated with a Features bundle, see the relevant documentation β and the associated
FeaturesBundle
config entity type.Each package can have a set of configuration items assigned to
Package::config
.In the current implementation, as you've noted, we parse the set of feature packages into extensions in
FeaturesManager:: addPackageFiles()
. We model most of the packages as modules, but optionally we special-case one package to be parsed as an install profile.Regarding your question about approaches:
1. Overwrite the current method and assume that recipes are the way forward (probably only acceptable if this would be the basis of a new major version)
2. Put code into a submodule that would decorate the current FeaturesManager class
3. Add some kind of global configuration option to export recipes instead of standard modules
4. Add a per-feature option to export a recipe instead of a standard moduleMy personal take is: #3 is the way to go. Rationale:
- Recipes appear to be here to stay. If feasible, our best approach is to introduce recipe support directly in Features rather than through a submodule.
- The changes would be significant enough as to require a new minor release, but should be feasible within the existing major version. At first glance, it look like a lot of the work - possibly most - can be done within existing classes without deprecations.
:: addPackageFiles()
, for example, is a protected method that we're free to modify as needed.
So, yes, concretely, we'd start by adding a new property to the
FeaturesBundle
object--perhaps::targetRenderFormat
or similar.. We'd add accompanying getter and setter methods toFeaturesBundleInterface
. as is done in core per the relevant documentation β . Values would bemodule
andrecipe
. Perhaps we declare these in an Enum inFeaturesBundleInterface
. We'd need:- At the UI level, a new radio element on the bundle configuration form to manage the new setting.
- An update to set the value to
module
in existing bundle configuration.
From there, we can get a general sense of what's needed by looking look at our two different target formats - modules and recipes - and highlighting (a) how they differ and (b) how the parsing to modules is currently handled in Features (so we know where changes are needed). I'll give that some thought and follow up when I can with some further notes.
- π¨π¦Canada nedjo
Here are those further notes--certainly not an exhaustive list of what's needed, but a start ;)
Scope
We'll want to limit scope in an initial spec. That might basically mean: we replicate for recipes what's already supported for modules but leave for later any recipe-specific functionality. For example, recipes can provide config actions, while modules cannot, so for purposes of this issue feature-style recipes can't provide config actions.
Parsing config into packages
We parse site config and assign it to a set of packages. Most of that parsing and assigning are not specific to the module use case, but a few pieces need to be updated to support recipes.
The
packages
assignment plugin, current description "Detect and add existing package modules", needs to be updated to conditionally handle recipes.Methods invoked there that need to be updated and/or renamed and/or deprecated and replaced to handle recipes as well as modules include:
FeaturesManagerInterface::getFeaturesModules()
FeaturesManager::initPackageFromExtension()
(Is there a term in use that we can adopt to cover both modules and recipes?)
The
optional
assignment plugin needs to be disabled for recipes.Render packages as recipes rather than modules
Currently we render packages as modules. We need to optionally render instead as recipes.
For our purposes, a key advantage of the current implementation is that the renderer plugins - responsible for generating features - basically just render a bunch of files based on data previously assigned to a nested array. So, to queue up generation of recipes instead of modules, we just need to change what's assigned to that array. The key method involved here is
FeaturesManager::addPackageFiles()
, which in turn callsFeaturesManager::addInfoFile()
andPackage::appendFile()
.Of course, the method name
::addInfoFile()
is no longer exactly right either, since recipes are defined through arecipe.yml
file rather than amodulename.info
one. Maybe we rename this protected method to::addDefinitionFile()
or similar? In any case, it will need to conditionally generate a YML file data structure that's in the expected recipe format rather than the module.info
format. So, for example, while the.info
file has an array ofdependencies
, recipes need to separate those out into a set of requiredrecipes
and an additionalinstall
array. We also need to set the recipeconfig.import
array to ensure expected module and theme config is installed.FeaturesManager::mergeInfoArray()
will similarly need updating.Generation
For the most part the generation plugins are agnostic as to render target, but a few changes will be needed. For example,
FeaturesGenerationMethodBase::prepare()
callsFeaturesManager::getExportInfo()
, which currently has the hard-coded string 'modules' for the$path
variable. This will need to be conditionally set to 'recipes'.Validation
We don't validate the
.info
file we generate for modules. It would be a valuable addition, though, to validate our.recipe
files. There's relevant code in the core recipe API, in which recipes are validated before being applied. We could add additional constraints, such as that the generated recipe:- doesn't have any config actions (not yet supported by Features)
- doesn't provide any optional config (not supported by the core recipes API)
- has config set to
strict: false
Tracking applied recipes
Currently in the UI we distinguish between installed feature modules and those that have not been installed. The closest equivalent with recipes is whether they've been applied. Core doesn't track this info, but there are (at least!) two contrib modules that do: Project Browser β and Recipe Tracker β . We might introduce a soft dependency through a method that looks for each of these modules and uses the first one found or gracefully defaults.
- πΊπΈUnited States thejimbirch Cape Cod, Massachusetts
We have a core Sue for this also
https://www.drupal.org/project/drupal/issues/3446561 β¨ Automatic recipe generation from config diff Active
And some folks in contrib have created a sandbox module