From fd88e036edb89277983f009be53a3d32919fb957 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Sat, 11 Apr 2026 13:36:18 +0800 Subject: [PATCH] feat: add cross-compilation support with --target flag - Updated build and run commands to accept a --target option for specifying target platforms. - Enhanced documentation to include cross-compilation usage and supported targets. - Modified configuration to allow target-specific settings and toolchains. - Introduced a new test command to run project tests with specified profiles and targets. - Improved handling of target directories and output structure for different platforms. - Added support for multiple targets in the configuration file. - Implemented inheritance for workspace and dependencies based on target and profile. --- CHANGELOG.md | 27 ++ CONTRIBUTING.md | 21 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 39 +++ docs/SUMMARY.md | 2 + docs/commands/dcr-build.md | 3 + docs/commands/dcr-run.md | 2 + docs/commands/dcr-test.md | 44 +++ docs/configuration/build-section.md | 36 +++ docs/configuration/dcr.toml-overview.md | 7 + .../compilers-and-linking.md | 4 +- .../cross-compilation.md | 201 +++++++++++++ .../target-directory.md | 4 +- docs/getting-started/build-and-run.md | 7 +- docs/getting-started/project-structure.md | 12 +- src/cli/build.rs | 269 +++++++++++++----- src/cli/clean.rs | 2 +- src/cli/flags.rs | 17 +- src/cli/gen.rs | 31 +- src/cli/run.rs | 46 ++- src/core/builder/mod.rs | 2 + src/core/builder/msvc.rs | 14 +- src/core/builder/unix_cc.rs | 6 +- src/core/config.rs | 1 + src/core/deps.rs | 39 ++- src/core/workspace.rs | 29 +- 27 files changed, 749 insertions(+), 120 deletions(-) create mode 100644 docs/commands/dcr-test.md create mode 100644 docs/dependencies-and-build/cross-compilation.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 306e579..439712c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## [0.5.0] - 2026-04-10 + +Added: + +- Full inheritance system for all config sections: `build`, `toolchain`, `run`, `workspace`, `dependencies` + - Order: `section.target.profile` → `section.profile.target` → `section.target` → `section.profile` → `section` + - `inherit = false` to disable inheritance completely + - Target-specific overrides: `[build.linux]`, `[toolchain.windows]`, `[run.macos]`, etc. +- Cross-compilation support with `--target ` + - Full target triples and short names (`linux`, `macos`, `windows`) + - Target-specific config sections: `[build.]`, `[toolchain.]`, `[run.]` +- Workspace and dependencies support target/profile inheritance +- Multiple targets build: `[build.targets]` to build for multiple targets simultaneously +- Target directory structure: `target///` (native or triple) +- Updated documentation with cross-compilation guide and Arch Linux examples + +## [0.4.0] - 2026-04-10 + +Added: + +- Test command: `dcr test` and `dcr tests` + - Automatic compilation and linking for tests without manual configuration + - `dcr test --init` creates test template and header + - Test framework with EXPECT/SKIP macros in `dcr_test.h` +- Improved test integration: added test CLI module and basic test suite +- Documentation updates for testing functionality + ## [0.3.0] - 2026-03-18 Added: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f6fd39..abb508a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,27 @@ cargo check - Keep functions small and focused. - Prefer clear names over clever ones. +## Target System + +DCR supports cross-compilation via `--target ` or short names: + +- `dcr build --target linux --release` (maps to `x86_64-unknown-linux-gnu`) +- `dcr build --target macos --release` (maps to `x86_64-apple-darwin`) +- `dcr build --target windows --release` (maps to `x86_64-pc-windows-msvc`) +- Full triples: `dcr build --target aarch64-linux-gnu --release` +- Configure multiple targets in `dcr.toml`: + ``` + [build.targets] + targets = ["linux", "macos"] + + [build.linux] + compiler = "gcc" + + [build.macos] + compiler = "clang" + ``` +- If no `--target`, builds for targets in `build.targets` or native if empty. + ## Checks Before PR, run: diff --git a/Cargo.lock b/Cargo.lock index 4ceeb9d..a9f2b80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,7 +90,7 @@ dependencies = [ [[package]] name = "dcr" -version = "0.4.0" +version = "0.5.0" dependencies = [ "glob", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index f02502e..9482cd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dcr" -version = "0.4.0" +version = "0.5.0" edition = "2024" build = "build.rs" diff --git a/README.md b/README.md index 6a89ae2..5cce7b7 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ The current implementation is written in Rust. - Build static and shared libraries - ASM projects with NASM/GAS or via GCC/Clang - Mixed language projects (`language = ["c", "asm"]` and similar) +- Cross-compilation with `--target` (short names: `linux`, `macos`, `windows`) +- Target-specific configurations and inheritance - Update the binary via `dcr --update` (GitHub Releases, not for pacman/AUR installs) ## Supported Platforms @@ -119,6 +121,16 @@ Run manually: Removes the `target` directory in the project root. Use `dcr clean --all` in a workspace root to clean all member projects. +### `dcr gen` +Generates IDE integration files and build tools. +- `dcr gen vscode`: VS Code workspace config +- `dcr gen clion`: CLion project files +- `dcr gen compile-commands`: `compile_commands.json` for clang tools + +### `dcr test` +Runs the test suite. +Use `dcr test --init` to create test files. + ## Build Profiles Two profiles are supported: - `--debug` (default) - built-in flags per compiler @@ -136,6 +148,33 @@ Use `build.roots` and `build.src_disable` to replace the default `src/` root. Use `build.steps` and `build.post_steps` to run custom commands. Use `build.clean` for extra cleanup paths and `[run].cmd` to override run command. +## Cross-compilation + +DCR supports cross-compilation via `--target ` or short names: + +```sh +dcr build --target linux --release # x86_64-unknown-linux-gnu +dcr build --target macos --release # x86_64-apple-darwin +dcr build --target windows --release # x86_64-pc-windows-msvc +dcr run --target aarch64-linux-gnu +``` + +Configure multiple targets and target-specific settings in `dcr.toml`: + +```toml +[build.targets] +targets = ["linux", "windows"] + +[build.linux] +compiler = "gcc" +cflags = ["-O2"] + +[build.windows] +compiler = "x86_64-w64-mingw32-gcc" +``` + +See [Cross-compilation guide](docs/dependencies-and-build/cross-compilation.md) for details. + ## Configuration The main project file is `dcr.toml`. diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 93b36a1..1ab1d6d 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -25,6 +25,7 @@ * [dcr run](commands/dcr-run.md) * [dcr clean](commands/dcr-clean.md) * [dcr gen](commands/dcr-gen.md) +* [dcr test](commands/dcr-test.md) ## Dependencies & Build @@ -32,6 +33,7 @@ * [Local dependencies](dependencies-and-build/local-dependencies.md) * [Versioning & lock file](dependencies-and-build/versioning-and-lock-file.md) * [Target directory](dependencies-and-build/target-directory.md) +* [Cross-compilation and targets](dependencies-and-build/cross-compilation.md) * [Compilers & linking](dependencies-and-build/compilers-and-linking.md) ## Reference & Help diff --git a/docs/commands/dcr-build.md b/docs/commands/dcr-build.md index e8029f2..01e64a9 100644 --- a/docs/commands/dcr-build.md +++ b/docs/commands/dcr-build.md @@ -8,6 +8,7 @@ Builds the project in `debug` or `release` profile. dcr build dcr build --debug dcr build --release +dcr build --target linux --release dcr build --release --force dcr build --debug --clean ``` @@ -54,6 +55,8 @@ dcr build --debug --clean - Profile flag (`--debug` / `--release`) can appear in any argument position (duplicates are rejected). - Unknown profile flags return an error. +- `--target ` builds for specified target (short names: `linux`, `macos`, `windows`). +- If no `--target`, builds for `build.targets` or native. - Incremental rebuild checks source/object mtime and tracked header dependencies. - `--force` skips build cache checks and recompiles. - `--clean` removes `target/` and `build.clean` paths before building. diff --git a/docs/commands/dcr-run.md b/docs/commands/dcr-run.md index 335488a..b5f09f7 100644 --- a/docs/commands/dcr-run.md +++ b/docs/commands/dcr-run.md @@ -8,6 +8,7 @@ Builds the project and runs the resulting binary. dcr run dcr run --debug dcr run --release +dcr run --target linux --release dcr run --release --force dcr run --debug --clean ``` @@ -24,6 +25,7 @@ dcr run --debug --clean - If `build.kind = "staticlib"` or `build.kind = "sharedlib"`, `run` exits with error. - The profile is selected from the first argument. +- `--target ` runs the binary for specified target (short names: `linux`, `macos`, `windows`). - `--force` skips build cache checks and recompiles. - `--clean` removes `target/` and `build.clean` paths before building. diff --git a/docs/commands/dcr-test.md b/docs/commands/dcr-test.md new file mode 100644 index 0000000..01d9011 --- /dev/null +++ b/docs/commands/dcr-test.md @@ -0,0 +1,44 @@ +# dcr test + +Runs the project's test suite. + +## Usage + +```sh +dcr test +dcr test --release +dcr test --target linux +dcr test --init +``` + +## What `test` does + +1. Builds the project with the specified profile/target. +2. Compiles test files in `tests/` (e.g., `tests/test.c`) into a test binary. +3. Runs the test binary and parses output for PASS/SKIP/FAIL results. +4. Reports summary with colored output. + +## Test Structure + +DCR uses a simple test framework: + +- Header: `tests/dcr_test.h` (generated by `dcr test --init`) +- Test file: `tests/test.c` (template generated by `dcr test --init`) +- Macros: `TEST(name)`, `TEST_CASE(name)`, `EXPECT(condition)`, `SKIP(reason)` +- Output: `[PASS] test_name`, `[FAIL] test_name`, or `[SKIP] test_name` + +## Config values used + +- `package.name` +- `build.compiler`, `build.language`, `build.standard` +- `build.cflags`, `build.ldflags` +- `build.debug` / `build.release` +- `build.targets` (if specified) + +## Notes + +- Test binary is linked with the main project (for lib projects) or standalone. +- `--init` creates `tests/dcr_test.h` and `tests/test.c` if missing. +- Tests run in release mode by default for performance. +- Custom test logic can be added by editing `tests/test.c`. +- Fails if no `tests/test.c` exists (run `dcr test --init` first). \ No newline at end of file diff --git a/docs/configuration/build-section.md b/docs/configuration/build-section.md index 43d44de..bcedd6d 100644 --- a/docs/configuration/build-section.md +++ b/docs/configuration/build-section.md @@ -9,6 +9,8 @@ standard = "c11" compiler = "clang" kind = "bin" # optional: +# inherit = true # Inherit from base (default true) +# targets = ["linux", "macos"] # Multiple targets to build # target = "./dist" # platform = "x86_64" # cflags = ["-Wall"] @@ -30,6 +32,8 @@ kind = "bin" - `standard` (string, required): language standard passed to compiler. - `compiler` (string, required): compiler command (for example `clang`, `gcc`, `cl`). - `kind` (string, required): `bin`, `staticlib`, or `sharedlib`. +- `inherit` (bool, optional): inherit settings from base `[build]` (default `true`). If `false`, only use target/profile specific settings. +- `targets` (string array, optional): list of targets to build simultaneously. Supports short names (`linux`, `macos`, `windows`). - `target` (string, optional): custom output directory for final artifact. - `platform` (string, optional): architecture hint for compiler (used for `-march` or `/arch`). - `cflags` (string array, optional): extra compile flags. @@ -82,6 +86,15 @@ Step notes: Profile tables inherit all values from `[build]`. Any field present in the profile table replaces the base value. Array fields (like `cflags`/`ldflags`) are appended to the base array. +Target tables override profile and base settings for specific targets. Full inheritance order: +1. `[build..]` +2. `[build..]` +3. `[build.]` +4. `[build.]` +5. `[build]` + +If custom `cflags` are set (non-empty), default compiler flags are disabled. + ```toml [build.debug] cflags = ["-g3"] @@ -89,6 +102,29 @@ cflags = ["-g3"] [build.release] cflags = ["-O3"] ldflags = ["-s"] + +# Target-specific overrides (applied after profile) +[build.linux] +compiler = "gcc" +cflags = ["-march=x86_64"] + +[build.macos] +compiler = "clang" +cflags = ["-arch", "x86_64"] + +# Full inheritance example +[build.linux.release] +cflags = ["-O3", "-march=native"] # Overrides profile and base + +[build.release.linux] +ldflags = ["-static"] # Alternative order + +[build.windows.debug] +compiler = "x86_64-w64-mingw32-gcc" +cflags = [] # Empty to disable defaults + +[build.release] +inherit = false # No inheritance, only explicit settings ``` ## Toolchain section diff --git a/docs/configuration/dcr.toml-overview.md b/docs/configuration/dcr.toml-overview.md index 22f9734..13b8fba 100644 --- a/docs/configuration/dcr.toml-overview.md +++ b/docs/configuration/dcr.toml-overview.md @@ -43,7 +43,10 @@ Workspace-only root: ## Optional keys +- `build.inherit` +- `build.targets` - `build.target` +- `build.platform` - `build.cflags` - `build.ldflags` - `build.exclude` @@ -58,7 +61,11 @@ Workspace-only root: - `build.clean` - `build.debug` - `build.release` +- `build.` (target-specific overrides) - dependency fields (`include`, `lib`, `libs`, `system`) - `[toolchain]` overrides (`cc`, `cxx`, `as`, `ar`, `ld`, `uic`, `moc`, `rcc`) +- `[toolchain.]` (target-specific toolchain) - `[workspace]` members +- `[workspace.]` (target-specific workspace) - `[run]` overrides (`cmd`) +- `[run.]` (target-specific run) diff --git a/docs/dependencies-and-build/compilers-and-linking.md b/docs/dependencies-and-build/compilers-and-linking.md index d24214e..94f5cca 100644 --- a/docs/dependencies-and-build/compilers-and-linking.md +++ b/docs/dependencies-and-build/compilers-and-linking.md @@ -17,7 +17,7 @@ DCR picks a backend from `build.compiler`: - Source files are found recursively in `src/` based on `build.language`. - `build.roots` and `build.src_disable` can replace the default `src/` root. - Mixed language builds use arrays, for example `build.language = ["c", "asm"]`. -- Each source is compiled to `target//obj/.o` (`.obj` on MSVC). +- Each source is compiled to `target///obj/.o` (`.obj` on MSVC, where `` is `native` or target triple). - Recompile happens when source mtime is newer than object mtime. - `build.exclude` removes paths from source/header collection; `build.include` re-allows matching paths and has priority over `exclude`. @@ -35,6 +35,8 @@ MSVC: - `debug`: `/Od /Zi /W4 /DDCR_DEBUG /Oy-` - `release`: `/O2 /DNDEBUG` +Note: If custom `cflags` are set in `dcr.toml`, default profile flags are disabled to allow full control. + ## Platform hint If `build.platform` is set, DCR passes it as an architecture hint: diff --git a/docs/dependencies-and-build/cross-compilation.md b/docs/dependencies-and-build/cross-compilation.md new file mode 100644 index 0000000..c4c4898 --- /dev/null +++ b/docs/dependencies-and-build/cross-compilation.md @@ -0,0 +1,201 @@ +# Cross-compilation and Targets + +DCR supports cross-compilation to different platforms using the `--target` flag. This allows building your project for architectures and operating systems different from the host. + +## Basic Usage + +```bash +# Build for a specific target +dcr build --target x86_64-unknown-linux-gnu --release + +# Short names for common platforms +dcr build --target linux --release # x86_64-unknown-linux-gnu +dcr build --target macos --release # x86_64-apple-darwin +dcr build --target windows --release # x86_64-pc-windows-msvc + +# Run for a specific target +dcr run --target linux --release +``` + +## Supported Targets + +### Short Names +- `linux` → `x86_64-unknown-linux-gnu` +- `macos` → `x86_64-apple-darwin` +- `windows` → `x86_64-pc-windows-msvc` + +### Full Target Triples +DCR supports any valid target triple in the format `---`. Common examples: +- `x86_64-unknown-linux-gnu` +- `aarch64-linux-gnu` +- `x86_64-apple-darwin` +- `aarch64-apple-darwin` +- `x86_64-pc-windows-msvc` +- `i686-pc-windows-gnu` + +## Configuration + +### Multiple Targets + +Configure multiple targets to build simultaneously: + +```toml +[build.targets] +targets = ["linux", "macos", "windows"] + +# Or per profile +[build.release.targets] +targets = ["linux", "macos"] +``` + +If no `--target` is specified, DCR builds for all targets in `build.targets` (or native if empty). + +### Target-Specific Settings + +Override settings for specific targets: + +```toml +[build.linux] +compiler = "gcc" +cflags = ["-Wall", "-O2"] + +[build.macos] +compiler = "clang" +cflags = ["-Wall", "-O2", "-arch", "x86_64"] + +[build.windows] +compiler = "x86_64-w64-mingw32-gcc" +ldflags = ["-static"] +``` + +### Target-Specific Toolchain + +Configure toolchain per target: + +```toml +[toolchain.linux] +cc = "gcc" +ld = "ld" + +[toolchain.macos] +cc = "clang" +ld = "ld" + +[toolchain.windows] +cc = "x86_64-w64-mingw32-gcc" +ld = "x86_64-w64-mingw32-ld" +``` + +### Target-Specific Run Commands + +Use custom run commands per target: + +```toml +[run.linux] +cmd = "./target/linux/release/myapp" + +[run.macos] +cmd = "open ./target/macos/release/myapp.app" + +[run.windows] +cmd = "wine ./target/windows/release/myapp.exe" +``` + +## Workspace and Dependencies Inheritance + +Workspace and dependencies sections also support target/profile inheritance: + +```toml +[workspace.linux] +members = [ + { name = "lib1", path = "libs/lib1" }, +] + +[dependencies.linux] +sdl2 = { system = true, include = ["/usr/include/SDL2"] } + +[dependencies.windows] +sdl2 = { system = true, include = ["C:/SDL2/include"] } +``` + +Order: `workspace.target.profile` → `workspace.profile.target` → `workspace.target` → `workspace.profile` → `workspace`. + +Add `inherit = false` to disable inheritance in any section. + +Settings are applied in order: +1. `[build.]` +2. `[build.]` +3. `[build]` + +### Toolchain Configuration + +For cross-compilation, configure toolchains: + +```toml +[toolchain] +cc = "aarch64-linux-gnu-gcc" +cxx = "aarch64-linux-gnu-g++" +ar = "aarch64-linux-gnu-ar" +ld = "aarch64-linux-gnu-ld" +``` + +## Output Structure + +Build outputs are organized by target: + +``` +target/ +├── native/ +│ ├── debug/ +│ └── release/ +├── x86_64-unknown-linux-gnu/ +│ ├── debug/ +│ └── release/ +└── aarch64-linux-gnu/ + ├── debug/ + └── release/ +``` + +## Requirements + +- Cross-compilation requires appropriate toolchains installed (e.g., `aarch64-linux-gnu-gcc` on Arch Linux). +- For Windows targets on Linux, install MinGW: `pacman -S mingw-w64-gcc`. +- For macOS targets on Linux, use `osxcross` or similar. + +## Examples + +### ARM Linux Build + +```bash +# Install toolchain (Arch Linux) +pacman -S aarch64-linux-gnu-gcc + +# Configure +echo '[build.aarch64-linux-gnu] +compiler = "aarch64-linux-gnu-gcc"' >> dcr.toml + +# Build +dcr build --target aarch64-linux-gnu --release +``` + +### Windows Build on Linux + +```bash +# Install toolchain (Arch Linux) +pacman -S mingw-w64-gcc + +# Configure +echo '[build.windows] +compiler = "x86_64-w64-mingw32-gcc"' >> dcr.toml + +# Build +dcr build --target windows --release +``` + +## Troubleshooting + +- **Toolchain not found**: Ensure the compiler is installed and in PATH, or configure `[toolchain]` in `dcr.toml`. For Arch Linux: `pacman -S aarch64-linux-gnu-gcc` for ARM, `pacman -S mingw-w64-gcc` for Windows cross-compilation. +- **Linking errors**: Check `ldflags` and library paths for cross-compilation. +- **Header issues**: Include paths may differ; use target-specific `cflags`. + +For more help, see [Troubleshooting map](troubleshooting-map.md). \ No newline at end of file diff --git a/docs/dependencies-and-build/target-directory.md b/docs/dependencies-and-build/target-directory.md index c4d333e..18c9ff9 100644 --- a/docs/dependencies-and-build/target-directory.md +++ b/docs/dependencies-and-build/target-directory.md @@ -1,6 +1,6 @@ # Target directory -By default, DCR writes final artifacts to `target//`. +By default, DCR writes final artifacts to `target///` (where `` is `native` or target triple). You can override this with `build.target`: @@ -20,4 +20,4 @@ target = "./dist" ## Important detail -`build.target` changes only the final artifact location. Object cache and dependency sync directories still use `target//...`. +`build.target` changes only the final artifact location. Object cache and dependency sync directories still use `target///...`. diff --git a/docs/getting-started/build-and-run.md b/docs/getting-started/build-and-run.md index 24c1040..470a255 100644 --- a/docs/getting-started/build-and-run.md +++ b/docs/getting-started/build-and-run.md @@ -8,6 +8,7 @@ Debug (default): dcr build # or dcr build --debug +dcr build --target linux --debug dcr build --debug --clean ``` @@ -15,6 +16,7 @@ Release: ```sh dcr build --release +dcr build --target windows --release ``` ## Run @@ -22,7 +24,9 @@ dcr build --release ```sh dcr run # or +dcr run --target macos dcr run --release +dcr run --target linux --release dcr run --release --force ``` @@ -49,4 +53,5 @@ dcr clean --release --all - Build profile flag (`--debug` / `--release`) can be passed in any argument position. - Unknown profile flags produce an error. - In workspace root, `clean --all` cleans all member projects. -- `--clean` for `build`/`run` removes `target/` and paths from `build.clean`. +- `--target` builds/runs for specified target (short names: `linux`, `macos`, `windows`). +- `--clean` for `build`/`run` removes `target//` and paths from `build.clean`. diff --git a/docs/getting-started/project-structure.md b/docs/getting-started/project-structure.md index a077cc5..9292a71 100644 --- a/docs/getting-started/project-structure.md +++ b/docs/getting-started/project-structure.md @@ -31,10 +31,10 @@ project/ - `dcr.toml`: main project configuration. - `dcr.lock`: generated lock file for resolved path dependencies. - `src/`: recursively scanned source directory. -- `target//obj/`: cached object files for incremental rebuild. -- `target//deps/`: synchronized copies of path dependencies. -- `target//` (or `.exe` on Windows): output for `kind = "bin"`. -- `target//lib.a` (or `.lib` on Windows): output for `kind = "staticlib"`. -- `target//lib.so` (or `.dylib` on macOS, `.dll` on Windows): output for `kind = "sharedlib"`. +- `target///obj/`: cached object files for incremental rebuild (where `` is `native` or target triple). +- `target///deps/`: synchronized copies of path dependencies. +- `target///` (or `.exe` on Windows): output for `kind = "bin"`. +- `target///lib.a` (or `.lib` on Windows): output for `kind = "staticlib"`. +- `target///lib.so` (or `.dylib` on macOS, `.dll` on Windows): output for `kind = "sharedlib"`. -If `build.target` is set, the final artifact is written to that custom directory, while object cache still stays in `target//obj`. +If `build.target` is set, the final artifact is written to that custom directory, while object cache still stays in `target///obj`. diff --git a/src/cli/build.rs b/src/cli/build.rs index 29a8e67..c7495b8 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -6,7 +6,7 @@ use crate::core::config::Config; use crate::core::deps::resolve_deps; use crate::core::workspace::parse_workspace; use crate::utils::fs::{check_dir, find_project_root}; -use crate::utils::log::error; +use crate::utils::log::{error, warn}; use crate::utils::text::{BOLD_GREEN, colored}; use glob::glob; use sha2::{Digest, Sha256}; @@ -43,7 +43,7 @@ pub fn build(args: &[String]) -> i32 { let _ = clean(&clean_args); } match with_dir(&root, || { - build_from_root(&root, &flags.profile, flags.force) + build_from_root(&root, &flags.profile, flags.target.as_deref(), flags.force) }) { Ok(()) => 0, Err(msg) => { @@ -126,18 +126,41 @@ fn profile_table<'a>(config: &'a Config, profile: &str) -> Option<&'a toml::valu .and_then(|v| v.as_table()) } -fn get_string_with_profile(config: &Config, field: &str, profile: &str) -> String { - let base = get_config_str(config, &format!("build.{field}")); - let Some(table) = profile_table(config, profile) else { - return base; - }; - let value = table.get(field).and_then(|v| v.as_str()).unwrap_or(""); - let trimmed = value.trim(); - if trimmed.is_empty() { - base - } else { - trimmed.to_string() +fn get_config_value_raw(config: &Config, section: &str, field: &str, profile: &str, target: Option<&str>) -> Option { + // Order: target.profile, profile.target, target, profile, base + let keys = [ + target.map(|t| format!("{}.{}.{}.{}", section, normalize_target_os(t), profile, field)), + target.map(|t| format!("{}.{}.{}.{}", section, profile, normalize_target_os(t), field)), + target.map(|t| format!("{}.{}.{}", section, normalize_target_os(t), field)), + Some(format!("{}.{}.{}", section, profile, field)), + Some(format!("{}.{}", section, field)), + ]; + for key in keys.into_iter().flatten() { + if let Some(val) = config.get(&key) { + return Some(val.clone()); + } } + None +} + +fn get_inherit(config: &Config, section: &str, profile: &str, target: Option<&str>) -> bool { + get_config_value_raw(config, section, "inherit", profile, target).and_then(|v| v.as_bool()).unwrap_or(true) +} + +fn get_config_value(config: &Config, section: &str, field: &str, profile: &str, target: Option<&str>) -> Option { + get_config_value_raw(config, section, field, profile, target).and_then(|v| v.as_str().map(|s| s.trim().to_string())).filter(|s| !s.is_empty()) +} + +fn get_string_with_profile_and_target(config: &Config, field: &str, profile: &str, target: Option<&str>) -> String { + if get_inherit(config, "build", profile, target) { + get_config_value(config, "build", field, profile, target).unwrap_or_else(|| get_config_str(config, &format!("build.{field}"))) + } else { + get_config_value(config, "build", field, profile, target).unwrap_or_default() + } +} + +fn get_string_with_profile(config: &Config, field: &str, profile: &str) -> String { + get_string_with_profile_and_target(config, field, profile, None) } fn parse_string_array(value: &toml::Value, key: &str) -> Result, String> { @@ -154,19 +177,73 @@ fn parse_string_array(value: &toml::Value, key: &str) -> Result, Str Ok(out) } +fn get_list_with_profile_and_target( + config: &Config, + field: &str, + profile: &str, + target: Option<&str>, +) -> Result, String> { + let inherit = get_inherit(config, "build", profile, target); + let mut out = if inherit { + get_config_list(config, &format!("build.{field}"))? + } else { + Vec::new() + }; + // Custom from target/profile + if let Some(val) = get_config_value_raw(config, "build", field, profile, target) { + if let Some(_arr) = val.as_array() { + let custom = parse_string_array(&val, &format!("build.{field}"))?; + if inherit { + out.extend(custom); + } else { + out = custom; + } + } + } else if inherit { + // Legacy append + if let Some(table) = profile_table(config, profile) + && let Some(value) = table.get(field) + { + let mut extra = parse_string_array(value, &format!("build.{profile}.{field}"))?; + out.append(&mut extra); + } + if let Some(t) = target { + let normalized_t = normalize_target_os(t); + if let Some(table) = profile_table(config, normalized_t) + && let Some(value) = table.get(field) + { + let mut extra = parse_string_array(value, &format!("build.{normalized_t}.{field}"))?; + out.append(&mut extra); + } + } + } + Ok(out) +} + +#[cfg(test)] +mod tests { + #[test] + fn test_normalize_target_os() { + assert_eq!(super::normalize_target_os("linux"), "x86_64-unknown-linux-gnu"); + assert_eq!(super::normalize_target_os("macos"), "x86_64-apple-darwin"); + assert_eq!(super::normalize_target_os("windows"), "x86_64-pc-windows-msvc"); + assert_eq!(super::normalize_target_os("x86_64-unknown-linux-gnu"), "x86_64-unknown-linux-gnu"); + assert_eq!(super::normalize_target_os("unknown"), "unknown"); + } +} + + + fn get_list_with_profile( config: &Config, field: &str, profile: &str, ) -> Result, String> { - let mut out = get_config_list(config, &format!("build.{field}"))?; - if let Some(table) = profile_table(config, profile) - && let Some(value) = table.get(field) - { - let mut extra = parse_string_array(value, &format!("build.{profile}.{field}"))?; - out.append(&mut extra); - } - Ok(out) + get_list_with_profile_and_target(config, field, profile, None) +} + +fn get_targets(config: &Config, profile: &str) -> Result, String> { + get_list_with_profile(config, "targets", profile) } fn get_bool_with_profile(config: &Config, field: &str, profile: &str, default: bool) -> bool { @@ -274,16 +351,17 @@ fn get_build_steps_with_profile( get_build_steps(config, &format!("build.{field}")) } -fn ensure_target_dirs(items: &[String], profile: &str, target_dir: Option<&str>) { - if let Some(dir) = target_dir { - let _ = fs::create_dir_all(dir); - } +fn ensure_target_dirs(items: &[String], profile: &str, target_dir: Option) { if !items.contains(&"target".to_string()) { let _ = fs::create_dir("./target"); } - let target_items = check_dir(Some("target")).unwrap_or_default(); - if !target_items.contains(&profile.to_string()) { - let _ = fs::create_dir(format!("./target/{profile}")); + if let Some(dir) = &target_dir { + let _ = fs::create_dir_all(dir); + } else { + let target_items = check_dir(Some("target")).unwrap_or_default(); + if !target_items.contains(&profile.to_string()) { + let _ = fs::create_dir(format!("./target/{profile}")); + } } } @@ -302,29 +380,49 @@ fn run_build(ctx: &BuildContext) -> Result { } } -fn build_from_root(root: &Path, profile: &str, force: bool) -> Result<(), String> { +#[allow(unused_variables)] +fn build_from_root(root: &Path, profile: &str, target: Option<&str>, force: bool) -> Result<(), String> { let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?; let project_name = get_config_str(&config, "package.name"); + let project_version = get_config_str(&config, "package.version"); + + let targets_to_build: Vec> = if let Some(t) = target { + vec![Some(normalize_target_os(t).to_string())] + } else { + let config_targets = get_targets(&config, profile)?; + if config_targets.is_empty() { + vec![None] + } else { + config_targets.into_iter().map(|t| Some(normalize_target_os(&t).to_string())).collect() + } + }; + let start_time = Instant::now(); - println!( - " Building project `{}`\n Profile: {}", - colored(&project_name, BOLD_GREEN), - colored(profile, BOLD_GREEN) - ); - if let Some(workspace) = parse_workspace(&config, root)? { - build_workspace(&workspace, profile, force)?; - let excludes: Vec = - workspace.members.iter().map(|m| m.path.clone()).collect(); - build_project_at(root, profile, &excludes, force)?; - let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; - println!( - " {} Build completed successfully in {} seconds", - colored("✔", BOLD_GREEN), - colored(&elapsed.to_string(), BOLD_GREEN) - ); - return Ok(()); + for (i, build_target) in targets_to_build.iter().enumerate() { + if targets_to_build.len() > 1 { + println!( + " Building target {} of {}: {}", + i + 1, + targets_to_build.len(), + build_target.as_ref().map_or("native", |t| t.as_str()) + ); + } else { + println!( + " Building project `{}`\n Profile: {}\n Target: {}", + colored(&project_name, BOLD_GREEN), + colored(profile, BOLD_GREEN), + colored(build_target.as_ref().map_or("native", |t| t.as_str()), BOLD_GREEN) + ); + } + if let Some(workspace) = parse_workspace(&config, profile, build_target.as_deref(), root)? { + build_workspace(&workspace, profile, build_target.as_deref(), force)?; + let excludes: Vec = + workspace.members.iter().map(|m| m.path.clone()).collect(); + build_project_at(root, profile, build_target.as_deref(), &excludes, force)?; + } else { + build_project_at(root, profile, build_target.as_deref(), &[], force)?; + } } - build_project_at(root, profile, &[], force)?; let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; println!( " {} Build completed successfully in {} seconds", @@ -337,10 +435,11 @@ fn build_from_root(root: &Path, profile: &str, force: bool) -> Result<(), String fn build_workspace( workspace: &crate::core::workspace::Workspace, profile: &str, + target: Option<&str>, force: bool, ) -> Result<(), String> { for member in &workspace.members { - build_project_at(&member.path, profile, &[], force)?; + build_project_at(&member.path, profile, target, &[], force)?; } Ok(()) } @@ -348,6 +447,7 @@ fn build_workspace( fn build_project_at( project_root: &Path, profile: &str, + target: Option<&str>, exclude_dirs: &[std::path::PathBuf], force: bool, ) -> Result<(), String> { @@ -358,30 +458,31 @@ fn build_project_at( } let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?; let project_name = get_config_str(&config, "package.name"); - let project_version = get_config_str(&config, "package.version"); - let project_compiler = get_string_with_profile(&config, "compiler", profile); + let project_version = get_config_str(&config, "package.version"); + let build_target_config = get_string_with_profile(&config, "target", profile); + let build_target = target.or(if build_target_config.is_empty() { None } else { Some(build_target_config.as_str()) }); + let project_compiler = get_string_with_profile_and_target(&config, "compiler", profile, build_target); let build_language = get_language_with_profile(&config, profile)?; - let build_standard = get_string_with_profile(&config, "standard", profile); - let build_target = get_string_with_profile(&config, "target", profile); - let build_kind = get_string_with_profile(&config, "kind", profile); - let build_platform = get_string_with_profile(&config, "platform", profile); - let tc_cc = get_config_opt(&config, "toolchain.cc"); - let tc_cxx = get_config_opt(&config, "toolchain.cxx"); - let tc_as = get_config_opt(&config, "toolchain.as"); - let tc_ar = get_config_opt(&config, "toolchain.ar"); - let tc_ld = get_config_opt(&config, "toolchain.ld"); - let tc_uic = get_config_opt(&config, "toolchain.uic"); - let tc_moc = get_config_opt(&config, "toolchain.moc"); - let tc_rcc = get_config_opt(&config, "toolchain.rcc"); - let build_cflags = get_list_with_profile(&config, "cflags", profile)?; - let build_ldflags = get_list_with_profile(&config, "ldflags", profile)?; - let build_excludes = get_list_with_profile(&config, "exclude", profile)?; - let build_includes = get_list_with_profile(&config, "include", profile)?; - let build_roots = get_list_with_profile(&config, "roots", profile)?; + let build_standard = get_string_with_profile_and_target(&config, "standard", profile, build_target); + let build_kind = get_string_with_profile_and_target(&config, "kind", profile, build_target); + let build_platform = get_string_with_profile_and_target(&config, "platform", profile, build_target); + let tc_cc = get_config_value(&config, "toolchain", "cc", profile, build_target).or_else(|| get_config_opt(&config, "toolchain.cc")); + let tc_cxx = get_config_value(&config, "toolchain", "cxx", profile, build_target).or_else(|| get_config_opt(&config, "toolchain.cxx")); + let tc_as = get_config_value(&config, "toolchain", "as", profile, build_target).or_else(|| get_config_opt(&config, "toolchain.as")); + let tc_ar = get_config_value(&config, "toolchain", "ar", profile, build_target).or_else(|| get_config_opt(&config, "toolchain.ar")); + let tc_ld = get_config_value(&config, "toolchain", "ld", profile, build_target).or_else(|| get_config_opt(&config, "toolchain.ld")); + let tc_uic = get_config_value(&config, "toolchain", "uic", profile, build_target).or_else(|| get_config_opt(&config, "toolchain.uic")); + let tc_moc = get_config_value(&config, "toolchain", "moc", profile, build_target).or_else(|| get_config_opt(&config, "toolchain.moc")); + let tc_rcc = get_config_value(&config, "toolchain", "rcc", profile, build_target).or_else(|| get_config_opt(&config, "toolchain.rcc")); + let build_cflags = get_list_with_profile_and_target(&config, "cflags", profile, build_target)?; + let build_ldflags = get_list_with_profile_and_target(&config, "ldflags", profile, build_target)?; + let build_excludes = get_list_with_profile_and_target(&config, "exclude", profile, build_target)?; + let build_includes = get_list_with_profile_and_target(&config, "include", profile, build_target)?; + let build_roots = get_list_with_profile_and_target(&config, "roots", profile, build_target)?; let src_disable = get_bool_with_profile(&config, "src_disable", profile, false); - let build_expects = get_list_with_profile(&config, "expect", profile)?; - let pkg_configs = get_list_with_profile(&config, "pkg_config", profile)?; - let build_generated = get_list_with_profile(&config, "generated", profile)?; + let build_expects = get_list_with_profile_and_target(&config, "expect", profile, build_target)?; + let pkg_configs = get_list_with_profile_and_target(&config, "pkg_config", profile, build_target)?; + let build_generated = get_list_with_profile_and_target(&config, "generated", profile, build_target)?; let build_steps = get_build_steps_with_profile(&config, "steps", profile)?; let build_post_steps = get_build_steps_with_profile(&config, "post_steps", profile)?; @@ -395,9 +496,10 @@ fn build_project_at( let resolved_linker = resolve_tool("DCR_LD", tc_ld.as_deref()); let resolved_archiver = resolve_tool("DCR_AR", tc_ar.as_deref()); - ensure_target_dirs(&items, profile, normalize_target(&build_target)); + let target_dir = build_target.map(|t| format!("target/{t}/{profile}")); + ensure_target_dirs(&items, profile, target_dir); - let resolved = resolve_deps(&config, profile, project_root)?; + let resolved = resolve_deps(&config, profile, build_target, project_root)?; let (resolved_cflags, resolved_ldflags) = resolve_pkg_config_flags(&pkg_configs, &build_cflags, &build_ldflags)?; let mut combined_excludes = Vec::new(); @@ -466,13 +568,15 @@ fn build_project_at( } } + let target_dir_binding = normalize_target(build_target.unwrap_or(""), profile); let ctx = BuildContext { profile, project_name: &project_name, compiler: &resolved_compiler, language: &build_language, standard: &build_standard, - target_dir: normalize_target(&build_target), + target: build_target, + target_dir: target_dir_binding.as_deref(), kind: normalize_kind(&build_kind), platform: normalize_platform(&build_platform), linker: resolved_linker.as_deref(), @@ -556,12 +660,25 @@ where result } -fn normalize_target(target: &str) -> Option<&str> { - let trimmed = target.trim(); +pub fn normalize_target_os(target: &str) -> &str { + match target { + "linux" => "x86_64-unknown-linux-gnu", + "macos" => "x86_64-apple-darwin", + "windows" => "x86_64-pc-windows-msvc", + _ if target.contains('-') => target, // Assume valid triple + _ => { + warn(&format!("Unknown target '{}', using as-is. Supported short names: linux, macos, windows", target)); + target + } + } +} + +fn normalize_target(target: &str, profile: &str) -> Option { + let trimmed = normalize_target_os(target.trim()); if trimmed.is_empty() { None } else { - Some(trimmed) + Some(format!("target/{trimmed}/{profile}")) } } diff --git a/src/cli/clean.rs b/src/cli/clean.rs index c270629..fd79e39 100644 --- a/src/cli/clean.rs +++ b/src/cli/clean.rs @@ -75,7 +75,7 @@ fn parse_clean_flags(args: &[String]) -> Result { fn clean_from_root(root: &Path, flags: &CleanFlags) -> Result<(), String> { let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?; if flags.all - && let Some(workspace) = parse_workspace(&config, root)? + && let Some(workspace) = parse_workspace(&config, flags.profile.as_deref().unwrap_or("debug"), None, root)? { for member in &workspace.members { clean_project_at(&member.path, flags.profile.as_deref())?; diff --git a/src/cli/flags.rs b/src/cli/flags.rs index 9f1e3aa..2c21af0 100644 --- a/src/cli/flags.rs +++ b/src/cli/flags.rs @@ -1,18 +1,21 @@ -use crate::config::{PROFILE, flags}; +use crate::config::{flags, PROFILE}; use crate::utils::log::warn; pub struct BuildRunFlags { pub profile: String, + pub target: Option, pub force: bool, pub clean: bool, } pub fn parse_build_run_flags(args: &[String]) -> Result { let mut profile = PROFILE.to_string(); + let mut target = None; let mut force = false; let mut clean = false; + let mut iter = args.iter(); - for arg in args { + while let Some(arg) = iter.next() { if !arg.starts_with("--") { warn("Unknown argument"); return Err(1); @@ -26,6 +29,15 @@ pub fn parse_build_run_flags(args: &[String]) -> Result { clean = true; continue; } + if candidate == "target" { + if let Some(t) = iter.next() { + target = Some(t.clone()); + } else { + warn("--target requires a value"); + return Err(1); + } + continue; + } if flags(candidate).is_some() { if profile != PROFILE { warn("Duplicate profile flag"); @@ -40,6 +52,7 @@ pub fn parse_build_run_flags(args: &[String]) -> Result { Ok(BuildRunFlags { profile, + target, force, clean, }) diff --git a/src/cli/gen.rs b/src/cli/gen.rs index 9dffc4f..4d78be6 100644 --- a/src/cli/gen.rs +++ b/src/cli/gen.rs @@ -4,7 +4,7 @@ use crate::core::builder::common; use crate::core::config::Config; use crate::core::workspace::parse_workspace; use crate::utils::fs::find_project_root; -use crate::utils::log::error; +use crate::utils::log::{error, warn}; use std::path::{Path, PathBuf}; // ── helpers copied from cli::build (private there) ───────────────────────── @@ -528,13 +528,15 @@ fn collect_project_info_inner(root: &Path, profile: &str) -> Result Result, String> { let mut all = Vec::new(); - if let Ok(Some(ws)) = parse_workspace(&config, root) { + if let Ok(Some(ws)) = parse_workspace(&config, profile, None, root) { for member in &ws.members { match collect_project_info(&member.path, profile) { Ok(info) => all.push(info), @@ -1392,9 +1394,26 @@ fn parse_gen_args(args: &[String]) -> Result<(PathBuf, String), i32> { Ok((root, profile)) } -fn normalize_target(s: &str) -> Option<&str> { - let t = s.trim(); - if t.is_empty() { None } else { Some(t) } +fn normalize_target_os(s: &str) -> &str { + match s { + "linux" => "x86_64-unknown-linux-gnu", + "macos" => "x86_64-apple-darwin", + "windows" => "x86_64-pc-windows-msvc", + _ if s.contains('-') => s, // Assume valid triple + _ => { + warn(&format!("Unknown target '{}', using as-is. Supported short names: linux, macos, windows", s)); + s + } + } +} + +fn normalize_target(s: &str, profile: &str) -> Option { + let trimmed = normalize_target_os(s.trim()); + if trimmed.is_empty() { + None + } else { + Some(format!("target/{trimmed}/{profile}")) + } } fn normalize_kind(s: &str) -> &str { diff --git a/src/cli/run.rs b/src/cli/run.rs index d6c7b06..3aaa4f0 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -4,10 +4,47 @@ use crate::core::config::Config; use crate::core::runner::run_binary; use crate::utils::fs::find_project_root; use crate::utils::log::error; -use crate::utils::text::{BOLD_GREEN, colored}; +use crate::utils::text::{colored, BOLD_GREEN}; use std::path::Path; use std::process::Command; +fn get_run_cmd( + config: &Config, + profile: &str, + target: Option<&str>, + version: &str, +) -> Option { + let base = config.get("run.cmd").and_then(|v| v.as_str()); + let target_cmd = if let Some(t) = target { + let normalized_t = normalize_target_os(t); + config + .get(&format!("run.{}.cmd", normalized_t)) + .or_else(|| config.get(&format!("run.{}.cmd", t))) + .and_then(|v| v.as_str()) + } else { + None + }; + let profile_cmd = config + .get(&format!("run.{}.cmd", profile)) + .and_then(|v| v.as_str()); + let cmd = target_cmd.or(profile_cmd).or(base)?; + let trimmed = cmd.trim(); + if trimmed.is_empty() { + None + } else { + Some(substitute_run_vars(trimmed, profile, version)) + } +} + +fn normalize_target_os(s: &str) -> &str { + match s { + "linux" => "x86_64-unknown-linux-gnu", + "macos" => "x86_64-apple-darwin", + "windows" => "x86_64-pc-windows-msvc", + _ => s, + } +} + pub fn run(args: &[String]) -> i32 { let start_dir = match std::env::current_dir() { Ok(dir) => dir, @@ -59,12 +96,7 @@ pub fn run(args: &[String]) -> i32 { .get("package.version") .and_then(|v| v.as_str()) .unwrap_or(""); - let run_cmd = config - .get("run.cmd") - .and_then(|v| v.as_str()) - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) - .map(|cmd| substitute_run_vars(&cmd, &flags.profile, version)); + let run_cmd = get_run_cmd(&config, &flags.profile, flags.target.as_deref(), version); let kind = build_kind.trim(); if run_cmd.is_none() && (kind == "staticlib" || kind == "sharedlib") { diff --git a/src/core/builder/mod.rs b/src/core/builder/mod.rs index a513912..d491cb3 100644 --- a/src/core/builder/mod.rs +++ b/src/core/builder/mod.rs @@ -10,6 +10,8 @@ pub struct BuildContext<'a> { pub compiler: &'a str, pub language: &'a str, pub standard: &'a str, + #[allow(dead_code)] + pub target: Option<&'a str>, pub target_dir: Option<&'a str>, pub kind: &'a str, pub platform: Option<&'a str>, diff --git a/src/core/builder/msvc.rs b/src/core/builder/msvc.rs index 6c2cd4b..5a49e71 100644 --- a/src/core/builder/msvc.rs +++ b/src/core/builder/msvc.rs @@ -1,5 +1,5 @@ -use crate::core::builder::BuildContext; use crate::core::builder::common; +use crate::core::builder::BuildContext; use crate::platform; use std::fs; use std::path::Path; @@ -66,8 +66,10 @@ pub fn build(ctx: &BuildContext) -> Result { for obj in &objects { cmd.arg(obj); } - for flag in default_flags(ctx.profile) { - cmd.arg(flag); + if ctx.cflags.is_empty() { + for flag in default_flags(ctx.profile) { + cmd.arg(flag); + } } for flag in ctx.cflags { cmd.arg(flag); @@ -217,8 +219,10 @@ fn build_objects( if let Some(flag) = msvc_arch_flag(ctx.platform) { cmd.arg(flag); } - for flag in default_flags(ctx.profile) { - cmd.arg(flag); + if ctx.cflags.is_empty() { + for flag in default_flags(ctx.profile) { + cmd.arg(flag); + } } for flag in ctx.cflags { cmd.arg(flag); diff --git a/src/core/builder/unix_cc.rs b/src/core/builder/unix_cc.rs index 635f66c..e53b282 100644 --- a/src/core/builder/unix_cc.rs +++ b/src/core/builder/unix_cc.rs @@ -155,8 +155,10 @@ fn build_objects( if !ctx.standard.is_empty() && ctx.language.to_lowercase() != "asm" { cmd.arg(format!("-std={}", ctx.standard)); } - for flag in default_flags(ctx.profile) { - cmd.arg(flag); + if ctx.cflags.is_empty() { + for flag in default_flags(ctx.profile) { + cmd.arg(flag); + } } for flag in ctx.cflags { cmd.arg(flag); diff --git a/src/core/config.rs b/src/core/config.rs index 367d181..3223a95 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -300,6 +300,7 @@ impl Config { "generated", "expect", "clean", + "targets", ] { if let Some(val) = table.get(key) { let arr = val.as_array().ok_or_else(|| { diff --git a/src/core/deps.rs b/src/core/deps.rs index 7d90518..60b5694 100644 --- a/src/core/deps.rs +++ b/src/core/deps.rs @@ -23,6 +23,7 @@ pub struct ResolvedDeps { pub fn resolve_deps( config: &Config, profile: &str, + target: Option<&str>, project_root: &Path, ) -> Result { let project_name = config @@ -35,7 +36,7 @@ pub fn resolve_deps( .and_then(|v| v.as_str()) .unwrap_or("") .to_string(); - let deps = parse_dependencies(config)?; + let deps = parse_dependencies(config, profile, target)?; if deps.is_empty() { return Ok(ResolvedDeps { include_dirs: Vec::new(), @@ -124,14 +125,38 @@ struct DepLock { source: String, } -fn parse_dependencies(config: &Config) -> Result, String> { - let deps_val = match config.get("dependencies") { - Some(v) => v, +fn parse_dependencies( + config: &Config, + profile: &str, + target: Option<&str>, +) -> Result, String> { + let mut deps_table = None; + // Order: dependencies.target.profile, dependencies.profile.target, dependencies.target, dependencies.profile, dependencies + let combinations = if let Some(t) = target { + let normalized_t = crate::cli::build::normalize_target_os(t); + vec![ + format!("dependencies.{}.{}", normalized_t, profile), + format!("dependencies.{}.{}", profile, normalized_t), + format!("dependencies.{}", normalized_t), + format!("dependencies.{}", profile), + "dependencies".to_string(), + ] + } else { + vec![ + format!("dependencies.{}", profile), + "dependencies".to_string(), + ] + }; + for key in combinations { + if let Some(val) = config.get(&key).and_then(|v| v.as_table()) { + deps_table = Some(val); + break; + } + } + let deps_table = match deps_table { + Some(t) => t, None => return Ok(Vec::new()), }; - let deps_table = deps_val - .as_table() - .ok_or_else(|| "dependencies is not a table".to_string())?; let mut deps = Vec::new(); for (name, value) in deps_table { match value { diff --git a/src/core/workspace.rs b/src/core/workspace.rs index d71a6f6..52ef056 100644 --- a/src/core/workspace.rs +++ b/src/core/workspace.rs @@ -15,8 +15,33 @@ pub struct Workspace { pub members: Vec, } -pub fn parse_workspace(config: &Config, root: &Path) -> Result, String> { - let table = match config.get("workspace").and_then(|v| v.as_table()) { +pub fn parse_workspace( + config: &Config, + profile: &str, + target: Option<&str>, + root: &Path, +) -> Result, String> { + let mut table = None; + // Order: workspace.target.profile, workspace.profile.target, workspace.target, workspace.profile, workspace + let combinations = if let Some(t) = target { + let normalized_t = crate::cli::build::normalize_target_os(t); + vec![ + format!("workspace.{}.{}", normalized_t, profile), + format!("workspace.{}.{}", profile, normalized_t), + format!("workspace.{}", normalized_t), + format!("workspace.{}", profile), + "workspace".to_string(), + ] + } else { + vec![format!("workspace.{}", profile), "workspace".to_string()] + }; + for key in combinations { + if let Some(val) = config.get(&key).and_then(|v| v.as_table()) { + table = Some(val); + break; + } + } + let table = match table { Some(t) => t, None => return Ok(None), };