Invalidate units when loaded sources change

This commit is contained in:
udo 2026-07-13 07:42:52 +00:00
parent d857930ceb
commit 02ed75fc85
4 changed files with 94 additions and 2 deletions

View File

@ -48,4 +48,7 @@ if [[ ! -S "$socket_path" ]]; then
fi
url="http://localhost/tests/cli_runner.uce?action=${action}&include_kill=${include_kill}&skip_local_service_pages=${skip_local_service_pages}"
exec curl -sS --fail-with-body --unix-socket "$socket_path" "$url"
curl -sS --fail-with-body --unix-socket "$socket_path" "$url"
if [[ "$action" == "run" ]]; then
scripts/test_dependency_invalidation.sh
fi

View File

@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
test_name="dependency-cache-test-$$"
source_dir="site/$test_name"
cache_dir="${BIN_DIRECTORY:-/tmp/uce/work}$(pwd)/$source_dir"
cleanup() {
rm -rf "$source_dir" "$cache_dir"
}
trap cleanup EXIT
mkdir -p "$source_dir"
printf '%s\n' \
'#ifndef UCE_DEPENDENCY_CACHE_CHILD' \
'#define UCE_DEPENDENCY_CACHE_CHILD' \
'String dependency_cache_marker() { return("dependency-marker-a"); }' \
'#endif' >"$source_dir/child.uce"
printf '%s\n' \
'#load "child.uce"' \
'CLI(Request& context) { print(dependency_cache_marker()); }' \
'RENDER(Request& context) { print(dependency_cache_marker()); }' >"$source_dir/parent.uce"
first=$(scripts/uce-cli "/$test_name/parent.uce")
if [[ "$first" != *dependency-marker-a* ]]; then
echo "dependency invalidation setup failed: $first" >&2
exit 1
fi
sed -i 's/dependency-marker-a/dependency-marker-b/' "$source_dir/child.uce"
second=$(scripts/uce-cli "/$test_name/parent.uce")
if [[ "$second" != *dependency-marker-b* ]]; then
echo "loaded dependency change did not recompile parent: $second" >&2
exit 1
fi
echo "dependency invalidation passed"

View File

@ -12,6 +12,8 @@ Includes another UCE file.
Use `#load` when you want the current file to pull in declarations or reusable content from another UCE source file.
Loaded sources are tracked transitively. Changing a direct or nested `#load` dependency invalidates and recompiles the entry unit before its next execution.
:example
// #load "file.uce" includes another unit at COMPILE time (a preprocessor directive,

View File

@ -87,6 +87,55 @@ StringMap compiler_parse_unit_metadata(String content)
return(metadata);
}
StringList compiler_unit_load_paths(String file_name, String content)
{
StringList paths;
u64 line_start = 0;
while(line_start < content.length())
{
u64 line_end = content.find("\n", line_start);
if(line_end == String::npos)
break;
String line = content.substr(line_start, line_end - line_start);
if(str_starts_with(line, "#load "))
{
u64 first_quote = line.find('"', 6);
u64 second_quote = first_quote == String::npos ? String::npos : line.find('"', first_quote + 1);
if(first_quote != String::npos && second_quote != String::npos)
{
String loaded = line.substr(first_quote + 1, second_quote - first_quote - 1);
if(loaded != "")
paths.push_back(loaded[0] == '/' ? loaded : expand_path(loaded, dirname(file_name)));
}
}
line_start = line_end + 1;
}
return(paths);
}
void compiler_append_unit_source_signature(String file_name, std::set<String>& visited, String& signature)
{
String normalized = path_real(file_name);
if(normalized == "")
normalized = file_name;
if(visited.find(normalized) != visited.end())
return;
visited.insert(normalized);
String content = file_get_contents(file_name);
signature += normalized + ":" + gen_sha1(content) + "\n";
for(String loaded : compiler_unit_load_paths(file_name, content))
compiler_append_unit_source_signature(loaded, visited, signature);
}
String compiler_unit_source_signature(String file_name)
{
std::set<String> visited;
String signature = "uce-load-graph-v1\n";
compiler_append_unit_source_signature(file_name, visited, signature);
return(gen_sha1(signature));
}
String compiler_unit_input_signature(Request* context, SharedUnit* su)
{
if(!context || !su || !file_exists(su->file_name))
@ -94,7 +143,7 @@ String compiler_unit_input_signature(Request* context, SharedUnit* su)
String setup_template = context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"];
return(
gen_sha1(file_get_contents(su->file_name)) + ":" +
compiler_unit_source_signature(su->file_name) + ":" +
gen_sha1(file_get_contents(setup_template)) + ":" +
std::to_string(UCE_UNIT_ABI_VERSION)
);