///|
fn join(left : String, right : String) -> String {
left + "/" + right
}
///|
/// moon runs `MOON_CC` and `MOON_AR` from other directories, so the paths must be absolute.
fn absolute(path : String) -> String {
if path.has_prefix("/") {
return path
}
match @env.current_dir() {
Some(cwd) => join(cwd, path)
None => path
}
}
///|
async fn fail(message : String) -> Unit {
@stdio.stderr.write("❌ zig-cross: \{message}\n")
@sys.exit(1)
}
///|
async fn write_script(path : String, text : String) -> Unit {
@fs.write_file(path, text, permission=0o755)
// write_file applies the permission only when it creates the file.
@fs.chmod(path, 0o755)
}
///|
/// Write the `cc` and `ar` wrappers of one target to `/zig/`, and return the
/// environment that makes moon use them.
async fn prepare(target : Target, target_dir : String) -> Map[String, String] {
let dir = absolute(join(join(target_dir, "zig"), target.name()))
@fs.mkdir(dir, recursive=true, allow_exist=true)
let cc = join(dir, "cc")
let ar = join(dir, "ar")
write_script(cc, cc_wrapper(target))
write_script(ar, ar_wrapper(target))
{ "MOON_CC": cc, "MOON_AR": ar }
}
///|
/// One line of the summary: an executable that a build produced, or why there is none.
priv struct Row {
target : Target
path : String
size : Int64
problem : String?
}
///|
async fn check_executable(target : Target, path : String) -> Row {
let size = @fs.file_size(path)
let file = @fs.open(path, mode=ReadOnly)
defer file.close()
let header = file.read_some(max_len=4096).unwrap_or(b"")
{ target, path, size, problem: check_format(target, header), }
}
///|
/// Build one target. The rows are the executables found, or one row with the problem.
async fn build_target(
target : Target,
target_dir : String,
paths : Array[String],
) -> Array[Row] {
let env = prepare(target, target_dir)
let out = join(target_dir, target.name())
let args = ["build", "--target", "native", "--release", "--target-dir", out]
args.append(paths)
println("🔨 Building for \{target.name()}...")
let code = @shell.Cmd("moon", args, env~, stdout=Inherit, stderr=Inherit).status()
if code != 0 {
return [
{
target,
path: out,
size: 0L,
problem: Some(
"\{target.name()}: moon build failed with exit code \{code}",
),
},
]
}
let found = @shell.glob(join(out, "native/release/build/**/*.exe"))
if found.is_empty() {
return [
{
target,
path: out,
size: 0L,
problem: Some(
"\{target.name()}: no executable under \{out}/native/release/build",
),
},
]
}
found.sort()
let rows = []
for path in found {
rows.push(check_executable(target, path))
}
rows
}
///|
fn max_int(a : Int32, b : Int32) -> Int32 {
if a > b {
a
} else {
b
}
}
///|
fn pad(text : String, width : Int32) -> String {
let mut result = text
while result.length() < width {
result += " "
}
result
}
///|
/// Print the table (target, path, size, result) and the problems. Return the number of failures.
fn print_summary(rows : Array[Row]) -> Int32 {
let mut target_width = 6
let mut path_width = 4
for row in rows {
target_width = max_int(target_width, row.target.name().length())
path_width = max_int(path_width, row.path.length())
}
println(
"\{pad("target", target_width)} \{pad("path", path_width)} size (bytes) result",
)
let mut failures = 0
for row in rows {
let mark = if row.problem is None { "✅" } else { "❌" }
println(
"\{pad(row.target.name(), target_width)} \{pad(row.path, path_width)} \{pad(row.size.to_string(), 12)} \{mark}",
)
if row.problem is Some(message) {
failures += 1
println(" \{message}")
}
}
failures
}
///|
async fn run_build(
target_dir : String,
targets : Array[Target],
paths : Array[String],
) -> Int32 {
let rows = []
for target in targets {
rows.append(build_target(target, target_dir, paths))
}
if print_summary(rows) > 0 {
1
} else {
0
}
}
///|
/// Run a command with `MOON_CC` and `MOON_AR` set to the wrappers of `target`.
async fn run_exec(target : Target, argv : Array[String]) -> Int32 {
let env = prepare(target, default_target_dir)
let code = @shell.Cmd(
argv[0],
argv[1:].to_owned(),
env~,
stdin=Inherit,
stdout=Inherit,
stderr=Inherit,
).status()
if code > 0 {
code
} else if code == 0 {
0
} else {
1
}
}
///|
async fn main {
if @env.get_env_var("OS") == Some("Windows_NT") {
fail("zig-cross runs on Linux or macOS only")
return
}
match parse_args(@env.args()[1:].to_owned()) {
Err(message) => {
@stdio.stderr.write("❌ zig-cross: \{message}\n\n\{help_text()}\n")
@sys.exit(2)
}
Ok(Help) => @stdio.stdout.write(help_text() + "\n")
Ok(Version) => @stdio.stdout.write(version_text() + "\n")
Ok(Build(target_dir~, targets~, paths~)) => {
let code = run_build(target_dir, targets, paths)
if code != 0 {
@sys.exit(code)
}
}
Ok(Exec(target~, argv~)) => {
let code = run_exec(target, argv)
if code != 0 {
@sys.exit(code)
}
}
}
}