Multi-File Configurations and Merging

Real projects split configuration across files: the platform team ships defaults, the project team adds its messages, a transformer tool contributes derived values. The definition decides what is allowed via configurationFileMode.

This chapter explains the four modes, entity merging and priorities.

The Four File Modes

Mode Multiple files Entities merge Conflict handling

SingleFile

no

-

second file for the definition is an error

MultipleFiles

yes

no

files are validated independently

MultipleFilesEntitiesMergeable

yes

yes

same value twice is fine, different values are an error

MultipleFilesEntitiesMergeableWithPriority

yes

yes

higher $fileType priority wins, equal priority conflicts error

Merging happens per CfgSpace, files in different CfgSpaces never merge.

Mode selection:

  • One owner, one file: SingleFile Simplest to reason about.

  • Several files, but each entity fully owned by one file: MultipleFiles

  • Entities assembled from several contributions (platform plus project): MultipleFilesEntitiesMergeable

  • Layered overrides (defaults, user configuration, generated values, mixins): MultipleFilesEntitiesMergeableWithPriority

In the MultipleFiles mode the multiplicity of a top level entity counts across the files of a CfgSpace, so a 1..1 entity sits in exactly one of them. Everything below the top level is checked per file, since each entity lives in one file.

The mergeable modes check presence on the merged result at every level. The user manual’s Working with Multiple Files shows both levels from the configuration side.

The configurationFileMode is not only a possible file count. The two mergeable modes also switch on the configuration features built on merging: entity fragments, mixins, and, in the priority mode, $fileType. None of them has a switch in the definition, so choosing a mergeable mode hands your users all of them at once. Feature Interactions has the full feature matrix.

Entity Merging

In the mergeable modes, entities with the same identity (name or id) and the same type are merged across the files of a CfgSpace.

The CanStack example splits one CAN channel between two teams:

definitionName: "CanStack",
definitionVersion: "1.0",
configurationFileMode: "MultipleFilesEntitiesMergeable",

The platform configuration file owns the channel and its base timing:

{
    $definition: "CanStack@1.0",
    channels: [
        {
            name: "Body",
            baudRate: 500,
            messages: [
                {
                    name: "EcuStatus",
                    canId: "0x1A0",
                    cycleTime: 100
                }
            ]
        }
    ]
}

The project configuration file adds its own message to the same channel entity, matched by name: "Body":

{
    $definition: "CanStack@1.0",
    channels: [
        {
            name: "Body",
            // baudRate is contributed by the platform file.
            // Setting a different value here would be a merge conflict.
            busOffRecovery: false,
            messages: [
                {
                    name: "DoorState",
                    canId: "0x2B4",
                    dlc: 2,
                    cycleTime: 50
                }
            ]
        }
    ]
}

The merged model contains one Body channel with baudRate, busOffRecovery, and both messages.

cfu cfg export-merged writes this merged view as a single file:

cfu cfg export-merged -o merged.json .

Or you can inspect it with:

cfu cfg inspect

Entity Fragments

A configuration file may hold the content of one nested entity instead of the structure down from the definition root. Such a fragment declares its anchor with a top-level $path / $type pair, and both keys are rejected outside the mergeable modes:

{
    $definition: "CanStack@1.0",
    $path: "/CanStack/Body",
    $type: "CanStack::Channel",
    messages: [
        {
            name: "LightRequest",
            canId: "0x3C8",
            dlc: 1,
            cycleTime: 20
        }
    ]
}

$path is the absolute entity path, same as an absolute reference. $type is a type path of the entity to configure. It could be its own type or one it inherits from.

A fragment never creates its own anchor entity, so another file must declare the place of the entity.

Mixins

A mergeable mode also gives configuration authors mixins: a block of content written once in the top level $mixins list and merged into every element its selector matches. A named mixin ($mixin) reaches the elements that ask for it via $applyMixins, leave the name out and it reaches every element of its $type.

Nothing in the definition declares or restricts mixins, so a definition in a mergeable mode has to tolerate that any of its entity and struct types may be configured class-wide rather than element by element. It is also how the definition ships its own default or preconfig values, see Default and Preconfiguration Files. The configuration side is documented in Reusing Content with Mixins.

Merge Conflicts

Without priorities, all files are equal. If two files set the same parameter of the same entity to different values, that is a merge conflict error. Setting it to the same value in both files is accepted but useless.

Multiplicity Across Merged Files

In mergeable modes, min/max multiplicity counts are checked on the merged result, not per file. The platform file alone satisfies baudRate’s `1..1 even though the project file never mentions it. The corresponding diagnostics say so explicitly, for example "too few elements across merged files".

Priority Merging

MultipleFilesEntitiesMergeableWithPriority adds a file level priority. Each configuration file declares its $fileType, when two files disagree on a value, the higher priority wins silently, and only equal priorities conflict.

The Nm definition declares the mode:

definitionName: "Nm",
definitionVersion: "1.0",
configurationFileMode: "MultipleFilesEntitiesMergeableWithPriority",

The platform configuration file ships the timing at normal priority:

{
    $definition: "Nm@1.0",
    $fileType: "UserConfig",
    nmNodes: [
        {
            name: "Gateway",
            timeoutTime: 2000,
            repeatMessageTime: 1600
        },
        {
            name: "Diagnostic",
            timeoutTime: 5000
        }
    ]
}

The project overrides one value with a higher priority file:

{
    $definition: "Nm@1.0",
    $fileType: "UserConfigPriorityHigh",
    nmNodes: [
        {
            name: "Gateway",
            timeoutTime: 1500
        }
    ]
}

The merged Gateway node has timeoutTime: 1500 (project wins) and repeatMessageTime: 1600 (only the platform file sets it).

File Types and Their Priority

From lowest to highest priority:

  1. DefaultValues

  2. DefaultValuesProject

  3. GeneratedBelowUserConfig

  4. UserConfigPriorityLow

  5. UserConfig (the default when $fileType is omitted)

  6. UserConfigPriorityHigh

  7. GeneratedAboveUserConfig

  8. PreConfigurationProject

  9. PreConfiguration

  10. UserOverride

  11. PreConfigurationNoOverride

The names encode the intended layering: generated content can sit below or above user configuration, preconfiguration outranks users, and PreConfigurationNoOverride is absolute.

The DefaultValues* types must live in .default.def.json5 files and the PreConfiguration* types in .preconfig.def.json5 files, so their role is visible in the file name. The user facing types use the normal .cfg.json5 suffix.

Default and Preconfiguration Files

.default.def.json(5) and .preconfig.def.json(5) files are how a definition ships configuration values of its own. They are part of the definition and not changeable by the configuration author. Despite the .def suffix they are written in configuration syntax, and their $fileType decides the layer, a DefaultValues* type below the user configuration and a PreConfiguration* type above it.

Most of the time the content is an unnamed mixin, which applies values to every element of a type instead of to one the file would have to name:

{
    $definition: "SensorLoop@1.0",
    $fileType: "DefaultValues",
    $mixins: [
        {
            $type: "SensorLoop::Sensor",
            sampleRate: 100
        }
    ]
}

This also ties the feature to the file mode: $mixins needs a mergeable mode, so a SingleFile definition can’t ship default or preconfig data.

A definition default on a parameter and a default file are different mechanisms, and both have their place. The parameter default applies wherever the value is absent, shows up in hover and costs nothing. A default file contributes a real value into the merge, which the origin export attributes to that file and a project can override with an ordinary configuration file. Use the parameter default for a per-value fallback and a default file for a coherent block of project-level values.

Default and preconfig files never activate the definition in a workspace: per CfgSpace, their content participates in merging only when at least one .cfg.json(5) declares the definition.