UVL feature model
The UVL (Universal Variability Language) file is the formal model of an SPL’s variability. It defines which features exist, whether they are mandatory or optional, and what constraints govern their combination. The UVL is not stored inside a product. It lives in its own repository while it is being authored, and is resolved from a DOI everywhere else.
Table of contents
- What it governs
- Location
- Format
- Who reads the UVL
- Lifecycle of the UVL
- UVL vs pyproject.toml
- See also
What it governs
pyproject.toml declares which features a product uses and at which version. The UVL declares which combinations are valid (structure, optionality, and cross-tree constraints).
Together they answer two different questions.
| Question | Answered by |
|---|---|
| What features does this product include? | pyproject.toml |
| Is this combination of features valid? | UVL |
The UVL does not contain version numbers. Versions are managed exclusively in pyproject.toml via feature:attach.
Location
There are three homes for a model, tried in this order. There is no catalog repository any more, and nothing in the CLI reads one.
<workspace>/
├── splent_spl_<core>/ <- 1. the working copy, yours
│ ├── metadata.toml <- name, description, DOI
│ └── <spl_name>.uvl <- the feature model, tracked
├── .splent_cache/spls/ <- 2. the cache, derived data
│ └── <spl_name>@<version or DOI>/
│ ├── metadata.toml
│ └── <spl_name>.uvl
├── <product_a>/
│ └── pyproject.toml <- spl + DOI
└── <product_b>/
└── pyproject.toml <- same SPL, its own pin
Third is UVLHub itself, by DOI. Whatever comes down is written into the cache, so the next resolution and every one after it works offline. The working copy is never overwritten by a fetch.
Products reference an SPL by name and by DOI in pyproject.toml,
because a name on its own cannot be turned into a download.
[tool.splent]
spl = "sample_splent_app"
[tool.splent.spl_model]
mirror = "uvlhub.io"
doi = "10.5281/zenodo.19219696"
concept_doi = "10.5281/zenodo.19219695"
version = "v2"
doi pins the exact bytes, the way org/repo@version pins a feature.
concept_doi identifies the line across versions, which is what
spl:outdated uses to say a newer model exists. The
block is written by spl:pin, and by
spl:publish into the working copy’s
metadata.toml.
With the DOI travelling inside the product, deriving needs the product’s own repository and UVLHub. It needs no catalog, no marketplace, and no other repository in the workspace.
Multiple products can derive from the same SPL, each pinning its own version. Constraints are properties of the feature model (the SPL), not of individual products.
Legacy support. Products that still use [tool.splent.uvl] in their pyproject.toml (with the UVL file in product/uvl/) continue to work. All UVL commands resolve the named SPL first, then fall back to the legacy per-product location.
Coming from a workspace with
splent_catalog/? Runspl:migrate-catalogonce. It copies the models into the cache and writes each product’s DOI, and it never deletes the catalog directory. Until it has run, no product carries a pin and nothing can resolve a model, whichspl:list,spl:infoandproduct:validateall say out loud.
Format
UVL is a domain-specific language for variability modelling. A typical SPLENT product model looks like this.
features
sample_splent_app
mandatory
auth {org 'splent-io', package 'splent_feature_auth'}
mandatory
public {org 'splent-io', package 'splent_feature_public'}
optional
redis {org 'splent-io', package 'splent_feature_redis'}
optional
mail {org 'splent-io', package 'splent_feature_mail'}
optional
confirmemail {org 'splent-io', package 'splent_feature_confirmemail'}
optional
profile {org 'splent-io', package 'splent_feature_profile'}
optional
reset {org 'splent-io', package 'splent_feature_reset'}
constraints
profile => auth
confirmemail => mail
reset => mail
Features block
Each feature is declared as mandatory or optional under the product root. The {org, package} attributes link the UVL name to its SPLENT package identity.
Constraints block
Cross-tree constraints express dependencies between features.
profile => authmeans selectingprofilerequiresauthconfirmemail => mailmeans selectingconfirmemailrequiresmail
These constraints are enforced by product:validate before derivation.
Who reads the UVL
| Consumer | What it reads | Why |
|---|---|---|
product:validate |
Full model + constraints | Validates that the product’s feature selection satisfies all constraints (uses Flamapy) |
feature:order |
Feature tree + constraints | Resolves the topological load order (dependencies load first) |
db:seed |
Feature tree + constraints | Runs seeders in topological order |
splent_framework (FeatureLoadOrderResolver) |
Feature tree + constraints | Determines runtime feature registration order |
product:validate |
Indirectly (via load order) | Checks inter-feature contract conflicts in dependency order |
Lifecycle of the UVL
Fetching
If the SPL’s model is already published on UVLHub, fetch it.
splent spl:fetch <spl_name>
This downloads the canonical model into .splent_cache/spls/ using the DOI recorded by whatever knows it, usually the product’s own pyproject.toml. It needs no account and no key. Most of the time you never run it by hand, because the commands that need a model resolve and download it themselves.
Creating from scratch
For new SPLs, use spl:create <name> (available in detached mode) to scaffold splent_spl_<core>/. Edit the .uvl file, then publish it.
splent login
splent spl:publish <spl_name>
splent spl:pin <spl_name>
spl:publish goes through the marketplace, which holds the only UVLHub key, and writes the DOI it reports back into metadata.toml. spl:pin records it in the product. A model with no DOI yet works fine locally, it simply cannot be resolved by anyone who does not have the working copy.
Fixing constraints
spl:add-constraints writes to the model, not to the product. Constraints are properties of the SPL’s feature model. They are shared across all products that derive from it, and they only reach those products once the model is published and each one is re-pinned.
Validating
Run product:validate every time you add or remove a feature.
splent product:validate
OK: configuration is satisfiable.
If the check fails, the product cannot be derived.
UVL vs pyproject.toml
A common question is why there are two files.
- pyproject.toml is the operational truth. It tells the system exactly what to install and run.
- The UVL is the formal truth. It proves that the chosen configuration is valid according to the product line model.
Both are required. A product with a valid pyproject.toml but an invalid UVL configuration will be rejected at derivation time. A product with a valid UVL but missing feature entries in pyproject.toml will fail at sync time.
See also
spl:fetch. Download a model from UVLHub by DOIspl:publish. Publish a model through the marketplacespl:pin. Record the DOI in a productspl:outdated. Report products on an older modelproduct:validate. Validate the current feature selectionproduct:auto-require. Synchronise the local UVL with the published versionfeature:order. Display the resolved load order- UVLHub. Public registry for UVL models