6 Commits

Author SHA1 Message Date
peoplemiau1
478d1ad0c5 Remove DragonFly BSD support 2026-05-29 09:57:07 +03:00
peoplemiau1
060b92111f Fix dcr run to inherit stdout/stderr properly 2026-05-29 09:54:13 +03:00
peoplemiau1
86f0566dc2 Trigger release 2026-05-29 09:21:03 +03:00
peoplemiau1
768e2fc0dc Fix release creation race condition by adding a dedicated create-release job 2026-05-29 09:18:27 +03:00
peoplemiau1
619493bcbd Fix install.sh: support Linux ARM64 and BSD systems, add robust JSON parser checks 2026-05-29 09:13:37 +03:00
peoplemiau1
1dac8661b6 Implement incremental linking and add DragonFly BSD VM build 2026-05-29 09:09:03 +03:00
8 changed files with 168 additions and 26 deletions

View File

@@ -100,10 +100,28 @@ jobs:
name: release-notes name: release-notes
path: .github/release-notes.md path: .github/release-notes.md
# ── Create Release (prevent race conditions) ───────────────────────────────
create-release:
needs: [setup, notes]
runs-on: ubuntu-latest
steps:
- name: Download Release Notes
uses: actions/download-artifact@v4
with:
name: release-notes
path: .github
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.setup.outputs.tag }}
body_path: .github/release-notes.md
prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
# ── Linux Binaries Matrix ────────────────────────────────────────────────── # ── Linux Binaries Matrix ──────────────────────────────────────────────────
build-linux: build-linux:
if: ${{ github.event_name == 'push' || github.event.inputs.run_linux == 'true' }} if: ${{ github.event_name == 'push' || github.event.inputs.run_linux == 'true' }}
needs: [setup, notes] needs: [setup, notes, create-release]
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
@@ -280,7 +298,7 @@ jobs:
# ── Publish Packages to Release (Out of Matrix) ──────────────────────────── # ── Publish Packages to Release (Out of Matrix) ────────────────────────────
publish-dexoron-packages: publish-dexoron-packages:
needs: [setup, notes, build-linux-packages] needs: [setup, notes, build-linux-packages, create-release]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download Release Notes - name: Download Release Notes
@@ -309,7 +327,7 @@ jobs:
# ── macOS Matrix ─────────────────────────────────────────────────────────── # ── macOS Matrix ───────────────────────────────────────────────────────────
build-macos: build-macos:
if: ${{ github.event_name == 'push' || github.event.inputs.run_macos == 'true' }} if: ${{ github.event_name == 'push' || github.event.inputs.run_macos == 'true' }}
needs: [setup, notes] needs: [setup, notes, create-release]
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -355,7 +373,7 @@ jobs:
# ── BSD Systems Matrix ───────────────────────────────────────────────────── # ── BSD Systems Matrix ─────────────────────────────────────────────────────
build-bsd: build-bsd:
if: ${{ github.event_name == 'push' || github.event.inputs.run_bsd == 'true' }} if: ${{ github.event_name == 'push' || github.event.inputs.run_bsd == 'true' }}
needs: [setup, notes] needs: [setup, notes, create-release]
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
@@ -428,6 +446,8 @@ jobs:
cp target/release/dcr "$ASSET" cp target/release/dcr "$ASSET"
echo "BSD_ASSET=${ASSET}" >> "$GITHUB_ENV" echo "BSD_ASSET=${ASSET}" >> "$GITHUB_ENV"
# --- UPLOAD TARGET --- # --- UPLOAD TARGET ---
- name: Upload BSD Asset to Release - name: Upload BSD Asset to Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
@@ -440,7 +460,7 @@ jobs:
# ── Windows Matrix ───────────────────────────────────────────────────────── # ── Windows Matrix ─────────────────────────────────────────────────────────
build-windows: build-windows:
if: ${{ github.event_name == 'push' || github.event.inputs.run_windows == 'true' }} if: ${{ github.event_name == 'push' || github.event.inputs.run_windows == 'true' }}
needs: [setup, notes] needs: [setup, notes, create-release]
runs-on: windows-latest runs-on: windows-latest
strategy: strategy:
fail-fast: false fail-fast: false

View File

@@ -30,8 +30,8 @@ CHANNEL=""
check_os() { check_os() {
case "$(uname -s)" in case "$(uname -s)" in
Linux|Darwin) ;; Linux|Darwin|FreeBSD|OpenBSD|NetBSD) ;;
*) error "Only Linux and macOS are supported"; exit 1 ;; *) error "Only Linux, macOS, and BSD systems are supported"; exit 1 ;;
esac esac
} }
@@ -41,15 +41,26 @@ detect_target() {
arch="$(uname -m)" arch="$(uname -m)"
case "$os:$arch" in case "$os:$arch" in
Linux:x86_64) TARGET_TRIPLE="x86_64-unknown-linux-gnu" ;; Linux:x86_64) TARGET_TRIPLE="x86_64-unknown-linux-gnu" ;;
Darwin:x86_64) TARGET_TRIPLE="x86_64-apple-darwin" ;; Linux:aarch64|Linux:arm64) TARGET_TRIPLE="aarch64-unknown-linux-gnu" ;;
Darwin:arm64|Darwin:aarch64) TARGET_TRIPLE="aarch64-apple-darwin" ;; Darwin:x86_64) TARGET_TRIPLE="x86_64-apple-darwin" ;;
Darwin:arm64|Darwin:aarch64) TARGET_TRIPLE="aarch64-apple-darwin" ;;
FreeBSD:x86_64|FreeBSD:amd64) TARGET_TRIPLE="x86_64-unknown-freebsd" ;;
OpenBSD:x86_64|OpenBSD:amd64) TARGET_TRIPLE="x86_64-unknown-openbsd" ;;
NetBSD:x86_64|NetBSD:amd64) TARGET_TRIPLE="x86_64-unknown-netbsd" ;;
*) error "Unsupported platform: $os/$arch"; exit 1 ;; *) error "Unsupported platform: $os/$arch"; exit 1 ;;
esac esac
} }
check_common_dependencies() { check_common_dependencies() {
command -v curl >/dev/null 2>&1 || { error "curl is not installed"; exit 1; } command -v curl >/dev/null 2>&1 || { error "curl is not installed"; exit 1; }
if [[ "$CHANNEL" == "dev" ]]; then
if ! command -v python3 >/dev/null 2>&1 && ! command -v jq >/dev/null 2>&1; then
error "For dev installations, either 'python3' or 'jq' must be installed to parse GitHub API response."
exit 1
fi
fi
} }
check_build_dependencies() { check_build_dependencies() {
@@ -83,15 +94,16 @@ select_install_mode() {
esac esac
} }
# Возвращает JSON нужного релиза в stdout # Returns JSON of the requested release to stdout
fetch_release_json() { fetch_release_json() {
if [[ "$CHANNEL" == "dev" ]]; then if [[ "$CHANNEL" == "dev" ]]; then
log "Looking for latest dev (pre-release)..." log "Looking for latest dev (pre-release)..."
local json local json result
json="$(curl -fsSL "$GITHUB_API_ALL")" json="$(curl -fsSL "$GITHUB_API_ALL")"
# Первый pre-release в списке if command -v jq >/dev/null 2>&1; then
local result result="$(printf '%s' "$json" | jq -e -c 'map(select(.prerelease == true))[0]')"
result="$(printf '%s' "$json" | python3 - <<'EOF' else
result="$(printf '%s' "$json" | python3 - <<'EOF'
import sys, json import sys, json
releases = json.load(sys.stdin) releases = json.load(sys.stdin)
pre = [r for r in releases if r.get("prerelease", False)] pre = [r for r in releases if r.get("prerelease", False)]
@@ -101,7 +113,9 @@ else:
print(json.dumps(pre[0]), end="") print(json.dumps(pre[0]), end="")
EOF EOF
)" )"
if [[ -z "$result" || "$result" == "{}" ]]; then fi
if [[ -z "$result" || "$result" == "{}" || "$result" == "null" ]]; then
error "No dev (pre-release) found on GitHub" error "No dev (pre-release) found on GitHub"
exit 1 exit 1
fi fi
@@ -123,9 +137,9 @@ download_binary() {
exit 1 exit 1
fi fi
# Версия без ведущего 'v' # Version without leading 'v'
version="${tag#v}" version="${tag#v}"
# Имя бинарника: dcr-<triple>-<version> # Binary name: dcr-<triple>-<version>
asset_name="dcr-${TARGET_TRIPLE}-${version}" asset_name="dcr-${TARGET_TRIPLE}-${version}"
log "Fetching release ${tag} (channel: ${CHANNEL})..." log "Fetching release ${tag} (channel: ${CHANNEL})..."
@@ -149,7 +163,7 @@ prepare_sources() {
rm -rf "$TMPDIR" rm -rf "$TMPDIR"
git clone --depth 1 "$REPO_URL" "$TMPDIR" git clone --depth 1 "$REPO_URL" "$TMPDIR"
if [[ "$CHANNEL" == "dev" ]]; then if [[ "$CHANNEL" == "dev" ]]; then
# Клонируем dev ветку, если она есть # Clone dev branch if it exists
git clone --depth 1 --branch dev "$REPO_URL" "$TMPDIR" 2>/dev/null || \ git clone --depth 1 --branch dev "$REPO_URL" "$TMPDIR" 2>/dev/null || \
git clone --depth 1 "$REPO_URL" "$TMPDIR" git clone --depth 1 "$REPO_URL" "$TMPDIR"
fi fi
@@ -193,8 +207,8 @@ main() {
check_os check_os
detect_target detect_target
check_common_dependencies
select_channel select_channel
check_common_dependencies
select_install_mode select_install_mode
cleanup cleanup

View File

@@ -316,6 +316,31 @@ pub fn needs_rebuild(source: &str, object: &str) -> bool {
false false
} }
pub fn needs_link(objects: &[String], output_path: &str) -> bool {
let out_meta = match fs::metadata(output_path) {
Ok(m) => m,
Err(_) => return true,
};
let out_time = match out_meta.modified() {
Ok(t) => t,
Err(_) => return true,
};
for obj in objects {
let obj_meta = match fs::metadata(obj) {
Ok(m) => m,
Err(_) => return true,
};
let obj_time = match obj_meta.modified() {
Ok(t) => t,
Err(_) => return true,
};
if obj_time > out_time {
return true;
}
}
false
}
fn parse_d_file(content: &str) -> Vec<String> { fn parse_d_file(content: &str) -> Vec<String> {
let mut deps = Vec::new(); let mut deps = Vec::new();
let text = content.replace("\\\n", " ").replace("\\\r\n", " "); let text = content.replace("\\\n", " ").replace("\\\r\n", " ");
@@ -418,6 +443,55 @@ mod tests {
assert_eq!(result, "target/debug/obj/main.c.obj"); assert_eq!(result, "target/debug/obj/main.c.obj");
} }
#[test]
fn needs_link_no_output() {
let dir = temp_dir("link_no_out");
let obj1 = dir.join("test1.o");
let obj2 = dir.join("test2.o");
let out = dir.join("app");
fs::write(&obj1, "").unwrap();
fs::write(&obj2, "").unwrap();
let objs = vec![
obj1.to_string_lossy().to_string(),
obj2.to_string_lossy().to_string(),
];
assert!(needs_link(&objs, &out.to_string_lossy()));
}
#[test]
fn needs_link_fresh() {
let dir = temp_dir("link_fresh");
let obj1 = dir.join("test1.o");
let obj2 = dir.join("test2.o");
let out = dir.join("app");
fs::write(&obj1, "").unwrap();
fs::write(&obj2, "").unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
fs::write(&out, "").unwrap();
let objs = vec![
obj1.to_string_lossy().to_string(),
obj2.to_string_lossy().to_string(),
];
assert!(!needs_link(&objs, &out.to_string_lossy()));
}
#[test]
fn needs_link_stale() {
let dir = temp_dir("link_stale");
let obj1 = dir.join("test1.o");
let obj2 = dir.join("test2.o");
let out = dir.join("app");
fs::write(&obj1, "").unwrap();
fs::write(&out, "").unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
fs::write(&obj2, "").unwrap();
let objs = vec![
obj1.to_string_lossy().to_string(),
obj2.to_string_lossy().to_string(),
];
assert!(needs_link(&objs, &out.to_string_lossy()));
}
#[test] #[test]
fn needs_rebuild_no_object() { fn needs_rebuild_no_object() {
let dir = temp_dir("rebuild_no_obj"); let dir = temp_dir("rebuild_no_obj");

View File

@@ -43,6 +43,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
if ctx.kind == "staticlib" { if ctx.kind == "staticlib" {
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir); let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
if !common::needs_link(&objects, &lib_path) {
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
return Ok(elapsed);
}
let archiver = ctx.archiver.unwrap_or(if cfg!(target_os = "windows") { let archiver = ctx.archiver.unwrap_or(if cfg!(target_os = "windows") {
"lib" "lib"
} else { } else {
@@ -105,6 +109,11 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
} else { } else {
platform::bin_path(ctx.profile, &final_name, ctx.target_dir) platform::bin_path(ctx.profile, &final_name, ctx.target_dir)
}; };
if !common::needs_link(&objects, &out_path) {
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
return Ok(elapsed);
}
cmd.arg("-o").arg(out_path); cmd.arg("-o").arg(out_path);
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {

View File

@@ -43,6 +43,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
if ctx.kind == "staticlib" { if ctx.kind == "staticlib" {
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir); let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
if !common::needs_link(&objects, &lib_path) {
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
return Ok(elapsed);
}
let mut cmd = Command::new(ctx.archiver.unwrap_or("lib")); let mut cmd = Command::new(ctx.archiver.unwrap_or("lib"));
cmd.arg("/nologo").arg(format!("/OUT:{lib_path}")); cmd.arg("/nologo").arg(format!("/OUT:{lib_path}"));
for obj in &objects { for obj in &objects {
@@ -117,6 +121,11 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
} else { } else {
platform::bin_path(ctx.profile, ctx.project_name, ctx.target_dir) platform::bin_path(ctx.profile, ctx.project_name, ctx.target_dir)
}; };
if !common::needs_link(&objects, &out_path) {
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
return Ok(elapsed);
}
cmd.arg(format!("/Fe:{out_path}")); cmd.arg(format!("/Fe:{out_path}"));
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {

View File

@@ -43,6 +43,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
if ctx.kind == "staticlib" { if ctx.kind == "staticlib" {
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir); let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
if !common::needs_link(&objects, &lib_path) {
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
return Ok(elapsed);
}
let archiver = ctx.archiver.unwrap_or(if cfg!(target_os = "windows") { let archiver = ctx.archiver.unwrap_or(if cfg!(target_os = "windows") {
"lib" "lib"
} else { } else {
@@ -105,6 +109,11 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
} else { } else {
platform::bin_path(ctx.profile, &final_name, ctx.target_dir) platform::bin_path(ctx.profile, &final_name, ctx.target_dir)
}; };
if !common::needs_link(&objects, &out_path) {
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
return Ok(elapsed);
}
cmd.arg("-o").arg(out_path); cmd.arg("-o").arg(out_path);
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {

View File

@@ -45,6 +45,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
if ctx.kind == "staticlib" { if ctx.kind == "staticlib" {
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir); let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
if !common::needs_link(&objects, &lib_path) {
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
return Ok(elapsed);
}
let mut cmd = Command::new(ctx.archiver.unwrap_or("ar")); let mut cmd = Command::new(ctx.archiver.unwrap_or("ar"));
cmd.arg("rcs").arg(&lib_path); cmd.arg("rcs").arg(&lib_path);
for obj in &objects { for obj in &objects {
@@ -106,6 +110,11 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
} else { } else {
platform::bin_path(ctx.profile, &final_name, ctx.target_dir) platform::bin_path(ctx.profile, &final_name, ctx.target_dir)
}; };
if !common::needs_link(&objects, &out_path) {
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
return Ok(elapsed);
}
cmd.arg("-o").arg(out_path); cmd.arg("-o").arg(out_path);
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {

View File

@@ -22,12 +22,10 @@ use std::process::Command;
pub fn run_binary(project_name: &str, profile: &str, target_dir: Option<&str>) -> i32 { pub fn run_binary(project_name: &str, profile: &str, target_dir: Option<&str>) -> i32 {
let bin_path = platform::bin_path(profile, project_name, target_dir); let bin_path = platform::bin_path(profile, project_name, target_dir);
if Path::new(&bin_path).exists() { if Path::new(&bin_path).exists() {
let output = Command::new(&bin_path).output(); let status = Command::new(&bin_path).status();
match output { match status {
Ok(o) => { Ok(s) => {
print!("{}", String::from_utf8_lossy(&o.stdout)); return s.code().unwrap_or(0);
eprint!("{}", String::from_utf8_lossy(&o.stderr));
return o.status.code().unwrap_or(0);
} }
Err(_) => { Err(_) => {
return 1; return 1;