mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 18:29:35 +00:00
fix: split target into target (triple) and out_dir (output directory)
build.target now holds the target triple (e.g. x86_64-unknown-linux-gnu) build.out_dir is a custom output directory that overrides the default target/<triple>/<profile> path.
This commit is contained in:
@@ -13,7 +13,8 @@ kind = "bin"
|
|||||||
# optional:
|
# optional:
|
||||||
# inherit = true # Inherit from base (default true)
|
# inherit = true # Inherit from base (default true)
|
||||||
# targets = ["linux", "macos"] # Multiple targets to build
|
# targets = ["linux", "macos"] # Multiple targets to build
|
||||||
# target = "./dist"
|
# out_dir = "./dist"
|
||||||
|
# target = "x86_64-unknown-linux-gnu"
|
||||||
# platform = "x86_64"
|
# platform = "x86_64"
|
||||||
# cflags = ["-Wall"]
|
# cflags = ["-Wall"]
|
||||||
# ldflags = ["-lm"]
|
# ldflags = ["-lm"]
|
||||||
@@ -40,8 +41,8 @@ kind = "bin"
|
|||||||
- `extension` (string, optional): custom file extension without the leading dot (e.g. `BIN`, `efi`, `EFI`). Combined with `filename`.
|
- `extension` (string, optional): custom file extension without the leading dot (e.g. `BIN`, `efi`, `EFI`). Combined with `filename`.
|
||||||
- `inherit` (bool, optional): inherit settings from base `[build]` (default `true`). If `false`, only use target/profile specific settings.
|
- `inherit` (bool, optional): inherit settings from base `[build]` (default `true`). If `false`, only use target/profile specific settings.
|
||||||
- `targets` (string array, optional): list of targets to build simultaneously. Supports short names (`linux`, `macos`, `windows`).
|
- `targets` (string array, optional): list of targets to build simultaneously. Supports short names (`linux`, `macos`, `windows`).
|
||||||
- `target` (string, optional): custom output directory for final artifact.
|
- `target` (string, optional): platform target triple (e.g. `x86_64-unknown-linux-gnu`) or short name (`linux`, `macos`, `windows`). Same as `--target` CLI flag.
|
||||||
When set, DCR automatically injects `--target=<value>` into `cflags` (useful for clang cross-compilation).
|
- `out_dir` (string, optional): custom output directory for final artifact (overrides default `target/<target>/<profile>/`).
|
||||||
- `platform` (string, optional): architecture hint for compiler (used for `-march` or `/arch`).
|
- `platform` (string, optional): architecture hint for compiler (used for `-march` or `/arch`).
|
||||||
- `cflags` (string array, optional): extra compile flags.
|
- `cflags` (string array, optional): extra compile flags.
|
||||||
Supports variable substitution — see [Variable substitution](#variable-substitution) below.
|
Supports variable substitution — see [Variable substitution](#variable-substitution) below.
|
||||||
|
|||||||
@@ -2,22 +2,29 @@
|
|||||||
|
|
||||||
By default, DCR writes final artifacts to `target/<target>/<profile>/` (where `<target>` is `native` or target triple).
|
By default, DCR writes final artifacts to `target/<target>/<profile>/` (where `<target>` is `native` or target triple).
|
||||||
|
|
||||||
You can override this with `build.target`:
|
You can override this with `build.out_dir`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[build]
|
[build]
|
||||||
target = "./dist"
|
out_dir = "./dist"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Behavior
|
## Behavior
|
||||||
|
|
||||||
- Binary mode:
|
- Binary mode:
|
||||||
- Linux/macOS: `<target>/<name>`
|
- Linux/macOS: `<out_dir>/<name>`
|
||||||
- Windows: `<target>/<name>.exe`
|
- Windows: `<out_dir>/<name>.exe`
|
||||||
- Static library mode:
|
- Static library mode:
|
||||||
- Linux/macOS: `<target>/lib<name>.a`
|
- Linux/macOS: `<out_dir>/lib<name>.a`
|
||||||
- Windows: `<target>/<name>.lib`
|
- Windows: `<out_dir>/<name>.lib`
|
||||||
|
|
||||||
## Important detail
|
## Important detail
|
||||||
|
|
||||||
`build.target` changes only the final artifact location. Object cache and dependency sync directories still use `target/<target>/<profile>/...`.
|
`build.out_dir` changes only the final artifact location. Object cache and dependency sync directories still use `target/<target>/<profile>/...`.
|
||||||
|
|
||||||
|
## Distinction from platform targets
|
||||||
|
|
||||||
|
- `build.out_dir` — custom **output directory** path.
|
||||||
|
- `build.target` / `--target <triple>` CLI flag — **platform target** for cross-compilation (e.g. `x86_64-unknown-linux-gnu`, or short name `linux`).
|
||||||
|
- `build.targets` (array) — list of **platform targets** to build simultaneously.
|
||||||
|
- `[build.<target>]` sections — **target-specific config overrides** (e.g. `[build.linux]`).
|
||||||
|
|||||||
192
scripts/sync-releases-to-gitlab.py
Normal file
192
scripts/sync-releases-to-gitlab.py
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Sync all GitHub Releases (with assets) to GitLab Releases."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
GL_TOKEN = os.environ.get("GL_TOKEN")
|
||||||
|
if not GL_TOKEN:
|
||||||
|
print("FATAL: GL_TOKEN environment variable not set", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
GL_PROJECT = "dexoron-studio%2Fdcr"
|
||||||
|
GL_PROJECT_RAW = "dexoron-studio/dcr"
|
||||||
|
GL_API = "https://gitlab.com/api/v4"
|
||||||
|
GH_REPO = "dexoron/dcr"
|
||||||
|
|
||||||
|
|
||||||
|
def gl_api(method, path, data=None, filepath=None, retries=5):
|
||||||
|
url = f"{GL_API}/{path}"
|
||||||
|
headers = {"PRIVATE-TOKEN": GL_TOKEN}
|
||||||
|
|
||||||
|
if data is not None and filepath is None:
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
body = json.dumps(data).encode()
|
||||||
|
elif filepath:
|
||||||
|
boundary = uuid.uuid4().hex
|
||||||
|
with open(filepath, "rb") as f:
|
||||||
|
fdata = f.read()
|
||||||
|
fname = os.path.basename(filepath)
|
||||||
|
parts = []
|
||||||
|
parts.append(f"--{boundary}".encode())
|
||||||
|
parts.append(f'Content-Disposition: form-data; name="file"; filename="{fname}"'.encode())
|
||||||
|
parts.append(b"Content-Type: application/octet-stream")
|
||||||
|
parts.append(b"")
|
||||||
|
parts.append(fdata)
|
||||||
|
parts.append(f"--{boundary}--".encode())
|
||||||
|
parts.append(b"")
|
||||||
|
body = b"\r\n".join(parts)
|
||||||
|
headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
|
||||||
|
else:
|
||||||
|
body = None
|
||||||
|
|
||||||
|
for attempt in range(retries):
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url, data=body, headers=headers, method=method)
|
||||||
|
with urllib.request.urlopen(req) as resp:
|
||||||
|
text = resp.read().decode()
|
||||||
|
return json.loads(text) if text else None
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
body_text = e.read().decode() if e.fp else ""
|
||||||
|
if e.code in (409, 429) and attempt < retries - 1:
|
||||||
|
time.sleep(3)
|
||||||
|
continue
|
||||||
|
if attempt < retries - 1:
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
print(f" HTTP {e.code}: {body_text[:300]}", file=sys.stderr)
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
if attempt < retries - 1:
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
print(f" Error: {e}", file=sys.stderr)
|
||||||
|
return None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def download_file(url, dest):
|
||||||
|
with urllib.request.urlopen(url) as resp:
|
||||||
|
with open(dest, "wb") as f:
|
||||||
|
f.write(resp.read())
|
||||||
|
|
||||||
|
|
||||||
|
# --- MAIN ---
|
||||||
|
print("Fetching GitHub releases...")
|
||||||
|
with urllib.request.urlopen(f"https://api.github.com/repos/{GH_REPO}/releases?per_page=50") as r:
|
||||||
|
gh_releases = json.loads(r.read().decode())
|
||||||
|
|
||||||
|
print(f"Found {len(gh_releases)} releases on GitHub")
|
||||||
|
|
||||||
|
# Reverse to process oldest first (GitHub returns newest first)
|
||||||
|
gh_releases.reverse()
|
||||||
|
|
||||||
|
print("Fetching existing GitLab releases and their assets...")
|
||||||
|
existing = gl_api("GET", f"projects/{GL_PROJECT}/releases?per_page=100") or []
|
||||||
|
existing = existing if isinstance(existing, list) else []
|
||||||
|
|
||||||
|
# Build map: tag -> set of existing asset names
|
||||||
|
gl_assets = {}
|
||||||
|
for rel in existing:
|
||||||
|
tag = rel["tag_name"]
|
||||||
|
links = rel.get("assets", {}).get("links", []) or rel.get("assets", {}).get("sources", []) or []
|
||||||
|
gl_assets[tag] = {l.get("name", "") for l in links if isinstance(l, dict)}
|
||||||
|
# Also get the release description to check if it needs updating
|
||||||
|
gl_assets[tag + "__desc"] = rel.get("description", "")
|
||||||
|
|
||||||
|
print(f"Existing GitLab releases: {len(existing)}")
|
||||||
|
|
||||||
|
download_dir = "/tmp/gh-assets"
|
||||||
|
os.makedirs(download_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Reverse: process older first so newer releases with many assets get their description too
|
||||||
|
# Actually, process in original order
|
||||||
|
total = len(gh_releases)
|
||||||
|
for i, rel in enumerate(gh_releases):
|
||||||
|
tag = rel["tag_name"]
|
||||||
|
body = rel.get("body", "") or ""
|
||||||
|
assets = rel.get("assets", [])
|
||||||
|
if isinstance(assets, dict):
|
||||||
|
assets = assets.get("nodes", []) or []
|
||||||
|
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print(f"[{i+1}/{total}] {tag} ({len(assets)} assets)")
|
||||||
|
|
||||||
|
# Create or update release
|
||||||
|
existing_tag = tag in gl_assets
|
||||||
|
if not existing_tag:
|
||||||
|
print(f" Creating release...")
|
||||||
|
result = gl_api("POST", f"projects/{GL_PROJECT}/releases", data={
|
||||||
|
"tag_name": tag,
|
||||||
|
"name": rel.get("name", tag),
|
||||||
|
"description": body,
|
||||||
|
"ref": "master",
|
||||||
|
})
|
||||||
|
if result is None:
|
||||||
|
print(f" FAILED — skip")
|
||||||
|
continue
|
||||||
|
print(f" OK")
|
||||||
|
else:
|
||||||
|
# Update description if needed
|
||||||
|
existing_desc = gl_assets.get(tag + "__desc", "")
|
||||||
|
if body and body != existing_desc:
|
||||||
|
print(f" Updating description...")
|
||||||
|
gl_api("PUT", f"projects/{GL_PROJECT}/releases/{urllib.request.quote(tag, safe='')}",
|
||||||
|
data={"description": body})
|
||||||
|
|
||||||
|
existing_links = gl_assets.get(tag, set())
|
||||||
|
|
||||||
|
for j, asset in enumerate(assets):
|
||||||
|
aname = asset.get("name", "")
|
||||||
|
aurl = asset.get("browser_download_url", "")
|
||||||
|
if not aurl:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if aname in existing_links:
|
||||||
|
print(f" [{j+1}/{len(assets)}] {aname} — already exists")
|
||||||
|
continue
|
||||||
|
|
||||||
|
fpath = os.path.join(download_dir, aname)
|
||||||
|
print(f" [{j+1}/{len(assets)}] {aname}...", end=" ", flush=True)
|
||||||
|
|
||||||
|
# Download from GitHub
|
||||||
|
try:
|
||||||
|
download_file(aurl, fpath)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"download failed: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Upload to GitLab
|
||||||
|
up = gl_api("POST", f"projects/{GL_PROJECT}/uploads", filepath=fpath)
|
||||||
|
if not up or "url" not in up:
|
||||||
|
size_mb = os.path.getsize(fpath) / (1024 * 1024)
|
||||||
|
print(f"upload failed ({size_mb:.1f} MB)")
|
||||||
|
os.remove(fpath)
|
||||||
|
continue
|
||||||
|
|
||||||
|
file_url = up["url"]
|
||||||
|
full_url = f"https://gitlab.com/{GL_PROJECT_RAW}{file_url}"
|
||||||
|
|
||||||
|
# Link asset to release
|
||||||
|
encoded_tag = urllib.request.quote(tag, safe='')
|
||||||
|
link = gl_api("POST",
|
||||||
|
f"projects/{GL_PROJECT}/releases/{encoded_tag}/assets/links",
|
||||||
|
data={"name": aname, "url": full_url},
|
||||||
|
retries=5)
|
||||||
|
if link:
|
||||||
|
print(f"linked")
|
||||||
|
existing_links.add(aname)
|
||||||
|
else:
|
||||||
|
print(f"link failed")
|
||||||
|
|
||||||
|
os.remove(fpath)
|
||||||
|
|
||||||
|
time.sleep(0.3)
|
||||||
|
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print("Done!")
|
||||||
@@ -211,7 +211,7 @@ fn get_string_with_profile_and_target(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_build_string_with_profile(config: &Config, field: &str, profile: &str) -> String {
|
pub fn get_build_string_with_profile(config: &Config, field: &str, profile: &str) -> String {
|
||||||
get_string_with_profile_and_target(config, field, profile, None)
|
get_string_with_profile_and_target(config, field, profile, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -712,7 +712,11 @@ fn build_project_at(
|
|||||||
let resolved_linker = resolve_tool("DCR_LD", tc_ld.as_deref());
|
let resolved_linker = resolve_tool("DCR_LD", tc_ld.as_deref());
|
||||||
let resolved_archiver = resolve_tool("DCR_AR", tc_ar.as_deref());
|
let resolved_archiver = resolve_tool("DCR_AR", tc_ar.as_deref());
|
||||||
|
|
||||||
let target_dir_binding = match workspace_root {
|
let out_dir_config = get_build_string_with_profile(&config, "out_dir", profile);
|
||||||
|
let target_dir_binding = if !out_dir_config.is_empty() {
|
||||||
|
out_dir_config
|
||||||
|
} else {
|
||||||
|
match workspace_root {
|
||||||
Some(root) => {
|
Some(root) => {
|
||||||
let target_str = build_target
|
let target_str = build_target
|
||||||
.map(normalize_target_os)
|
.map(normalize_target_os)
|
||||||
@@ -732,6 +736,7 @@ fn build_project_at(
|
|||||||
root.join(&rel).to_string_lossy().to_string()
|
root.join(&rel).to_string_lossy().to_string()
|
||||||
}
|
}
|
||||||
None => normalize_target(build_target.unwrap_or(""), profile).unwrap_or_default(),
|
None => normalize_target(build_target.unwrap_or(""), profile).unwrap_or_default(),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let target_dir = if target_dir_binding.is_empty() {
|
let target_dir = if target_dir_binding.is_empty() {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ pub fn run(args: &[String]) -> i32 {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let flags = match parse_build_run_flags(args) {
|
let flags = match parse_build_run_flags(args) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => return 1,
|
Err(_) => return 1,
|
||||||
@@ -188,7 +187,12 @@ fn run_project(
|
|||||||
.or_else(|| config.get("build.kind").and_then(|v| v.as_str()))
|
.or_else(|| config.get("build.kind").and_then(|v| v.as_str()))
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
|
|
||||||
let normalized_target_dir = match workspace_root {
|
let out_dir =
|
||||||
|
crate::cli::build::get_build_string_with_profile(&config, "out_dir", &flags.profile);
|
||||||
|
let normalized_target_dir = if !out_dir.is_empty() {
|
||||||
|
Some(out_dir)
|
||||||
|
} else {
|
||||||
|
match workspace_root {
|
||||||
Some(wr) => target
|
Some(wr) => target
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|t| crate::cli::build::normalize_target(t, &flags.profile))
|
.and_then(|t| crate::cli::build::normalize_target(t, &flags.profile))
|
||||||
@@ -196,6 +200,7 @@ fn run_project(
|
|||||||
None => target
|
None => target
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|t| crate::cli::build::normalize_target(t, &flags.profile)),
|
.and_then(|t| crate::cli::build::normalize_target(t, &flags.profile)),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let version = config
|
let version = config
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ pub struct BuildConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub target: Option<String>,
|
pub target: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub out_dir: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
pub platform: Option<String>,
|
pub platform: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub cflags: Vec<String>,
|
pub cflags: Vec<String>,
|
||||||
@@ -396,6 +398,9 @@ impl Config {
|
|||||||
if let Some(target) = &build.target {
|
if let Some(target) = &build.target {
|
||||||
validate_non_empty_string(target, "build.target")?;
|
validate_non_empty_string(target, "build.target")?;
|
||||||
}
|
}
|
||||||
|
if let Some(out_dir) = &build.out_dir {
|
||||||
|
validate_non_empty_string(out_dir, "build.out_dir")?;
|
||||||
|
}
|
||||||
for profile in ["release", "debug"] {
|
for profile in ["release", "debug"] {
|
||||||
if let Some(section) = self
|
if let Some(section) = self
|
||||||
.data
|
.data
|
||||||
@@ -539,7 +544,9 @@ impl Config {
|
|||||||
if let Some(lang) = table.get("language") {
|
if let Some(lang) = table.get("language") {
|
||||||
self.validate_language(lang)?;
|
self.validate_language(lang)?;
|
||||||
}
|
}
|
||||||
for key in ["standard", "compiler", "kind", "target", "platform"] {
|
for key in [
|
||||||
|
"standard", "compiler", "kind", "target", "out_dir", "platform",
|
||||||
|
] {
|
||||||
if let Some(value) = table.get(key) {
|
if let Some(value) = table.get(key) {
|
||||||
let s = value.as_str().unwrap_or("");
|
let s = value.as_str().unwrap_or("");
|
||||||
if s.trim().is_empty() {
|
if s.trim().is_empty() {
|
||||||
@@ -784,6 +791,11 @@ fn format_toml(value: &Value) -> Result<String, ConfigError> {
|
|||||||
{
|
{
|
||||||
out.push_str(&format!("target = \"{target}\"\n"));
|
out.push_str(&format!("target = \"{target}\"\n"));
|
||||||
}
|
}
|
||||||
|
if let Some(out_dir) = build.get("out_dir").and_then(|v| v.as_str())
|
||||||
|
&& !out_dir.trim().is_empty()
|
||||||
|
{
|
||||||
|
out.push_str(&format!("out_dir = \"{out_dir}\"\n"));
|
||||||
|
}
|
||||||
if let Some(cflags) = build.get("cflags") {
|
if let Some(cflags) = build.get("cflags") {
|
||||||
out.push_str(&format!("cflags = {}\n", format_string_array(cflags)));
|
out.push_str(&format!("cflags = {}\n", format_string_array(cflags)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -383,6 +383,96 @@ fn registry_dependency_is_built_from_cache() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_project_name(toml: &str) -> String {
|
||||||
|
toml.lines()
|
||||||
|
.find(|l| l.trim().starts_with("name ="))
|
||||||
|
.and_then(|l| l.split('=').nth(1))
|
||||||
|
.map(|s| s.trim().trim_matches('"').to_string())
|
||||||
|
.expect("could not parse project name from dcr.toml")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_artifact_path(project_root: &Path, project_name: &str) -> PathBuf {
|
||||||
|
project_root
|
||||||
|
.join("target")
|
||||||
|
.join("x86_64-unknown-linux-gnu")
|
||||||
|
.join("debug")
|
||||||
|
.join(project_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_with_target_config() {
|
||||||
|
let Some(compiler) = available_compiler() else {
|
||||||
|
eprintln!("no compiler found; skipping target config test");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let dir = unique_sandbox_dir("target_cfg");
|
||||||
|
let out = run_dcr(&["init"], &dir);
|
||||||
|
assert!(out.status.success(), "dcr init should succeed");
|
||||||
|
|
||||||
|
let toml_path = dir.join("dcr.toml");
|
||||||
|
let toml = std::fs::read_to_string(&toml_path).expect("failed to read dcr.toml");
|
||||||
|
let project_name = parse_project_name(&toml);
|
||||||
|
let updated = toml.replace("[build]", "[build]\ntarget = \"linux\"");
|
||||||
|
std::fs::write(&toml_path, updated).expect("failed to write dcr.toml");
|
||||||
|
|
||||||
|
let envs = [("DCR_COMPILER", compiler)];
|
||||||
|
let out = run_dcr_env(&["build"], &dir, &envs);
|
||||||
|
assert!(
|
||||||
|
out.status.success(),
|
||||||
|
"dcr build with target = \"linux\" should succeed"
|
||||||
|
);
|
||||||
|
|
||||||
|
let artifact = default_artifact_path(&dir, &project_name);
|
||||||
|
assert!(
|
||||||
|
artifact.is_file(),
|
||||||
|
"artifact should be at default path target/x86_64-unknown-linux-gnu/debug/{}",
|
||||||
|
project_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_with_out_dir() {
|
||||||
|
let Some(compiler) = available_compiler() else {
|
||||||
|
eprintln!("no compiler found; skipping out_dir test");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let dir = unique_sandbox_dir("out_dir");
|
||||||
|
let out = run_dcr(&["init"], &dir);
|
||||||
|
assert!(out.status.success(), "dcr init should succeed");
|
||||||
|
|
||||||
|
let toml_path = dir.join("dcr.toml");
|
||||||
|
let toml = std::fs::read_to_string(&toml_path).expect("failed to read dcr.toml");
|
||||||
|
let project_name = parse_project_name(&toml);
|
||||||
|
let updated = toml.replace("[build]", "[build]\nout_dir = \"./_BUILD\"");
|
||||||
|
std::fs::write(&toml_path, updated).expect("failed to write dcr.toml");
|
||||||
|
|
||||||
|
let envs = [("DCR_COMPILER", compiler)];
|
||||||
|
let out = run_dcr_env(&["build"], &dir, &envs);
|
||||||
|
if !out.status.success() {
|
||||||
|
eprintln!("stdout: {}", String::from_utf8_lossy(&out.stdout));
|
||||||
|
eprintln!("stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
out.status.success(),
|
||||||
|
"dcr build with out_dir should succeed"
|
||||||
|
);
|
||||||
|
|
||||||
|
let artifact = dir.join("_BUILD").join(&project_name);
|
||||||
|
assert!(
|
||||||
|
artifact.is_file(),
|
||||||
|
"artifact should be at _BUILD/{} (custom out_dir)",
|
||||||
|
project_name
|
||||||
|
);
|
||||||
|
|
||||||
|
let default_path = default_artifact_path(&dir, &project_name);
|
||||||
|
assert!(
|
||||||
|
!default_path.exists(),
|
||||||
|
"artifact should NOT be at default path when out_dir is set"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dcr_test_runs_without_sandbox_dependency() {
|
fn dcr_test_runs_without_sandbox_dependency() {
|
||||||
let Some(compiler) = available_compiler() else {
|
let Some(compiler) = available_compiler() else {
|
||||||
|
|||||||
Reference in New Issue
Block a user