1450 lines
44 KiB
Markdown
1450 lines
44 KiB
Markdown
# DBC Parser and Runtime Decode Module Documentation
|
|
|
|
## Overview
|
|
|
|
This module provides a minimal but extensible DBC parser and runtime decode foundation for the **FrameTap** project.
|
|
|
|
Its purpose is to:
|
|
|
|
- parse DBC files
|
|
- store parsed frame and signal metadata
|
|
- build a tree for future UI integration
|
|
- build a runtime-ready decode database
|
|
- decode live CAN frames
|
|
- decode CAN trace records using the same decoder
|
|
|
|
The implementation follows a simple and practical design:
|
|
|
|
- parser logic is separated from UI logic
|
|
- runtime decode structures are separated from tree structures
|
|
- Qt is not required at the parser or decoder level
|
|
- the same decode engine can be reused for live traffic and trace replay
|
|
|
|
The core library uses C++17 and has no Qt dependency. Qt integration targets the
|
|
installed Qt 6.8.2, which is the only supported Qt version. Qt Core and Qt Test are
|
|
used by the regression tests; model tests also link QtGui for graphical metatypes.
|
|
The optional adapter requires only Qt Core. Live CAN acquisition, trace-file readers,
|
|
are left to the consuming application. An optional Qt Core model adapter is provided in `qt/`.
|
|
|
|
See [changelog.md](changelog.md) for the release and development history,
|
|
[tests/README.md](tests/README.md) for test instructions, and
|
|
[generated API documentation](docs/html/index.html) for the API reference.
|
|
|
|
---
|
|
|
|
## High-Level Architecture
|
|
|
|
The module is divided into two main paths.
|
|
|
|
### 1. Parse and UI path
|
|
|
|
Used for:
|
|
|
|
- browsing frames and signals
|
|
- later integration with Qt `Model/View`
|
|
- displaying metadata
|
|
|
|
Pipeline:
|
|
|
|
```text
|
|
DBC file
|
|
↓
|
|
DbcParser
|
|
↓
|
|
DbcDatabase
|
|
↓
|
|
DbcTreeBuilder
|
|
↓
|
|
TreeNode hierarchy
|
|
↓
|
|
DbcTreeModel → Qt views
|
|
```
|
|
|
|
### 2. Runtime decode path
|
|
|
|
Used for:
|
|
|
|
- live CAN decoding
|
|
- CAN trace decoding
|
|
- fast lookup by CAN ID
|
|
|
|
Pipeline:
|
|
|
|
```text
|
|
DBC file
|
|
↓
|
|
DbcParser
|
|
↓
|
|
DbcDatabase
|
|
↓
|
|
DbcDecodeBuilder
|
|
↓
|
|
DecodeDatabase
|
|
↓
|
|
DbcDecoder
|
|
↓
|
|
Decoded values
|
|
```
|
|
|
|
This separation is intentional.
|
|
|
|
The tree is useful for UI, but it is **not** the main data structure for runtime decoding.
|
|
|
|
---
|
|
|
|
## Why the Runtime Decode Layer Exists
|
|
|
|
A tree structure is convenient for browsing, but a runtime decoder needs something different:
|
|
|
|
- fast lookup by CAN ID
|
|
- direct access to signal decode definitions
|
|
- minimal overhead during repeated decoding
|
|
- the same logic for live frames and trace frames
|
|
|
|
Because of that, the design uses a dedicated runtime-ready structure:
|
|
|
|
- `DecodeDatabase`
|
|
- `DecodeFrame`
|
|
- `DecodeSignal`
|
|
|
|
This avoids forcing UI-oriented structures into a decode role they were not meant for.
|
|
|
|
---
|
|
|
|
## Module Layout
|
|
|
|
```text
|
|
core/ C++17 parser, statement handlers, metadata, tree, and decoder
|
|
examples/ Command-line demo (built separately)
|
|
qt/ Optional read-only Qt Core model adapter
|
|
tests/ Parser, decoder, tree, and optional model tests
|
|
docs/ Generated Doxygen documentation
|
|
dbc.pro Root qmake project: core + tests
|
|
Doxyfile API documentation configuration
|
|
```
|
|
|
|
| Component | Source files |
|
|
| --- | --- |
|
|
| Parsed metadata | `core/dbc_database.h`, `core/frame_info.h`, `core/signal_info.h` |
|
|
| Parser and dispatch | `core/dbc_parser.*`, `core/dbc_statement_handler.*`, `core/typefactory.h` |
|
|
| UI tree | `core/tree_node.*`, `core/dbc_tree_builder.*` |
|
|
| Runtime database | `core/decode_database.h`, `core/dbc_decode_builder.*` |
|
|
| Decoder | `core/dbc_decoder.*` |
|
|
| Demo | `examples/main.cpp`, `examples/demo.pro` |
|
|
| Build integration | `core/core.pro`, `core/core_files.pri`, `core/core.pri` |
|
|
|
|
---
|
|
|
|
## Parsed Data Structures
|
|
|
|
## `SignalInfo`
|
|
|
|
Represents one signal as parsed from the DBC file.
|
|
|
|
Fields:
|
|
|
|
- `name`
|
|
- `startBit`
|
|
- `length`
|
|
- `isLittleEndian`
|
|
- `isSigned`
|
|
- `factor`
|
|
- `offset`
|
|
- `minimum`
|
|
- `maximum`
|
|
- `unit`
|
|
- `receivers`
|
|
- `comment`
|
|
|
|
Notes:
|
|
|
|
- `receivers` is a list because a signal may have more than one receiver ECU
|
|
- `factor` and `offset` define physical conversion
|
|
- this structure is close to DBC content and easy to inspect
|
|
|
|
Physical value rule:
|
|
|
|
```text
|
|
physical = raw * factor + offset
|
|
```
|
|
|
|
---
|
|
|
|
## `FrameInfo`
|
|
|
|
Represents one frame as parsed from the DBC file.
|
|
|
|
Fields:
|
|
|
|
- `name`
|
|
- `canId`
|
|
- `isExtended`
|
|
- `pgn`
|
|
- `hasPgn`
|
|
- `dlc`
|
|
- `transmitter`
|
|
- `comment`
|
|
- `signals`
|
|
|
|
Notes:
|
|
|
|
- `signals` is a list of `SignalInfo`
|
|
- `isExtended` is determined during CAN ID normalization
|
|
- `pgn` is derived using simplified J1939 logic when applicable
|
|
|
|
---
|
|
|
|
## `DbcDatabase`
|
|
|
|
Top-level parsed DBC container.
|
|
|
|
Conceptually:
|
|
|
|
```text
|
|
DbcDatabase
|
|
└── vector<FrameInfo>
|
|
```
|
|
|
|
This is the central structure produced by `DbcParser`.
|
|
|
|
---
|
|
|
|
## UI Tree Layer
|
|
|
|
## `TreeNode`
|
|
|
|
The UI tree contains three node types:
|
|
|
|
- `Root`
|
|
- `Frame`
|
|
- `Signal`
|
|
|
|
Example hierarchy:
|
|
|
|
```text
|
|
dbc
|
|
├── EngineData
|
|
│ ├── EngineSpeed
|
|
│ ├── OilTemp
|
|
│ └── CoolantTemp
|
|
└── VehicleData
|
|
├── VehicleSpeed
|
|
└── Odometer
|
|
```
|
|
|
|
Frame and signal nodes store copies of the corresponding metadata; the root
|
|
has no metadata payload. The payload types are:
|
|
|
|
- `FrameInfo`
|
|
- `SignalInfo`
|
|
|
|
The tree supports browsing through the optional `DbcTreeModel` adapter.
|
|
|
|
It is **not** the primary runtime decode structure.
|
|
|
|
---
|
|
|
|
## Runtime Decode Layer
|
|
|
|
## Purpose
|
|
|
|
The decode layer exists so that decoding can be fast and independent from UI concerns.
|
|
|
|
Instead of searching a tree, the decoder uses a prepared database with direct lookup.
|
|
|
|
---
|
|
|
|
## `ByteOrder`
|
|
|
|
Runtime byte order enum:
|
|
|
|
- `Intel`
|
|
- `Motorola`
|
|
|
|
This is better for decode code than passing around raw DBC characters.
|
|
|
|
---
|
|
|
|
## `ValueType`
|
|
|
|
Numeric type enum:
|
|
|
|
- `Unsigned`
|
|
- `Signed`
|
|
|
|
This is clearer than combining multiple boolean flags during runtime logic.
|
|
|
|
---
|
|
|
|
## `DecodeSignal`
|
|
|
|
Represents one runtime-ready signal definition.
|
|
|
|
Fields:
|
|
|
|
- `name`
|
|
- `startBit`
|
|
- `length`
|
|
- `byteOrder`
|
|
- `valueType`
|
|
- `factor`
|
|
- `offset`
|
|
- `minimum`
|
|
- `maximum`
|
|
- `unit`
|
|
- `receivers`
|
|
- `comment`
|
|
|
|
This structure contains all information required for extracting and converting a signal value from raw frame data.
|
|
|
|
---
|
|
|
|
## `DecodeFrame`
|
|
|
|
Represents one runtime-ready frame definition.
|
|
|
|
Fields:
|
|
|
|
- `name`
|
|
- `canId`
|
|
- `isExtended`
|
|
- `dlc`
|
|
- `pgn`
|
|
- `hasPgn`
|
|
- `transmitter`
|
|
- `comment`
|
|
- `signals`
|
|
|
|
This structure is used directly by the decoder.
|
|
|
|
---
|
|
|
|
## `FrameKey`
|
|
|
|
Fast lookup key for runtime frame matching.
|
|
|
|
Fields:
|
|
|
|
- `canId`
|
|
- `isExtended`
|
|
|
|
This matters because the same numeric identifier must not be confused between standard and extended frames.
|
|
|
|
---
|
|
|
|
## `DecodeDatabase`
|
|
|
|
Top-level runtime decode container.
|
|
|
|
Fields:
|
|
|
|
- `frames`
|
|
- `frameIndexByKey`
|
|
|
|
Conceptually:
|
|
|
|
```text
|
|
DecodeDatabase
|
|
├── vector<DecodeFrame>
|
|
└── unordered_map<FrameKey, index>
|
|
```
|
|
|
|
This gives the decoder fast access to a frame definition using CAN ID and frame type.
|
|
|
|
---
|
|
|
|
## Decoder Layer
|
|
|
|
## `RawCanFrame`
|
|
|
|
Represents a raw CAN frame to decode.
|
|
|
|
Fields:
|
|
|
|
- `canId`
|
|
- `isExtended`
|
|
- `data`
|
|
|
|
This same structure can be used for:
|
|
|
|
- live CAN bus input
|
|
- replayed trace records
|
|
- unit tests
|
|
|
|
---
|
|
|
|
## `DecodedSignalValue`
|
|
|
|
Represents one decoded signal result.
|
|
|
|
Fields:
|
|
|
|
- `definition`
|
|
- `rawValue`
|
|
- `physicalValue`
|
|
- `valid`
|
|
- `status`
|
|
|
|
---
|
|
|
|
### Raw value representation and precision
|
|
|
|
`DecodedSignalValue::rawValue` is a C++17
|
|
`std::variant<std::int64_t, std::uint64_t>`. For a valid signal, signed definitions
|
|
produce the `int64_t` alternative and unsigned definitions produce the `uint64_t`
|
|
alternative. Raw values remain exact across their full 64-bit ranges. Check
|
|
`valid` before using the value; an invalid result's default value is not decoded data.
|
|
|
|
Use `std::get` when the numeric type is known, or `std::get_if` to handle both:
|
|
|
|
```cpp
|
|
if (const std::int64_t *value = std::get_if<std::int64_t>(&decodedSignal.rawValue)) {
|
|
std::cout << *value;
|
|
} else {
|
|
std::cout << std::get<std::uint64_t>(decodedSignal.rawValue);
|
|
}
|
|
```
|
|
|
|
This replaces the previous `int64_t` field: consumers that read or print
|
|
`rawValue` directly must handle the variant.
|
|
|
|
Physical conversion uses the active numeric type:
|
|
`physicalValue = static_cast<double>(raw) * factor + offset`.
|
|
`physicalValue` remains a `double` and is approximate for large integers.
|
|
On usual IEEE 754 binary64 implementations, integers are exactly representable
|
|
through 2^53; above that, some integers round to the same floating-point value.
|
|
For example, unsigned `UINT64_MAX` remains exact in `rawValue`, but converts to
|
|
approximately 18446744073709551616 in binary64 before scaling.
|
|
Use `rawValue` whenever exact integer values are required.
|
|
|
|
---
|
|
|
|
## `DecodedFrameValue`
|
|
|
|
Represents one decoded frame result.
|
|
|
|
Fields:
|
|
|
|
- `definition`
|
|
- `signals`
|
|
- `valid`
|
|
|
|
`valid` means a matching frame definition was found. Check each signal's
|
|
`valid` separately: insufficient payload data can leave individual signals
|
|
invalid even when the frame is valid. An unknown frame returns `valid == false`.
|
|
|
|
The frame and signal `definition` pointers refer into `DecodeDatabase`. Keep
|
|
that database alive and avoid changes that invalidate its frame or signal
|
|
storage while using decoded results. The decoder applies factor and offset;
|
|
it does not clamp physical values to the declared minimum and maximum.
|
|
|
|
---
|
|
|
|
## `DbcDecoder`
|
|
|
|
Main runtime decoder class.
|
|
|
|
Responsibilities:
|
|
|
|
- find a frame definition by CAN ID
|
|
- decode unconditional signals and the active multiplexed branch
|
|
- extract raw values
|
|
- sign-extend signed values
|
|
- convert raw values into physical values
|
|
|
|
Main methods:
|
|
|
|
- `FindFrame(...)`
|
|
- `Decode(...)`
|
|
|
|
---
|
|
|
|
## Parser Support
|
|
|
|
The current parser supports the following DBC elements:
|
|
|
|
- `BO_`
|
|
- `SG_`
|
|
- `CM_ BO_`
|
|
- `CM_ SG_`
|
|
- `VAL_` signal value descriptions
|
|
- basic `M` / `mN` multiplexing
|
|
|
|
---
|
|
|
|
## Supported DBC Syntax
|
|
|
|
## Frame definition
|
|
|
|
Example:
|
|
|
|
```text
|
|
BO_ 256 EngineData: 8 EEC1
|
|
```
|
|
|
|
Parsed fields:
|
|
|
|
- frame CAN ID
|
|
- frame name
|
|
- DLC
|
|
- transmitter ECU
|
|
|
|
---
|
|
|
|
### Frame validation and parse errors
|
|
|
|
`BO_` definitions require a decimal CAN ID, a nonempty single-token frame name,
|
|
a colon, a decimal payload length, and a transmitter token. Spaces or tabs may
|
|
separate fields, and whitespace before the colon is accepted. Additional trailing
|
|
fields are rejected.
|
|
|
|
Numeric fields must contain decimal digits only; signs, hexadecimal notation,
|
|
fractional values, suffixes, and overflow are rejected. CAN IDs may contain the
|
|
29-bit identifier plus the optional DBC extended flag (bit 31). Bits 29 and 30
|
|
are rejected rather than silently discarded. For compatibility, unflagged IDs
|
|
above `0x7FF` are still treated as extended.
|
|
|
|
The payload length must fit `uint8_t` (`0..255`). This is a storage-range check,
|
|
not CAN or CAN FD protocol validation; no new 8-byte or 64-byte limit is imposed.
|
|
|
|
Parsing stops at the first detected error. Exceptions raised while parsing a
|
|
line are reported as `std::runtime_error` with the supplied file path and the
|
|
one-based physical line number, including blank lines:
|
|
|
|
```text
|
|
example.dbc:3: Invalid CAN ID: invalid
|
|
```
|
|
|
|
Frame and signal definitions are validated as described here; comprehensive
|
|
DBC grammar coverage is still planned. File-open errors contain the path but have no line number.
|
|
|
|
---
|
|
|
|
## Signal definition
|
|
|
|
Example:
|
|
|
|
```text
|
|
SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8000] "rpm" ECU1,ECU2
|
|
```
|
|
|
|
Parsed fields:
|
|
|
|
- signal name
|
|
- start bit
|
|
- signal length
|
|
- byte order
|
|
- signedness
|
|
- factor
|
|
- offset
|
|
- minimum
|
|
- maximum
|
|
- unit
|
|
- receivers
|
|
|
|
---
|
|
|
|
### Signal validation and parse errors
|
|
|
|
`SG_` statements are matched completely. Signal names must be nonempty and
|
|
contain no whitespace. Spaces and tabs are accepted between syntax elements;
|
|
byte order and signedness remain adjacent (`@0+`, `@0-`, `@1+`, or `@1-`).
|
|
A quoted unit (which may be empty) and a nonempty comma-separated receiver
|
|
list are required. Unexpected trailing fields are rejected.
|
|
|
|
Start bits must be unsigned decimal integers fitting `uint32_t`; lengths must
|
|
be within `1..64` bits. Signs, fractional values, numeric suffixes, and overflow
|
|
are rejected for these fields. Every signal bit must fit within the frame's
|
|
declared payload length, using Intel increasing bit positions or Motorola
|
|
sawtooth traversal. This uses the stored payload length without introducing
|
|
an additional CAN or CAN FD payload limit.
|
|
|
|
Factor, offset, minimum, and maximum accept signed decimal numbers, fractions,
|
|
and scientific notation. The entire numeric token must be valid; hexadecimal
|
|
numbers, NaN, infinity, conversion overflow, and conversion underflow are
|
|
rejected. Minimum must not exceed maximum. Zero and negative factors are allowed.
|
|
|
|
Basic multiplexing markers `M` and `mN` are supported as described below.
|
|
Overlapping signals are rejected unless their branch values differ.
|
|
Duplicate frame/signal name detection remains separate work.
|
|
|
|
Invalid signal definitions stop parsing with `path:line: description`, including
|
|
when the signal exceeds the declared payload length.
|
|
|
|
---
|
|
|
|
## Basic multiplexing
|
|
|
|
```dbc
|
|
BO_ 256 Status: 3 ECU
|
|
SG_ Mode M : 0|8@1+ (1,0) [0|255] "" ECU
|
|
SG_ Temperature m1 : 8|8@1- (1,0) [-128|127] "C" ECU
|
|
SG_ Pressure m2 : 8|8@1+ (2,0) [0|510] "kPa" ECU
|
|
SG_ Counter : 16|8@1+ (1,0) [0|255] "" ECU
|
|
```
|
|
|
|
`SignalInfo` and `DecodeSignal` contain `multiplexing`: a `MultiplexRole`
|
|
(`Unconditional`, `Selector`, or `Branch`) and an exact `uint64_t` branch `value`.
|
|
Each frame may have one unsigned selector (`M`); a branch (`mN`) references that
|
|
selector implicitly. The selector may appear after its branches. `N` must contain
|
|
only decimal digits, fit `uint64_t`, and fit the selector's bit width. Leading
|
|
zeros are accepted. A selector without branches is allowed. Signed selectors,
|
|
missing selectors, multiple selectors, nested/combined markers, and extended
|
|
`SG_MUL_VAL_` statements are rejected with the originating `path:line`.
|
|
The dispatcher registers the unsupported statement handler through `TypeFactory`.
|
|
|
|
Signal bits may overlap only between branches with different selector values.
|
|
This validation uses Intel or Motorola bit traversal, including mixed byte orders.
|
|
It also rejects overlapping unconditional signals. Metadata survives the runtime
|
|
and tree builders; the optional Qt adapter does not add multiplexing roles yet.
|
|
|
|
The shared decoder extracts the selector before processing branches, compares its
|
|
**raw unsigned value**, and leaves result order equal to definition order. Factor,
|
|
offset, and `VAL_` descriptions do not influence branch selection. Unconditional
|
|
signals continue to decode. An unmapped selector value leaves all branches inactive.
|
|
|
|
Each result has `SignalDecodeStatus status`:
|
|
|
|
| Status | Meaning |
|
|
| --- | --- |
|
|
| `Valid` | Raw and physical values are available; `valid == true` |
|
|
| `Inactive` | Selector is available and chooses another branch |
|
|
| `SelectorUnavailable` | Branch activation is unknown because selector bytes are missing |
|
|
| `InsufficientPayload` | Selected or unconditional signal bytes are missing |
|
|
| `InvalidDefinition` | Invalid bit length or missing/invalid selector in manually built runtime metadata |
|
|
|
|
All statuses except `Valid` have `valid == false`; their numeric fields contain
|
|
no decoded data and `GetValueDescription()` returns nullptr. Check `status` to
|
|
distinguish inactive branches from errors. Consumers constructing runtime metadata
|
|
manually must obey the same non-overlap rules as the parser. Extended and nested
|
|
multiplexing are outside this stage and are explicitly rejected, rather than
|
|
silently decoded as unconditional signals.
|
|
|
|
---
|
|
|
|
## Comments
|
|
|
|
Frame comment example:
|
|
|
|
```text
|
|
CM_ BO_ 256 "Engine data frame";
|
|
```
|
|
|
|
Signal comment example:
|
|
|
|
```text
|
|
CM_ SG_ 256 EngineSpeed "Actual engine speed";
|
|
```
|
|
|
|
Stored in:
|
|
|
|
- `FrameInfo::comment`
|
|
- `SignalInfo::comment`
|
|
|
|
---
|
|
|
|
## CAN ID Normalization
|
|
|
|
The parser normalizes frame identifiers.
|
|
|
|
Common DBC behavior:
|
|
|
|
- extended identifiers may be stored with bit 31 set
|
|
- the actual 29-bit identifier must be extracted from that value
|
|
|
|
The parser therefore stores:
|
|
|
|
- normalized `canId`
|
|
- separate `isExtended` flag
|
|
|
|
This is important both for correct lookup and for future interoperability with live CAN APIs.
|
|
|
|
---
|
|
|
|
## PGN Extraction
|
|
|
|
PGN is derived only when the frame is treated as extended.
|
|
|
|
The current logic is simplified J1939 extraction:
|
|
|
|
- `pf`
|
|
- `ps`
|
|
- `dp`
|
|
|
|
This is enough for a practical start but should not be treated as full J1939 validation.
|
|
|
|
---
|
|
|
|
## Decode Flow
|
|
|
|
Typical runtime decode flow:
|
|
|
|
```text
|
|
RawCanFrame
|
|
↓
|
|
Find frame in DecodeDatabase
|
|
↓
|
|
Extract multiplexor selector first (when present)
|
|
For each unconditional or active signal:
|
|
extract raw bits
|
|
apply sign extension if needed
|
|
convert to physical value
|
|
↓
|
|
DecodedFrameValue
|
|
```
|
|
|
|
---
|
|
|
|
## Intel and Motorola Extraction
|
|
|
|
The decoder currently has separate extraction paths:
|
|
|
|
- `ExtractIntel(...)`
|
|
- `ExtractMotorola(...)`
|
|
|
|
This is important because byte order is not just metadata once decoding starts.
|
|
|
|
Intel and Motorola require different bit extraction logic.
|
|
|
|
This is one of the main reasons why the runtime decode layer should be explicit and prepared in advance.
|
|
|
|
---
|
|
|
|
## Example Usage
|
|
|
|
Include the public headers from the `core/` include directory supplied by
|
|
`core/core.pri`:
|
|
|
|
```cpp
|
|
#include "dbc_parser.h"
|
|
#include "dbc_tree_builder.h"
|
|
#include "dbc_decode_builder.h"
|
|
#include "dbc_decoder.h"
|
|
```
|
|
|
|
### Parse DBC
|
|
|
|
```cpp
|
|
DbcParser parser;
|
|
DbcDatabase database = parser.ParseFile("example.dbc");
|
|
```
|
|
|
|
### Build UI tree
|
|
|
|
```cpp
|
|
DbcTreeBuilder treeBuilder;
|
|
std::unique_ptr<TreeNode> root = treeBuilder.Build(database);
|
|
```
|
|
|
|
### Build runtime decode database
|
|
|
|
```cpp
|
|
DbcDecodeBuilder decodeBuilder;
|
|
DecodeDatabase decodeDatabase = decodeBuilder.Build(database);
|
|
```
|
|
|
|
### Decode a raw frame
|
|
|
|
```cpp
|
|
RawCanFrame rawFrame;
|
|
rawFrame.canId = 256; // EngineData from the DBC example above
|
|
rawFrame.isExtended = false;
|
|
rawFrame.data = {0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
DbcDecoder decoder;
|
|
DecodedFrameValue decoded = decoder.Decode(decodeDatabase, rawFrame);
|
|
```
|
|
|
|
---
|
|
|
|
## Unified Decode Strategy
|
|
|
|
A key design goal is that the same decoder should work for both:
|
|
|
|
- live CAN frames
|
|
- trace replay frames
|
|
|
|
That means this architecture supports:
|
|
|
|
### live path
|
|
|
|
```text
|
|
live CAN input
|
|
↓
|
|
RawCanFrame
|
|
↓
|
|
DbcDecoder
|
|
↓
|
|
decoded signal values
|
|
```
|
|
|
|
### trace path
|
|
|
|
```text
|
|
trace reader
|
|
↓
|
|
RawCanFrame
|
|
↓
|
|
DbcDecoder
|
|
↓
|
|
decoded signal values
|
|
```
|
|
|
|
This avoids duplicating decode logic in two separate parts of the application.
|
|
|
|
---
|
|
|
|
## Intended Use in FrameTap
|
|
|
|
This module is meant to support at least the following FrameTap workflows:
|
|
|
|
- load a DBC file
|
|
- browse frames and signals
|
|
- search signals
|
|
- drag a signal into a plot later
|
|
- decode live CAN traffic
|
|
- decode recorded traces
|
|
- convert raw values into physical values
|
|
- show metadata like units, comments, transmitter, receivers, and PGN
|
|
|
|
Example combined workflow:
|
|
|
|
```text
|
|
Load DBC
|
|
↓
|
|
Parse into DbcDatabase
|
|
↓
|
|
Build UI tree
|
|
↓
|
|
Build DecodeDatabase
|
|
↓
|
|
Use same decode engine for:
|
|
- live frames
|
|
- trace replay
|
|
```
|
|
|
|
---
|
|
|
|
## Why the Tree Is Not Enough
|
|
|
|
The tree exists for browsing.
|
|
|
|
However, runtime decode should not rely on tree traversal because that would introduce unnecessary coupling and inefficiency.
|
|
|
|
A runtime decoder needs:
|
|
|
|
- fast key-based access
|
|
- minimal interpretation at decode time
|
|
- direct signal definitions already prepared
|
|
|
|
That is why `DecodeDatabase` is a separate layer.
|
|
|
|
---
|
|
|
|
## Statement Handler Factory
|
|
|
|
`DbcParser` owns file reading, trimming, line numbers, and error reporting.
|
|
Each `ParseFile()` call creates a fresh `DbcParseContext` and a
|
|
`DbcStatementDispatcher`. The context contains the parsed database and current
|
|
frame; handlers receive it by reference and retain no parsing state.
|
|
|
|
The dispatcher uses `TypeFactory<std::string, DbcStatementHandler>` to create
|
|
registered handlers once per parsing session, then reuses them for all matching
|
|
lines:
|
|
|
|
- `BO_` → `DbcFrameHandler`
|
|
- `SG_` → `DbcSignalHandler`
|
|
- `CM_` → `DbcCommentHandler`
|
|
- `VAL_` → `DbcValueDescriptionHandler`
|
|
- `SG_MUL_VAL_` → `DbcUnsupportedMultiplexingHandler`
|
|
|
|
Grammar implementations live in `core/dbc_statement_handler.cpp`; their interface,
|
|
context, and dispatcher live in `core/dbc_statement_handler.h`. `core/typefactory.h`
|
|
provides the generic registry and object creation.
|
|
|
|
To add a construct, implement a default-constructible handler deriving from
|
|
`DbcStatementHandler` with:
|
|
|
|
```cpp
|
|
void Handle(const std::string &line, DbcParseContext &context) const override;
|
|
```
|
|
|
|
Register it in the dispatcher constructor:
|
|
|
|
```cpp
|
|
RegisterType<MyCustomHandler>("CUSTOM_"); // after implementing MyCustomHandler
|
|
```
|
|
|
|
Registration creates one handler; replacing a registration replaces that instance.
|
|
Register during initialization before dispatching. A line is routed using its
|
|
full first whitespace-delimited keyword, so `BO_TX_BU_` cannot match `BO_`.
|
|
Unregistered keywords remain ignored. `SG_` and `CM_` accept spaces and tabs
|
|
after their keywords.
|
|
|
|
This factory handles statements inside DBC files. A separate format-level
|
|
factory can be introduced when another file format is implemented. The decoder
|
|
and database builders remain independent of statement dispatch.
|
|
|
|
---
|
|
|
|
## Signal value descriptions (`VAL_`)
|
|
|
|
Signal descriptions attach text to exact raw integer values:
|
|
|
|
```dbc
|
|
BO_ 256 Status: 8 ECU
|
|
SG_ State : 0|8@1+ (1,0) [0|255] "" ECU
|
|
VAL_ 256 State 0 "Off" 1 "On" 2 "Error";
|
|
```
|
|
|
|
Each statement must occupy one physical line, contain at least one integer/text
|
|
pair, and end with a semicolon. Spaces and tabs are accepted between fields;
|
|
descriptions may contain whitespace, semicolons, and empty text. Inside quoted
|
|
text, `\"` and `\\` represent a quote and a backslash; other escapes are rejected.
|
|
Keys are decimal integers in `INT64_MIN..UINT64_MAX`, with an optional minus
|
|
sign; plus signs, fractions, hexadecimal numbers, exponents, and overflow are
|
|
rejected. Keys never pass through `double`.
|
|
|
|
`VAL_` may appear before or after its frame and signal declarations. References
|
|
are resolved after reading the file using the same CAN ID normalization as
|
|
`BO_`, including bit 31 for extended identifiers. Standard and extended frames
|
|
with the same normalized ID remain distinct. Missing or ambiguous references,
|
|
malformed statements, and duplicate integer keys produce `path:line: description`
|
|
errors at the originating `VAL_` line. Distinct keys from multiple statements
|
|
are merged; `0`, `00`, and `-0` are the same key. Keys need not fit the referenced
|
|
signal's bit width or signedness; unreachable keys simply cannot match a decode.
|
|
|
|
Mappings are available as `SignalInfo::valueDescriptions` and
|
|
`DecodeSignal::valueDescriptions`, and survive both database builders, including
|
|
`TreeNode::GetSignal()`. Consumers can look up a decoded result directly:
|
|
|
|
```cpp
|
|
const DecodedSignalValue &value = decodedFrame.signals.at(0);
|
|
if (const std::string *description = value.GetValueDescription()) {
|
|
std::cout << *description;
|
|
}
|
|
```
|
|
|
|
`GetValueDescription()` uses the exact raw integer before factor/offset scaling.
|
|
It returns `nullptr` for an invalid decode or an unknown value; numeric decoding
|
|
and `physicalValue` are unchanged. A mapped empty string returns a non-null
|
|
pointer. The pointer belongs to the signal definition and stays valid while that
|
|
definition and mapping remain alive and unchanged.
|
|
|
|
For metadata or standalone raw values, use
|
|
`FindValueDescription(signal.valueDescriptions, rawValue)`, where `rawValue` is
|
|
`RawSignalValue` (`std::variant<std::int64_t, std::uint64_t>`). The helper matches
|
|
nonnegative signed and unsigned values identically and keeps negative values
|
|
distinct from large unsigned values. Stored keys use negative `int64_t` and
|
|
nonnegative `uint64_t`; when constructing mappings manually, normalize keys
|
|
with `NormalizeValueKey()` before insertion.
|
|
|
|
Named `VAL_TABLE_` tables, environment-variable mappings, multiline statements,
|
|
are outside this implementation; the optional Qt adapter exposes signal mappings.
|
|
|
|
---
|
|
|
|
## Current Limitations
|
|
|
|
This is still a minimal implementation.
|
|
|
|
Not supported yet:
|
|
|
|
- extended and nested multiplexing (`SG_MUL_VAL_`, `mNM`)
|
|
- named `VAL_TABLE_` tables and environment-variable value descriptions
|
|
- `BA_` attributes
|
|
- `BA_DEF_` definitions
|
|
- advanced comment handling
|
|
- full DBC grammar coverage
|
|
- full J1939 validation
|
|
- extensive edge-case handling for unusual DBC formatting
|
|
|
|
Additional behavior to account for:
|
|
|
|
- unregistered DBC keywords are ignored
|
|
- an `SG_` statement before the first frame is rejected
|
|
- duplicate frame keys (normalized CAN ID and frame type) and repeated signal
|
|
names within a frame are rejected at their definition lines; manually created
|
|
databases are also checked for ambiguous `VAL_` references during resolution
|
|
- payload lengths are checked against storage bounds (`0..255`), rather than
|
|
CAN or CAN FD protocol limits
|
|
- comment handling is limited to the supported forms and already parsed targets
|
|
|
|
Motorola extraction uses DBC sawtooth numbering: a start bit of 7 traverses
|
|
`7..0, 15..8, 23..16`. Regression tests cover aligned and unaligned signals,
|
|
byte boundaries, and 64-bit values.
|
|
|
|
---
|
|
|
|
## Recommended Next Steps
|
|
|
|
A practical development order would be:
|
|
|
|
### Stage 1 - already implemented
|
|
|
|
- `BO_`
|
|
- `SG_`
|
|
- `CM_`
|
|
- `VAL_` signal value descriptions
|
|
- normalized CAN ID
|
|
- `isExtended`
|
|
- transmitter
|
|
- receivers
|
|
- comments
|
|
- tree representation
|
|
- runtime decode database
|
|
- runtime decoder
|
|
|
|
### Stage 2
|
|
|
|
Recommended additions:
|
|
|
|
- selective decoding of only chosen signals
|
|
|
|
### Stage 3
|
|
|
|
Recommended additions:
|
|
|
|
- basic multiplexing (implemented)
|
|
- attribute parsing
|
|
- richer J1939 support
|
|
- CSV export of decoded traces
|
|
- optimized filtering and signal selection
|
|
|
|
### Stage 4
|
|
|
|
Advanced functionality:
|
|
|
|
- live plot integration
|
|
- signal subscriptions
|
|
- per-signal trace decode pipelines
|
|
- decoder-assisted export formats
|
|
|
|
---
|
|
|
|
## Build Integration
|
|
|
|
The repository supplies qmake projects. A C++17 compiler is required; the root
|
|
build also requires Qt Core and Qt Test development packages. Other build
|
|
systems can compile the sources listed in `core/core_files.pri`; no CMake
|
|
configuration is currently provided.
|
|
|
|
Qt-dependent qmake targets share `qt/qt_version.pri`, which rejects Qt versions
|
|
other than 6.8.2. The core library remains independent of Qt libraries.
|
|
|
|
Open `dbc.pro` in Qt Creator to see two subprojects: `core` and `tests`.
|
|
`core/core.pro` builds the C++17 static library `dbc_core` without a Qt dependency.
|
|
`tests/tests.pro` builds the QtTest executable `dbc_tests` and links that library.
|
|
The root project declares `tests.depends = core` for parallel builds.
|
|
|
|
Build out of source with Qt 6.8.2:
|
|
|
|
```sh
|
|
mkdir build
|
|
cd build
|
|
qmake6 ../dbc.pro
|
|
make -j
|
|
make check
|
|
```
|
|
|
|
`core/core_files.pri` lists the library sources and headers.
|
|
Consumers include `core/core.pri` for include paths and library linkage; the test
|
|
source lists live in `tests/parser_tests.pri` and `tests/decoder_tests.pri`.
|
|
When building a consumer separately, pass
|
|
`DBC_CORE_BUILD_DIR=/absolute/path/to/build/core` to qmake.
|
|
|
|
### Build core without Qt libraries
|
|
|
|
From the repository root:
|
|
|
|
```sh
|
|
mkdir -p build/core
|
|
cd build/core
|
|
qmake6 ../../core/core.pro
|
|
make -j
|
|
```
|
|
|
|
qmake is still the build tool; this target does not link Qt libraries.
|
|
|
|
### Optional Qt model adapter
|
|
|
|
The adapter and its `QAbstractItemModelTester` suite use Qt 6.8.2.
|
|
The adapter itself links only Qt Core.
|
|
|
|
The model test executable additionally links QtGui and uses QGuiApplication so
|
|
QAbstractItemModelTester can resolve graphical metatypes such as QPixmap. It
|
|
defaults to the `offscreen` platform when `QT_QPA_PLATFORM` is unset or empty;
|
|
no display server is required. An explicit platform setting is preserved.
|
|
|
|
Enable its library and focused QtTest suite:
|
|
|
|
```sh
|
|
mkdir -p build-qt
|
|
cd build-qt
|
|
qmake6 ../dbc.pro CONFIG+=dbc_qt
|
|
make -j
|
|
make check
|
|
```
|
|
|
|
Consumers include `qt/qt.pri` instead of `core/core.pri`, and set
|
|
`DBC_CORE_BUILD_DIR` and `DBC_QT_BUILD_DIR` to the corresponding library build
|
|
directories when building separately. A widget application also needs `QT += widgets`. The adapter integration enables
|
|
`no_keywords`, because core metadata has a `signals` member; use `Q_SIGNALS`,
|
|
`Q_SLOTS`, and `Q_EMIT` in consuming Qt classes.
|
|
|
|
```cpp
|
|
#include "dbc_parser.h"
|
|
#include "dbc_tree_builder.h"
|
|
#include "dbc_tree_model.h"
|
|
#include <QTreeView>
|
|
|
|
// Run after constructing QApplication; view owns the model through QObject.
|
|
QTreeView view;
|
|
DbcTreeModel *model = new DbcTreeModel(&view);
|
|
model->SetTree(DbcTreeBuilder().Build(DbcParser().ParseFile("example.dbc")));
|
|
view.setModel(model);
|
|
view.expandAll();
|
|
view.show();
|
|
// On reload, pass a newly built tree to SetTree(); to clear:
|
|
// model->SetTree(nullptr);
|
|
```
|
|
|
|
The root is invisible: frames are top-level rows, signals are their children.
|
|
Only column zero has children. `parent()` always returns column zero.
|
|
The model takes exclusive ownership of the tree; do not retain mutable aliases.
|
|
Metadata is copied by the tree builder, so the parsed database may be destroyed.
|
|
`TreeNode::GetParent()` is non-owning and `GetRow()` gives the zero-based sibling
|
|
position (zero for a detached node/root). Nodes cannot be copied or moved;
|
|
transferring their `unique_ptr` ownership preserves addresses and navigation.
|
|
|
|
Call `SetTree()` on the model's thread. It accepts a detached Root or nullptr,
|
|
uses `beginResetModel()`/`endResetModel()`, and destroys the previous tree during
|
|
reset. All ordinary QModelIndex values expire on reset and must not be reused;
|
|
QPersistentModelIndex values are invalidated by Qt. Destroying the model also
|
|
invalidates indexes. Invalid and foreign indexes return no data/flags/children.
|
|
Valid items are enabled and selectable, with no editing support.
|
|
|
|
| Column (zero-based enum) | Frame display | Signal display |
|
|
| --- | --- | --- |
|
|
| 0 `Name` | Name | Name |
|
|
| 1 `CanId` | Normalized hexadecimal ID | Empty |
|
|
| 2 `PayloadLength` | Bytes | Empty |
|
|
| 3 `StartBit` | Empty | DBC start bit |
|
|
| 4 `BitLength` | Empty | Bit count |
|
|
| 5 `ByteOrder` | Empty | Intel / Motorola |
|
|
| 6 `Signed` | Empty | Signed / Unsigned |
|
|
| 7 `Unit` | Empty | Unit |
|
|
| 8 `Comment` | Comment | Comment |
|
|
|
|
Horizontal DisplayRole headers describe these columns; other header requests
|
|
return invalid QVariant. ToolTipRole returns the comment on every column.
|
|
Custom roles also apply on every column; inapplicable roles return invalid QVariant:
|
|
|
|
| Role | Node types | QVariant content |
|
|
| --- | --- | --- |
|
|
| `NodeTypeRole` | Frame, signal | int corresponding to NodeType |
|
|
| `CanIdRole`, `PayloadLengthRole`, `ExtendedRole` | Frame | uint, int, bool |
|
|
| `StartBitRole`, `BitLengthRole` | Signal | uint |
|
|
| `LittleEndianRole`, `SignedRole` | Signal | bool |
|
|
| `UnitRole` | Signal | QString |
|
|
| `CommentRole` | Frame, signal | QString |
|
|
| `ValueDescriptionsRole` | Signal | QVariantList of QVariantMap entries |
|
|
|
|
Each value-description entry contains `value` (qlonglong for negative keys,
|
|
qulonglong for nonnegative keys) and `description` (QString). This preserves the
|
|
full signed/unsigned 64-bit range without converting through double. An unmapped
|
|
signal returns an empty list. Data comes from `SignalInfo::valueDescriptions`.
|
|
Unsupported roles return invalid QVariant.
|
|
|
|
### Standalone tree tests without Qt
|
|
|
|
From the repository root, use only a C++17 compiler:
|
|
|
|
```sh
|
|
c++ -std=c++17 -Icore tests/tree_node_test.cpp core/tree_node.cpp \
|
|
core/dbc_tree_builder.cpp -o /tmp/dbc_tree_node_test
|
|
/tmp/dbc_tree_node_test
|
|
```
|
|
|
|
### Build and run the demo
|
|
|
|
After building core, run these commands from the repository root:
|
|
|
|
```sh
|
|
mkdir -p build/examples
|
|
cd build/examples
|
|
qmake6 ../../examples/demo.pro DBC_CORE_BUILD_DIR=../core
|
|
make -j
|
|
./dbc_demo /absolute/path/to/example.dbc
|
|
```
|
|
|
|
The demo prints the parsed tree and decodes eight zero bytes using the first
|
|
frame definition. Replace that sample payload in `examples/main.cpp` to feed
|
|
live CAN data or trace records.
|
|
|
|
### Generate API documentation
|
|
|
|
With Doxygen installed, run from the repository root:
|
|
|
|
```sh
|
|
doxygen Doxyfile
|
|
```
|
|
|
|
Open `docs/html/index.html` to view the generated reference.
|
|
|
|
---
|
|
|
|
## Regression tests
|
|
|
|
See [tests/README.md](tests/README.md) for QtTest build and execution commands.
|
|
Tests cover frame validation and error locations, Intel and Motorola extraction,
|
|
signal syntax and payload bounds, `VAL_` syntax and reference validation,
|
|
raw description lookup and preservation through both builders, statement dispatch, signed and unsigned
|
|
64-bit boundaries, physical conversion, numeric output, and invalid signal
|
|
payloads.
|
|
|
|
## Measuring code coverage
|
|
|
|
Coverage instrumentation is separate from the QtTest suite named `CoverageTest`:
|
|
that suite runs extra regression checks, but coverage counters require a specially
|
|
instrumented build. Use a separate build directory for each coverage tool.
|
|
|
|
### GCC, gcov, and LCOV on Debian/Ubuntu
|
|
|
|
Install the build and report tools:
|
|
|
|
```sh
|
|
sudo apt install build-essential qt6-base-dev qt6-base-dev-tools qmake6 lcov
|
|
# Optional IDE:
|
|
sudo apt install qtcreator
|
|
```
|
|
|
|
GCC supplies `gcov`; the `lcov` package supplies `lcov` and `genhtml`. Use the
|
|
`gcov` version matching the GCC used by the selected Qt Creator kit. These
|
|
instructions use GCC and Qt 6.8.2.
|
|
|
|
From the repository root, create a fresh coverage build. `--coverage` must be
|
|
passed to both compilation and linking; Debug avoids optimization that can make
|
|
source-level results harder to interpret. `-fprofile-abs-path` makes source paths
|
|
in the coverage data independent of the compilation working directory.
|
|
|
|
```sh
|
|
DBC_SOURCE_DIR="$(pwd)"
|
|
mkdir -p build-coverage
|
|
cd build-coverage
|
|
|
|
qmake6 "$DBC_SOURCE_DIR/dbc.pro" \
|
|
CONFIG+=dbc_qt CONFIG+=debug CONFIG-=release \
|
|
'QMAKE_CXXFLAGS+=--coverage -fprofile-abs-path' \
|
|
'QMAKE_LFLAGS+=--coverage'
|
|
make -j4
|
|
|
|
# The standalone tree test is not a qmake target; instrument it separately.
|
|
mkdir -p standalone
|
|
c++ -std=c++17 -O0 -g --coverage -fprofile-abs-path \
|
|
-I"$DBC_SOURCE_DIR/core" \
|
|
"$DBC_SOURCE_DIR/tests/tree_node_test.cpp" \
|
|
"$DBC_SOURCE_DIR/core/tree_node.cpp" \
|
|
"$DBC_SOURCE_DIR/core/dbc_tree_builder.cpp" \
|
|
-o standalone/tree_node_tests
|
|
|
|
# Capture a zero-count baseline so unexecuted compiled code remains in the report.
|
|
lcov --capture --initial --directory . --branch-coverage \
|
|
--include "$DBC_SOURCE_DIR/core/*" --include "$DBC_SOURCE_DIR/qt/*" \
|
|
--output-file baseline.info
|
|
|
|
# Reset counts before each new measurement; otherwise executions accumulate.
|
|
lcov --zerocounters --directory .
|
|
make check
|
|
./standalone/tree_node_tests
|
|
|
|
lcov --capture --directory . --branch-coverage \
|
|
--include "$DBC_SOURCE_DIR/core/*" --include "$DBC_SOURCE_DIR/qt/*" \
|
|
--output-file executed.info
|
|
lcov --add-tracefile baseline.info --add-tracefile executed.info \
|
|
--branch-coverage --output-file coverage.info
|
|
|
|
genhtml coverage.info --branch-coverage --demangle-cpp --output-directory html
|
|
lcov --summary coverage.info --branch-coverage
|
|
```
|
|
|
|
Open `build-coverage/html/index.html` in a browser. The report includes the
|
|
library's `core/` and `qt/` sources and instantiated header code; test sources,
|
|
generated moc files, and Qt/STL headers are excluded. Click a filename to inspect
|
|
execution counts, uncovered lines, and branch outcomes. Branch coverage is a
|
|
separate metric: executing a line does not necessarily exercise every outcome.
|
|
C++ branch data can also include compiler-generated exception paths.
|
|
|
|
For another run with unchanged sources, repeat the reset, test execution,
|
|
capture, merge, and HTML generation steps. After changing sources or compiler
|
|
flags, rebuild and regenerate `baseline.info` before measuring again. A fresh
|
|
build directory avoids mixing old profiling data with a different build.
|
|
|
|
An initial measurement with GCC 14.2 and Qt 6.8.2 on 2026-10-07, including both
|
|
QtTest executables and the standalone tree test, yielded 97.8% line coverage
|
|
(724/740) and 70.7% branch coverage (833/1178). These are a snapshot, not fixed
|
|
expected values for later changes or other toolchains.
|
|
|
|
After adding direct comment-handler and ambiguous `VAL_` reference tests, the
|
|
same configuration measured 98.4% line coverage (728/740) and 71.4% branch
|
|
coverage (841/1178).
|
|
|
|
See [GCC instrumentation options](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html)
|
|
and the [LCOV project documentation](https://github.com/linux-test-project/lcov).
|
|
|
|
### Configure the GCC coverage build in Qt Creator
|
|
|
|
1. Open `dbc.pro` and select a GCC kit with Qt 6.8.2.
|
|
2. In **Projects > Build Settings**, clone **Debug** as **Coverage**.
|
|
3. Choose a separate build directory, such as `build-coverage`.
|
|
4. Expand the **qmake** build step and add these **Additional arguments**:
|
|
|
|
```text
|
|
CONFIG+=dbc_qt CONFIG+=debug CONFIG-=release "QMAKE_CXXFLAGS+=--coverage -fprofile-abs-path" "QMAKE_LFLAGS+=--coverage"
|
|
```
|
|
|
|
5. Run qmake and build the project. Both `dbc_tests` and `dbc_model_tests` must
|
|
run to include coverage from the core and optional model adapter.
|
|
6. In a terminal in the coverage build directory, compile the standalone test
|
|
and run the LCOV/report commands above. Set `DBC_SOURCE_DIR` to the absolute
|
|
repository path if using a new terminal. `make check` runs both QtTest
|
|
executables; the standalone tree executable must be run separately.
|
|
7. View `html/index.html` in a browser. This workflow produces HTML reports;
|
|
it does not require a licensed coverage toolchain.
|
|
|
|
### Configure all build variants in Qt Creator
|
|
|
|
The local `dbc.pro.user` stores machine-specific kit IDs and build/run settings.
|
|
It is ignored by Git (`*.user`) and should not be committed or copied between
|
|
machines. Recreate the configurations below through the UI when setting up a
|
|
new checkout. The `Qt4ProjectManager` identifiers inside `.user` are historical
|
|
Qt Creator setting names; the project still requires Qt 6.8.2.
|
|
|
|
The local configuration set uses these independent build directories:
|
|
|
|
| Configuration | Build directory example | Purpose |
|
|
| --- | --- | --- |
|
|
| Debug | `/tmp/build/dbc/Desktop-Debug` | Core library and parser/decoder/coverage regression tests |
|
|
| Release | `/tmp/build/dbc/Desktop-Release` | The same targets with release settings |
|
|
| Profile | `/tmp/build/dbc/Desktop-Profile` | Existing Qt Creator profiling configuration; not code coverage |
|
|
| Debug Qt Models | `/tmp/build/dbc/Desktop-Debug-Qt-Models` | Debug targets plus optional adapter and model tests |
|
|
| Release Qt Models | `/tmp/build/dbc/Desktop-Release-Qt-Models` | Release targets plus optional adapter and model tests |
|
|
| Debug Qt Models Strict | `/tmp/build/dbc/Desktop-Debug-Qt-Models-Strict` | Model build with `QT_FATAL_WARNINGS=1` for builds and runs |
|
|
| Coverage | `/tmp/build/dbc/Desktop-Coverage` | Instrumented core/model build, test execution, and LCOV HTML report |
|
|
| Core Only | `/tmp/build/dbc/Desktop-Core-Only` | The Qt-free core static library |
|
|
| Tree Tests | `/tmp/build/dbc/Desktop-Tree-Tests` | Standalone C++17 tree test without Qt |
|
|
| Demo | `/tmp/build/dbc/Desktop-Demo` | Core library and command-line demo |
|
|
|
|
The temporary paths are examples; choose permanent build directories if desired.
|
|
Do not share a build directory between these configurations.
|
|
|
|
To add the ordinary qmake configurations:
|
|
|
|
1. Open `dbc.pro` with a GCC/Qt 6.8.2 kit.
|
|
2. Go to **Projects > Build Settings**. Keep the existing Debug, Release, and
|
|
Profile configurations. Remove or disable a Coco build step if one remains.
|
|
3. Select Debug, press **Clone**, and name the clone **Debug Qt Models**. Set
|
|
its build directory from the table and add `CONFIG+=dbc_qt` to the qmake
|
|
step's **Additional arguments**.
|
|
4. Clone Release as **Release Qt Models** with the same qmake argument and its
|
|
own build directory.
|
|
5. Clone Debug Qt Models as **Debug Qt Models Strict**. In **Build Environment**,
|
|
add `QT_FATAL_WARNINGS=1`. Run configurations should use the build environment.
|
|
6. Clone Debug Qt Models as **Coverage**. In qmake **Additional arguments**, use:
|
|
|
|
```text
|
|
CONFIG+=dbc_qt CONFIG+=debug CONFIG-=release "QMAKE_CXXFLAGS+=--coverage -fprofile-abs-path" "QMAKE_LFLAGS+=--coverage"
|
|
```
|
|
|
|
7. For Coverage, select **Add Build Step > Custom Process Step** after qmake
|
|
and Make, then set:
|
|
|
|
| Field | Value |
|
|
| --- | --- |
|
|
| Command | `/bin/sh` |
|
|
| Arguments | `"%{sourceDir}/tests/coverage_report.sh" "%{sourceDir}" "%{buildDir}"` |
|
|
| Working directory | `%{buildDir}` |
|
|
|
|
This script compiles the standalone tree test, resets counters, runs both
|
|
QtTest executables and the tree test, and generates `%{buildDir}/html/index.html`.
|
|
It stops if any test fails. Therefore building Coverage also executes tests.
|
|
`lcov` and `genhtml` must be installed. Use this configuration with the GCC kit;
|
|
`gcov` and the compiler used for the standalone test must match that kit.
|
|
8. Run qmake and build each new configuration once. The default root builds
|
|
expose `dbc_tests`; model configurations also expose `dbc_model_tests`.
|
|
|
|
Qt Creator expands `%{sourceDir}` and `%{buildDir}` in custom steps to the source
|
|
and active build directories. Use the field's variable chooser if needed.
|
|
|
|
For the three independent builds, clone Debug, assign the name and build
|
|
directory from the table, and replace its qmake/Make build steps with the custom
|
|
process steps below. These steps are local IDE settings and do not change
|
|
`dbc.pro`. Give each step a descriptive name and retain the listed order.
|
|
|
|
For **Core Only**:
|
|
|
|
| Command | Arguments | Working directory |
|
|
| --- | --- | --- |
|
|
| `/usr/bin/mkdir` | `-p "%{buildDir}"` | `%{sourceDir}` |
|
|
| `/usr/bin/qmake6` | `"%{sourceDir}/core/core.pro" CONFIG+=debug CONFIG-=release` | `%{buildDir}` |
|
|
| `/usr/bin/make` | `-j4` | `%{buildDir}` |
|
|
|
|
Keep a clean step with `make clean` in `%{buildDir}`. Core Only creates a static
|
|
library, so there is no executable to run.
|
|
|
|
For **Tree Tests**:
|
|
|
|
1. Add a custom step with command `/usr/bin/mkdir`, arguments `-p "%{buildDir}"`,
|
|
and working directory `%{sourceDir}`.
|
|
2. Add a custom step with command `/usr/bin/c++`, working directory `%{buildDir}`,
|
|
and these arguments:
|
|
|
|
```text
|
|
-std=c++17 -O0 -g -I"%{sourceDir}/core" "%{sourceDir}/tests/tree_node_test.cpp" "%{sourceDir}/core/tree_node.cpp" "%{sourceDir}/core/dbc_tree_builder.cpp" -o "%{buildDir}/tree_node_tests"
|
|
```
|
|
|
|
3. Remove the default Make clean step: this direct compiler build creates no
|
|
Makefile. Run the resulting executable as described below.
|
|
|
|
For **Demo**:
|
|
|
|
| Command | Arguments | Working directory |
|
|
| --- | --- | --- |
|
|
| `/usr/bin/mkdir` | `-p "%{buildDir}/core" "%{buildDir}/examples"` | `%{sourceDir}` |
|
|
| `/usr/bin/qmake6` | `"%{sourceDir}/core/core.pro" CONFIG+=debug CONFIG-=release` | `%{buildDir}/core` |
|
|
| `/usr/bin/make` | `-j4` | `%{buildDir}/core` |
|
|
| `/usr/bin/qmake6` | `"%{sourceDir}/examples/demo.pro" "DBC_CORE_BUILD_DIR=%{buildDir}/core" CONFIG+=debug CONFIG-=release` | `%{buildDir}/examples` |
|
|
| `/usr/bin/make` | `-j4` | `%{buildDir}/examples` |
|
|
|
|
Replace the default clean step with two custom steps: `/usr/bin/make` with
|
|
arguments `clean`, first in `%{buildDir}/examples`, then in `%{buildDir}/core`.
|
|
|
|
To configure execution and switch variants:
|
|
|
|
1. Go to **Projects > Run Settings** and select the appropriate qmake target:
|
|
`dbc_tests` for ordinary builds or `dbc_model_tests` for model builds.
|
|
2. To run all QtTest suites together, add a **Custom Executable** named
|
|
**All QtTest suites**, with executable `/usr/bin/make`, arguments `check`,
|
|
and working directory `%{buildDir}`. Use it with an ordinary or model qmake
|
|
configuration. Coverage already runs every test in its final build step.
|
|
3. For Tree Tests, add a **Custom Executable** named **Standalone tree test**,
|
|
executable `%{buildDir}/tree_node_tests`, working directory `%{buildDir}`,
|
|
and no arguments.
|
|
4. For Demo, add a **Custom Executable** named **Demo**, executable
|
|
`%{buildDir}/examples/dbc_demo`, working directory `%{buildDir}/examples`.
|
|
Set **Command line arguments** to the quoted absolute path of your DBC file.
|
|
5. For Coverage on Linux, optionally add a **Custom Executable** named
|
|
**Coverage HTML report**, executable `/usr/bin/xdg-open`, arguments
|
|
`"%{buildDir}/html/index.html"`, working directory `%{buildDir}`. Running it
|
|
opens the generated report in the default browser.
|
|
6. Use the kit/build/run selector above the Run button to choose the build
|
|
configuration and its corresponding executable. Build with **Ctrl+B**,
|
|
then run with **Ctrl+R**. The Build Settings configuration list can also
|
|
switch the active build.
|
|
7. If `.user` was edited externally, close and reopen the project so Qt Creator
|
|
reads the new settings. Do not edit it while the project is open, because
|
|
Qt Creator can overwrite external changes when saving its session.
|
|
|
|
See Qt Creator's [build configuration guide](https://doc.qt.io/qtcreator/creator-build-settings.html)
|
|
and [run configuration guide](https://doc.qt.io/qtcreator/creator-run-settings.html).
|