diff --git a/README.md b/README.md
index 06b3a51..a73181c 100644
--- a/README.md
+++ b/README.md
@@ -77,21 +77,28 @@ cargo install dcr
-| Linux / macOS (script) |
+dcrup (Linux / macOS / BSD / Windows(Bash)) |
```sh
-curl -fsSL https://dcr.dexoron.su/install.sh | sh
+curl -fsSL https://sh.dcr-tool.ru | sh -s -- self-install
+export PATH="$HOME/.dcr/bin:$PATH"
+dcrup install stable
```
|
-| BSD (script) |
+dcrup (Windows PowerShell) |
-```sh
-curl -fsSL https://dcr.dexoron.su/install_bsd.sh | sh
+```powershell
+irm https://ps1.dcr-tool.ru | iex
+# or:
+# irm https://ps1.dcr-tool.ru -OutFile dcrup.ps1
+# powershell -File .\dcrup.ps1 self-install
+# $env:Path += ";$env:USERPROFILE\.dcr\bin"
+dcrup install stable
```
|
@@ -107,16 +114,6 @@ curl -fsSL https://dcr.dexoron.su/install_bsd.sh | sh
sudo snap install --classic --dangerous dcrup_*.snap
```
-
-
-
-| Windows (PowerShell) |
-
-
-```powershell
-irm https://dcr.dexoron.su/install.ps1 | iex
-```
-
|
@@ -125,7 +122,7 @@ irm https://dcr.dexoron.su/install.ps1 | iex
```sh
git clone https://github.com/dexoron/dcr.git
-cd dcr && cargo build --release
+cd dcr && cargo build --release --features archive
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
```
diff --git a/dcrup.cmd b/dcrup.cmd
new file mode 100755
index 0000000..e601f29
--- /dev/null
+++ b/dcrup.cmd
@@ -0,0 +1,13 @@
+@echo off
+setlocal
+set "SCRIPT_DIR=%~dp0"
+if exist "%SCRIPT_DIR%dcrup.ps1" (
+ powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%dcrup.ps1" %*
+ exit /b %ERRORLEVEL%
+)
+if exist "%USERPROFILE%\.dcr\bin\dcrup.ps1" (
+ powershell -NoProfile -ExecutionPolicy Bypass -File "%USERPROFILE%\.dcr\bin\dcrup.ps1" %*
+ exit /b %ERRORLEVEL%
+)
+echo [error] dcrup.ps1 not found near dcrup.cmd >&2
+exit /b 1
diff --git a/dcrup.ps1 b/dcrup.ps1
new file mode 100644
index 0000000..a02786e
--- /dev/null
+++ b/dcrup.ps1
@@ -0,0 +1,367 @@
+#Requires -Version 5.1
+$ErrorActionPreference = 'Stop'
+
+$DcrupHome = if ($env:DCRUP_HOME) { $env:DCRUP_HOME } else { Join-Path $env:USERPROFILE '.dcr' }
+$DcrupBin = if ($env:DCRUP_BIN) { $env:DCRUP_BIN } else { Join-Path $DcrupHome 'bin' }
+$DcrupTc = Join-Path $DcrupHome 'toolchains'
+$DcrupMeta = Join-Path $DcrupHome 'meta'
+$RepoUrl = if ($env:DCRUP_REPO) { $env:DCRUP_REPO } else { 'https://github.com/dexoron/dcr' }
+$ApiLatest = 'https://api.github.com/repos/dexoron/dcr/releases/latest'
+$ApiAll = 'https://api.github.com/repos/dexoron/dcr/releases'
+$Features = if ($env:DCR_FEATURES) { $env:DCR_FEATURES } else { 'archive' }
+
+function Info($m) { Write-Host "[dcrup] $m" -ForegroundColor Cyan }
+function Ok($m) { Write-Host "[ok] $m" -ForegroundColor Green }
+function Warn($m) { Write-Host "[warn] $m" -ForegroundColor Yellow }
+function Die($m) { Write-Host "[error] $m" -ForegroundColor Red; exit 1 }
+
+function Show-Usage {
+ @'
+dcrup — install and switch DCR versions (Windows)
+
+Usage:
+ dcrup install [--build|--release] [--force]
+ dcrup default
+ dcrup update
+ dcrup list
+ dcrup show
+ dcrup which
+ dcrup uninstall [|--all]
+ dcrup self-install [--to DIR]
+ dcrup help
+
+Spec:
+ stable | dev | night
+ VERSION → VERSION@stable (e.g. 0.8.2)
+ VERSION@stable
+ VERSION@dev
+ night → always build from branch "dev" HEAD
+
+Modes:
+ --release download GitHub Release binary (default for stable/dev)
+ --build cargo build --release --features archive
+ night always --build (prebuilt not available)
+
+Env:
+ DCRUP_HOME default ~/.dcr
+ DCRUP_BIN default $DCRUP_HOME/bin
+ DCR_FEATURES default archive
+ DCRUP_REPO git/GitHub repo URL
+'@ | Write-Host
+}
+
+function Ensure-Dirs {
+ New-Item -ItemType Directory -Force -Path $DcrupTc, $DcrupBin, $DcrupMeta | Out-Null
+}
+
+function Get-Target {
+ $arch = $env:PROCESSOR_ARCHITECTURE
+ if ($env:PROCESSOR_ARCHITEW6432) { $arch = $env:PROCESSOR_ARCHITEW6432 }
+ switch ($arch) {
+ 'AMD64' { return 'x86_64-pc-windows-msvc' }
+ 'ARM64' { return 'aarch64-pc-windows-msvc' }
+ 'x86' { return 'i686-pc-windows-msvc' }
+ default { Die "unsupported arch: $arch" }
+ }
+}
+
+function Normalize-Version([string]$v) {
+ if ($v.StartsWith('v')) { return $v.Substring(1) }
+ return $v
+}
+
+function Parse-Spec([string]$raw) {
+ $script:SpecRaw = $raw
+ $script:SpecChannel = $null
+ $script:SpecVersion = $null
+ $script:SpecFloating = $false
+
+ if ($raw -in @('stable', 'dev', 'night')) {
+ $script:SpecChannel = $raw
+ $script:SpecFloating = $true
+ return
+ }
+ if ($raw -match '^(.*)@(.*)$') {
+ $script:SpecVersion = Normalize-Version $Matches[1]
+ $script:SpecChannel = $Matches[2]
+ if ($script:SpecChannel -eq 'night') { Die 'cannot pin version on night' }
+ if ($script:SpecChannel -notin @('stable', 'dev')) { Die "unknown channel: $($script:SpecChannel)" }
+ return
+ }
+ $script:SpecVersion = Normalize-Version $raw
+ $script:SpecChannel = 'stable'
+}
+
+function Toolchain-Id([string]$extra = '') {
+ if ($script:SpecChannel -eq 'night') {
+ return "night-$extra"
+ }
+ if ($script:SpecFloating) {
+ return "$($script:SpecChannel)-$extra"
+ }
+ return "$($script:SpecVersion)-$($script:SpecChannel)"
+}
+
+function Active-Path { Join-Path $DcrupMeta 'active' }
+
+function Read-Active {
+ $p = Active-Path
+ if (Test-Path $p) { return (Get-Content $p -Raw).Trim() }
+ return $null
+}
+
+function Write-Active([string]$id) {
+ Set-Content -Path (Active-Path) -Value $id -NoNewline
+}
+
+function Link-Default([string]$id) {
+ $src = Join-Path (Join-Path $DcrupTc $id) 'dcr.exe'
+ if (-not (Test-Path $src)) { Die "toolchain not installed: $id" }
+ $dst = Join-Path $DcrupBin 'dcr.exe'
+ Copy-Item $src $dst -Force
+ Write-Active $id
+ Ok "default → $id ($dst)"
+}
+
+function Fetch-Release($channel, $version) {
+ if ($version) {
+ return Invoke-RestMethod -Uri "https://api.github.com/repos/dexoron/dcr/releases/tags/v$version"
+ }
+ if ($channel -eq 'dev') {
+ $all = Invoke-RestMethod -Uri $ApiAll
+ $pre = $all | Where-Object { $_.prerelease } | Select-Object -First 1
+ if (-not $pre) { Die 'no prerelease (dev) found' }
+ return $pre
+ }
+ return Invoke-RestMethod -Uri $ApiLatest
+}
+
+function Install-Prebuilt([switch]$Force) {
+ $triple = Get-Target
+ $rel = Fetch-Release $script:SpecChannel $script:SpecVersion
+ $tag = $rel.tag_name
+ $ver = Normalize-Version $tag
+ $assetName = "dcr-$triple-$ver.exe"
+ $asset = $rel.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1
+ if (-not $asset) { Die "asset not found: $assetName" }
+
+ $id = if ($script:SpecFloating) { Toolchain-Id $ver } else { Toolchain-Id }
+ $dest = Join-Path $DcrupTc $id
+ $bin = Join-Path $dest 'dcr.exe'
+ if ((Test-Path $bin) -and -not $Force) {
+ Ok "already installed: $id"
+ Link-Default $id
+ return
+ }
+ Ensure-Dirs
+ New-Item -ItemType Directory -Force -Path $dest | Out-Null
+ Info "downloading $assetName …"
+ Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $bin
+ @"
+channel=$($script:SpecChannel)
+version=$ver
+tag=$tag
+source=release
+triple=$triple
+"@ | Set-Content (Join-Path $dest 'dcrup-meta')
+ Ok "installed prebuilt $id"
+ Link-Default $id
+}
+
+function Install-Build([switch]$Force) {
+ if (-not (Get-Command git -ErrorAction SilentlyContinue)) { Die 'git not found' }
+ if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) { Die 'cargo not found' }
+
+ $tmp = Join-Path $env:TEMP ("dcrup-" + [guid]::NewGuid().ToString('N'))
+ New-Item -ItemType Directory -Path $tmp | Out-Null
+ try {
+ $src = Join-Path $tmp 'src'
+ if ($script:SpecChannel -eq 'night') {
+ Info "cloning dev for night …"
+ git clone --depth 1 --branch dev $RepoUrl $src 2>$null
+ if ($LASTEXITCODE -ne 0) { git clone --depth 1 $RepoUrl $src | Out-Null }
+ $sha = (git -C $src rev-parse --short HEAD).Trim()
+ $id = Toolchain-Id $sha
+ $ver = 'night'
+ } elseif ($script:SpecFloating) {
+ $rel = Fetch-Release $script:SpecChannel $null
+ $tag = $rel.tag_name
+ $ver = Normalize-Version $tag
+ $id = Toolchain-Id $ver
+ Info "cloning $tag …"
+ git clone --depth 1 --branch $tag $RepoUrl $src 2>$null
+ if ($LASTEXITCODE -ne 0) {
+ git clone --depth 1 $RepoUrl $src | Out-Null
+ git -C $src fetch --depth 1 origin "refs/tags/${tag}:refs/tags/${tag}"
+ git -C $src checkout $tag
+ }
+ $sha = (git -C $src rev-parse --short HEAD).Trim()
+ } else {
+ $tag = "v$($script:SpecVersion)"
+ $ver = $script:SpecVersion
+ $id = Toolchain-Id
+ Info "cloning $tag …"
+ git clone --depth 1 --branch $tag $RepoUrl $src 2>$null
+ if ($LASTEXITCODE -ne 0) {
+ git clone --depth 1 $RepoUrl $src | Out-Null
+ git -C $src fetch --depth 1 origin "refs/tags/${tag}:refs/tags/${tag}"
+ git -C $src checkout $tag
+ }
+ $sha = (git -C $src rev-parse --short HEAD).Trim()
+ }
+
+ $dest = Join-Path $DcrupTc $id
+ $bin = Join-Path $dest 'dcr.exe'
+ if ((Test-Path $bin) -and -not $Force) {
+ Ok "already installed: $id"
+ Link-Default $id
+ return
+ }
+
+ $triple = Get-Target
+ Info "building release (features: $Features) …"
+ Push-Location $src
+ cargo build --release --features $Features --target $triple
+ if ($LASTEXITCODE -ne 0) { Die 'cargo build failed' }
+ Pop-Location
+
+ New-Item -ItemType Directory -Force -Path $dest | Out-Null
+ Copy-Item (Join-Path $src "target\$triple\release\dcr.exe") $bin -Force
+ @"
+channel=$($script:SpecChannel)
+version=$ver
+git=$sha
+source=build
+features=$Features
+"@ | Set-Content (Join-Path $dest 'dcrup-meta')
+ Ok "installed build $id"
+ Link-Default $id
+ } finally {
+ Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
+ }
+}
+
+function Ensure-UserPath {
+ $userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
+ if (-not $userPath) { $userPath = '' }
+ if (-not ($userPath -split ';' | Where-Object { $_ -eq $DcrupBin })) {
+ $new = if ($userPath) { "$userPath;$DcrupBin" } else { $DcrupBin }
+ [Environment]::SetEnvironmentVariable('Path', $new, 'User')
+ $env:Path = "$env:Path;$DcrupBin"
+ Warn "added to user PATH: $DcrupBin (restart shell if needed)"
+ }
+}
+
+function Install-CmdShim {
+ $cmd = Join-Path $DcrupBin 'dcrup.cmd'
+ $ps1 = Join-Path $DcrupBin 'dcrup.ps1'
+ @"
+@echo off
+setlocal
+powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0dcrup.ps1" %*
+"@ | Set-Content -Path $cmd -Encoding ASCII
+ if (-not (Test-Path $ps1)) {
+ Copy-Item $PSCommandPath $ps1 -Force
+ }
+}
+
+function Cmd-Install {
+ param([string[]]$Args)
+ $mode = 'release'
+ $force = $false
+ $spec = $null
+ foreach ($a in $Args) {
+ switch -Regex ($a) {
+ '^--build$' { $mode = 'build'; continue }
+ '^--release$' { $mode = 'release'; continue }
+ '^--force$' { $force = $true; continue }
+ '^-' { Die "unknown flag: $a" }
+ default {
+ if ($spec) { Die "unexpected: $a" }
+ $spec = $a
+ }
+ }
+ }
+ if (-not $spec) { Die 'usage: dcrup install [--build|--release] [--force]' }
+ Parse-Spec $spec
+ Ensure-Dirs
+ if ($script:SpecChannel -eq 'night') { $mode = 'build' }
+ Info "install spec=$spec channel=$($script:SpecChannel) mode=$mode"
+ if ($mode -eq 'build') { Install-Build -Force:$force } else { Install-Prebuilt -Force:$force }
+ Ensure-UserPath
+ Install-CmdShim
+}
+
+function Cmd-List {
+ Ensure-Dirs
+ $active = Read-Active
+ Get-ChildItem $DcrupTc -Directory -ErrorAction SilentlyContinue | ForEach-Object {
+ $mark = if ($_.Name -eq $active) { ' *' } else { ' ' }
+ Write-Host "$mark $($_.Name)"
+ }
+}
+
+function Cmd-Show {
+ Ensure-Dirs
+ Write-Host "DCRUP_HOME=$DcrupHome"
+ Write-Host "DCRUP_BIN=$DcrupBin"
+ Write-Host "active=$(Read-Active)"
+ $exe = Join-Path $DcrupBin 'dcr.exe'
+ if (Test-Path $exe) { & $exe --version }
+}
+
+function Cmd-SelfInstall {
+ Ensure-Dirs
+ Copy-Item $PSCommandPath (Join-Path $DcrupBin 'dcrup.ps1') -Force
+ Install-CmdShim
+ Ensure-UserPath
+ Ok "dcrup installed to $DcrupBin (use: dcrup …)"
+}
+
+$cmd = if ($args.Count -ge 1) { $args[0] } else { 'help' }
+$rest = @()
+if ($args.Count -gt 1) { $rest = $args[1..($args.Count - 1)] }
+
+switch ($cmd) {
+ { $_ -in @('help', '-h', '--help') } { Show-Usage }
+ 'install' { Cmd-Install $rest }
+ 'list' { Cmd-List }
+ 'show' { Cmd-Show }
+ 'which' {
+ $p = Join-Path $DcrupBin 'dcr.exe'
+ if (Test-Path $p) { $p } else { Die 'dcr not installed' }
+ }
+ 'default' {
+ if ($rest.Count -ne 1) { Die 'usage: dcrup default ' }
+ Parse-Spec $rest[0]
+ if ($script:SpecFloating -or $script:SpecChannel -eq 'night') {
+ $id = Get-ChildItem $DcrupTc -Directory |
+ Where-Object { $_.Name -like "$($script:SpecChannel)-*" } |
+ Sort-Object Name | Select-Object -Last 1
+ if (-not $id) { Die "not installed: $($rest[0])" }
+ Link-Default $id.Name
+ } else {
+ Link-Default (Toolchain-Id)
+ }
+ }
+ 'update' {
+ $a = Read-Active
+ if (-not $a) { Die 'no active toolchain' }
+ if ($a -like 'night-*') { Cmd-Install @('night', '--build', '--force') }
+ elseif ($a -like 'stable-*') { Cmd-Install @('stable', '--force') }
+ elseif ($a -like 'dev-*') { Cmd-Install @('dev', '--force') }
+ else { Ok "pinned $a — install a newer version explicitly" }
+ }
+ 'uninstall' {
+ if ($rest -contains '--all') {
+ Remove-Item -Recurse -Force $DcrupTc -ErrorAction SilentlyContinue
+ Remove-Item (Join-Path $DcrupBin 'dcr.exe') -ErrorAction SilentlyContinue
+ Remove-Item (Active-Path) -ErrorAction SilentlyContinue
+ Ok 'removed all'
+ } else {
+ Die 'use: dcrup uninstall --all (or install a replacement first)'
+ }
+ }
+ 'self-install' { Cmd-SelfInstall }
+ default { Die "unknown command: $cmd" }
+}
diff --git a/dcrup.sh b/dcrup.sh
new file mode 100755
index 0000000..33cc7e4
--- /dev/null
+++ b/dcrup.sh
@@ -0,0 +1,599 @@
+#!/usr/bin/env bash
+set -Eeuo pipefail
+
+DCRUP_HOME="${DCRUP_HOME:-$HOME/.dcr}"
+DCRUP_BIN="${DCRUP_BIN:-$DCRUP_HOME/bin}"
+DCRUP_TC="${DCRUP_HOME}/toolchains"
+DCRUP_META="${DCRUP_HOME}/meta"
+REPO_URL="${DCRUP_REPO:-https://github.com/dexoron/dcr}"
+API_LATEST="https://api.github.com/repos/dexoron/dcr/releases/latest"
+API_ALL="https://api.github.com/repos/dexoron/dcr/releases"
+FEATURES="${DCR_FEATURES:-archive}"
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+CYAN='\033[0;36m'
+NC='\033[0m'
+
+log() { printf "${CYAN}[dcrup]${NC} %s\n" "$*"; }
+ok() { printf "${GREEN}[ok]${NC} %s\n" "$*"; }
+warn() { printf "${YELLOW}[warn]${NC} %s\n" "$*"; }
+die() { printf "${RED}[error]${NC} %s\n" "$*" >&2; exit 1; }
+
+usage() {
+ cat <<'EOF'
+dcrup — install and switch DCR versions
+
+Usage:
+ dcrup install [--build|--release] [--force] [--libc gnu|musl|auto]
+ dcrup default
+ dcrup update
+ dcrup list
+ dcrup show
+ dcrup which
+ dcrup uninstall [|--all]
+ dcrup self-install [--to DIR]
+ dcrup help
+
+Spec:
+ stable | dev | night
+ VERSION → VERSION@stable (e.g. 0.8.2)
+ VERSION@stable
+ VERSION@dev
+ night → always build from branch "dev" HEAD
+
+Modes:
+ --release download GitHub Release binary (default for stable/dev)
+ --build cargo build --release --features archive
+ night always --build (prebuilt not available)
+
+Linux libc (prebuilt triple only):
+ --libc gnu x86_64-unknown-linux-gnu (default)
+ --libc musl x86_64-unknown-linux-musl
+ --libc auto musl if host ldd/libc is musl, else gnu
+
+Env:
+ DCRUP_HOME default ~/.dcr
+ DCRUP_BIN default $DCRUP_HOME/bin
+ DCR_FEATURES default archive
+ DCRUP_REPO git/GitHub repo URL
+ DCRUP_LIBC gnu|musl|auto (default: gnu)
+EOF
+}
+
+need_cmd() {
+ command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
+}
+
+detect_linux_libc() {
+ local pref="${DCRUP_LIBC:-gnu}"
+ case "$pref" in
+ gnu|glibc) echo "gnu" ;;
+ musl) echo "musl" ;;
+ auto)
+ if command -v ldd >/dev/null 2>&1; then
+ if ldd --version 2>&1 | grep -qi musl; then
+ echo "musl"
+ return 0
+ fi
+ fi
+ if [[ -e /lib/ld-musl-x86_64.so.1 || -e /lib/ld-musl-aarch64.so.1 ]]; then
+ echo "musl"
+ return 0
+ fi
+ if command -v getconf >/dev/null 2>&1 && getconf GNU_LIBC_VERSION >/dev/null 2>&1; then
+ echo "gnu"
+ return 0
+ fi
+ echo "gnu"
+ ;;
+ *) die "invalid DCRUP_LIBC / --libc: $pref (use gnu|musl|auto)" ;;
+ esac
+}
+
+detect_triple() {
+ local os arch libc
+ os="$(uname -s)"
+ arch="$(uname -m)"
+ case "$os:$arch" in
+ Linux:x86_64)
+ libc="$(detect_linux_libc)"
+ if [[ "$libc" == "musl" ]]; then
+ echo "x86_64-unknown-linux-musl"
+ else
+ echo "x86_64-unknown-linux-gnu"
+ fi
+ ;;
+ Linux:aarch64|Linux:arm64)
+ libc="$(detect_linux_libc)"
+ if [[ "$libc" == "musl" ]]; then
+ echo "aarch64-unknown-linux-musl"
+ else
+ echo "aarch64-unknown-linux-gnu"
+ fi
+ ;;
+ Linux:i686|Linux:i386)
+ libc="$(detect_linux_libc)"
+ if [[ "$libc" == "musl" ]]; then
+ echo "i686-unknown-linux-musl"
+ else
+ echo "i686-unknown-linux-gnu"
+ fi
+ ;;
+ Linux:armv7l|Linux:armv7)
+ libc="$(detect_linux_libc)"
+ if [[ "$libc" == "musl" ]]; then
+ echo "armv7-unknown-linux-musleabihf"
+ else
+ echo "armv7-unknown-linux-gnueabihf"
+ fi
+ ;;
+ Linux:riscv64)
+ libc="$(detect_linux_libc)"
+ if [[ "$libc" == "musl" ]]; then
+ echo "riscv64gc-unknown-linux-musl"
+ else
+ echo "riscv64gc-unknown-linux-gnu"
+ fi
+ ;;
+ Darwin:x86_64) echo "x86_64-apple-darwin" ;;
+ Darwin:arm64|Darwin:aarch64) echo "aarch64-apple-darwin" ;;
+ FreeBSD:x86_64|FreeBSD:amd64) echo "x86_64-unknown-freebsd" ;;
+ FreeBSD:aarch64|FreeBSD:arm64) echo "aarch64-unknown-freebsd" ;;
+ OpenBSD:x86_64|OpenBSD:amd64) echo "x86_64-unknown-openbsd" ;;
+ NetBSD:x86_64|NetBSD:amd64) echo "x86_64-unknown-netbsd" ;;
+ MINGW*|MSYS*|CYGWIN*)
+ case "$arch" in
+ x86_64|amd64) echo "x86_64-pc-windows-gnu" ;;
+ aarch64|arm64) echo "aarch64-pc-windows-gnullvm" ;;
+ i686|i386) echo "i686-pc-windows-gnu" ;;
+ *) die "unsupported Windows arch under MSYS: $arch" ;;
+ esac
+ ;;
+ *) die "unsupported platform: $os/$arch" ;;
+ esac
+}
+
+normalize_version() {
+ local v="$1"
+ v="${v#v}"
+ printf '%s' "$v"
+}
+
+parse_spec() {
+ local raw="$1"
+ SPEC_RAW="$raw"
+ SPEC_CHANNEL=""
+ SPEC_VERSION=""
+ SPEC_FLOATING=0
+
+ case "$raw" in
+ "") die "missing version/channel spec" ;;
+ stable|dev|night)
+ SPEC_CHANNEL="$raw"
+ SPEC_VERSION=""
+ SPEC_FLOATING=1
+ ;;
+ *@*)
+ SPEC_VERSION="$(normalize_version "${raw%@*}")"
+ SPEC_CHANNEL="${raw#*@}"
+ case "$SPEC_CHANNEL" in
+ stable|dev) ;;
+ night) die "cannot pin a version on night (use: dcrup install night)" ;;
+ *) die "unknown channel in spec: $SPEC_CHANNEL (use stable|dev)" ;;
+ esac
+ [[ -n "$SPEC_VERSION" ]] || die "empty version in spec: $raw"
+ ;;
+ *)
+ SPEC_VERSION="$(normalize_version "$raw")"
+ SPEC_CHANNEL="stable"
+ [[ -n "$SPEC_VERSION" ]] || die "invalid spec: $raw"
+ ;;
+ esac
+}
+
+toolchain_id() {
+ if [[ "$SPEC_CHANNEL" == "night" ]]; then
+ local sha="${1:-unknown}"
+ echo "night-${sha}"
+ elif [[ "$SPEC_FLOATING" -eq 1 ]]; then
+ local ver="${1:-latest}"
+ echo "${SPEC_CHANNEL}-${ver}"
+ else
+ echo "${SPEC_VERSION}-${SPEC_CHANNEL}"
+ fi
+}
+
+ensure_dirs() {
+ mkdir -p "$DCRUP_TC" "$DCRUP_BIN" "$DCRUP_META"
+}
+
+active_file() { echo "$DCRUP_META/active"; }
+
+read_active() {
+ if [[ -f "$(active_file)" ]]; then
+ cat "$(active_file)"
+ fi
+}
+
+write_active() {
+ printf '%s\n' "$1" >"$(active_file)"
+}
+
+link_default() {
+ local id="$1"
+ local src="$DCRUP_TC/$id/dcr"
+ [[ -x "$src" ]] || die "toolchain not installed: $id"
+ ln -sfn "$src" "$DCRUP_BIN/dcr"
+ write_active "$id"
+ ok "default → $id ($DCRUP_BIN/dcr)"
+}
+
+fetch_json() {
+ need_cmd curl
+ curl -fsSL "$1"
+}
+
+json_tag() {
+ printf '%s' "$1" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1
+}
+
+json_asset_url() {
+ local json="$1" asset="$2"
+ printf '%s' "$json" | sed -n "s#.*\"browser_download_url\"[[:space:]]*:[[:space:]]*\"\([^\"]*/${asset}\)\"#\1#p" | head -n1
+}
+
+latest_stable_json() {
+ fetch_json "$API_LATEST"
+}
+
+latest_dev_json() {
+ need_cmd curl
+ local json result
+ json="$(fetch_json "$API_ALL")"
+ if command -v jq >/dev/null 2>&1; then
+ result="$(printf '%s' "$json" | jq -e -c '[.[] | select(.prerelease == true)][0]')" || true
+ elif command -v python3 >/dev/null 2>&1; then
+ result="$(printf '%s' "$json" | python3 -c '
+import sys, json
+rels = json.load(sys.stdin)
+pre = [r for r in rels if r.get("prerelease")]
+print(json.dumps(pre[0]) if pre else "", end="")
+')"
+ else
+ die "dev channel needs jq or python3 to parse GitHub API"
+ fi
+ [[ -n "$result" && "$result" != "null" ]] || die "no prerelease (dev) found"
+ printf '%s' "$result"
+}
+
+release_json_for_spec() {
+ if [[ "$SPEC_FLOATING" -eq 1 ]]; then
+ case "$SPEC_CHANNEL" in
+ stable) latest_stable_json ;;
+ dev) latest_dev_json ;;
+ night) die "night has no GitHub release binary" ;;
+ esac
+ else
+ fetch_json "https://api.github.com/repos/dexoron/dcr/releases/tags/v${SPEC_VERSION}"
+ fi
+}
+
+install_prebuilt() {
+ local triple asset tag version url json id dest
+ need_cmd curl
+ triple="$(detect_triple)"
+ json="$(release_json_for_spec)"
+ tag="$(json_tag "$json")"
+ [[ -n "$tag" ]] || die "failed to parse release tag"
+ version="$(normalize_version "$tag")"
+ asset="dcr-${triple}-${version}"
+ case "$triple" in
+ *windows*) asset="${asset}.exe" ;;
+ esac
+ url="$(json_asset_url "$json" "$asset")"
+ [[ -n "$url" ]] || die "asset not found: $asset (tag $tag)"
+
+ id="$(toolchain_id "$version")"
+ dest="$DCRUP_TC/$id"
+ if [[ -x "$dest/dcr" && "${FORCE:-0}" != "1" ]]; then
+ ok "already installed: $id"
+ link_default "$id"
+ return 0
+ fi
+
+ log "downloading $asset …"
+ mkdir -p "$dest"
+ curl -fL "$url" -o "$dest/dcr"
+ chmod +x "$dest/dcr"
+ printf '%s\n' \
+ "channel=$SPEC_CHANNEL" \
+ "version=$version" \
+ "tag=$tag" \
+ "source=release" \
+ "triple=$triple" \
+ "libc=${DCRUP_LIBC:-gnu}" \
+ >"$dest/dcrup-meta"
+ ok "installed prebuilt $id ($triple)"
+ link_default "$id"
+}
+
+install_build() {
+ local tmp id dest sha version branch_or_tag
+ need_cmd git
+ need_cmd cargo
+
+ tmp="$(mktemp -d "${TMPDIR:-/tmp}/dcrup.XXXXXX")"
+ cleanup_tmp() { rm -rf "$tmp"; }
+ trap cleanup_tmp EXIT
+
+ if [[ "$SPEC_CHANNEL" == "night" ]]; then
+ log "cloning $REPO_URL (branch dev) for night …"
+ git clone --depth 1 --branch dev "$REPO_URL" "$tmp/src" 2>/dev/null \
+ || git clone --depth 1 "$REPO_URL" "$tmp/src"
+ sha="$(git -C "$tmp/src" rev-parse --short HEAD)"
+ version="night"
+ id="$(toolchain_id "$sha")"
+ elif [[ "$SPEC_FLOATING" -eq 1 ]]; then
+ local json tag
+ json="$(release_json_for_spec)"
+ tag="$(json_tag "$json")"
+ [[ -n "$tag" ]] || die "failed to resolve floating channel tag"
+ version="$(normalize_version "$tag")"
+ id="$(toolchain_id "$version")"
+ log "cloning $REPO_URL @ $tag …"
+ git clone --depth 1 --branch "$tag" "$REPO_URL" "$tmp/src" 2>/dev/null \
+ || {
+ git clone --depth 1 "$REPO_URL" "$tmp/src"
+ git -C "$tmp/src" fetch --depth 1 origin "refs/tags/${tag}:refs/tags/${tag}"
+ git -C "$tmp/src" checkout "$tag"
+ }
+ sha="$(git -C "$tmp/src" rev-parse --short HEAD)"
+ else
+ version="$SPEC_VERSION"
+ id="$(toolchain_id)"
+ local tag="v${SPEC_VERSION}"
+ log "cloning $REPO_URL @ $tag …"
+ git clone --depth 1 --branch "$tag" "$REPO_URL" "$tmp/src" 2>/dev/null \
+ || {
+ git clone --depth 1 "$REPO_URL" "$tmp/src"
+ git -C "$tmp/src" fetch --depth 1 origin "refs/tags/${tag}:refs/tags/${tag}"
+ git -C "$tmp/src" checkout "$tag"
+ }
+ sha="$(git -C "$tmp/src" rev-parse --short HEAD)"
+ fi
+
+ dest="$DCRUP_TC/$id"
+ if [[ -x "$dest/dcr" && "${FORCE:-0}" != "1" ]]; then
+ ok "already installed: $id"
+ link_default "$id"
+ return 0
+ fi
+
+ log "building release (features: $FEATURES) …"
+ (
+ cd "$tmp/src"
+ # shellcheck disable=SC2086
+ cargo build --release --features $FEATURES
+ )
+ mkdir -p "$dest"
+ cp "$tmp/src/target/release/dcr" "$dest/dcr"
+ chmod +x "$dest/dcr"
+ printf '%s\n' "channel=$SPEC_CHANNEL" "version=$version" "git=$sha" "source=build" "features=$FEATURES" >"$dest/dcrup-meta"
+ ok "installed build $id"
+ link_default "$id"
+ trap - EXIT
+ cleanup_tmp
+}
+
+cmd_install() {
+ local mode="release" FORCE=0
+ local spec=""
+ local libc_cli=""
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --build) mode="build"; shift ;;
+ --release) mode="release"; shift ;;
+ --force) FORCE=1; shift ;;
+ --libc)
+ [[ $# -ge 2 ]] || die "--libc needs gnu|musl|auto"
+ libc_cli="$2"
+ shift 2
+ ;;
+ --libc=*)
+ libc_cli="${1#--libc=}"
+ shift
+ ;;
+ -h|--help) usage; exit 0 ;;
+ -*) die "unknown flag: $1" ;;
+ *)
+ [[ -z "$spec" ]] || die "unexpected argument: $1"
+ spec="$1"
+ shift
+ ;;
+ esac
+ done
+ [[ -n "$spec" ]] || die "usage: dcrup install [--build|--release] [--force] [--libc gnu|musl|auto]"
+ parse_spec "$spec"
+ ensure_dirs
+
+ if [[ -n "$libc_cli" ]]; then
+ case "$libc_cli" in
+ gnu|glibc|musl|auto) DCRUP_LIBC="$libc_cli" ;;
+ *) die "invalid --libc: $libc_cli (use gnu|musl|auto)" ;;
+ esac
+ fi
+ export DCRUP_LIBC="${DCRUP_LIBC:-gnu}"
+
+ if [[ "$SPEC_CHANNEL" == "night" ]]; then
+ mode="build"
+ fi
+
+ export FORCE
+ log "install spec=$SPEC_RAW channel=$SPEC_CHANNEL version=${SPEC_VERSION:-∅} mode=$mode libc=$DCRUP_LIBC"
+ if [[ "$mode" == "build" ]]; then
+ install_build
+ else
+ install_prebuilt
+ fi
+ check_path
+}
+
+cmd_default() {
+ [[ $# -eq 1 ]] || die "usage: dcrup default "
+ parse_spec "$1"
+ ensure_dirs
+ local id
+ if [[ "$SPEC_CHANNEL" == "night" || "$SPEC_FLOATING" -eq 1 ]]; then
+ id="$(ls -1 "$DCRUP_TC" 2>/dev/null | grep -E "^${SPEC_CHANNEL}-" | sort | tail -n1 || true)"
+ [[ -n "$id" ]] || die "no installed toolchain for channel $SPEC_CHANNEL (run: dcrup install $SPEC_CHANNEL)"
+ else
+ id="$(toolchain_id)"
+ [[ -d "$DCRUP_TC/$id" ]] || die "not installed: $id (run: dcrup install $1)"
+ fi
+ link_default "$id"
+}
+
+cmd_update() {
+ ensure_dirs
+ local active
+ active="$(read_active)"
+ [[ -n "$active" ]] || die "no active toolchain (run: dcrup install stable)"
+ case "$active" in
+ night-*)
+ log "updating night (rebuild HEAD) …"
+ FORCE=1 cmd_install night --build --force
+ ;;
+ stable-*|dev-*)
+ local ch="${active%%-*}"
+ log "updating floating channel $ch …"
+ FORCE=1 cmd_install "$ch" --force
+ ;;
+ *-stable|*-dev)
+ ok "pinned toolchain $active — not floating; install a newer version explicitly"
+ ;;
+ *)
+ warn "unknown active id $active; try: dcrup install stable"
+ ;;
+ esac
+}
+
+cmd_list() {
+ ensure_dirs
+ local active
+ active="$(read_active)"
+ if [[ ! -d "$DCRUP_TC" ]] || [[ -z "$(ls -A "$DCRUP_TC" 2>/dev/null || true)" ]]; then
+ warn "no toolchains installed"
+ return 0
+ fi
+ local d base
+ for d in "$DCRUP_TC"/*; do
+ [[ -d "$d" ]] || continue
+ base="$(basename "$d")"
+ if [[ "$base" == "$active" ]]; then
+ printf ' * %s\n' "$base"
+ else
+ printf ' %s\n' "$base"
+ fi
+ done
+}
+
+cmd_show() {
+ ensure_dirs
+ local active
+ active="$(read_active)"
+ echo "DCRUP_HOME=$DCRUP_HOME"
+ echo "DCRUP_BIN=$DCRUP_BIN"
+ echo "active=${active:-none}"
+ if [[ -n "$active" && -f "$DCRUP_TC/$active/dcrup-meta" ]]; then
+ echo "--- meta ---"
+ cat "$DCRUP_TC/$active/dcrup-meta"
+ fi
+ if [[ -x "$DCRUP_BIN/dcr" ]]; then
+ echo "--- dcr --version ---"
+ "$DCRUP_BIN/dcr" --version 2>/dev/null || true
+ fi
+}
+
+cmd_which() {
+ if [[ -L "$DCRUP_BIN/dcr" || -e "$DCRUP_BIN/dcr" ]]; then
+ readlink -f "$DCRUP_BIN/dcr" 2>/dev/null || readlink "$DCRUP_BIN/dcr" 2>/dev/null || echo "$DCRUP_BIN/dcr"
+ else
+ die "dcr not linked (run: dcrup install stable)"
+ fi
+}
+
+cmd_uninstall() {
+ ensure_dirs
+ if [[ "${1:-}" == "--all" ]]; then
+ rm -rf "$DCRUP_TC"
+ rm -f "$DCRUP_BIN/dcr" "$(active_file)"
+ ok "removed all toolchains"
+ return 0
+ fi
+ [[ $# -eq 1 ]] || die "usage: dcrup uninstall |--all"
+ parse_spec "$1"
+ local id
+ if [[ "$SPEC_FLOATING" -eq 1 || "$SPEC_CHANNEL" == "night" ]]; then
+ id="$(ls -1 "$DCRUP_TC" 2>/dev/null | grep -E "^${SPEC_CHANNEL}-" | sort | tail -n1 || true)"
+ else
+ id="$(toolchain_id)"
+ fi
+ [[ -n "$id" && -d "$DCRUP_TC/$id" ]] || die "not installed: $1"
+ rm -rf "$DCRUP_TC/$id"
+ if [[ "$(read_active)" == "$id" ]]; then
+ rm -f "$DCRUP_BIN/dcr" "$(active_file)"
+ warn "removed active toolchain; run: dcrup install stable"
+ fi
+ ok "removed $id"
+}
+
+check_path() {
+ case ":$PATH:" in
+ *":$DCRUP_BIN:"*) ;;
+ *)
+ warn "$DCRUP_BIN is not in PATH"
+ echo "Add to ~/.bashrc / ~/.zshrc / ~/.profile:"
+ echo " export PATH=\"$DCRUP_BIN:\$PATH\""
+ ;;
+ esac
+}
+
+cmd_self_install() {
+ local dest="$DCRUP_BIN"
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --to) dest="$2"; shift 2 ;;
+ *) die "usage: dcrup self-install [--to DIR]" ;;
+ esac
+ done
+ ensure_dirs
+ mkdir -p "$dest"
+ local self
+ self="$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")"
+ cp "$self" "$dest/dcrup"
+ chmod +x "$dest/dcrup"
+ ok "dcrup installed to $dest/dcrup"
+ check_path
+ echo "Then: dcrup install stable"
+}
+
+main() {
+ local cmd="${1:-}"
+ shift || true
+ case "$cmd" in
+ ""|-h|--help|help) usage ;;
+ install) cmd_install "$@" ;;
+ default) cmd_default "$@" ;;
+ update) cmd_update "$@" ;;
+ list) cmd_list "$@" ;;
+ show) cmd_show "$@" ;;
+ which) cmd_which "$@" ;;
+ uninstall) cmd_uninstall "$@" ;;
+ self-install) cmd_self_install "$@" ;;
+ *) die "unknown command: $cmd (try: dcrup help)" ;;
+ esac
+}
+
+main "$@"
diff --git a/docs/README.mdx b/docs/README.mdx
index da4008e..f4e4d7f 100644
--- a/docs/README.mdx
+++ b/docs/README.mdx
@@ -8,8 +8,10 @@ sidebar_label: Introduction
Quick reference
```bash
-# Installation
-curl -fsSL https://dcr.dexoron.su/install.sh | sh
+# Installation (dcrup)
+curl -fsSL https://sh.dcr-tool.ru | sh -s -- self-install
+export PATH="$HOME/.dcr/bin:$PATH"
+dcrup install stable
# Create a project
dcr new my-app
diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx
index 95c19b9..edfb38d 100644
--- a/docs/getting-started/installation.mdx
+++ b/docs/getting-started/installation.mdx
@@ -4,6 +4,64 @@ sidebar_label: Installation
# Installation
+## dcrup (recommended)
+
+Install and switch DCR versions (stable / dev / night, pin `0.8.2`, optional build from source).
+
+### Linux / macOS / BSD / Windows (bash)
+
+```sh
+curl -fsSL https://sh.dcr-tool.ru | sh -s -- self-install
+export PATH="$HOME/.dcr/bin:$PATH"
+dcrup install stable
+```
+
+Non-interactive install of DCR in one shot:
+
+```sh
+curl -fsSL https://sh.dcr-tool.ru | sh -s -- install stable
+export PATH="$HOME/.dcr/bin:$PATH"
+```
+
+### Windows (PowerShell)
+
+```powershell
+irm https://ps1.dcr-tool.ru | iex
+# or download then run:
+# irm https://ps1.dcr-tool.ru -OutFile dcrup.ps1
+# powershell -File .\dcrup.ps1 self-install
+# $env:Path += ";$env:USERPROFILE\.dcr\bin"
+dcrup install stable
+```
+
+Optional cmd bootstrap (if you prefer `curl` of the cmd shim):
+
+```bat
+curl -fsSL -o dcrup.cmd https://cmd.dcr-tool.ru
+```
+
+After `self-install`, the command is **`dcrup`** (no `.sh` / `.ps1`): shims live in `~/.dcr/bin` (Unix) or `%USERPROFILE%\.dcr\bin` (Windows `dcrup.cmd`).
+
+### Common dcrup commands
+
+```sh
+dcrup install stable # latest stable prebuilt
+dcrup install 0.8.2 # pin → 0.8.2@stable
+dcrup install 0.8.2@dev
+dcrup install stable --libc musl # Linux: musl asset (default: gnu)
+dcrup install stable --build # cargo build --features archive
+dcrup install night # always build from branch dev HEAD
+dcrup default 0.8.2
+dcrup update
+dcrup list
+dcrup show
+dcrup which
+```
+
+Layout: `~/.dcr/toolchains//dcr` and `~/.dcr/bin/dcr` → active version.
+
+---
+
## Arch Linux (AUR)
```sh
@@ -23,11 +81,12 @@ brew install dcr
sudo snap install dcrup
```
+> If classic Snap Store publishing is unavailable, install the `.snap` from [GitHub Releases](https://github.com/dexoron/dcr/releases/latest) with `--dangerous`.
+
## Nix (flake)
```sh
nix run github:dexoron/dcr
-# or install to profile:
nix profile install github:dexoron/dcr
```
@@ -37,48 +96,37 @@ nix profile install github:dexoron/dcr
cargo install dcr
```
-## Linux / macOS (script)
+Note: crates.io builds may omit optional features. For FAT disk images (`[archive]`), prefer release binaries or:
```sh
-curl -fsSL https://dcr.dexoron.su/install.sh | sh
-```
-
-## BSD (script)
-
-```sh
-curl -fsSL https://dcr.dexoron.su/install_bsd.sh | sh
-```
-
-## Windows (PowerShell)
-
-```powershell
-irm https://dcr.dexoron.su/install.ps1 | iex
+cargo install dcr --features archive
```
## From source
```sh
git clone https://github.com/dexoron/dcr.git
-cd dcr && cargo build --release
+cd dcr
+cargo build --release --features archive
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
+# or manage versions with dcrup install night / --build
```
## Post-install
-Verify the installation:
-
```bash
dcr --version
+dcrup show # if installed via dcrup
```
-Man pages are available after installation:
+Man pages (package installs / release assets):
```bash
man dcr
man dcr-build
```
-To use the registry, create `~/.dcr/config.toml`:
+Registry (optional) — `~/.dcr/config.toml`:
```toml
[registry.default]
diff --git a/src/core/build/common.rs b/src/core/build/common.rs
index 3b873c7..a035ae6 100644
--- a/src/core/build/common.rs
+++ b/src/core/build/common.rs
@@ -158,7 +158,26 @@ pub fn is_excluded(path: &Path, exclude_dirs: &[PathBuf], include_paths: &[Strin
if matches_patterns(path, include_paths, false) {
return true;
}
- exclude_dirs.iter().any(|dir| path.starts_with(dir))
+ exclude_dirs.iter().any(|dir| path_is_under(path, dir))
+}
+
+fn path_is_under(path: &Path, prefix: &Path) -> bool {
+ if path.starts_with(prefix) {
+ return true;
+ }
+ match (fs::canonicalize(path), fs::canonicalize(prefix)) {
+ (Ok(p), Ok(pref)) => p.starts_with(pref),
+ _ => {
+ let p = path.to_string_lossy().replace('\\', "/");
+ let pref = prefix.to_string_lossy().replace('\\', "/");
+ if p == pref || p.starts_with(&format!("{pref}/")) {
+ return true;
+ }
+ let p = p.strip_prefix("/private").unwrap_or(&p);
+ let pref = pref.strip_prefix("/private").unwrap_or(&pref);
+ p == pref || p.starts_with(&format!("{pref}/"))
+ }
+ }
}
fn matches_patterns(path: &Path, patterns: &[String], positive: bool) -> bool {
@@ -379,7 +398,7 @@ pub fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
} else {
out.set_extension(obj_ext);
}
- out.to_string_lossy().to_string()
+ out.to_string_lossy().replace('\\', "/")
}
pub fn needs_rebuild(source: &str, object: &str) -> bool {
@@ -509,12 +528,18 @@ mod tests {
static TEST_COUNTER: AtomicUsize = AtomicUsize::new(0);
static CWD_LOCK: Mutex<()> = Mutex::new(());
+ fn cwd_lock() -> std::sync::MutexGuard<'static, ()> {
+ CWD_LOCK
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner())
+ }
+
fn temp_dir(prefix: &str) -> PathBuf {
let n = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
let dir = std::env::temp_dir().join(format!("dcr_test_{prefix}_{n}"));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
- dir
+ fs::canonicalize(&dir).unwrap_or(dir)
}
fn default_roots() -> Vec {
@@ -679,7 +704,7 @@ mod tests {
fs::write(src.join("utils.c"), "").unwrap();
fs::write(src.join("README.md"), "").unwrap(); // should be ignored
- let _guard = CWD_LOCK.lock().unwrap();
+ let _guard = cwd_lock();
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(&dir).unwrap();
let include: Vec = Vec::new();
@@ -703,7 +728,7 @@ mod tests {
fs::write(src.join("other.cc"), "").unwrap();
fs::write(src.join("skip.c"), "").unwrap(); // should be ignored
- let _guard = CWD_LOCK.lock().unwrap();
+ let _guard = cwd_lock();
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(&dir).unwrap();
let include: Vec = Vec::new();
@@ -721,7 +746,7 @@ mod tests {
let src = dir.join("src");
fs::create_dir_all(&src).unwrap();
- let _guard = CWD_LOCK.lock().unwrap();
+ let _guard = cwd_lock();
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(&dir).unwrap();
let include: Vec = Vec::new();
@@ -743,7 +768,7 @@ mod tests {
fs::write(vendor.join("lib.c"), "").unwrap(); // should be excluded
let exclude = vec![vendor.clone()];
- let _guard = CWD_LOCK.lock().unwrap();
+ let _guard = cwd_lock();
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(&dir).unwrap();
let include: Vec = Vec::new();
@@ -765,7 +790,7 @@ mod tests {
fs::write(src.join("main.c"), "").unwrap();
fs::write(sub.join("nested.c"), "").unwrap();
- let _guard = CWD_LOCK.lock().unwrap();
+ let _guard = cwd_lock();
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(&dir).unwrap();
let include: Vec = Vec::new();
@@ -789,7 +814,7 @@ mod tests {
let exclude = vec![boot.clone()];
let include = vec!["src/boot/arch/**".to_string()];
- let _guard = CWD_LOCK.lock().unwrap();
+ let _guard = cwd_lock();
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(&dir).unwrap();
let roots = default_roots();
@@ -816,7 +841,7 @@ mod tests {
"!src/legacy/**".to_string(),
"src/legacy/allow/**".to_string(),
];
- let _guard = CWD_LOCK.lock().unwrap();
+ let _guard = cwd_lock();
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(&dir).unwrap();
let roots = default_roots();
@@ -839,7 +864,7 @@ mod tests {
let exclude: Vec = Vec::new();
let include = vec!["!src/gen/**".to_string()];
- let _guard = CWD_LOCK.lock().unwrap();
+ let _guard = cwd_lock();
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(&dir).unwrap();
let roots = default_roots();
@@ -881,7 +906,7 @@ mod tests {
fs::write(&src, "int main() {}").unwrap();
fs::write(&header, "#define A 1").unwrap();
- std::thread::sleep(std::time::Duration::from_millis(50));
+ std::thread::sleep(std::time::Duration::from_millis(1100));
fs::write(&obj, "").unwrap();
let d_content = format!(
@@ -897,8 +922,8 @@ mod tests {
"should be fresh"
);
- std::thread::sleep(std::time::Duration::from_millis(50));
- fs::write(&header, "#define A 2").unwrap(); // modify header
+ std::thread::sleep(std::time::Duration::from_millis(1100));
+ fs::write(&header, "#define A 2").unwrap();
assert!(
needs_rebuild(&src.to_string_lossy(), &obj.to_string_lossy()),
diff --git a/src/core/build/engine.rs b/src/core/build/engine.rs
index 02521ad..f0d1ee4 100644
--- a/src/core/build/engine.rs
+++ b/src/core/build/engine.rs
@@ -950,7 +950,8 @@ fn absolutize_flag_path(flag: &str, project_root: &Path, prefixes: &[&str]) -> S
return flag.to_string();
}
let abs = project_root.join(rest);
- return format!("{prefix}{}", abs.to_string_lossy());
+ let path = abs.to_string_lossy().replace('\\', "/");
+ return format!("{prefix}{path}");
}
}
flag.to_string()
diff --git a/src/core/deps/mod.rs b/src/core/deps/mod.rs
index d1d6675..f6c163c 100644
--- a/src/core/deps/mod.rs
+++ b/src/core/deps/mod.rs
@@ -242,10 +242,13 @@ mod tests {
.unwrap()
.as_nanos()
));
- std::fs::create_dir_all(root.join("target/lib")).unwrap();
+ std::fs::create_dir_all(root.join("target").join("lib")).unwrap();
let mut paths = Vec::new();
push_default_lib_dirs(&mut paths, &root);
- assert!(paths.iter().any(|p| p.ends_with("target/lib")));
+ assert!(paths.iter().any(|p| {
+ let n = p.replace('\\', "/");
+ n.ends_with("target/lib")
+ }));
let _ = std::fs::remove_dir_all(root);
}
}
diff --git a/src/core/deps/register.rs b/src/core/deps/register.rs
index c9e3789..ccea2c2 100644
--- a/src/core/deps/register.rs
+++ b/src/core/deps/register.rs
@@ -219,16 +219,29 @@ mod tests {
#[test]
fn registry_package_root_accepts_package_dir_or_manifest_path() {
- let pkg = serde_json::json!({ "path": "/tmp/pkg" });
+ let root = std::env::temp_dir().join(format!("dcr_reg_pkg_{}", std::process::id()));
+ let _ = std::fs::remove_dir_all(&root);
+ std::fs::create_dir_all(&root).unwrap();
+ let root_str = root.to_string_lossy().replace('\\', "/");
+
+ let pkg = serde_json::json!({ "path": root_str });
assert_eq!(
- package_root_from_registry_info(&pkg).unwrap(),
- PathBuf::from("/tmp/pkg")
+ package_root_from_registry_info(&pkg)
+ .unwrap()
+ .to_string_lossy()
+ .replace('\\', "/"),
+ root_str
);
- let pkg = serde_json::json!({ "path": "/tmp/pkg/dcr.toml" });
+ let manifest = format!("{root_str}/dcr.toml");
+ let pkg = serde_json::json!({ "path": manifest });
assert_eq!(
- package_root_from_registry_info(&pkg).unwrap(),
- PathBuf::from("/tmp/pkg")
+ package_root_from_registry_info(&pkg)
+ .unwrap()
+ .to_string_lossy()
+ .replace('\\', "/"),
+ root_str
);
+ let _ = std::fs::remove_dir_all(&root);
}
}