Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 97 additions & 1 deletion docs/dmd-file-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Lines starting with `#` are treated as comments and ignored:

### Module Entries

Modules are specified by name, optionally followed by a version or version constraint:
Modules are specified by name, optionally followed by a version or version constraint, and optionally a configuration file path:

```dmd
# Module without version (downloads latest available)
Expand All @@ -35,6 +35,13 @@ dmffs
driver@1.0
spi@2.5.1

# Module with configuration file
dmclk@1.0 mcu/stm32f7.ini
uart@2.5 configs/uart_115200.ini

# Module with configuration but no version
gpio configs/gpio_default.cfg

# Module with version range - all versions >= 1.0
dmffs@>=1.0

Expand All @@ -49,6 +56,85 @@ i2c@>1.0 # Greater than 1.0 (exclusive)
can@<2.0 # Less than 2.0 (exclusive)
```

#### Configuration Files

Modules can optionally specify a configuration file that should be copied during installation. The configuration file path is relative to the module's configuration directory (as defined in the module's `.dmr` file, or the default `config/` directory).

**Format**: `module[@version] [config_path] [custom_dest_name]`

**Basic usage**:
```dmd
# Install dmclk version 1.0 with mcu/stm32f7.ini configuration
# Copies to: <config-dir>/dmclk/stm32f7.ini
dmclk@1.0 mcu/stm32f7.ini

# Install uart with configuration but use latest version
uart configs/uart_default.ini
```

**Custom destination filename**:
```dmd
# Specify custom destination filename (no module subdirectory)
# Copies to: <config-dir>/clk.ini
dmclk@1.0 mcu/stm32f7.ini clk.ini
```

**Multiple configurations from same driver**:
```dmd
# Use driver version 1.0, but copy configs from different versions
dmclk@1.0 # Install driver v1.0
dmclk@0.1 mcu/stm32f7.ini # Copy config from v0.1 to dmclk/stm32f7.ini
dmclk@0.2 mcu/high-speed.ini # Copy config from v0.2 to dmclk/high-speed.ini
```

This allows using a specific driver version while accessing configuration files from multiple versions of the module.

**Variable substitution in configuration paths**:

Configuration paths support variable substitution using `${VARIABLE_NAME}` syntax. Variables can be defined via command-line options or environment variables:

```dmd
# Use variable in config path
dmclk@1.0 boards/${BOARD}/config.ini

# Variable in both config path and destination
uart@2.0 boards/${BOARD}/uart.ini ${BOARD}_uart.ini
```

Command-line usage:
```bash
# Define variables with -D or --define
dmf-get -d project-deps.dmd --config-dir ./config -D BOARD=stm32f7

# Multiple variables
dmf-get -d deps.dmd --config-dir ./config -D BOARD=stm32f7 -D VERSION=v1
```

Variables are substituted in:
- Configuration file paths (source)
- Custom destination filenames

Variable lookup order:
1. User-defined variables (via `-D` option)
2. Environment variables

If a variable is not found, the original `${VAR}` syntax is kept in the path.

To copy the configuration files during installation, use the `--config-dir` option with dmf-get:

```bash
# Install modules and copy configuration files to ./config directory
dmf-get -d project-deps.dmd --config-dir ./config
```

**Configuration file lookup**:
1. The path specified in the module's `.dmr` file with the `config` or `configs` resource key
2. If not found, the default location: `<module_install_dir>/<module_name>/config/<config_path>`

**Destination naming**:
- **Default**: `<config-dir>/<module_name>/<filename>`
- **With custom name**: `<config-dir>/<custom_dest_name>`

#### Version Constraint Syntax

Version constraints support the following operators:
Expand Down Expand Up @@ -95,6 +181,16 @@ module2@2.0 $from https://special-registry.com/manifest.dmm
module3@1.5
```

Configuration files can be combined with inline `$from`:

```dmd
# Module with configuration and custom manifest
dmclk@1.0 mcu/stm32f7.ini $from https://hw-registry.com/manifest.dmm

# Configuration with default manifest
uart@2.5 configs/uart.ini
```

### Include Directive

The `$include` directive allows you to include another `.dmd` file:
Expand Down
23 changes: 23 additions & 0 deletions docs/dmr-file-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,29 @@ api_header=./api.h => ${destination}/${module}/include/api.h
dmd=./module.dmd => ${destination}/${module}.dmd
```

#### Configuration Files

Configuration directories can be specified so that individual configuration files can be copied during module installation:

```dmr
# Configuration directory
config=./configs => ${destination}/${module}/config
# Alternative naming
configs=./config-files => ${destination}/${module}/configs
```

When a module specifies a configuration file in the `.dmd` file (e.g., `dmclk@1.0 mcu/stm32f7.ini`), dmf-get will look for that file in:
1. The path specified by the `config` or `configs` resource in the `.dmr` file
2. If not found, the default location: `${destination}/${module}/config/`

Configuration files are copied when using the `--config-dir` option with dmf-get:

```bash
# Install modules and copy specified configuration files
dmf-get -d project-deps.dmd --config-dir ./project-config
```

#### License and Legal

```dmr
Expand Down
179 changes: 179 additions & 0 deletions tests/lib/tests_dmod_dependencies.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,182 @@ TEST_F(DmodDependenciesTest, ParseEmptyInlineFromDirective) {

Dmod_Dependencies_Free(ctx);
}

// ===============================================================
// Configuration Tests
// ===============================================================

TEST_F(DmodDependenciesTest, ParseModuleWithConfig) {
Dmod_DependenciesContext_t* ctx = Dmod_Dependencies_Init("https://example.com/manifest.dmm", MockDownloadFunc, nullptr);
ASSERT_NE(ctx, nullptr);

const char* dependencies = "dmclk@1.0 mcu/stm32f7.ini\n";

ASSERT_TRUE(Dmod_Dependencies_Parse(ctx, dependencies));
EXPECT_EQ(Dmod_Dependencies_GetEntryCount(ctx), 1);

Dmod_DependencyEntry_t entry;
ASSERT_TRUE(Dmod_Dependencies_GetEntry(ctx, 0, &entry));
EXPECT_STREQ(entry.name, "dmclk");
EXPECT_STREQ(entry.version, "1.0");
EXPECT_STREQ(entry.config, "mcu/stm32f7.ini");
EXPECT_STREQ(entry.manifest, "https://example.com/manifest.dmm");

Dmod_Dependencies_Free(ctx);
}

TEST_F(DmodDependenciesTest, ParseModuleWithoutVersionButWithConfig) {
Dmod_DependenciesContext_t* ctx = Dmod_Dependencies_Init("https://example.com/manifest.dmm", MockDownloadFunc, nullptr);
ASSERT_NE(ctx, nullptr);

const char* dependencies = "dmclk configs/default.ini\n";

ASSERT_TRUE(Dmod_Dependencies_Parse(ctx, dependencies));
EXPECT_EQ(Dmod_Dependencies_GetEntryCount(ctx), 1);

Dmod_DependencyEntry_t entry;
ASSERT_TRUE(Dmod_Dependencies_GetEntry(ctx, 0, &entry));
EXPECT_STREQ(entry.name, "dmclk");
EXPECT_STREQ(entry.version, "");
EXPECT_STREQ(entry.config, "configs/default.ini");
EXPECT_STREQ(entry.manifest, "https://example.com/manifest.dmm");

Dmod_Dependencies_Free(ctx);
}

TEST_F(DmodDependenciesTest, ParseMultipleModulesWithConfig) {
Dmod_DependenciesContext_t* ctx = Dmod_Dependencies_Init("https://example.com/manifest.dmm", MockDownloadFunc, nullptr);
ASSERT_NE(ctx, nullptr);

const char* dependencies =
"dmclk@1.0 mcu/stm32f7.ini\n"
"uart@2.5 uart/config.ini\n"
"spi\n" // No config
"i2c@1.2\n"; // No config

ASSERT_TRUE(Dmod_Dependencies_Parse(ctx, dependencies));
EXPECT_EQ(Dmod_Dependencies_GetEntryCount(ctx), 4);

Dmod_DependencyEntry_t entry;

// First module with config
ASSERT_TRUE(Dmod_Dependencies_GetEntry(ctx, 0, &entry));
EXPECT_STREQ(entry.name, "dmclk");
EXPECT_STREQ(entry.version, "1.0");
EXPECT_STREQ(entry.config, "mcu/stm32f7.ini");

// Second module with config
ASSERT_TRUE(Dmod_Dependencies_GetEntry(ctx, 1, &entry));
EXPECT_STREQ(entry.name, "uart");
EXPECT_STREQ(entry.version, "2.5");
EXPECT_STREQ(entry.config, "uart/config.ini");

// Third module without config
ASSERT_TRUE(Dmod_Dependencies_GetEntry(ctx, 2, &entry));
EXPECT_STREQ(entry.name, "spi");
EXPECT_STREQ(entry.version, "");
EXPECT_STREQ(entry.config, "");

// Fourth module without config
ASSERT_TRUE(Dmod_Dependencies_GetEntry(ctx, 3, &entry));
EXPECT_STREQ(entry.name, "i2c");
EXPECT_STREQ(entry.version, "1.2");
EXPECT_STREQ(entry.config, "");

Dmod_Dependencies_Free(ctx);
}

TEST_F(DmodDependenciesTest, ParseModuleWithConfigAndInlineFrom) {
Dmod_DependenciesContext_t* ctx = Dmod_Dependencies_Init("https://example.com/manifest.dmm", MockDownloadFunc, nullptr);
ASSERT_NE(ctx, nullptr);

const char* dependencies = "dmclk@1.0 mcu/stm32f7.ini $from https://custom.com/manifest.dmm\n";

ASSERT_TRUE(Dmod_Dependencies_Parse(ctx, dependencies));
EXPECT_EQ(Dmod_Dependencies_GetEntryCount(ctx), 1);

Dmod_DependencyEntry_t entry;
ASSERT_TRUE(Dmod_Dependencies_GetEntry(ctx, 0, &entry));
EXPECT_STREQ(entry.name, "dmclk");
EXPECT_STREQ(entry.version, "1.0");
EXPECT_STREQ(entry.config, "mcu/stm32f7.ini");
EXPECT_STREQ(entry.manifest, "https://custom.com/manifest.dmm");

Dmod_Dependencies_Free(ctx);
}

TEST_F(DmodDependenciesTest, ParseModuleWithConfigAndCustomDestName) {
Dmod_DependenciesContext_t* ctx = Dmod_Dependencies_Init("https://example.com/manifest.dmm", MockDownloadFunc, nullptr);
ASSERT_NE(ctx, nullptr);

const char* dependencies = "dmclk@1.0 mcu/stm32f7.ini clk.ini\n";

ASSERT_TRUE(Dmod_Dependencies_Parse(ctx, dependencies));
EXPECT_EQ(Dmod_Dependencies_GetEntryCount(ctx), 1);

Dmod_DependencyEntry_t entry;
ASSERT_TRUE(Dmod_Dependencies_GetEntry(ctx, 0, &entry));
EXPECT_STREQ(entry.name, "dmclk");
EXPECT_STREQ(entry.version, "1.0");
EXPECT_STREQ(entry.config, "mcu/stm32f7.ini");
EXPECT_STREQ(entry.config_dest, "clk.ini");
EXPECT_STREQ(entry.manifest, "https://example.com/manifest.dmm");

Dmod_Dependencies_Free(ctx);
}

TEST_F(DmodDependenciesTest, ParseModuleWithConfigNoVersionButWithCustomDest) {
Dmod_DependenciesContext_t* ctx = Dmod_Dependencies_Init("https://example.com/manifest.dmm", MockDownloadFunc, nullptr);
ASSERT_NE(ctx, nullptr);

const char* dependencies = "dmclk configs/default.ini my-config.ini\n";

ASSERT_TRUE(Dmod_Dependencies_Parse(ctx, dependencies));
EXPECT_EQ(Dmod_Dependencies_GetEntryCount(ctx), 1);

Dmod_DependencyEntry_t entry;
ASSERT_TRUE(Dmod_Dependencies_GetEntry(ctx, 0, &entry));
EXPECT_STREQ(entry.name, "dmclk");
EXPECT_STREQ(entry.version, "");
EXPECT_STREQ(entry.config, "configs/default.ini");
EXPECT_STREQ(entry.config_dest, "my-config.ini");
EXPECT_STREQ(entry.manifest, "https://example.com/manifest.dmm");

Dmod_Dependencies_Free(ctx);
}

TEST_F(DmodDependenciesTest, ParseMultipleEntriesSameDriverDifferentConfigs) {
Dmod_DependenciesContext_t* ctx = Dmod_Dependencies_Init("https://example.com/manifest.dmm", MockDownloadFunc, nullptr);
ASSERT_NE(ctx, nullptr);

// Test case from user comment: same driver with different versions for configs
const char* dependencies =
"dmclk@1.0\n"
"dmclk@0.1 mcu/stm32f7.ini\n"
"dmclk@0.2 mcu/high-speed.ini\n";

ASSERT_TRUE(Dmod_Dependencies_Parse(ctx, dependencies));
EXPECT_EQ(Dmod_Dependencies_GetEntryCount(ctx), 3);

Dmod_DependencyEntry_t entry;

// First entry: driver only
ASSERT_TRUE(Dmod_Dependencies_GetEntry(ctx, 0, &entry));
EXPECT_STREQ(entry.name, "dmclk");
EXPECT_STREQ(entry.version, "1.0");
EXPECT_STREQ(entry.config, "");

// Second entry: same driver with config from version 0.1
ASSERT_TRUE(Dmod_Dependencies_GetEntry(ctx, 1, &entry));
EXPECT_STREQ(entry.name, "dmclk");
EXPECT_STREQ(entry.version, "0.1");
EXPECT_STREQ(entry.config, "mcu/stm32f7.ini");

// Third entry: same driver with config from version 0.2
ASSERT_TRUE(Dmod_Dependencies_GetEntry(ctx, 2, &entry));
EXPECT_STREQ(entry.name, "dmclk");
EXPECT_STREQ(entry.version, "0.2");
EXPECT_STREQ(entry.config, "mcu/high-speed.ini");

Dmod_Dependencies_Free(ctx);
}
Loading
Loading