The compiler carries its runtime and std inside itself; host module for the system it runs on

src/embedded.fch (made by tools/pack.fch, checked by tests/run.sh) holds runtime/finch_rt.c and std/; when they aren't
beside the binary they're written to the cache folder once. All the sh/cmd specifics of the driver (quoting, temp dir,
mkdir, rm, mv, null device, path separator, cache dir, binary stdio) moved into host.fch / host_windows.fch.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015f73FesGZGYPkmDvUcj6QX
This commit is contained in:
Nexoniarz 2026-10-25 22:32:02 +01:00
commit 837ad45177
12 changed files with 354 additions and 59 deletions

View file

@ -6,8 +6,8 @@
# bootstrap/build.sh seed also write bootstrap/<system>.ll anew (do this when the compiler got a feature # bootstrap/build.sh seed also write bootstrap/<system>.ll anew (do this when the compiler got a feature
# its own source uses, or the seed is old: the next build starts from it) # its own source uses, or the seed is old: the next build starts from it)
# #
# Needs clang and libclang (the C headers are read through it). The compiler is then in build/finch; it looks for the # Needs clang and libclang (the C headers are read through it). The compiler is then in build/finch. It carries the
# runtime and the standard library next to build/, in runtime/ and std/. # runtime and the standard library inside itself (src/embedded.fch), so it runs from anywhere.
set -eu set -eu
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
mkdir -p build mkdir -p build
@ -28,6 +28,8 @@ export FINCH_RT=$PWD/runtime/finch_rt.c
echo "bootstrap: seed ($seed) -> build/finch0" echo "bootstrap: seed ($seed) -> build/finch0"
$CLANG -O1 -w "bootstrap/$seed.ll" runtime/finch_rt.c -o build/finch0 -lm $LIBS $CLANG -O1 -w "bootstrap/$seed.ll" runtime/finch_rt.c -o build/finch0 -lm $LIBS
echo "bootstrap: build/finch0 packs the runtime and std into src/embedded.fch"
bootstrap/pack.sh build/finch0
echo "bootstrap: build/finch0 builds the compiler -> build/finch1" echo "bootstrap: build/finch0 builds the compiler -> build/finch1"
build/finch0 build src/main.fch -o build/finch1 build/finch0 build src/main.fch -o build/finch1
echo "bootstrap: build/finch1 builds the compiler -> build/finch" echo "bootstrap: build/finch1 builds the compiler -> build/finch"

6
bootstrap/pack.sh Executable file
View file

@ -0,0 +1,6 @@
#!/bin/sh
# Writes src/embedded.fch: the runtime and the standard library as text, so the compiler can carry them inside itself.
# bootstrap/pack.sh <a Finch compiler> [out.fch]
set -eu
cd "$(dirname "$0")/.."
FINCH_RT=$PWD/runtime/finch_rt.c "$1" run tools/pack.fch "${2:-src/embedded.fch}" runtime/finch_rt.c std/*.fch std/*.h

View file

@ -8,6 +8,7 @@
// //
// C structs by value appear in types as the named type `module "C"`. // C structs by value appear in types as the named type `module "C"`.
import ast import ast
import host
import "clang-c/Index.h" import "clang-c/Index.h"
link "clang" link "clang"
@ -101,8 +102,11 @@ fn fixed(ast.Type elem, int count) -> ast.Type {
// Ask the system C compiler where it looks for headers, so we find the same stdio.h it would. // Ask the system C compiler where it looks for headers, so we find the same stdio.h it would.
fn systemIncludeArgs(str cc) -> []str { fn systemIncludeArgs(str cc) -> []str {
[]str dirs []str dirs
tmp := "/tmp/finch_cc_dirs.txt" tmp := host.tempDir() + "/finch_cc_dirs_" + str(host.pid()) + ".txt"
shell(cc + " -E -v -x c /dev/null 2> '" + tmp + "' > /dev/null") empty := host.tempDir() + "/finch_empty_" + str(host.pid()) + ".c"
write_file(empty, "")
host.run(cc + " -E -v -x c " + host.quote(empty) + " 2> " + host.quote(tmp) + " > " + host.nullDevice())
host.remove(empty)
if !file_exists(tmp) { if !file_exists(tmp) {
return dirs return dirs
} }
@ -126,7 +130,7 @@ fn systemIncludeArgs(str cc) -> []str {
dirs.push("-isystem" + l) dirs.push("-isystem" + l)
} }
} }
shell("rm -f '" + tmp + "'") host.remove(tmp)
return dirs return dirs
} }

28
src/embedded.fch Normal file

File diff suppressed because one or more lines are too long

77
src/host.fch Normal file
View file

@ -0,0 +1,77 @@
// What differs between the systems the compiler itself runs on: how a command line is quoted and run,
// where temporary files go, making and removing files. This one is for Linux and macOS (name_windows.fch has
// the Windows one).
import "stdlib.h"
import "unistd.h"
fn quote(str a) -> str {
return "'" + a.replace("'", "'\\''") + "'"
}
// run a command line through the shell; its exit code
fn run(str cmd) -> int {
return shell(cmd)
}
fn nullDevice() -> str {
return "/dev/null"
}
// the folder for temporary files
fn tempDir() -> str {
t := getenv("TMPDIR")
if t.len > 0 {
if t.ends_with("/") {
return t.sub(0, t.len - 1)
}
return t
}
return "/tmp"
}
fn pathSeparator() -> str {
return ":"
}
fn pid() -> int {
return int(getpid())
}
// the folder the compiler was started in
fn cwd() -> str {
return getenv("PWD")
}
// where files that are made once and kept go
fn cacheDir() -> str {
x := getenv("XDG_CACHE_HOME")
if x.len > 0 {
return x + "/finch"
}
h := getenv("HOME")
if h.len > 0 {
return h + "/.cache/finch"
}
return tempDir() + "/finch-cache"
}
// the editor protocol is bytes, not text lines
fn binaryStdio() {
}
fn makeDirs(str path) {
shell("mkdir -p " + quote(path))
}
fn remove(str path) {
delete_file(path)
}
fn removeTree(str path) {
shell("rm -rf " + quote(path))
}
// move a file over another
fn move(str from, str to) {
shell("mv " + quote(from) + " " + quote(to))
}

97
src/host_windows.fch Normal file
View file

@ -0,0 +1,97 @@
// What differs between the systems the compiler itself runs on: how a command line is quoted and run,
// where temporary files go, making and removing files. This one is for Windows (host.fch is for Linux and macOS).
import "stdlib.h"
import "stdio.h"
import "process.h"
import "direct.h"
import "io.h"
// a Windows argument: in double quotes, with the quotes inside doubled up the way CreateProcess reads them
fn quote(str a) -> str {
return "\"" + a.replace("\"", "\\\"") + "\""
}
// Paths use / inside the compiler; cmd.exe wants \
fn native(str path) -> str {
return path.replace("/", "\\")
}
// run a command line through cmd.exe; its exit code.
// system() runs `cmd /c <line>`, and cmd drops the first and last quote of a line that starts with one: so the line
// gets one more pair around it.
fn run(str cmd) -> int {
return shell("\"" + cmd + "\"")
}
fn nullDevice() -> str {
return "NUL"
}
fn tempDir() -> str {
for name in ["TEMP", "TMP"] {
t := getenv(name)
if t.len > 0 {
return t.replace("\\", "/")
}
}
return "."
}
fn pathSeparator() -> str {
return ";"
}
fn pid() -> int {
return int(getpid())
}
fn cwd() -> str {
here := _getcwd(null, 0) // C allocates the buffer
if here.len == 0 {
return "."
}
return here.replace("\\", "/")
}
fn cacheDir() -> str {
x := getenv("LOCALAPPDATA")
if x.len > 0 {
return x.replace("\\", "/") + "/finch"
}
return tempDir() + "/finch-cache"
}
// the editor protocol is bytes, not text lines: no \r\n translation on stdin and stdout
fn binaryStdio() {
_setmode(0, 32768) // _O_BINARY
_setmode(1, 32768)
}
fn makeDirs(str path) {
p := path.replace("\\", "/")
at := 0
for part in p.split("/") {
if at > 0 {
at += 1
}
at += part.len
if part.len == 0 || part.ends_with(":") {
continue // "" before a leading / and the drive letter
}
_mkdir(p.sub(0, at).ptr) // fails when it is there already, which is fine
}
}
fn remove(str path) {
delete_file(path)
}
fn removeTree(str path) {
shell("\"rmdir /s /q " + quote(native(path)) + " 2> NUL\"")
}
// move a file over another
fn move(str from, str to) {
delete_file(to)
rename(from.ptr, to.ptr)
}

View file

@ -2,8 +2,7 @@
// and C files the program asks for, and turn the linker's complaints into something a person can act on. // and C files the program asks for, and turn the linker's complaints into something a person can act on.
import platform import platform
import cimport import cimport
import "stdlib.h" import host
import "unistd.h"
// 32-bit FNV-1a, as hex: names the cached runtime object after what it was built from // 32-bit FNV-1a, as hex: names the cached runtime object after what it was built from
fn fingerprint(str text) -> str { fn fingerprint(str text) -> str {
@ -20,20 +19,7 @@ fn fingerprint(str text) -> str {
} }
fn quote(str a) -> str { fn quote(str a) -> str {
return "'" + a.replace("'", "'\\''") + "'" return host.quote(a)
}
// where files that are made once and kept go: ~/.cache/finch
fn cacheDir() -> str {
x := getenv("XDG_CACHE_HOME")
if x.len > 0 {
return x + "/finch"
}
h := getenv("HOME")
if h.len > 0 {
return h + "/.cache/finch"
}
return "/tmp/finch-cache"
} }
// "-lname", or whatever pkg-config says the library needs // "-lname", or whatever pkg-config says the library needs
@ -42,11 +28,11 @@ fn libFlags(str name, platform.Target tg) -> str {
return "-framework " + quote(name.sub(0, name.len - 10)) // macOS: link "Cocoa.framework" return "-framework " + quote(name.sub(0, name.len - 10)) // macOS: link "Cocoa.framework"
} }
if !tg.cross { if !tg.cross {
tmp := "/tmp/finch_pkg_flags.txt" tmp := host.tempDir() + "/finch_pkg_flags_" + str(host.pid()) + ".txt"
code := shell("pkg-config --libs " + quote(name) + " > '" + tmp + "' 2> /dev/null") code := host.run("pkg-config --libs " + quote(name) + " > " + quote(tmp) + " 2> " + host.nullDevice())
if code == 0 && file_exists(tmp) { if code == 0 && file_exists(tmp) {
flags := (read_file(tmp) or "").trim() flags := (read_file(tmp) or "").trim()
shell("rm -f '" + tmp + "'") host.remove(tmp)
if flags.len > 0 { if flags.len > 0 {
return flags return flags
} }
@ -185,25 +171,27 @@ fn linkFailed(str output, cimport.CImports ci, platform.Target tg) -> int {
// The runtime, compiled once per system and compiler and kept in the cache folder. // The runtime, compiled once per system and compiler and kept in the cache folder.
fn runtimeObject(str rt, platform.Target tg, str cc) -> str! { fn runtimeObject(str rt, platform.Target tg, str cc) -> str! {
source := try read_file(rt) source := try read_file(rt)
dir := cacheDir() dir := host.cacheDir()
obj := dir + "/rt-" + fingerprint(source + cc + tg.triple + "2.5") + ".o" obj := dir + "/rt-" + fingerprint(source + cc + tg.triple + "2.5") + ".o"
if file_exists(obj) { if file_exists(obj) {
return obj return obj
} }
shell("mkdir -p " + quote(dir)) host.makeDirs(dir)
part := obj + ".part" + str(int(getpid())) part := obj + ".part" + str(host.pid())
pic := " -fPIC" pic := " -fPIC"
if tg.windows { if tg.windows {
pic = "" pic = ""
} }
log := part + ".log" log := part + ".log"
code := shell(cc + " -O2" + pic + " -c " + quote(rt) + " -o " + quote(part) + " > " + quote(log) + " 2>&1") code := host.run(cc + " -O2" + pic + " -c " + quote(rt) + " -o " + quote(part) + " > " + quote(log) + " 2>&1")
if code != 0 { if code != 0 {
text := read_file(log) or "" text := read_file(log) or ""
shell("rm -f " + quote(part) + " " + quote(log)) host.remove(part)
host.remove(log)
return error(text + "error: couldn't build the Finch runtime with '" + cc + "' (is a C compiler installed?)") return error(text + "error: couldn't build the Finch runtime with '" + cc + "' (is a C compiler installed?)")
} }
shell("rm -f " + quote(log) + " && mv " + quote(part) + " " + quote(obj)) host.remove(log)
host.move(part, obj)
return obj return obj
} }
@ -242,12 +230,13 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
target = " --target=" + tg.triple target = " --target=" + tg.triple
} }
log := b.out + ".log" log := b.out + ".log"
code := shell(clang + target + " " + b.opt + b.extra + " -w -c " + quote(ll) + " -o " + quote(obj) + " > " + quote(log) + " 2>&1") code := host.run(clang + target + " " + b.opt + b.extra + " -w -c " + quote(ll) + " -o " + quote(obj) + " > " + quote(log) + " 2>&1")
shell("rm -f " + quote(ll)) host.remove(ll)
if code != 0 { if code != 0 {
print(read_file(log) or "") print(read_file(log) or "")
print("error: clang couldn't compile the program's IR") print("error: clang couldn't compile the program's IR")
shell("rm -f " + quote(obj) + " " + quote(log)) host.remove(obj)
host.remove(log)
return 1 return 1
} }
cmd := cc + " " + quote(obj) + " " + quote(rtobj) + " -o " + quote(b.out) cmd := cc + " " + quote(obj) + " " + quote(rtobj) + " -o " + quote(b.out)
@ -270,10 +259,12 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
if tg.windows { if tg.windows {
pic = "" pic = ""
} }
c := shell(cc + " -O2" + pic + " -c " + quote(l) + " -o " + quote(o) + " > " + quote(log) + " 2>&1") c := host.run(cc + " -O2" + pic + " -c " + quote(l) + " -o " + quote(o) + " > " + quote(log) + " 2>&1")
if c != 0 { if c != 0 {
print((read_file(log) or "") + "error: couldn't compile " + l) print((read_file(log) or "") + "error: couldn't compile " + l)
shell("rm -f " + quote(obj) + " " + quote(log) + " " + quote(o)) host.remove(obj)
host.remove(log)
host.remove(o)
return 1 return 1
} }
temps.push(o) temps.push(o)
@ -284,11 +275,12 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
cmd = cmd + " " + libFlags(l, tg) cmd = cmd + " " + libFlags(l, tg)
} }
} }
status := shell(cmd + " > " + quote(log) + " 2>&1") status := host.run(cmd + " > " + quote(log) + " 2>&1")
output := read_file(log) or "" output := read_file(log) or ""
shell("rm -f " + quote(obj) + " " + quote(log)) host.remove(obj)
host.remove(log)
for t in temps { for t in temps {
shell("rm -f " + quote(t)) host.remove(t)
} }
if status == 127 { if status == 127 {
print("error: no C compiler found to link with ('" + cc + "'; install one, or set FINCH_CC)") print("error: no C compiler found to link with ('" + cc + "'; install one, or set FINCH_CC)")

View file

@ -5,9 +5,9 @@
// It asks the compiler instead of being one: for every change it runs `finch index` on the text the editor has // It asks the compiler instead of being one: for every change it runs `finch index` on the text the editor has
// (see the index records in gen.fch), and answers from what comes back. // (see the index records in gen.fch), and answers from what comes back.
import json import json
import host
import docs import docs
import "stdio.h" import "stdio.h"
import "unistd.h"
import "stdlib.h" import "stdlib.h"
// ---------- what one run of the compiler told ---------- // ---------- what one run of the compiler told ----------
@ -325,17 +325,17 @@ fn parseOutput(str output) -> Analysis {
fn Server.analyse(str path) { fn Server.analyse(str path) {
// the editor's text, as files the compiler can read: it asks for the file by its real name // the editor's text, as files the compiler can read: it asks for the file by its real name
shell("mkdir -p '" + self.tmp + "'") host.makeDirs(self.tmp)
overlays := "" overlays := ""
n := 0 n := 0
for p in self.docs.keys() { for p in self.docs.keys() {
copy := self.tmp + "/doc" + str(n) + ".fch" copy := self.tmp + "/doc" + str(n) + ".fch"
write_file(copy, self.docs[p]) write_file(copy, self.docs[p])
overlays = overlays + " --overlay '" + p + "=" + copy + "'" overlays = overlays + " --overlay " + host.quote(p + "=" + copy)
n += 1 n += 1
} }
out := self.tmp + "/out.txt" out := self.tmp + "/out.txt"
shell("'" + self.self + "' index '" + path + "'" + overlays + " > '" + out + "' 2>&1") host.run(host.quote(self.self) + " index " + host.quote(path) + overlays + " > " + host.quote(out) + " 2>&1")
a := parseOutput(read_file(out) or "") a := parseOutput(read_file(out) or "")
if !a.diag.has { if !a.diag.has {
self.good[path] = a self.good[path] = a
@ -863,13 +863,14 @@ fn Server.answer(json.Json msg) {
// finch lsp // finch lsp
fn run(str self) -> int { fn run(str self) -> int {
s := Server(self: self, tmp: "/tmp/finch_lsp_" + str(int(getpid()))) host.binaryStdio()
s := Server(self: self, tmp: host.tempDir() + "/finch_lsp_" + str(host.pid()))
while true { while true {
msg := readMessage() or { msg := readMessage() or {
break break
} }
s.answer(msg) s.answer(msg)
} }
shell("rm -rf '" + s.tmp + "'") host.removeTree(s.tmp)
return 0 return 0
} }

View file

@ -13,6 +13,8 @@ import cimport
import platform import platform
import linker import linker
import lsp import lsp
import host
import embedded
import "stdlib.h" import "stdlib.h"
fn dirOf(str path) -> str { fn dirOf(str path) -> str {
@ -129,6 +131,23 @@ fn usage() {
exit(1) exit(1)
} }
// The runtime and the standard library are carried inside the compiler (embedded.fch). When they aren't found as
// files next to it, they are written to the cache folder, once, and used from there.
fn unpack() -> str {
names := embedded.names()
dir := host.cacheDir() + "/files-" + linker.fingerprint(embedded.texts().join("\n"))
if file_exists(dir + "/done") {
return dir
}
texts := embedded.texts()
for i in 0..names.len {
host.makeDirs(dirOf(dir + "/" + names[i]))
write_file(dir + "/" + names[i], texts[i])
}
write_file(dir + "/done", "")
return dir
}
fn main([]str args) -> int { fn main([]str args) -> int {
if args.len == 2 && (args[1] == "version" || args[1] == "--version") { if args.len == 2 && (args[1] == "version" || args[1] == "--version") {
print("Finch 2.4.0 (the compiler is written in Finch)") print("Finch 2.4.0 (the compiler is written in Finch)")
@ -196,7 +215,7 @@ fn main([]str args) -> int {
} }
tg := platform.select(targetName) tg := platform.select(targetName)
if cmd == "run" { if cmd == "run" {
out = "/tmp/finch_run_" + stem(file) + tg.exe out = host.tempDir() + "/finch_run_" + str(host.pid()) + "_" + stem(file) + tg.exe
} else if shared && !outGiven { } else if shared && !outGiven {
if tg.windows { if tg.windows {
out = out + ".dll" out = out + ".dll"
@ -221,9 +240,12 @@ fn main([]str args) -> int {
loader.std = cand loader.std = cand
} }
} }
if loader.std.len == 0 {
loader.std = unpack() + "/std"
}
loader.overlayPaths = overlayPaths loader.overlayPaths = overlayPaths
loader.overlayFiles = overlayFiles loader.overlayFiles = overlayFiles
for d in getenv("FINCH_PATH").split(":") { for d in getenv("FINCH_PATH").split(host.pathSeparator()) {
if d.len > 0 { if d.len > 0 {
loader.paths.push(d) loader.paths.push(d)
} }
@ -262,7 +284,7 @@ fn main([]str args) -> int {
print(err) print(err)
return 1 return 1
} }
ir := gen.generate(loader.progs, loader.files, ci, gen.Options(shared: shared, debug: debug, index: cmd == "index", cwd: getenv("PWD"), target: tg)) ir := gen.generate(loader.progs, loader.files, ci, gen.Options(shared: shared, debug: debug, index: cmd == "index", cwd: host.cwd(), target: tg))
if cmd == "ir" || cmd == "index" { if cmd == "ir" || cmd == "index" {
print(ir) print(ir)
return 0 return 0
@ -283,8 +305,7 @@ fn main([]str args) -> int {
rt = "runtime/finch_rt.c" rt = "runtime/finch_rt.c"
} }
if !file_exists(rt) { if !file_exists(rt) {
print("error: can't find the Finch runtime (runtime/finch_rt.c)") rt = unpack() + "/runtime/finch_rt.c"
return 1
} }
extra := "" extra := ""
if shared { if shared {
@ -301,8 +322,8 @@ fn main([]str args) -> int {
for a in progArgs { for a in progArgs {
quoted.push(linker.quote(a)) quoted.push(linker.quote(a))
} }
status := shell(tg.runner + linker.quote(out) + " " + quoted.join(" ")) status := host.run(tg.runner + linker.quote(out) + " " + quoted.join(" "))
shell("rm -f " + linker.quote(out)) host.remove(out)
return status return status
} }
return 0 return 0

View file

@ -1,6 +1,7 @@
// The system a program is built for, and what depends on it: the C compiler that links, the file // The system a program is built for, and what depends on it: the C compiler that links, the file
// name of the program, how it is run here, and (through the flags) the C calling convention. // name of the program, how it is run here, and (through the flags) the C calling convention.
// The same as src/target.cpp. // The same as src/target.cpp.
import host
import "stdlib.h" import "stdlib.h"
struct Target { struct Target {
@ -22,10 +23,10 @@ fn firstLine(str text) -> str {
// what clang calls the system it runs on, like x86_64-pc-linux-gnu // what clang calls the system it runs on, like x86_64-pc-linux-gnu
fn hostTriple() -> str { fn hostTriple() -> str {
tmp := "/tmp/finch_triple.txt" tmp := host.tempDir() + "/finch_triple_" + str(host.pid()) + ".txt"
shell("clang -dumpmachine > '" + tmp + "' 2> /dev/null") host.run("clang -dumpmachine > " + host.quote(tmp) + " 2> " + host.nullDevice())
out := read_file(tmp) or "" out := read_file(tmp) or ""
shell("rm -f '" + tmp + "'") host.remove(tmp)
if out.trim().len == 0 { if out.trim().len == 0 {
return "x86_64-unknown-linux-gnu" return "x86_64-unknown-linux-gnu"
} }
@ -34,10 +35,10 @@ fn hostTriple() -> str {
// "windows", "linux", "macos", "arm64" (ARM64 Linux), or a full LLVM triple; "" = this system // "windows", "linux", "macos", "arm64" (ARM64 Linux), or a full LLVM triple; "" = this system
fn select(str name) -> Target { fn select(str name) -> Target {
host := hostTriple() hostName := hostTriple()
t := name t := name
if name.len == 0 { if name.len == 0 {
t = host t = hostName
} else if name == "windows" { } else if name == "windows" {
t = "x86_64-w64-mingw32" t = "x86_64-w64-mingw32"
} else if name == "linux" { } else if name == "linux" {
@ -45,7 +46,7 @@ fn select(str name) -> Target {
} else if name == "arm64" || name == "aarch64" || name == "linux-arm64" { } else if name == "arm64" || name == "aarch64" || name == "linux-arm64" {
t = "aarch64-unknown-linux-gnu" t = "aarch64-unknown-linux-gnu"
} else if name == "macos" { } else if name == "macos" {
if host.starts_with("aarch64") || host.starts_with("arm64") { if hostName.starts_with("aarch64") || hostName.starts_with("arm64") {
t = "arm64-apple-macosx14.0.0" t = "arm64-apple-macosx14.0.0"
} else { } else {
t = "x86_64-apple-macosx14.0.0" t = "x86_64-apple-macosx14.0.0"
@ -56,8 +57,8 @@ fn select(str name) -> Target {
tg.msvc = t.contains("msvc") tg.msvc = t.contains("msvc")
tg.darwin = t.contains("apple") || t.contains("darwin") || t.contains("macos") tg.darwin = t.contains("apple") || t.contains("darwin") || t.contains("macos")
tg.arm64 = t.starts_with("aarch64") || t.starts_with("arm64") tg.arm64 = t.starts_with("aarch64") || t.starts_with("arm64")
hostArm := host.starts_with("aarch64") || host.starts_with("arm64") hostArm := hostName.starts_with("aarch64") || hostName.starts_with("arm64")
hostDarwin := host.contains("apple") || host.contains("darwin") hostDarwin := hostName.contains("apple") || hostName.contains("darwin")
tg.cross = tg.windows || tg.darwin != hostDarwin || tg.arm64 != hostArm tg.cross = tg.windows || tg.darwin != hostDarwin || tg.arm64 != hostArm
tg.exe = "" tg.exe = ""
if tg.windows { if tg.windows {
@ -73,6 +74,8 @@ fn select(str name) -> Target {
tg.cc = "aarch64-linux-gnu-gcc" tg.cc = "aarch64-linux-gnu-gcc"
} else if tg.cross { } else if tg.cross {
tg.cc = "clang --target=" + t tg.cc = "clang --target=" + t
} else if host.pathSeparator() == ";" {
tg.cc = "clang" // Windows has no cc
} else { } else {
tg.cc = "cc" tg.cc = "cc"
} }

View file

@ -10,6 +10,7 @@ set -u
cd "$(dirname "$0")" cd "$(dirname "$0")"
FINCH=${FINCH:-../build/finch} FINCH=${FINCH:-../build/finch}
FINCH="$(cd "$(dirname "$FINCH")" && pwd)/$(basename "$FINCH")" # tests/shared.sh runs from another folder FINCH="$(cd "$(dirname "$FINCH")" && pwd)/$(basename "$FINCH")" # tests/shared.sh runs from another folder
FINCH_ROOT="$(cd .. && pwd)"
export FINCH_RT=${FINCH_RT:-$PWD/../runtime/finch_rt.c} export FINCH_RT=${FINCH_RT:-$PWD/../runtime/finch_rt.c}
pass=0; failed=0 pass=0; failed=0
tmp=$(mktemp -d) tmp=$(mktemp -d)
@ -78,6 +79,15 @@ else
head -5 "$tmp/shared.out" head -5 "$tmp/shared.out"
fi fi
# the runtime and std carried inside the compiler are the files as they are now
"$FINCH_ROOT/bootstrap/pack.sh" "$FINCH" "$tmp/embedded.fch" 2>/dev/null
if cmp -s "$tmp/embedded.fch" "$FINCH_ROOT/src/embedded.fch"; then
pass=$((pass + 1))
else
failed=$((failed + 1))
echo "FAIL src/embedded.fch is out of date (run bootstrap/build.sh)"
fi
# the language server, talked to the way an editor does # the language server, talked to the way an editor does
if command -v python3 > /dev/null 2>&1; then if command -v python3 > /dev/null 2>&1; then
if FINCH="$FINCH" python3 lsp_test.py > "$tmp/lsp.out" 2>&1; then if FINCH="$FINCH" python3 lsp_test.py > "$tmp/lsp.out" 2>&1; then

54
tools/pack.fch Normal file
View file

@ -0,0 +1,54 @@
// finch run tools/pack.fch src/embedded.fch runtime/finch_rt.c std/json.fch ...
// Writes a Finch module that holds the given files as text, so the compiler can carry its runtime and standard library
// inside itself (bootstrap/build.sh runs this; tests/run.sh checks the result is up to date).
fn literal(str text) -> str {
[]str parts = ["\""]
for c in text {
if c == '\\' {
parts.push("\\\\")
} else if c == '"' {
parts.push("\\\"")
} else if c == '\n' {
parts.push("\\n")
} else if c == '\t' {
parts.push("\\t")
} else if c == '\r' {
parts.push("\\r")
} else {
parts.push(str(c))
}
}
parts.push("\"")
return parts.join("")
}
fn main([]str args) -> int {
if args.len < 3 {
print("usage: pack out.fch file...")
return 1
}
[]str out = [
"// The runtime and the standard library, as text. Made by tools/pack.fch: don't edit it, run bootstrap/build.sh.",
"// Used when the compiler can't find runtime/ and std/ next to itself.",
"",
"fn names() -> []str {",
" []str list",
]
for i in 2..args.len {
out.push(" list.push(" + literal(args[i]) + ")")
}
out.push(" return list")
out.push("}")
out.push("")
out.push("fn texts() -> []str {")
out.push(" []str list")
for i in 2..args.len {
out.push(" list.push(" + literal(read_file(args[i])) + ")")
}
out.push(" return list")
out.push("}")
out.push("")
write_file(args[1], out.join("\n"))
return 0
}