Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -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)。
|
||||
@@ -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
@@ -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()
|
||||
@@ -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.'
|
||||
Reference in new issue
Block a user