From 75eace9d73c7db5eb5a795df5d1b7ad0e5d34bae Mon Sep 17 00:00:00 2001 From: Dexoron Date: Thu, 23 Jul 2026 14:58:49 +0800 Subject: [PATCH] feat: add dcrup (sh/ps1/cmd) and fix cross-platform unit tests --- README.md | 29 +- dcrup.cmd | 13 + dcrup.ps1 | 367 ++++++++++++++++ dcrup.sh | 599 ++++++++++++++++++++++++++ docs/README.mdx | 6 +- docs/getting-started/installation.mdx | 88 +++- src/core/build/common.rs | 53 ++- src/core/build/engine.rs | 3 +- src/core/deps/mod.rs | 7 +- src/core/deps/register.rs | 25 +- 10 files changed, 1129 insertions(+), 61 deletions(-) create mode 100755 dcrup.cmd create mode 100644 dcrup.ps1 create mode 100755 dcrup.sh 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); } }