mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 08:48:30 +00:00
feat: enhance build configuration with C++ standard support and variable substitution
- Added `cxx_standard` field to the build configuration for specifying C++ standards. - Updated documentation to reflect new `cxx_standard` and variable substitution capabilities in `cflags`, `ldflags`, and `include`. - Implemented logic to handle C++ standard flags in the build process. - Modified object file naming to include source file extensions. - Updated various builder modules to support new language parsing logic. - Introduced issue and feature request templates for better community engagement.
This commit is contained in:
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: Bug Report
|
||||||
|
about: Something is broken or behaves unexpectedly
|
||||||
|
title: "[bug] "
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- A clear and concise description of the bug -->
|
||||||
|
|
||||||
|
## Steps to Reproduce
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
<!-- What should have happened -->
|
||||||
|
|
||||||
|
## Actual Behavior
|
||||||
|
|
||||||
|
<!-- What actually happened, include full error output -->
|
||||||
|
|
||||||
|
```
|
||||||
|
paste error output here
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
- **DCR version** (`dcr --version`):
|
||||||
|
- **OS**:
|
||||||
|
- **Compiler / toolchain** (`gcc --version` or `clang --version`):
|
||||||
|
|
||||||
|
## Minimal Reproducible Project
|
||||||
|
|
||||||
|
<!-- If possible, share a minimal dcr.toml + source that reproduces the issue -->
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# dcr.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
```c
|
||||||
|
// src/main.c
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
|
||||||
|
<!-- Anything else that might help -->
|
||||||
35
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
35
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
name: Feature Request
|
||||||
|
about: Suggest an idea or improvement
|
||||||
|
title: "[feat] "
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problem / Motivation
|
||||||
|
|
||||||
|
<!-- What problem does this solve? What are you trying to do that's currently hard or impossible? -->
|
||||||
|
|
||||||
|
## Proposed Solution
|
||||||
|
|
||||||
|
<!-- Describe what you want. Be as specific as possible — CLI flags, config syntax, behavior -->
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
<!-- Show how it would look in practice (command, dcr.toml snippet, output) -->
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# example command
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# example config
|
||||||
|
```
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
|
||||||
|
<!-- Have you tried workarounds? Are there other ways to solve this? -->
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
|
||||||
|
<!-- Links, prior art, related issues, etc. -->
|
||||||
57
.github/workflows/release.yml
vendored
57
.github/workflows/release.yml
vendored
@@ -13,6 +13,10 @@ on:
|
|||||||
description: "Build Linux"
|
description: "Build Linux"
|
||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: true
|
||||||
|
run_bsd:
|
||||||
|
description: "Build BSD"
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
run_macos:
|
run_macos:
|
||||||
description: "Build macOS"
|
description: "Build macOS"
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -190,6 +194,55 @@ jobs:
|
|||||||
body_path: .github/release-notes.md
|
body_path: .github/release-notes.md
|
||||||
prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
|
prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
|
||||||
|
|
||||||
|
# ── BSD ────────────────────────────────────────────────────────────────────
|
||||||
|
build-bsd:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event.inputs.run_bsd == 'true' }}
|
||||||
|
needs: [setup, notes]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- target: x86_64-unknown-freebsd
|
||||||
|
# При необходимости сюда можно докинуть x86_64-unknown-netbsd
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.setup.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Download Release Notes
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-notes
|
||||||
|
path: .github
|
||||||
|
|
||||||
|
- name: Install cargo-zigbuild
|
||||||
|
run: |
|
||||||
|
pip install cargo-zigbuild
|
||||||
|
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Setup Rust
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cargo zigbuild --release --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Prepare Artifact
|
||||||
|
run: |
|
||||||
|
ASSET="dcr-${{ matrix.target }}-${{ needs.setup.outputs.version }}"
|
||||||
|
cp target/${{ matrix.target }}/release/dcr "$ASSET"
|
||||||
|
echo "ASSET_NAME=${ASSET}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Upload Release Asset
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ needs.setup.outputs.tag }}
|
||||||
|
files: ${{ env.ASSET_NAME }}
|
||||||
|
body_path: .github/release-notes.md
|
||||||
|
prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
|
||||||
|
|
||||||
# ── Windows ────────────────────────────────────────────────────────────────
|
# ── Windows ────────────────────────────────────────────────────────────────
|
||||||
build-windows:
|
build-windows:
|
||||||
if: ${{ github.event_name == 'push' || github.event.inputs.run_windows == 'true' }}
|
if: ${{ github.event_name == 'push' || github.event.inputs.run_windows == 'true' }}
|
||||||
@@ -241,7 +294,7 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
(github.event_name == 'push' && needs.setup.outputs.prerelease == 'false') ||
|
(github.event_name == 'push' && needs.setup.outputs.prerelease == 'false') ||
|
||||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_publish == 'true')
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_publish == 'true')
|
||||||
needs: [setup, build-linux, build-macos, build-windows]
|
needs: [setup, build-linux, build-macos, build-windows, build-bsd]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -258,7 +311,7 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
(github.event_name == 'push' && needs.setup.outputs.prerelease == 'false') ||
|
(github.event_name == 'push' && needs.setup.outputs.prerelease == 'false') ||
|
||||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_brew == 'true')
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_brew == 'true')
|
||||||
needs: [setup, build-linux, build-macos, build-windows]
|
needs: [setup, build-linux, build-macos]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download release assets and compute sha256
|
- name: Download release assets and compute sha256
|
||||||
|
|||||||
51
CHANGELOG.md
51
CHANGELOG.md
@@ -1,5 +1,56 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.6.8] - 2026-05-27
|
||||||
|
|
||||||
|
Added:
|
||||||
|
|
||||||
|
- `build.cxx_standard` — separate C++ language standard for `.cpp`/`.cxx`/`.cc` files.
|
||||||
|
When set, overrides `build.standard` for C++ sources. Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[build]
|
||||||
|
language = ["c", "c++"]
|
||||||
|
standard = "gnu11"
|
||||||
|
cxx_standard = "gnu++17"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Variable substitution for `cflags`, `ldflags`, and `include` directory entries.
|
||||||
|
All `{version}`, `{version_major}`, `{name}`, `{profile}` and other step variables
|
||||||
|
are now available. Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[build]
|
||||||
|
cflags = ['-DPROJECT_VERSION="{version}"']
|
||||||
|
```
|
||||||
|
|
||||||
|
- FreeBSD build support in CI — new `run_bsd` workflow input and `build-bsd` job
|
||||||
|
that cross-compiles `x86_64-unknown-freebsd` via `cargo-zigbuild`.
|
||||||
|
|
||||||
|
Changed:
|
||||||
|
|
||||||
|
- License changed from MIT to **GPL-3.0-or-later**. All source files now carry
|
||||||
|
the GPL-3.0 header. Default license in `dcr new` / `dcr init` changed to
|
||||||
|
`"GPL-3.0-or-later"`. README, FAQ, and CONTRIBUTING.md updated accordingly.
|
||||||
|
- `get_list_with_profile_and_target` no longer duplicates list values when
|
||||||
|
`inherit = true` and the profile/target value is identical to the base value.
|
||||||
|
|
||||||
|
Fixed:
|
||||||
|
|
||||||
|
- Multi-language `language` array (e.g. `["c", "c++", "asm"]`) no longer drops `c++`
|
||||||
|
during internal parsing — `.cxx`/`.cpp`/`.cc` source files are correctly discovered
|
||||||
|
and compiled.
|
||||||
|
- `-std=` flag is no longer passed to C++ files when only `standard` (C standard) is
|
||||||
|
configured — uses `cxx_standard` when available, otherwise skips `-std=` for C++
|
||||||
|
sources, avoiding clang error `invalid argument '-std=c11' not allowed with 'C++'`.
|
||||||
|
- Object file collision: source files with the same stem but different extensions
|
||||||
|
(e.g. `gdt.cxx` and `gdt.S`) no longer overwrite each other. Object filenames
|
||||||
|
now include the original source extension (e.g. `gdt.cxx.o`, `gdt.S.o`).
|
||||||
|
- MSVC backend compile step now detects language per source file extension instead
|
||||||
|
of using the global `language` field, fixing mixed C/C++ compilation with MSVC.
|
||||||
|
- Assembler `-x` flag in `unix_cc.rs` and `gen.rs` is now placed **before** the
|
||||||
|
source file, as required by clang/gcc.
|
||||||
|
- `{version}` variables in `build.clean` are now correctly substituted.
|
||||||
|
|
||||||
## [0.6.7] - 2026-05-26
|
## [0.6.7] - 2026-05-26
|
||||||
|
|
||||||
Added:
|
Added:
|
||||||
|
|||||||
367
README.md
367
README.md
@@ -1,283 +1,208 @@
|
|||||||
# DCR (Dexoron Cargo Realization)
|
<div align="center">
|
||||||
|
|
||||||
DCR is a utility for managing C/C++ projects in a Cargo-like style.
|
# DCR — Dexoron Cargo Realization
|
||||||
Created and maintained by **Dexoron (Bezotechestvo Vladimir)**.
|
|
||||||
|
|
||||||
The current implementation is written in Rust.
|
**A Cargo-style build tool for C/C++ projects**
|
||||||
|
|
||||||
## Why DCR
|
[](https://github.com/dexoron/dcr/actions/workflows/ci.yml)
|
||||||
|
[](https://github.com/dexoron/dcr/releases/latest)
|
||||||
|
[](https://github.com/dexoron/dcr/releases)
|
||||||
|
<br/>
|
||||||
|
[](https://aur.archlinux.org/packages/dcr)
|
||||||
|
[](https://crates.io/crates/dcr)
|
||||||
|
[](https://github.com/dexoron/homebrew-dexoron)
|
||||||
|
<br/>
|
||||||
|
[](https://github.com/dexoron/dcr/stargazers)
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
- Unified project structure without manual setup
|
</div>
|
||||||
- Simple commands for common tasks
|
|
||||||
- Transparent compilation and predictable build profiles
|
|
||||||
|
|
||||||
## Features
|
---
|
||||||
|
|
||||||
- Create a new project or initialize the current directory
|
## Why DCR?
|
||||||
- Build a project with `debug` and `release` profiles
|
|
||||||
- Run the compiled binary
|
|
||||||
- Clean build artifacts
|
|
||||||
- Generate IDE integration files (VS Code, CLion) and compilation databases
|
|
||||||
- Generate a minimal C project template
|
|
||||||
- Build static, shared libraries, UEFI PE32+ executables, and bare-metal ELF executables
|
|
||||||
- Dependency management:
|
|
||||||
- Git and Path dependencies with `dcr.lock`
|
|
||||||
- Registry-based dependency resolution
|
|
||||||
- Library packaging functionality (auto-generate `include/` and `lib/` for `type = "lib"` packages)
|
|
||||||
- ASM projects with NASM/GAS or via GCC/Clang
|
|
||||||
- Mixed language projects
|
|
||||||
- Cross-compilation with `--target` (full triple support and short names)
|
|
||||||
- Target-specific configurations and inheritance
|
|
||||||
- Update the binary via `dcr --update` (GitHub Releases)
|
|
||||||
|
|
||||||
## Supported Platforms
|
- **No boilerplate** — one config file, predictable structure
|
||||||
|
- **Cargo-like workflow** — `build`, `run`, `clean`, `test`, `add`
|
||||||
|
- **Cross-compilation** — full target triple support with short names
|
||||||
|
- **IDE integration** — VS Code, CLion, `compile_commands.json` out of the box
|
||||||
|
- **Dependencies** — path, git, and registry-based with lock file
|
||||||
|
|
||||||
- Linux: x86_64/aarch64 (GNU and Musl)
|
---
|
||||||
- macOS: x86_64/aarch64
|
|
||||||
- Windows: x86_64/aarch64 (MSVC, GNU, LLVM)
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Package Manager
|
<table>
|
||||||
|
<tr>
|
||||||
**Arch Linux**
|
<td><b>Arch Linux (AUR)</b></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yay -S dcr # or paru and other AUR package managers
|
yay -S dcr
|
||||||
```
|
```
|
||||||
|
|
||||||
### From Source
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>macOS/Linux(GNU) (Homebrew)</b></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/dexoron/dcr.git
|
brew tap dexoron/dexoron
|
||||||
cd dcr
|
brew install dcr
|
||||||
cargo build --release
|
|
||||||
mkdir -p ~/.local/bin
|
|
||||||
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Via `install.sh` (Linux/macOS)
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>Cargo (crates.io)</b></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo install dcr
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>Linux / macOS (script)</b></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -fsSL https://dcr.dexoron.su/install.sh | bash
|
curl -fsSL https://dcr.dexoron.su/install.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
### Via `install.ps1` (Windows)
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>Windows (PowerShell)</b></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
irm https://dcr.dexoron.su/install.ps1 | iex
|
irm https://dcr.dexoron.su/install.ps1 | iex
|
||||||
```
|
```
|
||||||
|
|
||||||
When executed, both scripts ask whether to:
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>From source</b></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
- download a prebuilt binary from GitHub Releases
|
```sh
|
||||||
- or build the project from `git`
|
git clone https://github.com/dexoron/dcr.git
|
||||||
|
cd dcr && cargo build --release
|
||||||
|
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
|
||||||
|
```
|
||||||
|
|
||||||
Release assets:
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
- `dcr-x86_64-unknown-linux-gnu`
|
---
|
||||||
- `dcr-x86_64-apple-darwin`
|
|
||||||
- `dcr-aarch64-apple-darwin`
|
|
||||||
- `dcr-x86_64-pc-windows-msvc.exe`
|
|
||||||
|
|
||||||
## Update
|
|
||||||
|
|
||||||
- If DCR was installed from GitHub release assets, `install.sh`, `install.ps1`, or built manually:
|
|
||||||
- use `dcr --update`
|
|
||||||
- If DCR is installed via `pacman/AUR`:
|
|
||||||
- update with your package manager: `paru/yay -Syu dcr` or `sudo pacman -Syu dcr`
|
|
||||||
- `dcr --update` detects package-managed installs and asks you to update via package manager
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
Create a new project:
|
|
||||||
`dcr new hello`
|
|
||||||
|
|
||||||
Or initialize the current directory (the directory must be empty):
|
|
||||||
`dcr init`
|
|
||||||
|
|
||||||
Project structure:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
hello/
|
|
||||||
- src/
|
|
||||||
- - main.c
|
|
||||||
- dcr.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
Build and run the project:
|
|
||||||
`dcr run` or `dcr run --release`
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
### `dcr new <name>`
|
|
||||||
|
|
||||||
Creates a project with the specified name in the current directory.
|
|
||||||
|
|
||||||
### `dcr init`
|
|
||||||
|
|
||||||
Creates a project in the current directory. The project name is taken from the directory name. The directory must be empty.
|
|
||||||
|
|
||||||
### `dcr setup`
|
|
||||||
|
|
||||||
Initializes the registry system by reading `~/.dcr/config.toml` and verifying the availability of configured indices.
|
|
||||||
|
|
||||||
### `dcr add <name> <source>`
|
|
||||||
|
|
||||||
Adds a dependency to `dcr.toml`.
|
|
||||||
If a registry is configured, `dcr` automatically finds the package.
|
|
||||||
Git/Path syntax: `path:`, `github:`, `gitlab:`, `git:`, or a full URL.
|
|
||||||
Use `--branch`, `--tag`, or `--rev` for Git dependencies.
|
|
||||||
|
|
||||||
### `dcr build [profile]`
|
|
||||||
|
|
||||||
Builds the project. If no profile is specified, `--debug` is used.
|
|
||||||
Use `--force` to rebuild without cache, `--clean` to clean before build.
|
|
||||||
|
|
||||||
### `dcr run [profile]`
|
|
||||||
|
|
||||||
Builds the project and runs the binary. If no profile is specified, `--debug` is used.
|
|
||||||
Use `--force` to rebuild without cache, `--clean` to clean before build.
|
|
||||||
|
|
||||||
Run manually:
|
|
||||||
`./target/<profile>/<name>`
|
|
||||||
|
|
||||||
### `dcr clean`
|
|
||||||
|
|
||||||
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
|
|
||||||
- `--release` - built-in flags per compiler
|
|
||||||
|
|
||||||
Custom flags can be added in `dcr.toml` via `build.cflags` and `build.ldflags`.
|
|
||||||
Profile-specific flags can be added via `[build.debug]` and `[build.release]`.
|
|
||||||
You can also set `build.target` to override the output directory (profile-independent).
|
|
||||||
You can set `build.platform` to pass `-march=<platform>` (GCC/Clang) or `/arch:*` (MSVC) where supported.
|
|
||||||
Use `build.kind = "staticlib"` to build a static library instead of a binary.
|
|
||||||
Use `build.kind = "sharedlib"` to build a shared library (`.so`/`.dylib`/`.dll`).
|
|
||||||
Use `build.kind = "efi"` to build a UEFI PE32+ executable (`.efi`).
|
|
||||||
Use `build.kind = "elf"` to build a bare-metal ELF executable.
|
|
||||||
`dcr run` is only for `build.kind = "bin"` and will fail for libraries, EFI, and ELF.
|
|
||||||
Use `build.exclude`/`build.include` to control source/header collection.
|
|
||||||
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 <triple>` or short names:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
dcr build --target linux --release # x86_64-unknown-linux-gnu
|
dcr new hello
|
||||||
dcr build --target macos --release # x86_64-apple-darwin
|
cd hello
|
||||||
dcr build --target windows --release # x86_64-pc-windows-msvc
|
dcr run
|
||||||
dcr run --target aarch64-linux-gnu
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure multiple targets and target-specific settings in `dcr.toml`:
|
**Project structure:**
|
||||||
|
|
||||||
```toml
|
```
|
||||||
[build.targets]
|
hello/
|
||||||
targets = ["linux", "windows"]
|
├── dcr.toml # project config
|
||||||
|
└── src/
|
||||||
[build.linux]
|
└── main.c
|
||||||
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
|
## dcr.toml Example
|
||||||
|
|
||||||
The main project file is `dcr.toml`.
|
|
||||||
For multi-registry management, use `~/.dcr/config.toml`.
|
|
||||||
|
|
||||||
Example `dcr.toml`:
|
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[package]
|
[package]
|
||||||
name = "hello"
|
name = "hello"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
type = "app" # or "lib", "none"
|
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
language = "c"
|
language = "c"
|
||||||
standard = "c11"
|
standard = "c11"
|
||||||
compiler = "clang"
|
compiler = "clang"
|
||||||
kind = "bin" # "bin", "staticlib", "sharedlib", "efi", "elf"
|
kind = "bin"
|
||||||
# Optional platform hint
|
|
||||||
# platform = "x86_64"
|
|
||||||
# Optional custom flags
|
|
||||||
cflags = ["-Wall", "-Wextra"]
|
|
||||||
ldflags = ["-lm"]
|
|
||||||
#
|
|
||||||
# Optional include/exclude:
|
|
||||||
# exclude = ["src/vendor", "src/legacy/**"]
|
|
||||||
# include = ["src/boot/arch/**"]
|
|
||||||
|
|
||||||
[toolchain]
|
|
||||||
# Optional tool overrides:
|
|
||||||
# cc = "clang"
|
|
||||||
# cxx = "clang++"
|
|
||||||
# as = "as"
|
|
||||||
# ar = "ar"
|
|
||||||
# ld = "ld"
|
|
||||||
# uic = "uic"
|
|
||||||
# moc = "moc"
|
|
||||||
# rcc = "rcc"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
my_lib = "path:./libs/my_lib"
|
|
||||||
cool_lib = { git = "https://github.com/user/cool_lib", tag = "v1.0" }
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Workspace example:
|
---
|
||||||
...
|
|
||||||
[workspace]
|
|
||||||
kernel = { path = "src/kernel", deps = ["core", "userspace"] }
|
|
||||||
core = { path = "src/core", deps = ["userspace"] }
|
|
||||||
userspace = { path = "src/userspace" }
|
|
||||||
|
|
||||||
|
## Base Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `dcr new <name>` | Create a new project |
|
||||||
|
| `dcr init` | Initialize current directory |
|
||||||
|
| `dcr build [--release]` | Build the project |
|
||||||
|
| `dcr run [--release]` | Build and run |
|
||||||
|
| `dcr clean` | Remove build artifacts |
|
||||||
|
|
||||||
|
other in [docs/](docs/) and `dcr --help`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Platforms
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
| OS | x86_64 | aarch64 | armv7 | i686 | riscv64 | GNU | Musl | MSVC | MinGW |
|
||||||
|
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||||
|
| Linux | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
| macOS | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||||
|
| Windows | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
|
||||||
|
| FreeBSD | ⚠️ | ⚠️ | ❌ | ❌ | ❌ | ⚠️ | ❌ | ❌ | ❌ |
|
||||||
|
| OpenBSD | ⚠️ | ⚠️ | ❌ | ❌ | ❌ | ⚠️ | ❌ | ❌ | ❌ |
|
||||||
|
| NetBSD | ⚠️ | ⚠️ | ❌ | ❌ | ❌ | ⚠️ | ❌ | ❌ | ❌ |
|
||||||
|
|
||||||
|
✅ officially supported · ⚠️ community / best-effort · ❌ not supported
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Full docs at **[dcr.dexoron.su](https://dcr.dexoron.su)** or in the [`docs/`](docs/) directory.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
| | Name | Role | GitHub |
|
||||||
|
|:---:|---|---|---|
|
||||||
|
| 👤 | Dexoron (Bezotechestvo Vladimir) | Maintainer, Creator | [@dexoron](https://github.com/dexoron) |
|
||||||
|
| 👤 | Kai | Maintainer | [@peoplemiau1](https://github.com/peoplemiau1) |
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](CONTRIBUTING.md). Before a PR:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo fmt --all
|
||||||
|
cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
cargo test
|
||||||
```
|
```
|
||||||
|
|
||||||
Git and Path dependencies are supported. DCR will resolve them on build and generate `dcr.lock`.
|
---
|
||||||
|
|
||||||
Incremental build note: object files are rebuilt when source `.c/.cpp` or included header files change.
|
<div align="center">
|
||||||
|
|
||||||
|
GPL-3.0 — see [LICENSE](LICENSE)<br/>
|
||||||
|
Made with ❤️ by [Dexoron](https://github.com/dexoron) and contributors.
|
||||||
|
|
||||||
## Requirements
|
</div>
|
||||||
- Rust toolchain (`rustc`, `cargo`) - for building DCR from source
|
|
||||||
- C compiler (`clang`, `gcc`, or 'cl'(msvc))
|
|
||||||
|
|
||||||
## Releases
|
|
||||||
Releases are built automatically via GitHub Actions (`.github/workflows/release.yml`) when a tag matching `v*` is pushed.
|
|
||||||
|
|
||||||
## Authors
|
|
||||||
|
|
||||||
- **Dexoron (Bezotechestvo Vladimir)** — main@dexoron.su — [github.com/dexoron](https://github.com/dexoron)
|
|
||||||
- **Kiedie** — [github.com/peoplemiau1](https://github.com/peoplemiau1)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is licensed under the **GNU General Public License v3.0**. See `LICENSE`.
|
|
||||||
@@ -31,7 +31,9 @@ kind = "bin"
|
|||||||
|
|
||||||
- `language` (string or array, required): `c`, `c++`, `cpp`, `cxx`, or `asm`.
|
- `language` (string or array, required): `c`, `c++`, `cpp`, `cxx`, or `asm`.
|
||||||
Mixed languages can be specified as an array, for example `["c", "c++", "asm"]`.
|
Mixed languages can be specified as an array, for example `["c", "c++", "asm"]`.
|
||||||
- `standard` (string, required): language standard passed to compiler.
|
- `standard` (string, required): C language standard passed to compiler (e.g. `c11`, `c17`, `gnu11`).
|
||||||
|
- `cxx_standard` (string, optional): C++ language standard passed to compiler for `.cpp`/`.cxx`/`.cc` files
|
||||||
|
(e.g. `c++17`, `gnu++17`, `c++20`). When set, overrides `standard` for C++ sources.
|
||||||
- `compiler` (string, required): compiler command (for example `clang`, `gcc`, `cl`).
|
- `compiler` (string, required): compiler command (for example `clang`, `gcc`, `cl`).
|
||||||
- `kind` (string, required): `bin`, `staticlib`, `sharedlib`, `efi`, or `elf`.
|
- `kind` (string, required): `bin`, `staticlib`, `sharedlib`, `efi`, or `elf`.
|
||||||
- `filename` (string, optional): custom output filename (without extension). Overrides `package.name` for the final artifact.
|
- `filename` (string, optional): custom output filename (without extension). Overrides `package.name` for the final artifact.
|
||||||
@@ -42,7 +44,9 @@ kind = "bin"
|
|||||||
When set, DCR automatically injects `--target=<value>` into `cflags` (useful for clang cross-compilation).
|
When set, DCR automatically injects `--target=<value>` into `cflags` (useful for clang cross-compilation).
|
||||||
- `platform` (string, optional): architecture hint for compiler (used for `-march` or `/arch`).
|
- `platform` (string, optional): architecture hint for compiler (used for `-march` or `/arch`).
|
||||||
- `cflags` (string array, optional): extra compile flags.
|
- `cflags` (string array, optional): extra compile flags.
|
||||||
|
Supports variable substitution — see [Variable substitution](#variable-substitution) below.
|
||||||
- `ldflags` (string array, optional): extra link flags.
|
- `ldflags` (string array, optional): extra link flags.
|
||||||
|
Supports variable substitution — see [Variable substitution](#variable-substitution) below.
|
||||||
- `ldscript` (string, optional): linker script path. Passed as `-T <path>` to the linker. Useful for bare-metal/embedded/freestanding targets.
|
- `ldscript` (string, optional): linker script path. Passed as `-T <path>` to the linker. Useful for bare-metal/embedded/freestanding targets.
|
||||||
- `exclude` (string array, optional): paths or glob patterns to skip during source/header collection.
|
- `exclude` (string array, optional): paths or glob patterns to skip during source/header collection.
|
||||||
- `include` (string array, optional): allowlist paths or globs that override `exclude`.
|
- `include` (string array, optional): allowlist paths or globs that override `exclude`.
|
||||||
@@ -60,9 +64,30 @@ Notes for `exclude`/`include`:
|
|||||||
- Use `include` to re-allow nested paths, for example exclude `src/boot` but allow `src/boot/arch/**`.
|
- Use `include` to re-allow nested paths, for example exclude `src/boot` but allow `src/boot/arch/**`.
|
||||||
When using glob patterns in `exclude`, DCR converts them internally, so `exclude` globs still work as expected.
|
When using glob patterns in `exclude`, DCR converts them internally, so `exclude` globs still work as expected.
|
||||||
|
|
||||||
### Build steps
|
### Variable substitution
|
||||||
|
|
||||||
Build steps run shell commands before compilation. Post steps run after linking.
|
`cflags`, `ldflags`, and `include` directory entries support the same `{placeholder}` variables
|
||||||
|
as build/post steps:
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|---|---|
|
||||||
|
| `{profile}` | Build profile (`debug` / `release`) |
|
||||||
|
| `{name}` | Package name |
|
||||||
|
| `{version}` | Full package version (e.g. `0.2.1`) |
|
||||||
|
| `{version_major}` | Major version number |
|
||||||
|
| `{version_minor}` | Minor version number |
|
||||||
|
| `{version_patch}` | Patch version number |
|
||||||
|
| `{version_suffix}` | Version suffix (e.g. `-rc1`) |
|
||||||
|
| `{version_suffix_dash}` | Suffix with leading dash (or empty) |
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[build]
|
||||||
|
cflags = ['-DPROJECT_VERSION="{version}"', '-DVERSION_MAJOR={version_major}']
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build steps
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[build.steps]]
|
[[build.steps]]
|
||||||
|
|||||||
@@ -4,9 +4,12 @@
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[package]
|
[package]
|
||||||
name = "my-app"
|
name = "my-app"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
type = "app"
|
type = "app"
|
||||||
|
description = "My application"
|
||||||
|
author = "John Doe"
|
||||||
|
license = "MIT"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fields
|
## Fields
|
||||||
@@ -14,6 +17,15 @@ type = "app"
|
|||||||
- `name` (string, required): used as output artifact base name.
|
- `name` (string, required): used as output artifact base name.
|
||||||
- `version` (string, required): used in `dcr.lock` project entry.
|
- `version` (string, required): used in `dcr.lock` project entry.
|
||||||
- `type` (string, optional): project type (`app`, `lib`, `none`).
|
- `type` (string, optional): project type (`app`, `lib`, `none`).
|
||||||
|
- `description` (string, optional): human-readable project description.
|
||||||
|
- `author` (string, optional): single author name.
|
||||||
|
- `authors` (array of strings, optional): list of authors.
|
||||||
|
- `license` (string, optional): project license identifier (e.g. `MIT`, `GPL-3.0-or-later`).
|
||||||
|
- `homepage` (string, optional): project homepage URL.
|
||||||
|
- `repository` (string, optional): source repository URL.
|
||||||
|
- `readme` (string, optional): path to README file.
|
||||||
|
- `keywords` (array of strings, optional): project keywords.
|
||||||
|
- `categories` (array of strings, optional): project categories.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
@@ -4,88 +4,131 @@
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[package]
|
[package]
|
||||||
name = "string" # required
|
name = "string" # required
|
||||||
version = "string" # required
|
version = "string" # required
|
||||||
|
type = "string" # optional: "app", "lib", "none"
|
||||||
|
description = "string" # optional
|
||||||
|
author = "string" # optional
|
||||||
|
authors = ["string", "..."] # optional
|
||||||
|
license = "string" # optional
|
||||||
|
homepage = "string" # optional
|
||||||
|
repository = "string" # optional
|
||||||
|
readme = "string" # optional
|
||||||
|
keywords = ["string", "..."] # optional
|
||||||
|
categories = ["string", "..."] # optional
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
language = "c|c++|cpp|cxx|asm" # required (string or array)
|
language = "c|c++|cpp|cxx|asm" # required (string or array)
|
||||||
standard = "string" # required
|
standard = "string" # required (e.g. "c11", "gnu11")
|
||||||
compiler = "string" # required
|
cxx_standard = "string" # optional (e.g. "c++17", "gnu++17")
|
||||||
kind = "bin|staticlib|sharedlib" # required
|
compiler = "string" # required (e.g. "clang", "gcc")
|
||||||
filename = "string" # optional (custom output name without extension)
|
kind = "bin|staticlib|sharedlib|efi|elf" # required
|
||||||
extension = "string" # optional (custom extension without dot, e.g. "BIN")
|
target = "string" # optional (cross-compilation target triple)
|
||||||
target = "string" # optional
|
platform = "string" # optional (architecture hint, -march)
|
||||||
platform = "string" # optional
|
type = "string" # optional
|
||||||
cflags = ["string", "..."] # optional
|
cflags = ["string", "..."] # optional (supports {version} etc.)
|
||||||
ldflags = ["string", "..."] # optional
|
ldflags = ["string", "..."] # optional (supports {version} etc.)
|
||||||
ldscript = "string" # optional (linker script, passed as -T<path>)
|
ldscript = "string" # optional (linker script path)
|
||||||
exclude = ["string", "..."] # optional
|
exclude = ["string", "..."] # optional (glob patterns)
|
||||||
include = ["string", "..."] # optional
|
include = ["string", "..."] # optional (allowlist + -I dirs)
|
||||||
roots = ["string", "..."] # optional
|
roots = ["string", "..."] # optional (source roots)
|
||||||
src_disable = false # optional
|
src_disable = false # optional (disable default src/)
|
||||||
pkg_config = ["string", "..."] # optional
|
pkg_config = ["string", "..."] # optional (pkg-config packages)
|
||||||
generated = ["string", "..."] # optional
|
generated = ["string", "..."] # optional (generated file patterns)
|
||||||
expect = ["string", "..."] # optional
|
expect = ["string", "..."] # optional (expected artifact patterns)
|
||||||
clean = ["string", "..."] # optional
|
clean = ["string", "..."] # optional (extra clean paths)
|
||||||
|
targets = ["string", "..."] # optional (build targets)
|
||||||
|
filename = "string" # optional (custom output name)
|
||||||
|
extension = "string" # optional (custom extension)
|
||||||
|
inherit = true # optional (inherit base settings)
|
||||||
|
|
||||||
[[build.steps]]
|
[[build.steps]]
|
||||||
name = "string"
|
name = "string" # required
|
||||||
in = "glob"
|
in = "glob" # required
|
||||||
out = "path or glob"
|
out = "path" # required
|
||||||
cmd = "string"
|
cmd = "string" # required
|
||||||
|
|
||||||
|
# Inline steps (equivalent to [[build.steps]])
|
||||||
|
steps = [
|
||||||
|
{ name = "string", in = "glob", out = "path", cmd = "string" },
|
||||||
|
]
|
||||||
|
|
||||||
[[build.post_steps]]
|
[[build.post_steps]]
|
||||||
name = "string"
|
name = "string" # required
|
||||||
in = "glob"
|
in = "glob" # required (or "target/...")
|
||||||
out = "path or glob"
|
out = "path" # required
|
||||||
cmd = "string"
|
cmd = "string" # required
|
||||||
|
|
||||||
# Inline array form (equivalent to [[build.steps]] / [[build.post_steps]])
|
# Inline post_steps (equivalent to [[build.post_steps]])
|
||||||
steps = [
|
|
||||||
{ name = "string", in = "glob", out = "path or glob", cmd = "string" },
|
|
||||||
]
|
|
||||||
post_steps = [
|
post_steps = [
|
||||||
{ name = "string", in = "glob", out = "path or glob", cmd = "string" },
|
{ name = "string", in = "glob", out = "path", cmd = "string" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Profile overrides (inherit all from [build])
|
||||||
[build.debug]
|
[build.debug]
|
||||||
cflags = ["string", "..."] # optional
|
cflags = ["string", "..."] # optional
|
||||||
ldflags = ["string", "..."] # optional
|
ldflags = ["string", "..."] # optional
|
||||||
|
|
||||||
[build.release]
|
[build.release]
|
||||||
cflags = ["string", "..."] # optional
|
cflags = ["string", "..."] # optional
|
||||||
ldflags = ["string", "..."] # optional
|
ldflags = ["string", "..."] # optional
|
||||||
|
|
||||||
|
# Target-specific overrides
|
||||||
|
[build.linux]
|
||||||
|
compiler = "string"
|
||||||
|
cflags = ["string", "..."]
|
||||||
|
|
||||||
|
[build.windows]
|
||||||
|
compiler = "string"
|
||||||
|
|
||||||
|
# Target + profile override
|
||||||
|
[build.linux.release]
|
||||||
|
cflags = ["string", "..."]
|
||||||
|
|
||||||
[toolchain]
|
[toolchain]
|
||||||
cc = "string" # optional
|
cc = "string" # optional (C compiler override)
|
||||||
cxx = "string" # optional
|
cxx = "string" # optional (C++ compiler override)
|
||||||
as = "string" # optional
|
as = "string" # optional (assembler override)
|
||||||
ar = "string" # optional
|
ar = "string" # optional (archiver override)
|
||||||
ld = "string" # optional
|
ld = "string" # optional (linker override)
|
||||||
uic = "string" # optional
|
uic = "string" # optional (Qt uic)
|
||||||
moc = "string" # optional
|
moc = "string" # optional (Qt moc)
|
||||||
rcc = "string" # optional
|
rcc = "string" # optional (Qt rcc)
|
||||||
|
|
||||||
[run]
|
[run]
|
||||||
cmd = "string" # optional
|
cmd = "string" # required for `dcr run`
|
||||||
|
|
||||||
|
# Debug run variant
|
||||||
|
[run.debug]
|
||||||
|
cmd = "string"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
name = { path = "string", deps = ["string", "..."] }
|
[workspace.member_name]
|
||||||
|
path = "string" # required
|
||||||
|
deps = ["string", "..."] # optional
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
name = {
|
[dependencies.dep_name]
|
||||||
path = "string", # required for each dependency
|
path = "string" # local path
|
||||||
include = ["string", "..."], # optional
|
# or
|
||||||
lib = ["string", "..."], # optional
|
version = "string" # registry version
|
||||||
libs = ["string", "..."], # optional
|
# or
|
||||||
}
|
git = "string" # git URL
|
||||||
|
branch = "string" # optional (git branch)
|
||||||
|
tag = "string" # optional (git tag)
|
||||||
|
rev = "string" # optional (git commit)
|
||||||
|
# common fields:
|
||||||
|
include = ["string", "..."] # optional (include dirs)
|
||||||
|
lib = ["string", "..."] # optional (lib dirs)
|
||||||
|
libs = ["string", "..."] # optional (link libs)
|
||||||
|
features = ["string", "..."] # optional (registry features)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Validation rules
|
## Validation rules
|
||||||
|
|
||||||
- `[package]`, `[build]`, `[dependencies]` must exist.
|
- `[package]`, `[build]`, `[dependencies]` must exist.
|
||||||
- Required string fields must be non-empty.
|
- Required string fields must be non-empty.
|
||||||
- `build.kind` must be `bin`, `staticlib`, or `sharedlib`.
|
- `build.kind` must be `bin`, `staticlib`, `sharedlib`, `efi`, or `elf`.
|
||||||
- Dependency fields `include/lib/libs` must be string arrays when provided.
|
- Dependency fields `include/lib/libs` must be string arrays when provided.
|
||||||
- `build.exclude`/`build.include` must be string arrays when provided.
|
- `build.exclude`/`build.include` must be string arrays when provided.
|
||||||
- `build.roots` must be a string array when provided.
|
- `build.roots` must be a string array when provided.
|
||||||
|
|||||||
@@ -230,11 +230,18 @@ fn get_list_with_profile_and_target(
|
|||||||
// Custom from target/profile
|
// Custom from target/profile
|
||||||
if let Some(val) = get_config_value_raw(config, "build", field, profile, target) {
|
if let Some(val) = get_config_value_raw(config, "build", field, profile, target) {
|
||||||
if let Some(_arr) = val.as_array() {
|
if let Some(_arr) = val.as_array() {
|
||||||
let custom = parse_string_array(&val, &format!("build.{field}"))?;
|
let is_base = inherit
|
||||||
if inherit {
|
&& config
|
||||||
out.extend(custom);
|
.get(&format!("build.{field}"))
|
||||||
} else {
|
.map(|v| v == &val)
|
||||||
out = custom;
|
.unwrap_or(false);
|
||||||
|
if !is_base {
|
||||||
|
let custom = parse_string_array(&val, &format!("build.{field}"))?;
|
||||||
|
if inherit {
|
||||||
|
out.extend(custom);
|
||||||
|
} else {
|
||||||
|
out = custom;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if inherit {
|
} else if inherit {
|
||||||
@@ -513,6 +520,8 @@ fn build_project_at(
|
|||||||
let build_language = get_language_with_profile(&config, profile)?;
|
let build_language = get_language_with_profile(&config, profile)?;
|
||||||
let build_standard =
|
let build_standard =
|
||||||
get_string_with_profile_and_target(&config, "standard", profile, build_target);
|
get_string_with_profile_and_target(&config, "standard", profile, build_target);
|
||||||
|
let build_cxx_standard =
|
||||||
|
get_string_with_profile_and_target(&config, "cxx_standard", profile, build_target);
|
||||||
let build_kind = get_string_with_profile_and_target(&config, "kind", profile, build_target);
|
let build_kind = get_string_with_profile_and_target(&config, "kind", profile, build_target);
|
||||||
let build_platform =
|
let build_platform =
|
||||||
get_string_with_profile_and_target(&config, "platform", profile, build_target);
|
get_string_with_profile_and_target(&config, "platform", profile, build_target);
|
||||||
@@ -716,12 +725,29 @@ fn build_project_at(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let target_dir_binding = normalize_target(build_target.unwrap_or(""), profile);
|
let target_dir_binding = normalize_target(build_target.unwrap_or(""), profile);
|
||||||
|
let version_info = parse_version_info(&project_version);
|
||||||
|
let substitute = |s: &str| -> String {
|
||||||
|
s.replace("{profile}", profile)
|
||||||
|
.replace("{name}", &project_name)
|
||||||
|
.replace("{version}", &version_info.full)
|
||||||
|
.replace("{version_major}", &version_info.major)
|
||||||
|
.replace("{version_minor}", &version_info.minor)
|
||||||
|
.replace("{version_patch}", &version_info.patch)
|
||||||
|
.replace("{version_suffix}", &version_info.suffix)
|
||||||
|
.replace("{version_suffix_dash}", &version_info.suffix_dash)
|
||||||
|
};
|
||||||
|
let resolved_cflags: Vec<String> = resolved_cflags.iter().map(|f| substitute(f)).collect();
|
||||||
|
let modified_ldflags: Vec<String> =
|
||||||
|
resolved_ldflags.iter().map(|f| substitute(f)).collect();
|
||||||
|
let merged_include_dirs: Vec<String> =
|
||||||
|
merged_include_dirs.iter().map(|f| substitute(f)).collect();
|
||||||
let ctx = BuildContext {
|
let ctx = BuildContext {
|
||||||
profile,
|
profile,
|
||||||
project_name: &project_name,
|
project_name: &project_name,
|
||||||
compiler: &resolved_compiler,
|
compiler: &resolved_compiler,
|
||||||
language: &build_language,
|
language: &build_language,
|
||||||
standard: &build_standard,
|
standard: &build_standard,
|
||||||
|
cxx_standard: &build_cxx_standard,
|
||||||
target: build_target,
|
target: build_target,
|
||||||
target_dir: target_dir_binding.as_deref(),
|
target_dir: target_dir_binding.as_deref(),
|
||||||
kind: normalize_kind(&build_kind),
|
kind: normalize_kind(&build_kind),
|
||||||
@@ -740,7 +766,7 @@ fn build_project_at(
|
|||||||
lib_dirs: &resolved.lib_dirs,
|
lib_dirs: &resolved.lib_dirs,
|
||||||
libs: &resolved.libs,
|
libs: &resolved.libs,
|
||||||
cflags: &resolved_cflags,
|
cflags: &resolved_cflags,
|
||||||
ldflags: &resolved_ldflags,
|
ldflags: &modified_ldflags,
|
||||||
output_filename: if output_filename.is_empty() {
|
output_filename: if output_filename.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@@ -763,7 +789,6 @@ fn build_project_at(
|
|||||||
let tool_execs = resolve_toolchain_execs(&tc_uic, &tc_moc, &tc_rcc, &pkg_configs);
|
let tool_execs = resolve_toolchain_execs(&tc_uic, &tc_moc, &tc_rcc, &pkg_configs);
|
||||||
let step_flags =
|
let step_flags =
|
||||||
build_step_flags(&resolved_cflags, &resolved.include_dirs, &resolved_compiler);
|
build_step_flags(&resolved_cflags, &resolved.include_dirs, &resolved_compiler);
|
||||||
let version_info = parse_version_info(&project_version);
|
|
||||||
let step_vars = StepVars {
|
let step_vars = StepVars {
|
||||||
profile,
|
profile,
|
||||||
version: &version_info.full,
|
version: &version_info.full,
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ pub struct ProjectInfo {
|
|||||||
pub profile: String,
|
pub profile: String,
|
||||||
pub language: String,
|
pub language: String,
|
||||||
pub standard: String,
|
pub standard: String,
|
||||||
|
pub cxx_standard: String,
|
||||||
pub compiler: String,
|
pub compiler: String,
|
||||||
pub kind: String,
|
pub kind: String,
|
||||||
pub sources: Vec<String>,
|
pub sources: Vec<String>,
|
||||||
@@ -228,6 +229,7 @@ fn collect_project_info_inner(root: &Path, profile: &str) -> Result<ProjectInfo,
|
|||||||
let version = get_config_str(&config, "package.version");
|
let version = get_config_str(&config, "package.version");
|
||||||
let language = get_language_with_profile_or_default(&config, profile);
|
let language = get_language_with_profile_or_default(&config, profile);
|
||||||
let standard = get_string_with_profile(&config, "standard", profile);
|
let standard = get_string_with_profile(&config, "standard", profile);
|
||||||
|
let cxx_standard = get_string_with_profile(&config, "cxx_standard", profile);
|
||||||
let compiler_s = get_string_with_profile(&config, "compiler", profile);
|
let compiler_s = get_string_with_profile(&config, "compiler", profile);
|
||||||
let kind = get_string_with_profile(&config, "kind", profile);
|
let kind = get_string_with_profile(&config, "kind", profile);
|
||||||
let build_target = get_string_with_profile(&config, "target", profile);
|
let build_target = get_string_with_profile(&config, "target", profile);
|
||||||
@@ -339,6 +341,7 @@ fn collect_project_info_inner(root: &Path, profile: &str) -> Result<ProjectInfo,
|
|||||||
compiler: &resolved_compiler,
|
compiler: &resolved_compiler,
|
||||||
language: &language,
|
language: &language,
|
||||||
standard: &standard,
|
standard: &standard,
|
||||||
|
cxx_standard: &cxx_standard,
|
||||||
target: Some(build_target.as_str()),
|
target: Some(build_target.as_str()),
|
||||||
target_dir: target_dir_binding.as_deref(),
|
target_dir: target_dir_binding.as_deref(),
|
||||||
kind: normalize_kind(&kind),
|
kind: normalize_kind(&kind),
|
||||||
@@ -381,6 +384,7 @@ fn collect_project_info_inner(root: &Path, profile: &str) -> Result<ProjectInfo,
|
|||||||
profile: profile.to_string(),
|
profile: profile.to_string(),
|
||||||
language,
|
language,
|
||||||
standard,
|
standard,
|
||||||
|
cxx_standard,
|
||||||
compiler: resolved_compiler,
|
compiler: resolved_compiler,
|
||||||
kind: normalize_kind(&kind).to_string(),
|
kind: normalize_kind(&kind).to_string(),
|
||||||
sources: abs_sources,
|
sources: abs_sources,
|
||||||
@@ -471,6 +475,10 @@ fn project_info_to_json(info: &ProjectInfo) -> String {
|
|||||||
" \"standard\": {},\n",
|
" \"standard\": {},\n",
|
||||||
json_str(&info.standard)
|
json_str(&info.standard)
|
||||||
));
|
));
|
||||||
|
out.push_str(&format!(
|
||||||
|
" \"cxx_standard\": {},\n",
|
||||||
|
json_str(&info.cxx_standard)
|
||||||
|
));
|
||||||
out.push_str(&format!(
|
out.push_str(&format!(
|
||||||
" \"compiler\": {},\n",
|
" \"compiler\": {},\n",
|
||||||
json_str(&info.compiler)
|
json_str(&info.compiler)
|
||||||
@@ -575,6 +583,13 @@ fn build_compile_command(info: &ProjectInfo, source: &str, profile: &str) -> Vec
|
|||||||
};
|
};
|
||||||
cmd.push(compiler.to_string());
|
cmd.push(compiler.to_string());
|
||||||
cmd.push("-c".to_string());
|
cmd.push("-c".to_string());
|
||||||
|
|
||||||
|
// ASM x flag — must be before source file
|
||||||
|
if let Some(flag) = asm_lang_flag(source) {
|
||||||
|
cmd.push("-x".to_string());
|
||||||
|
cmd.push(flag.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
cmd.push(source.to_string());
|
cmd.push(source.to_string());
|
||||||
|
|
||||||
// Object path (for -o, approximate — not critical for IntelliSense)
|
// Object path (for -o, approximate — not critical for IntelliSense)
|
||||||
@@ -595,15 +610,18 @@ fn build_compile_command(info: &ProjectInfo, source: &str, profile: &str) -> Vec
|
|||||||
cmd.push("-fPIC".to_string());
|
cmd.push("-fPIC".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ASM x flag
|
|
||||||
if let Some(flag) = asm_lang_flag(source) {
|
|
||||||
cmd.push("-x".to_string());
|
|
||||||
cmd.push(flag.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// -std=
|
// -std=
|
||||||
if !info.standard.is_empty() && info.language.to_lowercase() != "asm" {
|
if !info.standard.is_empty() && info.language.to_lowercase() != "asm" {
|
||||||
cmd.push(format!("-std={}", info.standard));
|
let ext = Path::new(source)
|
||||||
|
.extension()
|
||||||
|
.and_then(|e| e.to_str())
|
||||||
|
.unwrap_or("");
|
||||||
|
let is_cpp = matches!(ext, "cpp" | "cxx" | "cc");
|
||||||
|
if is_cpp && !info.cxx_standard.is_empty() {
|
||||||
|
cmd.push(format!("-std={}", info.cxx_standard));
|
||||||
|
} else if !is_cpp {
|
||||||
|
cmd.push(format!("-std={}", info.standard));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default profile flags (mirrors unix_cc.rs defaults)
|
// Default profile flags (mirrors unix_cc.rs defaults)
|
||||||
|
|||||||
@@ -262,7 +262,22 @@ pub fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
|
|||||||
stripped.to_path_buf()
|
stripped.to_path_buf()
|
||||||
};
|
};
|
||||||
let mut out = obj_dir.join(rel);
|
let mut out = obj_dir.join(rel);
|
||||||
out.set_extension(obj_ext.trim_start_matches('.'));
|
let ext = out
|
||||||
|
.extension()
|
||||||
|
.and_then(|e| e.to_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
let obj_ext = obj_ext.trim_start_matches('.');
|
||||||
|
if !ext.is_empty() {
|
||||||
|
let stem = out
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
out.set_file_name(format!("{stem}.{ext}.{obj_ext}"));
|
||||||
|
} else {
|
||||||
|
out.set_extension(obj_ext);
|
||||||
|
}
|
||||||
out.to_string_lossy().to_string()
|
out.to_string_lossy().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,28 +394,28 @@ mod tests {
|
|||||||
fn object_path_basic() {
|
fn object_path_basic() {
|
||||||
let obj_dir = Path::new("target/debug/obj");
|
let obj_dir = Path::new("target/debug/obj");
|
||||||
let result = object_path(obj_dir, "./src/main.c", "o");
|
let result = object_path(obj_dir, "./src/main.c", "o");
|
||||||
assert_eq!(result, "target/debug/obj/main.o");
|
assert_eq!(result, "target/debug/obj/main.c.o");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn object_path_nested() {
|
fn object_path_nested() {
|
||||||
let obj_dir = Path::new("target/debug/obj");
|
let obj_dir = Path::new("target/debug/obj");
|
||||||
let result = object_path(obj_dir, "./src/core/utils.c", "o");
|
let result = object_path(obj_dir, "./src/core/utils.c", "o");
|
||||||
assert_eq!(result, "target/debug/obj/core/utils.o");
|
assert_eq!(result, "target/debug/obj/core/utils.c.o");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn object_path_no_prefix() {
|
fn object_path_no_prefix() {
|
||||||
let obj_dir = Path::new("target/debug/obj");
|
let obj_dir = Path::new("target/debug/obj");
|
||||||
let result = object_path(obj_dir, "src/main.c", "o");
|
let result = object_path(obj_dir, "src/main.c", "o");
|
||||||
assert_eq!(result, "target/debug/obj/main.o");
|
assert_eq!(result, "target/debug/obj/main.c.o");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn object_path_msvc_ext() {
|
fn object_path_msvc_ext() {
|
||||||
let obj_dir = Path::new("target/debug/obj");
|
let obj_dir = Path::new("target/debug/obj");
|
||||||
let result = object_path(obj_dir, "./src/main.c", "obj");
|
let result = object_path(obj_dir, "./src/main.c", "obj");
|
||||||
assert_eq!(result, "target/debug/obj/main.obj");
|
assert_eq!(result, "target/debug/obj/main.c.obj");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ use std::time::Instant;
|
|||||||
|
|
||||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||||
let lang = ctx.language.to_lowercase();
|
let lang = ctx.language.to_lowercase();
|
||||||
if lang.split('+').any(|p| p.trim() != "asm") {
|
if lang.split(',').any(|p| p.trim() != "asm") {
|
||||||
return Err("GAS backend requires build.language = \"asm\"".to_string());
|
return Err("GAS backend requires build.language = \"asm\"".to_string());
|
||||||
}
|
}
|
||||||
let assembler = if ctx.compiler.is_empty() {
|
let assembler = if ctx.compiler.is_empty() {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ pub struct BuildContext<'a> {
|
|||||||
pub compiler: &'a str,
|
pub compiler: &'a str,
|
||||||
pub language: &'a str,
|
pub language: &'a str,
|
||||||
pub standard: &'a str,
|
pub standard: &'a str,
|
||||||
#[allow(dead_code)]
|
pub cxx_standard: &'a str,
|
||||||
pub target: Option<&'a str>,
|
pub target: Option<&'a str>,
|
||||||
pub target_dir: Option<&'a str>,
|
pub target_dir: Option<&'a str>,
|
||||||
pub kind: &'a str,
|
pub kind: &'a str,
|
||||||
|
|||||||
@@ -144,8 +144,8 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String>
|
|||||||
|
|
||||||
fn source_extensions(language: &str) -> Vec<&str> {
|
fn source_extensions(language: &str) -> Vec<&str> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
for part in language.split('+') {
|
for part in language.split(',').map(|s| s.trim()) {
|
||||||
let lang = part.trim().to_lowercase();
|
let lang = part.to_lowercase();
|
||||||
match lang.as_str() {
|
match lang.as_str() {
|
||||||
"c" => out.extend(["c"]),
|
"c" => out.extend(["c"]),
|
||||||
"c++" | "cpp" | "cxx" => out.extend(["cpp", "cxx", "cc"]),
|
"c++" | "cpp" | "cxx" => out.extend(["cpp", "cxx", "cc"]),
|
||||||
@@ -243,18 +243,26 @@ fn build_object(
|
|||||||
let mut cmd = Command::new(compiler);
|
let mut cmd = Command::new(compiler);
|
||||||
cmd.arg("/nologo");
|
cmd.arg("/nologo");
|
||||||
|
|
||||||
match ctx.language.to_lowercase().as_str() {
|
let ext = Path::new(source)
|
||||||
"c" => {
|
.extension()
|
||||||
cmd.arg("/TC");
|
.and_then(|e| e.to_str())
|
||||||
}
|
.unwrap_or("");
|
||||||
"c++" | "cpp" | "cxx" => {
|
let is_cpp = matches!(ext, "cpp" | "cxx" | "cc");
|
||||||
cmd.arg("/TP");
|
if is_cpp {
|
||||||
}
|
cmd.arg("/TP");
|
||||||
_ => return Err("Unsupported language".to_string()),
|
} else {
|
||||||
|
cmd.arg("/TC");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.standard.is_empty() && ctx.language.to_lowercase() != "asm" {
|
let std_val = if is_cpp && !ctx.cxx_standard.is_empty() {
|
||||||
let std_flag = msvc_standard_flag(ctx.language, ctx.standard)?;
|
ctx.cxx_standard
|
||||||
|
} else if !is_cpp && !ctx.standard.is_empty() {
|
||||||
|
ctx.standard
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
if !std_val.is_empty() {
|
||||||
|
let std_flag = msvc_standard_flag(if is_cpp { "c++" } else { "c" }, std_val)?;
|
||||||
cmd.arg(std_flag);
|
cmd.arg(std_flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ use std::time::Instant;
|
|||||||
|
|
||||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||||
let lang = ctx.language.to_lowercase();
|
let lang = ctx.language.to_lowercase();
|
||||||
if lang.split('+').any(|p| p.trim() != "asm") {
|
if lang.split(',').any(|p| p.trim() != "asm") {
|
||||||
return Err("NASM backend requires build.language = \"asm\"".to_string());
|
return Err("NASM backend requires build.language = \"asm\"".to_string());
|
||||||
}
|
}
|
||||||
let assembler = if ctx.compiler.is_empty() {
|
let assembler = if ctx.compiler.is_empty() {
|
||||||
|
|||||||
@@ -133,8 +133,8 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String>
|
|||||||
|
|
||||||
fn source_extensions(language: &str) -> Vec<&str> {
|
fn source_extensions(language: &str) -> Vec<&str> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
for part in language.split('+') {
|
for part in language.split(',').map(|s| s.trim()) {
|
||||||
let lang = part.trim().to_lowercase();
|
let lang = part.to_lowercase();
|
||||||
match lang.as_str() {
|
match lang.as_str() {
|
||||||
"c" => out.extend(["c"]),
|
"c" => out.extend(["c"]),
|
||||||
"c++" | "cpp" | "cxx" => out.extend(["cpp", "cxx", "cc"]),
|
"c++" | "cpp" | "cxx" => out.extend(["cpp", "cxx", "cc"]),
|
||||||
@@ -210,16 +210,18 @@ fn build_object(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut cmd = Command::new(compiler);
|
let mut cmd = Command::new(compiler);
|
||||||
cmd.arg("-c").arg(source).arg("-o").arg(obj_path);
|
cmd.arg("-c");
|
||||||
|
|
||||||
if ctx.kind == "sharedlib" {
|
|
||||||
cmd.arg("-fPIC");
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(flag) = asm_lang_flag(source) {
|
if let Some(flag) = asm_lang_flag(source) {
|
||||||
cmd.arg("-x").arg(flag);
|
cmd.arg("-x").arg(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.arg(source).arg("-o").arg(obj_path);
|
||||||
|
|
||||||
|
if ctx.kind == "sharedlib" {
|
||||||
|
cmd.arg("-fPIC");
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(platform) = ctx.platform
|
if let Some(platform) = ctx.platform
|
||||||
&& !platform.trim().is_empty()
|
&& !platform.trim().is_empty()
|
||||||
{
|
{
|
||||||
@@ -227,7 +229,16 @@ fn build_object(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.standard.is_empty() && ctx.language.to_lowercase() != "asm" {
|
if !ctx.standard.is_empty() && ctx.language.to_lowercase() != "asm" {
|
||||||
cmd.arg(format!("-std={}", ctx.standard));
|
let ext = Path::new(source)
|
||||||
|
.extension()
|
||||||
|
.and_then(|e| e.to_str())
|
||||||
|
.unwrap_or("");
|
||||||
|
let is_cpp = matches!(ext, "cpp" | "cxx" | "cc");
|
||||||
|
if is_cpp && !ctx.cxx_standard.is_empty() {
|
||||||
|
cmd.arg(format!("-std={}", ctx.cxx_standard));
|
||||||
|
} else if !is_cpp {
|
||||||
|
cmd.arg(format!("-std={}", ctx.standard));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let use_dcr_defaults = ctx.cflags.is_empty() && !is_bare_metal_target(ctx.target);
|
let use_dcr_defaults = ctx.cflags.is_empty() && !is_bare_metal_target(ctx.target);
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ pub struct PackageConfig {
|
|||||||
pub struct BuildConfig {
|
pub struct BuildConfig {
|
||||||
pub language: LanguageConfig,
|
pub language: LanguageConfig,
|
||||||
pub standard: String,
|
pub standard: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub cxx_standard: Option<String>,
|
||||||
pub compiler: String,
|
pub compiler: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub kind: Option<String>,
|
pub kind: Option<String>,
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ pub fn parse_language_value(value: &toml::Value, key: &str) -> Result<String, St
|
|||||||
if parts.is_empty() {
|
if parts.is_empty() {
|
||||||
return Err(format!("{key} is empty"));
|
return Err(format!("{key} is empty"));
|
||||||
}
|
}
|
||||||
Ok(parts.join("+"))
|
Ok(parts.join(","))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_compiler(
|
pub fn resolve_compiler(
|
||||||
@@ -258,7 +258,7 @@ fn toolchain_override_compiler(
|
|||||||
|
|
||||||
pub fn primary_language(language: &str) -> String {
|
pub fn primary_language(language: &str) -> String {
|
||||||
let parts: Vec<String> = language
|
let parts: Vec<String> = language
|
||||||
.split('+')
|
.split(',')
|
||||||
.map(|p| p.trim().to_lowercase())
|
.map(|p| p.trim().to_lowercase())
|
||||||
.filter(|p| !p.is_empty())
|
.filter(|p| !p.is_empty())
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
Reference in New Issue
Block a user