Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

+64
View File
@@ -0,0 +1,64 @@
# 安装 Dever
支持 Linux x86_64、glibc 2.41 及以上,已在 Debian 13 验证。应用开发只需安装 Dever,编译环境由安装包管理。
## 在线安装
```sh
curl -fsSL https://git.dever.cc/dever/core/releases/download/v0.0.1/install | bash
```
默认安装到 `~/.dever`。重新打开终端,或执行安装器打印的命令加载 PATH。
安装器先提供 Dever 命令,再后台下载编译环境。准备期间可使用 `new/fmt/check`;首次 `run/test/build` 会等待准备完成。下载中断后再次安装或构建即可继续。首次构建还会编译依赖,后续使用缓存。
CI 需要等待完整环境就绪:
```sh
curl -fsSL https://git.dever.cc/dever/core/releases/download/v0.0.1/install | bash -s -- --wait
```
| 选项 | 用途 |
| --- | --- |
| `--prefix <目录>` | 指定安装目录 |
| `--no-modify-path` | 不修改终端启动文件 |
| `--wait` | 等待编译环境就绪 |
| `--release-url <地址>` | 使用自托管发行资源 |
需要 Bash、支持并行下载的 curl、tar、xz、sha256sum、flock、getconf 和 nohup。`RUSTUP_DIST_SERVER` 可指定受信任的工具链镜像。
同版本安装会复用已有文件。验证重新打包的 `0.0.1` 时使用新的 `--prefix` 目录。安装不覆盖项目、系统编译器或用户 Cargo 配置。
## 离线安装
从 [发布页](https://git.dever.cc/dever/core/releases/tag/v0.0.1) 下载完整开发包及校验文件,在可写目录解压:
```sh
sha256sum -c dever-0.0.1-linux-x86_64-development.tar.xz.sha256
tar -xf dever-0.0.1-linux-x86_64-development.tar.xz
export PATH="$PWD/dever/bin:$PATH"
dever new hello
dever test hello
dever run hello -- hello.greeting.greet '{"name":"Dever"}'
```
完整包包含编译工具和离线依赖,无需另外安装 Rust、Cargo 或 GCC。外部 Python/JavaScript/Go 组件仍需准备它们各自的运行资源。
## 缓存与排错
在线安装的共享工具在 `toolchains/`,依赖缓存在 `cache/cargo/`;离线包使用包内 `toolchain/`。项目编译缓存位于安装包的 `rust/target/`。
环境准备失败时,命令会给出 `prepare.log` 路径;构建错误给出 `build.log` 路径。失败返回非零状态,修复后重试原命令。
安装包附 SHA-256,当前未签名。包中保留第三方许可证和工具版本记录。
## 交付程序
```sh
dever build hello --output ./hello-app
./hello-app hello.greeting.greet '{"name":"Dever"}'
```
部署只携带业务二进制、配置和必要资源;目标系统仍须具备匹配的系统运行库。安装包可以搬到新目录,移动后可能需要重新编译缓存。
编译器作者组装安装包的方法见 [源码开发](../rust/README.md)。
+280
View File
@@ -0,0 +1,280 @@
#!/usr/bin/env python3
"""在 Debian 13 开发容器内组装 Linux x86_64 完整离线开发包。"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import tomllib
SOURCE = Path(__file__).resolve().parents[1]
TRIPLE = "x86_64-unknown-linux-gnu"
PACKAGES = (
"gcc-14-x86-64-linux-gnu", "cpp-14-x86-64-linux-gnu", "libgcc-14-dev",
"libc6-dev", "linux-libc-dev", "binutils-x86-64-linux-gnu", "libc6", "libgcc-s1",
)
def native_input(path):
"""只保留原生 C 构建所需工具;Rust LTO 使用包内 LLVM,不需要 GCC LTO。"""
name = str(path)
if name.startswith("/usr/include/"):
return True
if path.parent == Path("/usr/bin"):
return path.name in {
"x86_64-linux-gnu-gcc-14", "x86_64-linux-gnu-as",
"x86_64-linux-gnu-ld", "x86_64-linux-gnu-ar", "x86_64-linux-gnu-ranlib",
}
if name.startswith("/usr/libexec/gcc/"):
return path.name in {"cc1", "collect2", "liblto_plugin.so", "lto-wrapper"}
if name.startswith("/usr/lib/gcc/"):
return "/include/" in name or path.name in {
"libgcc.a", "libgcc_eh.a", "libgcc_s.so", "crtbegin.o", "crtbeginS.o",
"crtbeginT.o", "crtend.o", "crtendS.o",
"liblto_plugin.so",
}
return path.parent in {Path("/usr/lib/x86_64-linux-gnu"), Path("/usr/lib64")}
def output(*arguments, **options):
return subprocess.check_output(arguments, text=True, **options).strip()
def digest(path):
with path.open("rb") as stream:
return hashlib.file_digest(stream, "sha256").hexdigest()
def copy(source, destination):
"""只解引用作者明确选定的工具文件,不复制 Cargo home 或用户配置。"""
if source.is_dir():
shutil.copytree(source, destination, dirs_exist_ok=True)
else:
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source, destination)
destination.chmod(0o755 if os.access(source, os.X_OK) else 0o644)
def script(path, body):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text('#!/bin/sh\nset -eu\ntools=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)\n' + body)
path.chmod(0o755)
def prepare(compiler, work):
"""两种交付方式共用现有本地打包入口,不另做编译器发行格式。"""
work.mkdir()
version = tomllib.loads((SOURCE / "rust/Cargo.toml").read_text())["workspace"]["package"]["version"]
rust_version = tomllib.loads((SOURCE / "rust/rust-toolchain.toml").read_text())["toolchain"]["channel"]
author = work / "author"
for path in (
"rust/Cargo.toml", "rust/Cargo.lock", "rust/rust-toolchain.toml", "rust/src",
"lib", "skills", "LANGUAGE.md", "DOCUMENTATION.md",
):
copy(SOURCE / path, author / "source" / path)
copy(compiler, author / "bin/dever")
(author / "config").mkdir()
(author / "config/setting.json").write_text(json.dumps({
"format": "dever-local-bootstrap-input-v1", "version": version,
"target": "linux-x86_64", "source": "source",
"core": {"source": "bin/dever", "sha256": digest(compiler)},
}))
bundle = work / "dever"
subprocess.run([str(compiler), "release", str(author), "--output", str(bundle)], check=True)
return bundle, version, rust_version
def record(bundle, paths):
"""把交付附带文件纳入原有清单校验。"""
manifest = json.loads((bundle / "bootstrap.json").read_text())
for path in sorted(paths):
if path.is_symlink():
raise RuntimeError(f"staged artifact must be a regular file: {path}")
if path.is_file():
manifest["artifacts"].append({
"path": path.relative_to(bundle).as_posix(), "size": path.stat().st_size,
"sha256": digest(path), "executable": os.access(path, os.X_OK),
})
manifest["artifacts"].sort(key=lambda artifact: artifact["path"])
(bundle / "bootstrap.json").write_text(json.dumps(manifest, indent=2) + "\n")
return manifest
def native_tools(tools):
"""保留 Debian 工具的相对安装布局;glibc 只进链接 sysroot,不覆盖宿主加载器。"""
base = {str(Path(path).resolve()) for path in output("dpkg-query", "-L", "libc6").splitlines()}
packages = set(PACKAGES)
executables = []
for package in PACKAGES:
for name in output("dpkg-query", "-L", package).splitlines():
path = Path(name)
if not path.is_file() or not native_input(path):
continue
copy(path, tools / "native" / path.relative_to("/"))
# Debian 的链接脚本使用 /lib;发行包展开 usrmerge 别名,不改写官方库文件。
if package == "libc6":
copy(path, tools / "native" / path.relative_to("/usr"))
if os.access(path, os.X_OK):
with path.open("rb") as stream:
if stream.read(4) == b"\x7fELF":
executables.append(path)
for name in ("as", "ld", "ar"):
copy(Path(f"/usr/bin/x86_64-linux-gnu-{name}"), tools / "native/usr/bin" / name)
libraries = set()
for executable in executables:
listing = subprocess.run(["ldd", str(executable)], text=True, capture_output=True)
if listing.returncode and "not a dynamic executable" not in listing.stderr + listing.stdout:
raise RuntimeError(f"ldd {executable}: {listing.stderr}{listing.stdout}")
for line in listing.stdout.splitlines():
if "=> not found" in line:
raise RuntimeError(line)
fields = line.split()
if "=>" in fields and fields[2].startswith("/"):
library = Path(fields[2])
if str(library.resolve()) not in base:
libraries.add(library)
for library in sorted(libraries):
copy(library, tools / "lib" / library.name)
owner = output("dpkg-query", "-S", str(library.resolve())).splitlines()[0].split(": ", 1)[0]
packages.add(owner)
copy(Path("/usr/lib/x86_64-linux-gnu/libgcc_s.so.1"), tools / "lib/libgcc_s.so.1")
packages.add("libgcc-s1")
for package in sorted(packages):
name = package.split(":", 1)[0]
copy(Path("/usr/share/doc") / name / "copyright", tools / "licenses" / name / "copyright")
copy(Path("/usr/share/common-licenses"), tools / "licenses/common")
(tools / "native-packages.txt").write_text(output("dpkg-query", "-W", *sorted(packages)) + "\n")
script(tools / "bin/cc", '''export LD_LIBRARY_PATH="$tools/lib"
exec "$tools/native/usr/bin/x86_64-linux-gnu-gcc-14" \\
--sysroot="$tools/native" \\
-B"$tools/native/usr/lib/gcc/x86_64-linux-gnu/14/" \\
-B"$tools/native/usr/libexec/gcc/x86_64-linux-gnu/14/" \\
-B"$tools/native/usr/bin/" \\
-B"$tools/native/usr/lib/x86_64-linux-gnu/" "$@"
''')
def trim_vendor(vendor, version):
"""开发包编译业务程序;按 Cargo 完整依赖图保留运行库的全部特性和平台依赖。"""
metadata = json.loads(output("cargo", f"+{version}", "metadata", "--locked", "--offline",
"--all-features", "--format-version", "1", cwd=SOURCE / "rust"))
dependencies = {node["id"]: node["dependencies"] for node in metadata["resolve"]["nodes"]}
pending = [package["id"] for package in metadata["packages"] if package["name"] == "dever-runtime"]
required = set()
while pending:
package = pending.pop()
if package not in required:
required.add(package)
pending.extend(dependencies[package])
for package in metadata["packages"]:
if package["source"] and package["id"] not in required:
directory = vendor / f'{package["name"]}-{package["version"]}'
# 编译器二进制仍使用这些依赖;源码可移除,第三方许可必须随包保留。
licenses = {path for path in directory.rglob("*") if path.is_file()
and path.name.upper().startswith(("LICENSE", "COPYING", "COPYRIGHT", "NOTICE", "AUTHORS"))}
if package["license_file"]:
licenses.add(directory / package["license_file"])
for path in licenses:
copy(path, vendor.parent / "licenses/cargo" / path.relative_to(vendor))
shutil.rmtree(directory)
def rust_tools(tools, version, vendor):
rust = Path(output("rustc", f"+{version}", "--print", "sysroot"))
if f"host: {TRIPLE}" not in output("rustc", f"+{version}", "-vV"):
raise RuntimeError(f"this package requires the official {TRIPLE} toolchain")
for name in ("cargo", "rustc"):
copy(rust / "bin" / name, tools / "rust/bin" / name)
for library in (rust / "lib").glob("*.so*"):
if not library.is_symlink():
copy(library, tools / "rust/lib" / library.name)
for name in ("lib", "bin"):
copy(rust / "lib/rustlib" / TRIPLE / name, tools / "rust/lib/rustlib" / TRIPLE / name)
# 只裁剪已链接的工具;标准库 rlib/rmeta 中的符号、类型元数据与位码仍用于编译。
binaries = [*(tools / "rust/bin").iterdir(), *(tools / "rust/lib").glob("*.so*")]
binaries.extend(path for path in (tools / "rust/lib/rustlib" / TRIPLE / "bin").rglob("*")
if path.is_file())
for binary in binaries:
# 官方 .so 目录也含链接脚本,按原样保留,只处理 ELF 文件。
with binary.open("rb") as stream:
if stream.read(4) == b"\x7fELF":
subprocess.run(["strip", "--strip-unneeded", str(binary)], check=True)
copy(rust / "share/doc/rust", tools / "licenses/rust")
if vendor:
copy(vendor, tools / "vendor")
else:
subprocess.run([
"cargo", f"+{version}", "vendor", "--locked", "--offline", "--versioned-dirs",
str(tools / "vendor"),
], cwd=SOURCE / "rust", check=True)
trim_vendor(tools / "vendor", version)
config = tools / ".cargo/config.toml"
config.parent.mkdir()
config.write_text('''[source.crates-io]
replace-with = "vendored-sources"
[source.vendored-sources]
directory = "vendor"
''')
script(tools / "bin/cargo", '''export CARGO_HOME="$tools/.cargo"
export RUSTC="$tools/rust/bin/rustc"
export CC="$tools/bin/cc"
export AR="$tools/native/usr/bin/x86_64-linux-gnu-ar"
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER="$CC"
export LD_LIBRARY_PATH="$tools/lib"
export PATH="$tools/native/usr/bin:/usr/bin:/bin"
exec "$tools/rust/bin/cargo" --config "$tools/.cargo/config.toml" "$@"
''')
script(tools / "bin/rustc", '''export LD_LIBRARY_PATH="$tools/lib"
exec "$tools/rust/bin/rustc" "$@"
''')
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--compiler", required=True, type=Path)
parser.add_argument("--work", required=True, type=Path, help="new staging directory")
parser.add_argument("--output", required=True, type=Path, help="new .tar.xz or .tar.gz archive")
parser.add_argument("--vendor", type=Path, help="optional cargo vendor --locked --versioned-dirs result")
args = parser.parse_args()
system = Path("/etc/os-release").read_text()
if 'ID=debian\n' not in system or 'VERSION_ID="13"' not in system:
parser.error("prepare this package in the Debian 13 development container")
args.compiler = args.compiler.resolve(strict=True)
args.work = args.work.absolute()
args.output = args.output.absolute()
if not args.output.name.endswith((".tar.xz", ".tar.gz")):
parser.error("output must end in .tar.xz or .tar.gz")
if args.output.exists() or Path(str(args.output) + ".sha256").exists():
parser.error("output archive and checksum must not exist")
bundle, _, rust_version = prepare(args.compiler, args.work)
author = args.work / "author"
tools = bundle / "toolchain"
tools.mkdir()
native_tools(tools)
rust_tools(tools, rust_version, args.vendor)
# 先实际链接,避免把缺少 sysroot 文件的包花时间压缩后才发现。
smoke = author / "link.rs"
smoke.write_text('fn main() { println!("private Rust/C linker ready"); }\n')
subprocess.run([str(tools / "bin/rustc"), str(smoke), "-C",
"linker=" + str(tools / "bin/cc"), "-o", str(author / "link")], check=True)
subprocess.run([str(author / "link")], check=True)
copy(SOURCE / "build/README.md", bundle / "DEVELOPMENT.md")
manifest = record(bundle, [bundle / "DEVELOPMENT.md", *tools.rglob("*")])
args.output.parent.mkdir(parents=True, exist_ok=True)
# 归档必须先于第一次构建,不能把 Cargo 编译缓存放进发行包。
with args.output.open("xb") as stream:
compression = "xz -T4 -6" if args.output.name.endswith(".xz") else "gzip -9"
subprocess.run(["tar", "-C", str(args.work), "-I", compression, "-cf", "-", "dever"],
stdout=stream, check=True)
Path(str(args.output) + ".sha256").write_text(f"{digest(args.output)} {args.output.name}\n")
print(json.dumps({"archive": str(args.output), "bytes": args.output.stat().st_size,
"files": len(manifest["artifacts"]), "compiler_sha256": digest(args.compiler)}))
if __name__ == "__main__":
main()
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""在开发容器组装在线安装资源;不下载工具链、不发布或激活版本。"""
import argparse
import json
from pathlib import Path
import subprocess
import tomllib
from development import SOURCE, TRIPLE, copy, digest, native_tools, output, prepare, record, script
def archive(directory, destination):
with destination.open("xb") as stream:
subprocess.run([
"tar", "--sort=name", "--mtime=@0", "--owner=0", "--group=0", "--numeric-owner",
"-C", str(directory.parent), "-I", "xz -T4 -6", "-cf", "-", directory.name,
], stdout=stream, check=True)
return digest(destination)
def checksum(path):
Path(str(path) + ".sha256").write_text(f"{digest(path)} {path.name}\n")
def dependencies(bundle, rust_version):
"""Cargo 自己裁剪锁文件;安装时只下载本机运行库依赖,不带作者编译依赖。"""
project = bundle / "toolchain/project"
(project / "src").mkdir(parents=True)
(project / "src/main.rs").write_text("fn main() {}\n")
features = sorted(tomllib.loads((bundle / "rust/src/runtime/Cargo.toml").read_text())["features"])
(project / "Cargo.toml").write_text(
'[package]\nname = "dever-development-dependencies"\nversion = "0.0.0"\nedition = "2024"\n'
'[workspace]\n[dependencies]\n'
f'dever-runtime = {{ path = "../../rust/src/runtime", features = {json.dumps(features)} }}\n'
)
copy(bundle / "rust/Cargo.lock", project / "Cargo.lock")
subprocess.run(["cargo", f"+{rust_version}", "metadata", "--offline", "--format-version", "1",
"--manifest-path", str(project / "Cargo.toml")], stdout=subprocess.DEVNULL, check=True)
def compiler_licenses(tools, rust_version):
metadata = json.loads(output("cargo", f"+{rust_version}", "metadata", "--locked", "--offline",
"--all-features", "--format-version", "1", cwd=SOURCE / "rust"))
for package in metadata["packages"]:
if not package["source"]:
continue
source = Path(package["manifest_path"]).parent
licenses = {path for path in source.rglob("*") if path.is_file()
and path.name.upper().startswith(("LICENSE", "COPYING", "COPYRIGHT", "NOTICE", "AUTHORS"))}
if package["license_file"]:
licenses.add(source / package["license_file"])
for path in licenses:
copy(path, tools / "licenses/cargo" / source.name / path.relative_to(source))
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--compiler", required=True, type=Path)
parser.add_argument("--work", required=True, type=Path)
parser.add_argument("--output", required=True, type=Path, help="new release assets directory")
args = parser.parse_args()
system = Path("/etc/os-release").read_text()
if 'ID=debian\n' not in system or 'VERSION_ID="13"' not in system:
parser.error("prepare native tools in the Debian 13 development container")
compiler = args.compiler.resolve(strict=True)
work, destination = args.work.absolute(), args.output.absolute()
if work.exists() or destination.exists():
parser.error("work and output must be new directories")
destination.mkdir(parents=True)
bundle, version, rust_version = prepare(compiler, work)
tools = bundle / "toolchain"
tools.mkdir()
native = work / "native-tools"
native.mkdir()
native_tools(native)
native_archive = destination / "native.tar.xz"
native_id = archive(native, native_archive)
native_archive = native_archive.rename(destination / f"dever-native-linux-x86_64-{native_id}.tar.xz")
checksum(native_archive)
copy(native / "bin/cc", tools / "bin/cc")
script(tools / "bin/cargo", '''export CARGO_HOME="$tools/cache"
export RUSTC="$tools/rust/bin/rustc"
export CC="$tools/bin/cc"
export AR="$tools/native/usr/bin/x86_64-linux-gnu-ar"
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER="$CC"
export LD_LIBRARY_PATH="$tools/lib"
export PATH="$tools/native/usr/bin:/usr/bin:/bin"
exec "$tools/rust/bin/cargo" "$@"
''')
script(tools / "bin/rustc", '''export LD_LIBRARY_PATH="$tools/lib"
exec "$tools/rust/bin/rustc" "$@"
''')
(tools / "online").write_text(f"{rust_version} {native_id}\n")
copy(SOURCE / "build/prepare.sh", tools / "prepare")
dependencies(bundle, rust_version)
compiler_licenses(tools, rust_version)
copy(SOURCE / "build/README.md", bundle / "DEVELOPMENT.md")
record(bundle, [bundle / "DEVELOPMENT.md", *tools.rglob("*")])
core_archive = destination / f"dever-{version}-linux-x86_64.tar.xz"
archive(bundle, core_archive)
checksum(core_archive)
installer = (SOURCE / "install").read_text().replace('@DEVER_VERSION@', version)
(destination / "install").write_text(installer)
(destination / "install").chmod(0o755)
checksum(destination / "install")
print(json.dumps({"version": version, "compiler_sha256": digest(compiler),
"core_bytes": core_archive.stat().st_size,
"native_bytes": native_archive.stat().st_size,
"installer_bytes": (destination / "install").stat().st_size,
"rust": rust_version, "target": TRIPLE}))
if __name__ == "__main__":
main()
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
# 在线包的唯一环境准备入口;安装器后台启动,编译入口等待同一把锁。
set -euo pipefail
package=$(cd -- "$(dirname -- "$0")/.." && pwd -P)
tools="$package/toolchain"
root=$(cd "$package/../.." && pwd -P)
[[ ! -f $tools/ready ]] || exit 0
exec 9>"$root/.prepare.lock"
previous=
until flock -w 1 9; do
status='Waiting for Dever environment preparation...'
if [[ -f $tools/status ]]; then read -r status < "$tools/status" || true; fi
if [[ $status != "$previous" ]]; then printf '%s\n' "$status" >&2; previous=$status; fi
done
[[ ! -f $tools/ready ]] || exit 0
exec 3>&2
exec >"$tools/prepare.log" 2>&1
stage=
rust_stage=
phase='Preparing Dever environment'
finish() {
local code=$?
if (( code )); then
printf 'Failed: %s (exit %s). Details: %s\n' "$phase" "$code" "$tools/prepare.log" > "$tools/status"
cat "$tools/status" >&3
fi
if [[ -n $stage ]]; then rm -rf -- "$stage"; fi
if [[ -n $rust_stage ]]; then rm -rf -- "$rust_stage"; fi
}
trap finish EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
progress() {
phase=$1
printf '%s\n' "$phase" > "$tools/status"
printf '%s\n' "$phase" >&3
}
read -r rust_version native_id < "$tools/online"
mapfile -t sources < "$tools/sources"
release_url=${sources[0]}
rust_dist=${sources[1]}
triple=x86_64-unknown-linux-gnu
rust_root="$root/toolchains/rust-$rust_version-$triple"
native_root="$root/toolchains/native-$native_id"
downloads="$root/cache/downloads"
mkdir -p "$downloads"
stage=$(mktemp -d "$root/.prepare.XXXXXX")
transfers=()
archives=()
queue() {
local url=$1 name=$2
archives+=("$name")
if (( ${#transfers[@]} )); then transfers+=(--next); fi
transfers+=(--fail --location --silent --show-error --retry 2 --connect-timeout 15
--speed-limit 1024 --speed-time 60 --continue-at - --output "$downloads/$name.part" "$url")
transfers+=(--next --fail --location --silent --show-error --retry 2 --connect-timeout 15
--output "$downloads/$name.sha256" "$url.sha256")
}
native_archive="dever-native-linux-x86_64-$native_id.tar.xz"
if [[ ! -d $native_root ]]; then queue "$release_url/$native_archive" "$native_archive"; fi
if [[ ! -d $rust_root ]]; then
for component in rustc rust-std cargo; do
name="$component-$rust_version-$triple.tar.xz"
queue "$rust_dist/dist/$name" "$name"
done
fi
if (( ${#archives[@]} )); then
progress 'Downloading Dever build environment...'
curl --parallel --parallel-immediate --parallel-max 3 --fail-early "${transfers[@]}"
progress 'Verifying Dever build environment...'
for name in "${archives[@]}"; do
read -r expected remainder < "$downloads/$name.sha256"
if [[ $name == "$native_archive" && $expected != "$native_id" ]] ||
! printf '%s %s\n' "$expected" "$downloads/$name.part" | sha256sum --check --status; then
rm -f -- "$downloads/$name.part" "$downloads/$name.sha256"
echo "Checksum mismatch: $name" >&2
exit 1
fi
done
fi
progress 'Installing Dever build environment...'
if [[ ! -d $rust_root ]]; then
# 官方脚本不支持含空格的 prefix;先在独占临时目录安装,再原子发布。
rust_stage=$(mktemp -d /tmp/dever-toolchain.XXXXXX)
for component in rustc rust-std cargo; do
name="$component-$rust_version-$triple"
tar -xJf "$downloads/$name.tar.xz.part" -C "$rust_stage"
bash "$rust_stage/$name/install.sh" --prefix="$rust_stage/toolchain" --disable-ldconfig
rm -rf -- "$rust_stage/$name"
done
mv "$rust_stage/toolchain" "$stage/rust"
mv "$stage/rust" "$rust_root"
fi
if [[ ! -d $native_root ]]; then
tar -xJf "$downloads/$native_archive.part" -C "$stage"
mv "$stage/native-tools" "$native_root"
fi
"$tools/bin/rustc" --version
progress 'Preparing Dever project dependencies...'
"$tools/bin/cargo" fetch --locked --target "$triple" --manifest-path "$tools/project/Cargo.toml"
for name in "$native_archive" "rustc-$rust_version-$triple.tar.xz" \
"rust-std-$rust_version-$triple.tar.xz" "cargo-$rust_version-$triple.tar.xz"; do
rm -f -- "$downloads/$name.part" "$downloads/$name.sha256"
done
touch "$tools/ready"
progress 'Dever build environment is ready.'