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

  1. What it governs
  2. Location
  3. Format
    1. Features block
    2. Constraints block
  4. Who reads the UVL
  5. Lifecycle of the UVL
    1. Fetching
    2. Creating from scratch
    3. Fixing constraints
    4. Validating
  6. UVL vs pyproject.toml
  7. 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/? Run spl:migrate-catalog once. 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, which spl:list, spl:info and product:validate all 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 => auth means selecting profile requires auth
  • confirmemail => mail means selecting confirmemail requires mail

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


Back to top

splent. Distributed by an LGPL license v3. Contact us: drorganvidez@us.es