You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
7.9 KiB
159 lines
7.9 KiB
//build_common.zig - shared configure-time helpers for the suite recipe files (G-102). |
|
//These run while the build graph is being CONSTRUCTED (not as build steps). Reads are |
|
//of staged/fetched source trees; generated content is never written into a source |
|
//tree - it goes through b.addWriteFiles() overlays into the build cache and is |
|
//consumed via overlay include dirs / overlay rc copies. |
|
const std = @import("std"); |
|
|
|
pub fn pathExists(b: *std.Build, path_from_root: []const u8) bool { |
|
const abs = b.pathFromRoot(path_from_root); |
|
std.Io.Dir.cwd().access(b.graph.io, abs, .{}) catch return false; |
|
return true; |
|
} |
|
|
|
pub fn readSourceFile(b: *std.Build, path_from_root: []const u8) []u8 { |
|
const abs = b.pathFromRoot(path_from_root); |
|
return std.Io.Dir.cwd().readFileAlloc(b.graph.io, abs, b.allocator, .limited(8 * 1024 * 1024)) catch |err| { |
|
std.debug.panic("suite recipe: cannot read {s}: {s}", .{ abs, @errorName(err) }); |
|
}; |
|
} |
|
|
|
//value of a '#define <name> "<value>"' line (e.g TCL_PATCH_LEVEL from tcl.h) |
|
pub fn parseDefineString(content: []const u8, name: []const u8) ?[]const u8 { |
|
var it = std.mem.splitScalar(u8, content, '\n'); |
|
while (it.next()) |line| { |
|
const trimmed = std.mem.trim(u8, line, " \t\r"); |
|
if (!std.mem.startsWith(u8, trimmed, "#")) continue; |
|
const after_hash = std.mem.trimStart(u8, trimmed[1..], " \t"); |
|
if (!std.mem.startsWith(u8, after_hash, "define")) continue; |
|
const after_def = std.mem.trimStart(u8, after_hash[6..], " \t"); |
|
if (!std.mem.startsWith(u8, after_def, name)) continue; |
|
const after_name = after_def[name.len..]; |
|
if (after_name.len == 0 or (after_name[0] != ' ' and after_name[0] != '\t')) continue; |
|
const q1 = std.mem.indexOfScalar(u8, after_name, '"') orelse continue; |
|
const rest = after_name[q1 + 1 ..]; |
|
const q2 = std.mem.indexOfScalar(u8, rest, '"') orelse continue; |
|
return rest[0..q2]; |
|
} |
|
return null; |
|
} |
|
|
|
pub fn replaceAll(b: *std.Build, input: []const u8, needle: []const u8, replacement: []const u8) []u8 { |
|
return std.mem.replaceOwned(u8, b.allocator, input, needle, replacement) catch @panic("OOM"); |
|
} |
|
|
|
//hermetic child shells (G-102): steps that run the suite-built tclsh must not see |
|
//the user's machine-level tcl environment (TCLLIBPATH pushing machine package dirs |
|
//misdirects installer default paths and risks false-positive smokes; TCL_LIBRARY/ |
|
//TK_LIBRARY misdirect init script resolution). suite.tcl scrubbed these for its |
|
//whole process; zig-driven runs scrub per step. |
|
pub fn scrubTclEnv(run: *std.Build.Step.Run) void { |
|
for ([_][]const u8{ "TCLLIBPATH", "TCL_LIBRARY", "TK_LIBRARY" }) |ev| { |
|
run.removeEnvironmentVariable(ev); |
|
} |
|
} |
|
|
|
//checkout uuid from a tree's manifest.uuid when the repo materializes it (a |
|
//per-repo fossil 'manifest' setting: tcl/tk/thread enable it, tclvfs/tcllib/ |
|
//tklib do not - true for live checkouts AND fossil tarball exports alike), else |
|
//"unrecorded" so provenance emission stays total (G-103 artifact metadata). |
|
pub fn manifestUuid(b: *std.Build, tree_from_root: []const u8) []const u8 { |
|
const abs = b.pathFromRoot(b.fmt("{s}/manifest.uuid", .{tree_from_root})); |
|
const data = std.Io.Dir.cwd().readFileAlloc(b.graph.io, abs, b.allocator, .limited(4096)) catch return "unrecorded"; |
|
return std.mem.trim(u8, data, " \t\r\n"); |
|
} |
|
|
|
//package version from a TEA tree's configure.ac 'AC_INIT([name],[version])' |
|
//line (thread, tclvfs, ...). G-107: version-derived artifact names must follow |
|
//the checkout - hardcoded versions stamp mismatched sources (the thread recipe |
|
//once compiled 3.0.7 sources as 3.0.1; the dll self-reported the wrong version |
|
//and the thread testsuite's exact-version require could never load it). |
|
pub fn acInitVersion(b: *std.Build, tree_from_root: []const u8) []const u8 { |
|
const data = readSourceFile(b, b.fmt("{s}/configure.ac", .{tree_from_root})); |
|
var it = std.mem.splitScalar(u8, data, '\n'); |
|
while (it.next()) |line| { |
|
const trimmed = std.mem.trim(u8, line, " \t\r"); |
|
if (!std.mem.startsWith(u8, trimmed, "AC_INIT")) continue; |
|
//the second [...] group is the version |
|
var rest = trimmed; |
|
var group: usize = 0; |
|
while (std.mem.indexOfScalar(u8, rest, '[')) |open| { |
|
const after = rest[open + 1 ..]; |
|
const close = std.mem.indexOfScalar(u8, after, ']') orelse break; |
|
group += 1; |
|
if (group == 2) return b.dupe(after[0..close]); |
|
rest = after[close + 1 ..]; |
|
} |
|
} |
|
std.debug.panic("no AC_INIT version in {s}/configure.ac", .{tree_from_root}); |
|
} |
|
|
|
pub fn stripChars(b: *std.Build, input: []const u8, drop: []const u8) []u8 { |
|
var out = std.array_list.Managed(u8).init(b.allocator); |
|
for (input) |ch| { |
|
if (std.mem.indexOfScalar(u8, drop, ch) != null) continue; |
|
out.append(ch) catch @panic("OOM"); |
|
} |
|
return out.items; |
|
} |
|
|
|
//tcllib-family installer trees carry 'package_version <v>' in |
|
//support/installation/version.tcl |
|
pub fn installerPackageVersion(b: *std.Build, tree_from_root: []const u8) []const u8 { |
|
const data = readSourceFile(b, b.fmt("{s}/support/installation/version.tcl", .{tree_from_root})); |
|
var lines = std.mem.splitScalar(u8, data, '\n'); |
|
while (lines.next()) |line| { |
|
var toks = std.mem.tokenizeAny(u8, line, " \t\r"); |
|
const first = toks.next() orelse continue; |
|
if (!std.mem.eql(u8, first, "package_version")) continue; |
|
const ver = toks.next() orelse continue; |
|
return b.dupe(ver); |
|
} |
|
std.debug.panic("no package_version in {s}/support/installation/version.tcl", .{tree_from_root}); |
|
} |
|
|
|
//tcllib's critcl module files from its version.tcl declarations: the critcl_main |
|
//module's files first, then every 'critcl' module's files (sak.tcl semantics - |
|
//the same derivation suite.tcl's tcllibc block used). Returned paths are relative |
|
//to the tcllib tree's modules/ dir. Tcl brace-list tokens are unwrapped by brace |
|
//stripping (the declared file names contain no spaces). |
|
pub fn tcllibCritclFiles(b: *std.Build, tcllib_from_root: []const u8) []const []const u8 { |
|
const data = readSourceFile(b, b.fmt("{s}/support/installation/version.tcl", .{tcllib_from_root})); |
|
var mains = std.array_list.Managed([]const u8).init(b.allocator); |
|
var mods = std.array_list.Managed([]const u8).init(b.allocator); |
|
var lines = std.mem.splitScalar(u8, data, '\n'); |
|
while (lines.next()) |line| { |
|
var toks = std.mem.tokenizeAny(u8, line, " \t\r"); |
|
const first = toks.next() orelse continue; |
|
const is_main = std.mem.eql(u8, first, "critcl_main"); |
|
if (!is_main and !std.mem.eql(u8, first, "critcl")) continue; |
|
_ = toks.next() orelse continue; //module name |
|
while (toks.next()) |tok| { |
|
var f = tok; |
|
f = std.mem.trim(u8, f, "{}"); |
|
if (f.len == 0) continue; |
|
(if (is_main) &mains else &mods).append(b.dupe(f)) catch @panic("OOM"); |
|
} |
|
} |
|
if (mains.items.len == 0) @panic("no critcl_main declaration found in tcllib version.tcl"); |
|
mains.appendSlice(mods.items) catch @panic("OOM"); |
|
return mains.items; |
|
} |
|
|
|
//TCL_WIN_VERSION / TK_WIN_VERSION as the makefiles derive them: |
|
//<dotversion>.<releaselevel>.<patchlevel with a/b/. stripped>, releaselevel |
|
//0=alpha 1=beta 2=final (e.g 9.0 + 9.0.5 -> 9.0.2.905) |
|
pub fn winResourceVersion(b: *std.Build, dotversion: []const u8, patchlevel: []const u8) []u8 { |
|
const rlevel: u8 = if (std.mem.indexOfScalar(u8, patchlevel, 'a') != null) |
|
'0' |
|
else if (std.mem.indexOfScalar(u8, patchlevel, 'b') != null) |
|
'1' |
|
else |
|
'2'; |
|
var stripped = std.array_list.Managed(u8).init(b.allocator); |
|
for (patchlevel) |ch| { |
|
if (ch == 'a' or ch == 'b' or ch == '.') continue; |
|
stripped.append(ch) catch @panic("OOM"); |
|
} |
|
return std.fmt.allocPrint(b.allocator, "{s}.{c}.{s}", .{ dotversion, rlevel, stripped.items }) catch @panic("OOM"); |
|
}
|
|
|