hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 4c72c067bfecd0daa9c622e2a08a761bc675b5c2
parent 86425c7d0d5337c4187b2e7e5de8c813a9e31cbd
Author: Drew DeVault <sir@cmpwn.com>
Date:   Wed, 24 Mar 2021 12:35:39 -0400

hare::module: implement cache manifest loading

Diffstat:
Mcmd/hare/schedule.ha | 11++++++++++-
Mhare/module/manifest.ha | 192++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mscripts/gen-stdlib | 5+++--
Mstdlib.mk | 8++++----
4 files changed, 208 insertions(+), 8 deletions(-)

diff --git a/cmd/hare/schedule.ha b/cmd/hare/schedule.ha @@ -155,7 +155,6 @@ fn sched_hare_object( }; let output = if (len(namespace) != 0) { - // TODO: consult/update cache manifest let version = hex::encode(ver.hash); let ns = unparse::ident_s(namespace); let env = module::ident_underscore(namespace); @@ -166,6 +165,15 @@ fn sched_hare_object( fmt::asprintf("HARE_VERSION_{}", env), version, )); + let manifest = match (module::manifest_load( + plan.context, namespace)) { + err: module::error => fmt::fatal( + "Error reading cache entry for {}: {}", + ns, module::errstr(err)), + m: module::manifest => m, + }; + defer module::manifest_finish(&manifest); + let name = fmt::asprintf("{}.{}", version, if (mixed) "a" else "o"); defer free(name); @@ -188,6 +196,7 @@ fn sched_hare_object( append(harec.cmd, path); }; }; + append(plan.scheduled, harec); let s = mkfile(plan, "s"); diff --git a/hare/module/manifest.ha b/hare/module/manifest.ha @@ -1,10 +1,15 @@ +use bufio; +use bytes; use encoding::hex; +use encoding::utf8; use fmt; use fs; use hare::ast; use hare::unparse; use io; use path; +use strconv; +use strings; use time; // The manifest file format is a series of line-oriented records. Lines starting @@ -16,6 +21,173 @@ use time; // - "module" is a version of a module, and includes the module hash and the set // of input hashes which produce it. +def VERSION: int = 1; + +fn getinput(m: *manifest, hash: []u8) nullable *input = { + for (let i = 0z; i < len(m.inputs); i += 1) { + if (bytes::equal(m.inputs[i].hash, hash)) { + return &m.inputs[i]; + }; + }; + return null; +}; + +// Loads the module manifest from the build cache for the given ident. The +// return value borrows the ident parameter. If the module is not found, an +// empty manifest is returned. +export fn manifest_load(ctx: *context, ident: ast::ident) (manifest | error) = { + let manifest = manifest { + ident = ident, + inputs = [], + versions = [], + }; + let ipath = ident_path(manifest.ident); + defer free(ipath); + let cachedir = path::join(ctx.cache, ipath); + defer free(cachedir); + + let mpath = path::join(cachedir, "manifest"); + defer free(mpath); + + // TODO: We can probably eliminate these locks by using atomic writes + // instead + let l = lock(ctx.fs, cachedir)?; + defer unlock(ctx.fs, cachedir, l); + + let file = match (fs::open(ctx.fs, mpath, fs::flags::RDONLY)) { + fs::noentry => return manifest, + err: fs::error => return err, + file: *io::stream => file, + }; + defer io::close(file); + + let inputs: []input = [], versions: []version = []; + + let buf: [4096]u8 = [0...]; + let file = bufio::buffered(file, buf, []); + for (true) { + let line = match (bufio::scanline(file)?) { + io::EOF => break, + line: []u8 => match (strings::try_from_utf8(line)) { + // Treat an invalid manifest as empty + utf8::invalid => return manifest, + s: str => s, + }, + }; + defer free(line); + + if (strings::has_prefix(line, "#")) { + continue; + }; + + let tok = strings::tokenize(line, " "); + let kind = match (strings::next_token(&tok)) { + void => continue, + s: str => s, + }; + + if (kind == "version") { + let ver = match (strings::next_token(&tok)) { + void => return manifest, + s: str => s, + }; + match (strconv::stoi(ver)) { + v: int => if (v != VERSION) { + return manifest; + }, + * => return manifest, + }; + } else if (kind == "input") { + let hash = match (strings::next_token(&tok)) { + void => return manifest, s: str => s, + }, path = match (strings::next_token(&tok)) { + void => return manifest, s: str => s, + }, inode = match (strings::next_token(&tok)) { + void => return manifest, s: str => s, + }, mtime = match (strings::next_token(&tok)) { + void => return manifest, s: str => s, + }; + + let hash = match (hex::decode(hash)) { + * => return manifest, + b: []u8 => b, + }; + let inode = match (strconv::stoz(inode)) { + * => return manifest, + z: size => z, + }; + let mtime = match (strconv::stoi64(mtime)) { + * => return manifest, + i: i64 => time::from_unix(i), + }; + + let parsed = parse_name(path); + let ftype = match (type_for_ext(path)) { + void => return manifest, + ft: filetype => ft, + }; + + append(inputs, input { + hash = hash, + path = strings::dup(path), + ft = ftype, + stat = fs::filestat { + mask = fs::stat_mask::MTIME | fs::stat_mask::INODE, + mtime = mtime, + inode = inode, + }, + basename = strings::dup(parsed.0), + tags = parsed.2, + }); + } else if (kind == "module") { + let modhash = match (strings::next_token(&tok)) { + void => return manifest, s: str => s, + }; + let modhash = match (hex::decode(modhash)) { + * => return manifest, + b: []u8 => b, + }; + + let inputs: []input = []; + for (true) { + let hash = match (strings::next_token(&tok)) { + void => break, + s: str => s, + }; + let hash = match (hex::decode(hash)) { + * => return manifest, + b: []u8 => b, + }; + defer free(hash); + + let input = match (getinput(&manifest, hash)) { + null => return manifest, + i: *input => i, + }; + + append(inputs, *input); + }; + + append(versions, version { + hash = modhash, + inputs = inputs, + }); + } else { + return manifest; + }; + + // Check for extra tokens + match (strings::next_token(&tok)) { + void => void, + s: str => return manifest, + }; + }; + + manifest.inputs = inputs; + manifest.versions = versions; + return manifest; +}; + // Writes a module manifest to the build cache. export fn manifest_write(ctx: *context, manifest: *manifest) (void | error) = { let ipath = ident_path(manifest.ident); @@ -37,7 +209,7 @@ export fn manifest_write(ctx: *context, manifest: *manifest) (void | error) = { fmt::fprintfln(fd, "# {}", ident)?; fmt::fprintln(fd, "# This file is an internal Hare implementation detail.")?; fmt::fprintln(fd, "# The format is not stable.")?; - fmt::fprintln(fd, "version 1")?; + fmt::fprintfln(fd, "version {}", VERSION)?; for (let i = 0z; i < len(manifest.inputs); i += 1) { const input = manifest.inputs[i]; let hash = hex::encode(input.hash); @@ -99,3 +271,21 @@ fn unlock(fs: *fs::fs, cachedir: str, s: *io::stream) void = { err: fs::error => abort("Error removing module lock"), }; }; + +fn input_finish(in: *input) void = { + free(in.hash); + free(in.path); + free(in.basename); + tags_free(in.tags); +}; + +// Frees resources associated with this manifest. +export fn manifest_finish(m: *manifest) void = { + for (let i = 0z; i < len(m.inputs); i += 1) { + input_finish(&m.inputs[i]); + }; + + for (let i = 0z; i < len(m.versions); i += 1) { + free(m.versions[i].inputs); + }; +}; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -188,7 +188,7 @@ encoding_hex() { printf '# encoding::hex\n' gen_srcs encoding::hex \ hex.ha - gen_ssa encoding::hex io strconv strio strings + gen_ssa encoding::hex io strconv strio strings bytes } encoding_utf8() { @@ -294,7 +294,8 @@ hare_module() { manifest.ha gen_ssa hare::module \ hare::ast hare::lex hare::parse hare::unparse strio fs io strings hash \ - crypto::sha256 dirs bytes encoding::utf8 ascii fmt time slice + crypto::sha256 dirs bytes encoding::utf8 ascii fmt time slice bufio \ + strconv } gensrcs_hare_parse() { diff --git a/stdlib.mk b/stdlib.mk @@ -276,7 +276,7 @@ $(HARECACHE)/dirs/dirs.ssa: $(stdlib_dirs_srcs) $(stdlib_rt) $(stdlib_fs) $(stdl stdlib_encoding_hex_srcs= \ $(STDLIB)/encoding/hex/hex.ha -$(HARECACHE)/encoding/hex/encoding_hex.ssa: $(stdlib_encoding_hex_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_strconv) $(stdlib_strio) $(stdlib_strings) +$(HARECACHE)/encoding/hex/encoding_hex.ssa: $(stdlib_encoding_hex_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_strconv) $(stdlib_strio) $(stdlib_strings) $(stdlib_bytes) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/encoding/hex @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nencoding::hex \ @@ -383,7 +383,7 @@ stdlib_hare_module_srcs= \ $(STDLIB)/hare/module/scan.ha \ $(STDLIB)/hare/module/manifest.ha -$(HARECACHE)/hare/module/hare_module.ssa: $(stdlib_hare_module_srcs) $(stdlib_rt) $(stdlib_hare_ast) $(stdlib_hare_lex) $(stdlib_hare_parse) $(stdlib_hare_unparse) $(stdlib_strio) $(stdlib_fs) $(stdlib_io) $(stdlib_strings) $(stdlib_hash) $(stdlib_crypto_sha256) $(stdlib_dirs) $(stdlib_bytes) $(stdlib_encoding_utf8) $(stdlib_ascii) $(stdlib_fmt) $(stdlib_time) $(stdlib_slice) +$(HARECACHE)/hare/module/hare_module.ssa: $(stdlib_hare_module_srcs) $(stdlib_rt) $(stdlib_hare_ast) $(stdlib_hare_lex) $(stdlib_hare_parse) $(stdlib_hare_unparse) $(stdlib_strio) $(stdlib_fs) $(stdlib_io) $(stdlib_strings) $(stdlib_hash) $(stdlib_crypto_sha256) $(stdlib_dirs) $(stdlib_bytes) $(stdlib_encoding_utf8) $(stdlib_ascii) $(stdlib_fmt) $(stdlib_time) $(stdlib_slice) $(stdlib_bufio) $(stdlib_strconv) @printf 'HAREC \t$@\n' @mkdir -p $(HARECACHE)/hare/module @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nhare::module \ @@ -927,7 +927,7 @@ $(TESTCACHE)/dirs/dirs.ssa: $(testlib_dirs_srcs) $(testlib_rt) $(testlib_fs) $(t testlib_encoding_hex_srcs= \ $(STDLIB)/encoding/hex/hex.ha -$(TESTCACHE)/encoding/hex/encoding_hex.ssa: $(testlib_encoding_hex_srcs) $(testlib_rt) $(testlib_io) $(testlib_strconv) $(testlib_strio) $(testlib_strings) +$(TESTCACHE)/encoding/hex/encoding_hex.ssa: $(testlib_encoding_hex_srcs) $(testlib_rt) $(testlib_io) $(testlib_strconv) $(testlib_strio) $(testlib_strings) $(testlib_bytes) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/encoding/hex @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nencoding::hex \ @@ -1035,7 +1035,7 @@ testlib_hare_module_srcs= \ $(STDLIB)/hare/module/scan.ha \ $(STDLIB)/hare/module/manifest.ha -$(TESTCACHE)/hare/module/hare_module.ssa: $(testlib_hare_module_srcs) $(testlib_rt) $(testlib_hare_ast) $(testlib_hare_lex) $(testlib_hare_parse) $(testlib_hare_unparse) $(testlib_strio) $(testlib_fs) $(testlib_io) $(testlib_strings) $(testlib_hash) $(testlib_crypto_sha256) $(testlib_dirs) $(testlib_bytes) $(testlib_encoding_utf8) $(testlib_ascii) $(testlib_fmt) $(testlib_time) $(testlib_slice) +$(TESTCACHE)/hare/module/hare_module.ssa: $(testlib_hare_module_srcs) $(testlib_rt) $(testlib_hare_ast) $(testlib_hare_lex) $(testlib_hare_parse) $(testlib_hare_unparse) $(testlib_strio) $(testlib_fs) $(testlib_io) $(testlib_strings) $(testlib_hash) $(testlib_crypto_sha256) $(testlib_dirs) $(testlib_bytes) $(testlib_encoding_utf8) $(testlib_ascii) $(testlib_fmt) $(testlib_time) $(testlib_slice) $(testlib_bufio) $(testlib_strconv) @printf 'HAREC \t$@\n' @mkdir -p $(TESTCACHE)/hare/module @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nhare::module \