UVL feature model
Table of contents
- Introduction
- Writing a model
- Pinning the model in your product
- Where the model is found
- Working with a model
- Who reads the model
- See also
Introduction
The UVL (Universal Variability Language) file is the formal model of an SPL. It says which features exist, which are mandatory or optional, and which combinations are valid. Your product does not store it. It pins it by DOI.
features
my_spl
mandatory
notes {org 'splent-io', package 'splent_feature_notes'}
optional
reports {org 'splent-io', package 'splent_feature_reports'}
constraints
reports => notes
pyproject.toml says which features your product runs, at which versions. The UVL says whether that combination is valid. The model has no version numbers.
Writing a model
A fuller model of the invented line my_spl looks like this. The sample line the tutorials use, sample_splent_spl, has the same shape.
features
my_spl
mandatory
notes {org 'splent-io', package 'splent_feature_notes'}
optional
notes_tags {org 'splent-io', package 'splent_feature_notes_tags'}
optional
reports {org 'splent-io', package 'splent_feature_reports'}
optional
cache {org 'splent-io', package 'splent_feature_cache'}
mandatory
session_type
alternative
sessions_in_cache {org 'splent-io', package 'splent_feature_sessions_in_cache'}
sessions_on_disk {org 'splent-io', package 'splent_feature_sessions_on_disk'}
constraints
notes_tags => notes
reports => notes
sessions_in_cache => cache
Each feature sits in a mandatory, optional or alternative group, and {org, package} links it to its SPLENT package. session_type has no package. Its alternative group means a product picks exactly one session backend.
The constraints are dependencies across the tree. reports => notes means selecting reports requires notes.
A node with a param attribute and no package stands for a value of a feature’s parameter, such as sells_subscriptions {param 'SHOP_SELLS', value 'subscriptions'}. See Letting a value select features.
Pinning the model in your product
[tool.splent]
spl = "sample_splent_spl"
[tool.splent.spl_model]
mirror = "uvlhub.io"
doi = "10.5281/zenodo.23076870"
concept_doi = "10.5281/zenodo.18873371"
version = "v4"
file = "sample_splent_spl.uvl"
doi pins the exact bytes, the way @v1.5.8 pins a feature. concept_doi identifies the line across versions, so spl:outdated can find a newer model. file is the filename on UVLHub, <spl>.uvl when left out.
spl:pin writes this block. With it, deriving needs only your product’s repository and UVLHub. Several products can follow one SPL, each pinning its own version.
Where the model is found
<workspace>/
├── splent_spl_<core>/ <- 1. the working copy, yours
│ ├── metadata.toml <- name, description, DOI
│ └── <spl_name>.uvl
├── .splent_cache/spls/ <- 2. the cache
│ └── <spl_name>@<version>/
└── <product>/pyproject.toml <- spl + DOI
The working copy is splent_spl_<name without _spl>/ or splent_spl_<name>/, and a fetch never overwrites it. The cache is keyed on the pin’s version, or on the DOI when there is none. A cached copy recording another DOI is skipped, so your product never derives from another product’s version. UVLHub is asked by DOI, and what comes down is cached.
The running app never downloads. It looks in the working copy, the cache, and <product>/uvl/<file> when you set [tool.splent.uvl].file. With no model on disk it still starts, and loads features in pyproject.toml order.
An SPL name must be a single directory name, without a path separator,
.., a leading.or a control character. Nothing is ever written outside.splent_cache/spls/.
Working with a model
Fetching it
splent spl:fetch <spl_name>
spl:fetch downloads a published model into the cache, with no account, and runs with no product selected. Commands that need a model fetch it themselves, so you rarely run it.
Creating and publishing one
spl:create scaffolds splent_spl_<core>/ with no product selected. Edit the .uvl, then publish it and pin it.
splent login
splent spl:publish <spl_name>
splent product:select <product>
splent spl:pin <spl_name>
spl:publish goes through the marketplace, which holds the only UVLHub key, and writes the DOI it gets back into metadata.toml. spl:pin records it in the selected product. A model without a DOI works locally, but nobody without your working copy can resolve it.
Changing constraints
spl:add-constraints writes to the model, with no product selected. A new constraint reaches your products once the model is published and each product is pinned again.
Validating a product
splent product:validate
✅ Product is valid.
Run product:validate after adding or removing a feature. It checks the selection against every constraint with Flamapy. A product that fails cannot be derived.
Who reads the model
| Reader | Why |
|---|---|
product:validate |
Checks the selection against the constraints |
feature:order |
Shows the load order, dependencies first |
db:seed |
Runs seeders in that order |
The framework (FeatureLoadOrderResolver) |
Orders feature registration at runtime |
See also
spl:fetch,spl:publish,spl:pinandspl:outdatedproduct:auto-require, add the features the constraints requirefeature:order, the resolved load order- UVLHub, the public registry of UVL models