Loads AUTOSAR models (ARXML) the way AUTOSAR tools see them: many files, one model. Elements split across files are merged by the AUTOSAR merge model, edits go back to the files they came from, and every element and value points back to its file and line.
Built on flatxml, which parses, shares names across files, and gives a merged view over many documents by rules its caller supplies. arxmodel supplies the AUTOSAR rules.
No AUTOSAR schema (XSD), no ARXML file and no table generated from either is committed: not as code, not as test data, not in a comment. The merge rules (split keys, which aggregations may be split, which are ordered, how many children each allows) are read at load time from the AUTOSAR schema the user has, the one the files declare. Tests use synthetic schemas and documents written here. Runs against real schemas and files happen locally, and record numbers only.
AUTOSAR is a registered trademark of the AUTOSAR development partnership. arxmodel is an independent project, not affiliated with or endorsed by AUTOSAR, and makes no claim of conformance to any AUTOSAR specification. Specification items are cited by their identifiers only, so a reader can look them up in the specifications AUTOSAR publishes.
See docs/plan.md.
A merge is made of three things, kept apart:
- Rules derived from the schema (split keys, splittable, ordered, most, XML shape): read from
the AUTOSAR XSD the files declare, on the user's machine.
arxmodel rules --emit FILEwrites them as flatxml rule text (*.rules.txt, ignored by git), andcheck --rules FILEmerges by that file instead of compiling the schema. Such a file is derived from the AUTOSAR schema, so it is never committed. - Policy (policy.toml, committed): every choice that is arxmodel's and not the
schema's, in TOML. The default follows the AUTOSAR specification (Generic Structure Template
R24-11); automat.policy.toml is a second built-in profile (
--policy automat), and--policy FILEreads one's own. Each setting has a comment saying why and the specification item it concerns:[merge]absent keys, the case of keys, attributes that may differ, items and primitive values named by their role alone, non-splittable repetitions, items of several types, ECUC values by definition, repeated-choice content, structured comments;[schema]which release governs, splittability;[references]the package element;[severity]each finding's severity. Every setting must be given, with a value of its type this build supports; an unknown table, key or value is refused by name. policy.schema.json is its JSON Schema (draft-07, generated from the code's one list of settings byarxmodel policy --schema; a test holds the committed file to it), which editors read through the files' first line,#:schema ./policy.schema.json(Taplo, Even Better TOML).arxmodel policyprints every setting with the values it takes, its value and its specification item;arxmodel policy --diff automathow two policies differ. Command-line flags (--case-sensitive,--check-attr,--ignore-attr) override it. - Mechanics (the code): reading annotations, resolving keys, scoping rules by parent path, merging on flatxml's view. It holds no choice of its own.
arxmodel load <file|dir>... # every .arxml below the paths, one set
arxmodel rules --schema-dir DIR <file|dir>... # and the merge rules of the schemas they declare
arxmodel rules --schema-dir DIR --problems --dump rules.tsv <file|dir>...
arxmodel rules --schema-dir DIR --emit autosar.rules.txt <file|dir>... # the merge rules, as a file
arxmodel check --schema-dir DIR [--all] [--ignore-attr NAME]... <file|dir>... # merge, and what the rules forbid
arxmodel check --rules autosar.rules.txt <file|dir>... # the same, by the rules file
arxmodel check --schema-dir DIR --format json <file|dir>... > check.json # or junit: for machines
arxmodel get --schema-dir DIR /Pkg/Name <file|dir>... # the merged element at an AUTOSAR path
arxmodel list --schema-dir DIR ECUC-MODULE-DEF <file|dir>... # the paths of every element of a name
arxmodel values --schema-dir DIR '/Pkg/**/Name' <file|dir>... # every value there, `path = "text"`
arxmodel serve --schema-dir DIR --socket m.sock <file|dir>... # read once; then, in milliseconds:
arxmodel values --socket m.sock '/Pkg/**/Name' # ... (get and list likewise)
arxmodel merge --schema-dir DIR [--force] -o model.arxml <file|dir>... # the merged model, as one ARXML
arxmodel diff A B # what changed, by AUTOSAR path (no schema needed)
arxmodel merge3 BASE OURS THEIRS -o OUT # a three-way merge (no schema needed)
arxmodel git-setup [REPO] # git's merge and diff drivers for *.arxmlA rules file starts with a header giving its format's version (# format:; a file of another
format, or one made before the line existed, is refused: make it again), naming the schema it came from (name, namespace, path and
SHA-256), the arxmodel and flatxml versions, the policy settings the rules were made by
(absent_keys, item_itself, governing, splittable, repeated_choice_as_mixed), every pair
in conflict with the rules that disagree, then the merge settings (!fold, !ignore with the
policy's attribute names, and # ignore-declarations), then one rule a line in the order the merge
uses them. Nothing in it depends on the documents it was made from: the same schema and policy give
the same file from any of them, and two releases' files diff line by line. When merging from a
file, its settings are the merge's (the flags still override; the documents' own declaration
attributes are added then), and its conflicts are placed against the documents being checked, so
the output and the exit code are those of --schema-dir on them. check --rules refuses a file
for another namespace or another schema family than the documents declare, and warns when they
declare a newer release than the file was made from, and for every policy setting that differs
from the file's (the file's applies). The rule lines are written by flatxml's own format_rules, so flatxml merge --rules reads the
file and merges as arxmodel does; it then also compares xsi:schemaLocation between fragments,
since the documents' declaration attributes are added only by arxmodel (ignore_declarations).
load reads every file whose name ends in .arxml (any case) below the paths, in parallel, into
one flatxml document set with namespaces resolved, and lists the files that fail to parse and
the directories that cannot be read. A file reached by two paths (a directory and a file in it,
two spellings of one directory) is read once.
rules also reads the schema each file declares (xsi:schemaLocation, the location paired with
the root element's namespace), finds it in the schema directory (DIR/<name>, then
DIR/<stem>/<name>, then anywhere below DIR; a name differing only in case is accepted and
said), compiles each distinct schema once, in parallel, and prints per schema: the aggregations,
how many have a split key, are splittable or ordered, how many key paths resolved, and an account
of every annotation. --problems lists each annotation that could not be applied; --dump writes
the rules as tab-separated lines (--dump - to standard output, the summary then going to
standard error). Both commands exit 1 when a file could not be read, or a schema
was not declared, not found or did not compile; what could be done is still printed.
Every command that reads models takes the same paths, and makes one predictable set of them:
- a file is itself, even with
*or?in its name (a glob only where no such file is), and@xis a file named so whenxis not one; a path that does not exist is an error; - a directory is every
.arxmlfile below it, recursively (DIR/*.arxmlfor its own files); - a glob (
'model/modules/*/*.arxml':*and?within one name,**any depth; quoted, so arxmodel expands it, not the shell) is the.arxmlfiles and the directories it matches; a glob whose last name has no wildcard ('*/model.xml') names its files, whatever their extension; a directory a glob walks that cannot be read is a failure, as in a directory; @FILEis the paths FILE lists: one a line, spaces and all (no shell splitting or quoting), blank lines and#comments ignored, relative ones read from FILE's directory, and a listed@FILEread the same way;--exclude GLOB(repeatable) leaves out the files it matches: a relative glob against the path as found, an absolute one against the absolute path;- a file reached twice (overlapping paths, two spellings) is one file;
- the set is sorted by path, as found: the load order, and so which file's value a merge keeps where files differ, depends on neither the order of the arguments nor the file system.
The extension .arxml is taken in any case (.ARXML), by directories and globs alike; every
other part of a path matches as the file system spells it. The set also knows where its files came
from (a directory given, a glob's fixed part, a file's directory, a list's directory): a filter's
relative file glob is read from there, and merge refuses an output that is one of the files or
lies in one of those directories. --refs-from takes the same rules and --excludes. merge3
works on a set of files (written under -o DIR, each where it lies below the base's common
directory) where a version is a directory, a glob or a list, or holds more than one document.
A glob that matches nothing, or a directory with no .arxml file, is a warning. Each command
prints how many files it loaded and the first and last three (--verbose: every one), so a CI log
shows what was checked.
In CI the build already knows its model's files; a makefile target writes them as a list, and the check reads it:
gen/arxmodel_args.txt: Makefile
mkdir -p gen
printf '%s\n' $(abspath $(ARXML_DIRS) $(ARXML_FILES)) > $@
check-arxml: gen/arxmodel_args.txt
arxmodel check --exclude '**/utest/**' @gen/arxmodel_args.txtThe list's relative paths would be read from gen/, so the target writes absolute ones
($(abspath ...)); a glob in the list (.../modules/*/*.arxml) is expanded by arxmodel.
An AUTOSAR schema describes each meta-class as an xsd:group and each role of the class as a
local element declaration in it, annotated with xsd:appinfo blocks: source="tags" holds
key="value" pairs separated by ;, source="stereotypes" a comma-separated list. flatxml keeps
every appinfo with its owner; arxmodel reads them into one Rule per role (arxmodel/rules.v):
| annotation | on | rule |
|---|---|---|
mmt.qualifiedName="Class.role" |
element declaration | a role: class, role; its XML element is the declaration's name |
mmt.qualifiedName="Class" (no role) |
element declaration | the reference inside a conditional wrapper, named by the class it refers to: the role is the class name with a lower-case first letter |
atp.Splitkey="a, b.c" |
role | the split key, one path per part, resolved to XML (below) |
stereotype atpSplitable |
role | splittable: items with one key from several files are one item |
pureMM.isOrdered="true" |
role | ordered: the order of the items means something |
pureMM.maxOccurs, pureMM.minOccurs |
role | max (-1 is no most), min |
xml.roleWrapperElement |
role | compared with the shape the schema gives the role |
Shape. A role element whose type is anonymous and holds only elements that are not roles
themselves wraps its items (SHELVES holding SHELF elements): the rule's items are those
elements, one per item kind, each with its type. An element of no role whose anonymous type holds
exactly one role element also wraps it (the role's tags then sit on the inner element). A role
declared xml.roleWrapperElement="true", xml.roleElement="false", xml.typeElement="false"
wraps its items' content with no element per item (flat). Otherwise the role element is itself
the item.
Where a rule applies. Every complex type's content model is walked through its sequences,
choices and the groups it references; each role element found there is held by that type. So a
class's roles apply to every type that includes its group, inheritance included, and
rule_for(type, child element) finds the rule for an element's child. types_of gives the
types an XML element name is declared with.
Split keys. Each part of atp.Splitkey is a path of meta-model roles. A leading part equal
to the role's own name is dropped; the role's name alone is the item itself. Each further role is found by its role name among the
roles of the type reached so far, and becomes that role's XML element: shortName becomes
SHORT-NAME, variationPoint.shortLabel becomes VARIATION-POINT/SHORT-LABEL. A role realised
as an XML attribute becomes @NAME. A role holding several values may end a path (its values in
order are the value, written *); one holding several items may not be passed through. A role
spelled with another case is matched and reported. Keys resolve per item kind, since several
kinds can sit in one aggregation. What cannot be resolved is reported with the part and the type
it was looked for in.
The item itself as key. A value (text, a reference) is its own key. An item with children is
identified by its single-valued element children (attributes do not count), in the order its type
writes them: each child role holding one value as text: not a wrapper, at most one by its rule
(which mixed content may raise), not splittable, its type simple or mixed. A role behind an
element of no role of its own is reached through that element. A child whose class has the
stereotype instanceRef (a reference to an instance, by context and target) counts by its own
such children, below it; other object children do not count. When the whole key comes out empty,
a single-valued role's fragments are the one item (a note, naming the type); for a multi-valued
role every item gets one key, which is reported.
Counts. max is the meta-model's. Where a class's content repeats its roles (mixed content:
text between the elements, or a content model that is a repeated choice of the roles), each
occurrence is one more element, so the XML schema's count is used, and the case counted;
occurrence counts are multiplied down the class's content model. The repetition of a wrapper's
anonymous type belongs to the wrapper's role, not to the roles inside it. Where pureMM.maxOccurs
is missing, a wrapper's most is the number of items its content model can hold (a sequence's
parts add up, a choice takes its largest alternative, both repeat). min is the meta-model's;
the XML schema makes most roles optional within a file.
Every annotation is accounted for (Census), by what the reader did with it: applied to a
rule (used), compared with the schema's structure (checked), a tag of a kind the reader uses
found where it does not read it (not read, by owner: a class name on a group or type, counts on
an attribute or on an element that is no role, tags on an attribute reference inside an attribute
group), every other tag, stereotype and appinfo source (other, counted per name as the user's
schema has it), and tag text that is not key="value" (malformed). The code names only the
annotations it applies; nothing is classified by a list of the schema's vocabulary, and nothing is
dropped without being counted.
The files of one model may declare different releases of the schema. The newest release the
files declare governs the merged model: one rule set, by which files of older releases are
merged too. A release is read from the schema's file name, <prefix>_<release>[_<qualifier>].xsd:
the first part after the prefix that is one number (_00049) or numbers joined by - (_4-2-2);
a qualifier after it (_STRICT) does not change the rank, and case does not matter. Releases
rank only within one family (one prefix). Dotted releases order number by number
(4-2-2 < 4-3-0 < 4-6-0), and any numbered release is newer than any dotted one
(4-6-0 < 00044 < 00046). Where one namespace has several families, the one most documents
declare is ranked and the others are reported. A name with no release cannot be ordered and is
said; it governs only when it is the one schema that compiled. One release under two spellings
is one release: one that compiled stands for it.
When the newest declared schema is not in the schema directory, or does not compile, the newest
one that does governs, and rules says so. Against the governing schema it reports, per older
schema at hand, the roles that have no rule in it (--problems lists them), and the element names
the documents use that it does not declare, with counts, files and the first position.
check merges every file into one model by the rules of the governing schema, on flatxml's
merged view (flatxml/merge.v), and checks it in four layers, each finding with one shape
(Finding: layer, kind, severity, element path, every file:line it involves). The element path
is an AUTOSAR path: the SHORT-NAMEs of the referrable elements above it (/Pkg/Sub/Name; a
variation point is not referrable and adds nothing), then # and the elements below the nearest
referrable one, each with its merge key where it has one
(/Pkg/C#PARAMETER-VALUES/ECUC-NUMERICAL-PARAM-VALUE[<key>]/DEFINITION-REF), or # and the
elements from the root where none is referrable. An element is referrable when its type, in the
governing schema, is a kind of the class whose own content declares SHORT-NAME (a class being a
named model group the type's content uses); merging from a rules file, with no schema, when it
has a SHORT-NAME. Every referrable element is indexed by its path, compared ignoring ASCII case
when the merge compares keys so (key_case); a path held by several elements the merge keeps
apart (two variants told apart by their variation points, or two definitions whose keys differ)
keeps every one, in document order.
- merge: what the merge rules forbid: one key twice where the role is not splittable, a key used in its scope already, an ordered role in two files, too many children, two fragments whose text or an attribute differ, one key under two names, a pair whose holders' rules no path tells apart;
- xml_schema: each file against the XML schema it declares (the governing one where its own is not at hand, said): every partial model must be valid on its own ([TPS_GST_00423]);
- multiplicity: each role's count in the merged model against the meta-model's
pureMM.minOccurs and maxOccurs ([constr_2502]). The XML schema makes lower bounds 0 so a model
may be split, so a role below its fewest can only be found after merging (
below_min); a role that is one alternative of a choice in its holder's content (mixed content) has no fewest of its own there. What the merge already reported on a role (too many, a collision) is not said again. A wrapper holding its items' content has no element per item to count: such roles are not checked, andchecksays how many occurrences that left out. - reference: every reference resolved against the path index. An element is a reference when
its declaration, under its holder's type, has a type carrying a
DESTattribute; the values that attribute's type enumerates are the DESTs it may give (an element of the same name declared elsewhere as plain text is not a reference). A reference resolves when any element at its path is the DEST element or a kind of it (an element is a kind of every class whose group its type's content uses, so a DEST naming an abstract class accepts its concrete subclasses). Otherwise:empty(no path);relative(aBASEattribute, or a path not starting at/: resolving it needs the REFERENCE-BASEs of its package, which this does not read, so it is reported, not guessed);wrong_kind(a DEST the reference may not give, or no element at the path of that kind);case_mismatch(the path found only by ignoring case); and, nothing being at the path,externalwhen its nearest existing ancestor is a package (the policy'spackage_element) or nothing of it exists, which is a definition not loaded, elseunresolved, a target missing from an element whose content is loaded.--refs-from <file|dir>loads more files (standard or vendor definitions) only to resolve references against: merged on their own, never reported. A path held by several elements is itself a finding,duplicate_path.checkprints the external and unresolved references by the top-level package they point into. ECUC values by their definition. The schema's split key for an ECUC parameter or reference value is the item itself (with its variation point's label). Copied, as the specification'sitem_itselfhas it, two files giving one parameter a value would give it two values; keyed by its single-valued children, assingle_valueshas it, the value is part of the identity, and two files giving it different values would again be two values, two sides of a merge changing it keeping both. The policy'sitem_identitynames the roles whose items are identified by one role of theirs instead (ECUC values bydefinition, and their variation point's label); a definition given more than once under one container in the documents read takes several values, which are a set told apart by content, as with the built-in rules. A rules file records these roles (# identity:), since rule text cannot say a set; likewise the repeated roles (# repeat:) and the elements not compared (# ignore-content:).
Each finding's severity comes from the policy's [severity] table ("error", "warning",
"ignore"; keyed by layer, "layer.kind" or "layer.kind:element" / "layer.kind:@attribute",
the most specific winning; a key naming an unknown layer or kind is refused). The defaults for
references: unresolved, wrong_kind, empty and duplicate_path are errors; external,
case_mismatch and relative warnings. A merge problem on an element pair in conflict keeps its
kind, marked (in conflict): a key for its kind applies to it, else merge.in_conflict. No XML schema
finding is dropped past a count; --jobs sets that layer's threads. check prints the
counts by severity, layer and kind, then the errors and warnings (the first 20 of each kind,
--all for every one), and exits 1 on errors only (0 clean, 1 errors, files not read or
declaring no schema, 2 when it cannot run, for every command). --format json|junit
writes the report for machines instead (arxmodel's own JSON with a versioned schema, JUnit XML;
docs/report.md) to standard output, what was read going to standard error. --skip-xml, --skip-multiplicity and
--skip-references leave a layer out; merging from a rules file has no schema, so it checks the
merge layer only (the path index is still built). get and list merge the same way and read
the index: get prints the element's name, how many elements it is merged from and where each
is (every one, for a path several hold), list the path of every referrable element of a name.
--jobs sets their loading threads. Without --jobs, loading and the XML schema layer use one
thread per CPU, at most 8, leaving room for what shares the machine.
flatxml names an aggregation by its parent (an element name, or a path of ancestor names) and its
children's element names; the schema by the parent's type and the role. So each rule becomes
flatxml rules per element name its holding type is declared with (arxmodel/merge.v):
- a role element that wraps its items is a single child of its holder, merged across fragments; its items are keyed by the split key and merged when the role is splittable, else one child each, ordered and limited as the role is; the item kinds of one role are one flatxml rule with several children, so ordered and the most hold across them;
- a role element that is itself the item is keyed or one child each in the same way;
- a role of one item is a single, merged child whatever its key and splittability: its element is in every fragment of the object that holds it, and fragments that disagree are reported as differing values;
- a wrapper of item content holds its content one child each, at most its most items times as often as one item holds each element;
- a split key with a part that did not resolve is not used: one child each (the rules report the part);
- keys are flatxml key paths: '' for the item's own text,
@NAMEfor an attribute, a final+for a part whose element repeats (every occurrence, in order); an item whose parts are all absent is the one with the empty key (keyless: empty), which every such item of the aggregation shares, so their fragments merge and what differs is reported. No key is computed by arxmodel.
Where roles of different holder types give one element pair different rules (a CONTAINERS keyed
under one class and not under another), the pair's first rule stays the rule by name, and each
holder whose rule differs gets its role's rules, every item kind of it together, under a parent
path: ECUC-MODULE-DEF/CONTAINERS for a wrapper's items (the holder above the wrapper), and for a
role written directly in its holder, PARENT/HOLDER, PARENT being an element that holds an
element of the holder's type. A path that still names holders with different rules, or a holder no
path names (the root), is a conflict: it is reported with its occurrences, one rule is applied, and
the problems on it are marked as the conflict's rather than counted as violations; check exits 1
when the documents use one.
flatxml puts a node's children in the order of the rules that name them, and those no rule names after them. So the rules of one parent are listed in the order its type's content model gives their children (where an element name is declared with several types, one order all of them keep), each scoped rule beside its unscoped counterpart, and a child element no role names, under a parent that has rules, gets a rule of the kind no rule gives (each its own) for its place. A merged model is then written in the order its schema requires.
values PATTERN <file|dir>... merges the files as get does and prints every value whose path
the pattern matches, or lies below a path it matches, one a line, path = "text" (the text a TOML
basic string), in the order of the merged view:
/Pkg/LampCfg/Dim = "10"
/Pkg/LampCfg/Signal = "/Sig/A"
/Pkg/Head#WATTS = "55"
A value is an element holding text and no element. One the merge names by its definition (an
item of the policy's item_identity roles, or of the built-in rules' by_definition: ECUC
parameter and reference values) is named by it: its container's path, /, and the definition's
last name, with its variation point's label as a key where it has one (Dim[v1]). Of such an
item, its last element, when that holds text only, is the value itself (the value comes last in
such an item); its other parts follow a # (/Pkg/LampCfg/Dim#IS-AUTO-VALUE). Any other value
is named as findings name elements: its nearest referrable ancestor's path, #, and the elements
below it with their keys. A referrable element's SHORT-NAME is its path, not a value; attributes
are not values; what the merge does not compare (structured comments) is left out. Where fragments give a value differently, the first file's is printed, as merge writes it
(check reports the difference).
The pattern is a path glob, as the policy's filters take them: * one segment or part of one,
** any segments, # matched only by itself or **, a keyed segment matched by its name unless
the pattern gives the key. /Pkg/**/Dim finds a definition's value wherever it is; /Pkg/LampCfg
gives every value of that container and its subcontainers; /** every value. A pattern that is a
file or directory is refused (the pattern comes first). Exit 1 when no value matches.
A script, an editor or an agent asking one model many questions pays the reading and merging
each time it runs a command. serve pays it once:
arxmodel serve --schema-dir DIR --socket m.sock model/ & # read and merge once
arxmodel values --socket m.sock '/Pkg/**/Dim' # answered from the loaded model
arxmodel get --socket m.sock /Pkg/LampCfg
arxmodel serve --socket m.sock --stopget, list and values with --socket PATH take their argument alone and print what they print
reading the files, with the same exit code. The server takes the options of those commands
(the rules, --policy, --exclude) and keeps them; asked with --socket, those options are
refused, as the server reads by its own.
- Never from changed files. Before each answer the server finds its input set again and
looks at each file's size, inode and modification time; where anything differs (a file added,
removed or written) it reads the files again first, and the answer says so on standard error
(
read again: 1 file changed). Modification times have one-second resolution, so a file written in the second the files were read counts as changed until that second has passed.serve --socket PATH --reloadreads them again anyway. - One model's memory. The model lives in a worker process (
serve --worker, its socket in a directory only this user can enter) that the server starts again for each reading, so its memory goes back to the system however often the files change; the server itself holds none. A worker that fails takes no server down. - Files that cannot be read just then (a tool writing them) give an answer saying why, exit 2; the next request tries again. No answer comes from part of the files.
- Stopping.
serve --socket PATH --stop; or after--idleminutes without a request (60 by default, 0 never); a worker whose server is gone stops too. A server killed outright leaves its socket, which a new server does not remove (whose it is cannot be told safely): remove it. - The socket is a Unix domain socket, readable by its owner only. Not on Windows: a socket
there takes its directory's access list, so
serveis not offered. Its path is limited to about 100 bytes: a relative path is read from the directory the command runs in.
Other programs can ask a server directly. One request a connection: COMMAND<tab>ARGUMENT<newline>
(values, get, list; reload, ping, stop); the answer is CODE OUTLEN ERRLEN<newline>
followed by OUTLEN bytes of standard output and ERRLEN bytes of standard error, as the command
prints them.
merge merges and checks the files as check does, with the same options, the summary going to
standard error, and writes the merged model as one ARXML (-o OUT, -o - for standard output), by
flatxml's writer of a merged view: each element once; what came from one file and was not merged
into is copied byte for byte, comments and indentation included; an element made of several
fragments is written from its first, with the attributes only the others have, its merged children
in the schema's order, each with the indentation and comments it had; values that differ are the
first fragment's, as the merged view has them (the policy's severities say whether that is an
error). An element read where other namespace bindings were in scope is given the declarations that
restore them. The prolog and root are the first file's; in the root's xsi:schemaLocation, the
pair for the model's namespace is replaced by the location the first file declaring the governing
release gives (the schema's release, or with --rules the release the rules were made from), the
other pairs kept. A relative location that names a file beside that file is written relative to
the output; one that does not is kept as written, and said. An output that is one of the inputs, or
inside an input directory, is refused. Nothing is written where check would exit 1
(findings the policy calls errors, files that could not be read or declare no schema), unless
--force; merge says so.
The default policy merges as the AUTOSAR FO TPS Generic Structure Template, R24-11, has it; what flatxml's merge does not do by itself is done here:
- an item whose split key parts are all absent stays an item of its own ([TPS_GST_00417]); such
items are counted as
merge.missing_key, ignored by default; - keys are compared as written ([TPS_GST_00021]), and keys of one aggregation that differ only in
case are reported,
merge.case_only([constr_2508]); - the items of a multi-valued role whose split key is the role alone are copied, never merged ([TPS_GST_00417]), and so are primitive values, duplicates kept ([TPS_GST_00449]);
- a non-splittable multi-valued role in several fragments must repeat identically ([constr_2524],
[TPS_GST_00447]): identical repetitions are one (unkeyed items keyed by their content, a
document's own duplicates kept apart), and documents that give it differently are reported,
merge.repeated_differs, as are two items of one key in one document; - a structured comment (atpStructuredComment, FILE-INFO-COMMENT) is not compared ([TPS_GST_00382]);
- the items of a role of several item types are keyed by the union of their types' key parts ([TPS_GST_00416]).
arxmodel policy --diff automat lists how the automat profile differs from it.
The policy's [[filter]] entries give a severity to the findings each matches, after
[severity]: a finding takes the first filter it matches, and must match every field the filter
gives. match (a layer, "layer.kind", "layer.kind:element", as [severity] keys;
"merge.in_conflict" matches a merge finding on a pair in conflict, whatever its kind, as in
[severity]), path (an AUTOSAR path glob: * one segment or part of one, ** any segments;
the # before the elements inside an identifiable one is a segment of its own, matched only by
# or **; an element with a key matched by its name unless the glob gives the key, and a key's
[, ] and \ are written escaped with \, as paths show them), file (a glob of the
finding's files, relative to the paths checked, or absolute), message (a substring),
message_re (an XML Schema pattern, flatxml's linear-time engine, which the whole message
matches; V's own regex module is not used), base and dest (a reference's BASE and DEST
attributes); then severity (required) and reason (recommended).
A filter that matches nothing is a finding, policy.unused_filter: a warning unless the policy's
[severity] says otherwise (the policy's own findings are warnings where it says nothing, every
other finding an error). Such a report is filtered like any finding, but not by the filter it
reports, so a filter ignoring policy.unused_filter quiets the others' reports and reports itself
if it matches none. A filter whose match names a layer that did not run (--skip-xml, a rules
file, no schema) is not reported. merge3 checks its result with the same policy but without file
filters, written against its inputs, and without unused reports.
check prints what each filter matched; the JSON report carries the filter and its reason on
each finding it changed, and the counts in its summary; JUnit lists the counts as properties. The
shipped policies have none; policy.toml's comments end with two examples.
A program that reads a model, rather than checks it, opens it:
import arxmodel
mut o := arxmodel.open(['system/'])! // or open_sources([arxmodel.Source{path, bytes}])
defer {
o.free()
}
for f in o.model.failed {
eprintln(f.msg)
}
for c in o.of_kind('CAN-CLUSTER') { // every element of a name, in document order
println('${o.path_of(c)} at ${o.pos(c)}')
}
frame := o.at('/Frames/F1') // by AUTOSAR path; an empty handle where there is none
if t := o.target(frame.child('PDU-REF', '')) { // a reference followed
println(t.local_name())
}The files are merged by the schema they declare when schema_dir (or rules_file, a rules file
from rules --emit) is given: the AUTOSAR merge model exactly. Without one they are merged by the
built-in identity rules, which key every named element by its SHORT-NAME; rules says which was
read by. Either way element names are compared without their prefix, so a file binding the
AUTOSAR namespace to ar: merges with one that does not, and siblings are read in the order
first written. What the merge found is in findings, and what one file describes twice in
repeated. The built-in rules match an unnamed element by its place, so where two files each give
a merged element one child of a name (a frame's FRAME-LENGTH), both are kept and the first is
the one read; if they differ, that is a merge.value_differs finding. rule_files lists the
files the rules were read from: the rules file, or each schema found and what it imports, and the
configuration file that named the schema directory (OpenOptions.config). A program that caches
what it opened can count them among its inputs.
open_sources opens documents a program has read already; roots names the directories they
came from, which a policy filter's relative file glob is read from as open reads it.
A model file (<name>.arxmodel.toml) states a model once, beside it, for a program that
opens it by name:
paths = ["system/", "extracts/*.arxml", "@more.txt"] # the input set
exclude = ["system/old/*.arxml"]
schema_dir = "/opt/autosar/schemas" # or rules = "autosar.rules.txt"
policy = "spec" # spec (the default), automat, or a policy fileRelative paths are read from the file's directory. With neither schema_dir nor rules, this
machine's schema directory is used (ARXMODEL_SCHEMA_DIR, else the user's configuration), else
the built-in rules. arxmodel.load_model_file(path)!.open()! opens it.
The library folder, arxmodel/, is self-contained: it embeds only its own files (the policies,
the identity rules) and the vendored flatxml's VENDORED, so a program vendors it as it is,
beside flatxml/.
Every command that merges says on one line which rules it read by (rules: built-in identity rules (...), rules: schema AUTOSAR_xxxxx from DIR (...), rules: rules file ...), so a CI log
and a local run can be compared. The rules are, in order: --no-schema forces the built-in ones;
--rules FILE; --schema-dir DIR; the schema directory of the environment
(ARXMODEL_SCHEMA_DIR) or of the user's configuration (schema_dir = "DIR" in
~/.config/arxmodel/config.toml, $XDG_CONFIG_HOME respected); else the built-in identity
rules (identity.toml, committed and built in), which need no schema, so CI and
git merges never need the AUTOSAR XSD:
- an element with a SHORT-NAME child is named by it (and by its VARIATION-POINT's SHORT-LABEL);
- ECUC values (
*-PARAM-VALUE,*-REFERENCE-VALUE,ECUC-CONTAINER-VALUE) by their DEFINITION-REF and variation point label (a container by its SHORT-NAME too); a definition given more than once under one parent, in any version read, takes several values: those are a set, each value told apart by its content, so two sides adding values merge, while two sides changing a single value conflict; - an element name some version holds with only named children (AR-PACKAGES, ELEMENTS, ...) is a list, one with its namesakes under a parent (an emptied one too), so the lists of several files merge;
- anything else is matched by its name and its place, and its order counts (a reorder is reported); the order of named elements does not.
They are hand-written conventions of the format, not read from a schema; a schema or a rules file
replaces them with the exact rules. Without a schema, check runs the merge layer and the
references (an element with a DEST attribute, resolved by path, any kind fitting); the XML schema
and multiplicity layers need the schema.
diff A B compares two versions (files, or directories of files) as models: both are merged by
the same rules (the built-in ones, or the schema of the newest release either declares), and
flatxml's diff is read by AUTOSAR path. Where an element is written, the order of items whose
order means nothing, and the attributes the merge lets differ (UUID, namespace declarations, xsi:
attributes) are not changes. One line a change: - removed, + added, > moved, ~ text,
@ attribute, each with its positions. Exit 0 when the versions are the same model, 1 when they
differ.
merge3 BASE OURS THEIRS [-o OUT] merges two changed versions on their base with flatxml's
three-way merge: every byte no change touched stays as the base has it. Where the sides change one
node differently, ours is taken and the conflict is printed by AUTOSAR path on standard error. The
result is checked (by the same rules: the merge and references, or every layer with a schema) and
what that finds is printed; the exit code says whether the merge was clean (0) or had conflicts
(1). On directories it writes every file of the base to OUT, as merged; a file new on one side is
merged into the base's files rather than written as a new one. Exit 2, for any command, when
something kept it from answering.
git-setup [REPO] adds *.arxml merge=arxml diff=arxml to the repository's .gitattributes and
configures the drivers there: merge3 %O %A %B -o %A as the merge driver (a conflict leaves the
file unmerged, holding ours, with the conflicts named) and git-diff as the diff driver. The rules
options given are recorded in the drivers; with none, they read by what is configured where they
run, so CI uses the built-in rules. scripts/git_driver_test.sh runs real merges with no schema
anywhere.
V3 only; .v-version and .vc-version pin the V commit and its C bootstrap, as in flatxml. CI
builds them and runs the tests and the command on the synthetic model.
export V_MACOS_V3_NO_FALLBACK=1
v test . # unit tests, on the synthetic schema in testdata/
v -prod -o bin/arxmodel cmd/arxmodel # the commandA release is cut by pushing a tag equal to v.mod's version (v0.1.0) on a commit of main:
.github/workflows/release.yml builds the command with -prod for Linux (statically linked, on
ubuntu-22.04) and for Windows (cross-built with mingw-w64, then run on a Windows runner), and
attaches both with their SHA-256 sums to the GitHub release. arxmodel version prints the
version.
flatxml is vendored in flatxml/ (the library, without its tests) at the commit
flatxml/VENDORED names, by scripts/vendor_flatxml.sh <full commit sha>; the copy is never
edited.
MIT, see LICENSE. The vendored flatxml is MIT too, by the same author.