feat(types): add core data structures
PackageIndex, Recipe, DepConstraint, BuildPlan, BuildResult, CacheManifest, BinaryCheckResult structs. Pool/BuildSystem/DepOp/Source enums. DepConstraint.parse() ported from ZETA vercmp.lua. TypesException for parsing errors. All @safe, all strings default to empty string.
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
Warning
|
||||
Warning ## Warning for package tofu ##
|
||||
Warning
|
||||
Warning The following compiler flags have been specified in the package description
|
||||
Warning file. They are handled by DUB and direct use in packages is discouraged.
|
||||
Warning Alternatively, you can set the DFLAGS environment variable to pass custom flags
|
||||
Warning to the compiler, or use one of the suggestions below:
|
||||
Warning
|
||||
Warning warningsAsErrors: Use "buildRequirements" to control the warning level
|
||||
Warning
|
||||
Generating test runner configuration 'tofu-test-application' for 'application' (executable).
|
||||
Warning Excluding main source file src/main.d from test.
|
||||
Starting Performing "unittest" build using /usr/bin/dmd for x86_64.
|
||||
Up-to-date toml 1.0.0: target for configuration [library] is up to date.
|
||||
Building tofu ~main: building configuration [tofu-test-application]
|
||||
src/tofu/config.d(125,9): Error: found `private` instead of statement
|
||||
private @trusted void warn() {
|
||||
^
|
||||
src/tofu/config.d(132,1): Error: unmatched closing brace
|
||||
}
|
||||
^
|
||||
src/tofu/types.d(147,13): Error: variable name expected after type `string`, not `version`
|
||||
string version = "";
|
||||
^
|
||||
src/tofu/types.d(147,13): `version` is a keyword, perhaps append `_` to make it an identifier
|
||||
src/tofu/types.d(147,21): Error: declaration expected, not `=`
|
||||
string version = "";
|
||||
^
|
||||
src/tofu/types.d(159,16): Error: variable name expected after type `string`, not `version`
|
||||
string version = "";
|
||||
^
|
||||
src/tofu/types.d(159,16): `version` is a keyword, perhaps append `_` to make it an identifier
|
||||
src/tofu/types.d(159,29): Error: declaration expected, not `=`
|
||||
string version = "";
|
||||
^
|
||||
src/tofu/types.d(185,12): Error: variable name expected after type `string`, not `version`
|
||||
string version = "";
|
||||
^
|
||||
src/tofu/types.d(185,12): `version` is a keyword, perhaps append `_` to make it an identifier
|
||||
src/tofu/types.d(185,20): Error: declaration expected, not `=`
|
||||
string version = "";
|
||||
^
|
||||
src/tofu/types.d(268,16): Error: identifier or `new` expected following `.`, not `version`
|
||||
result.version = spec[verStart .. verEnd];
|
||||
^
|
||||
src/tofu/types.d(326,12): Error: variable name expected after type `string`, not `version`
|
||||
string version = "";
|
||||
^
|
||||
src/tofu/types.d(326,12): `version` is a keyword, perhaps append `_` to make it an identifier
|
||||
src/tofu/types.d(326,22): Error: declaration expected, not `=`
|
||||
string version = "";
|
||||
^
|
||||
src/tofu/types.d(366,12): Error: variable name expected after type `string`, not `version`
|
||||
string version = "";
|
||||
^
|
||||
src/tofu/types.d(366,12): `version` is a keyword, perhaps append `_` to make it an identifier
|
||||
src/tofu/types.d(366,22): Error: declaration expected, not `=`
|
||||
string version = "";
|
||||
^
|
||||
src/tofu/types.d(405,18): Error: identifier or `new` expected following `.`, not `version`
|
||||
assert(d.version == "");
|
||||
^
|
||||
src/tofu/types.d(411,18): Error: identifier or `new` expected following `.`, not `version`
|
||||
assert(d.version == "");
|
||||
^
|
||||
src/tofu/types.d(422,18): Error: identifier or `new` expected following `.`, not `version`
|
||||
assert(d.version == "2.0");
|
||||
^
|
||||
src/tofu/types.d(428,18): Error: identifier or `new` expected following `.`, not `version`
|
||||
assert(d.version == "2.0");
|
||||
^
|
||||
src/tofu/types.d(434,18): Error: identifier or `new` expected following `.`, not `version`
|
||||
assert(d.version == "2.0");
|
||||
^
|
||||
src/tofu/types.d(440,18): Error: identifier or `new` expected following `.`, not `version`
|
||||
assert(d.version == "2.0");
|
||||
^
|
||||
src/tofu/types.d(446,18): Error: identifier or `new` expected following `.`, not `version`
|
||||
assert(d.version == "2.0");
|
||||
^
|
||||
error limit (20) reached, use `-verrors=0` to show all
|
||||
Error /usr/bin/dmd failed with exit code 1.
|
||||
Warning
|
||||
Warning ## Warning for package tofu ##
|
||||
Warning
|
||||
Warning The following compiler flags have been specified in the package description
|
||||
Warning file. They are handled by DUB and direct use in packages is discouraged.
|
||||
Warning Alternatively, you can set the DFLAGS environment variable to pass custom flags
|
||||
Warning to the compiler, or use one of the suggestions below:
|
||||
Warning
|
||||
Warning warningsAsErrors: Use "buildRequirements" to control the warning level
|
||||
Warning
|
||||
Generating test runner configuration 'tofu-test-application' for 'application' (executable).
|
||||
Warning Excluding main source file src/main.d from test.
|
||||
Starting Performing "unittest" build using /usr/bin/dmd for x86_64.
|
||||
Up-to-date toml 1.0.0: target for configuration [library] is up to date.
|
||||
Building tofu ~main: building configuration [tofu-test-application]
|
||||
src/tofu/config.d(126,20): Error: function `fprintf` is not callable using argument types `(File, string, immutable(char)*, immutable(char)*, int)`
|
||||
fprintf(stderr, "Warning: invalid %s '%s', using default %d\n",
|
||||
^
|
||||
src/tofu/config.d(126,20): cannot pass argument `makeGlobal()` of type `File` to parameter `shared(_IO_FILE)* stream`
|
||||
/usr/include/dlang/dmd/core/stdc/stdio.d(1347,13): `core.stdc.stdio.fprintf(shared(_IO_FILE)* stream, scope const(char*) format, scope const ...)` declared here
|
||||
int fprintf(FILE* stream, scope const char* format, scope const ...);
|
||||
^
|
||||
src/tofu/config.d(182,32): Error: `@safe` function `tofu.config.load` cannot call `@system` function `toml.toml.parseTOML`
|
||||
doc = parseTOML(content);
|
||||
^
|
||||
../../.dub/packages/toml/1.0.0/toml/src/toml/toml.d(431,14): `toml.toml.parseTOML` is declared here
|
||||
TOMLDocument parseTOML(string data, TOMLOptions options=TOMLOptions.none) {
|
||||
^
|
||||
src/tofu/config.d(187,28): Error: function `fprintf` is not callable using argument types `(File, string, immutable(char)*, immutable(char)*)`
|
||||
fprintf(stderr, "Warning: malformed TOML config at %s: %s\n",
|
||||
^
|
||||
src/tofu/config.d(187,28): cannot pass argument `makeGlobal()` of type `File` to parameter `shared(_IO_FILE)* stream`
|
||||
/usr/include/dlang/dmd/core/stdc/stdio.d(1347,13): `core.stdc.stdio.fprintf(shared(_IO_FILE)* stream, scope const(char*) format, scope const ...)` declared here
|
||||
int fprintf(FILE* stream, scope const char* format, scope const ...);
|
||||
^
|
||||
src/tofu/config.d(194,28): Error: function `fprintf` is not callable using argument types `(File, string, immutable(char)*)`
|
||||
fprintf(stderr, "Warning: unreadable TOML config at %s, "
|
||||
^
|
||||
src/tofu/config.d(194,28): cannot pass argument `makeGlobal()` of type `File` to parameter `shared(_IO_FILE)* stream`
|
||||
/usr/include/dlang/dmd/core/stdc/stdio.d(1347,13): `core.stdc.stdio.fprintf(shared(_IO_FILE)* stream, scope const(char*) format, scope const ...)` declared here
|
||||
int fprintf(FILE* stream, scope const char* format, scope const ...);
|
||||
^
|
||||
Error /usr/bin/dmd failed with exit code 1.
|
||||
Warning
|
||||
Warning ## Warning for package tofu ##
|
||||
Warning
|
||||
Warning The following compiler flags have been specified in the package description
|
||||
Warning file. They are handled by DUB and direct use in packages is discouraged.
|
||||
Warning Alternatively, you can set the DFLAGS environment variable to pass custom flags
|
||||
Warning to the compiler, or use one of the suggestions below:
|
||||
Warning
|
||||
Warning warningsAsErrors: Use "buildRequirements" to control the warning level
|
||||
Warning
|
||||
Generating test runner configuration 'tofu-test-application' for 'application' (executable).
|
||||
Warning Excluding main source file src/main.d from test.
|
||||
Starting Performing "unittest" build using /usr/bin/dmd for x86_64.
|
||||
Up-to-date toml 1.0.0: target for configuration [library] is up to date.
|
||||
Building tofu ~main: building configuration [tofu-test-application]
|
||||
Linking tofu-test-application
|
||||
Finished To force a rebuild of up-to-date targets, run again with --force
|
||||
Running tofu-test-application
|
||||
Warning: malformed TOML config at /tmp/tofu-test-config-bad-173904.toml: Invalid table key declaration (2:0)
|
||||
Warning: invalid TOFU_DEFAULT_JOBS 'not-a-number', using default 1
|
||||
3 modules passed unittests
|
||||
Warning
|
||||
Warning ## Warning for package tofu ##
|
||||
Warning
|
||||
Warning The following compiler flags have been specified in the package description
|
||||
Warning file. They are handled by DUB and direct use in packages is discouraged.
|
||||
Warning Alternatively, you can set the DFLAGS environment variable to pass custom flags
|
||||
Warning to the compiler, or use one of the suggestions below:
|
||||
Warning
|
||||
Warning warningsAsErrors: Use "buildRequirements" to control the warning level
|
||||
Warning
|
||||
Starting Performing "debug" build using /usr/bin/dmd for x86_64.
|
||||
Up-to-date toml 1.0.0: target for configuration [library] is up to date.
|
||||
Up-to-date tofu ~main: target for configuration [application] is up to date.
|
||||
Finished To force a rebuild of up-to-date targets, run again with --force
|
||||
@@ -0,0 +1,24 @@
|
||||
# Decisions — tofu-core
|
||||
|
||||
Architectural choices and rationales discovered during work on this plan.
|
||||
|
||||
_Auto-scaffolded by /start-work. Append new entries below - never overwrite._
|
||||
|
||||
---
|
||||
|
||||
### Task 4: `version` → `ver` rename
|
||||
`version` is a reserved keyword in D (conditional compilation). All struct fields bearing this name are renamed to `ver` — PackageIndex.ver, Recipe.ver, DepConstraint.ver, CacheManifest.ver, BinaryCheckResult.ver. This keeps the API readable while avoiding the keyword conflict. Downstream modules referencing these structs must use `.ver` for the version field.
|
||||
|
||||
### Task 4: DepConstraint uses typed `DepOp` enum, not string op
|
||||
The Lua reference stores `op` as a string (`nil`, `">="`, `"=="`, etc.). In D we use a typed `DepOp` enum (`ge, le, eq, ne, gt, lt, none`) for type safety and exhaustive switching. The `parse` factory handles string-to-enum conversion at parse time.
|
||||
|
||||
### Task 4: BuildPlan is a plain container — no sorting
|
||||
The plan specifies that `order()` returns entries in deps-first order *guaranteed by the caller*. The struct itself is just a container with `add()` and `order()` — no topological sort or dependency resolution. Sorting logic belongs in a later module (`tofu.resolver` or similar).
|
||||
|
||||
### Task 4: Single exception type `TypesException`
|
||||
One exception class for the entire types module — no separate subclasses per error category. The parse failures (bad dep spec, invalid pool) all throw `TypesException` with a descriptive message. Callers catch `TypesException` for all type-parsing errors.
|
||||
|
||||
### Task 4: Manual char scanning for dep spec parsing
|
||||
Rather than pulling in `std.regex`, the parser uses hand-written `isNameChar()` and `isWhite()` helpers with a simple position cursor. This keeps the module dependency-light (only `std.ascii`). The logic is a direct 1:1 port of the Lua reference patterns: `[A-Za-z0-9_.+-]` for names/versions, whitespace `[ \t]` for separators.
|
||||
|
||||
---
|
||||
@@ -0,0 +1,541 @@
|
||||
/// tofu.types — Core data structures shared across all tofu modules.
|
||||
///
|
||||
/// Pure data types with no I/O. All structs are @safe, all strings
|
||||
/// default to `""` (never null).
|
||||
module tofu.types;
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// Exception
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
/// Single exception type for the types module.
|
||||
class TypesException : Exception
|
||||
{
|
||||
@safe this(string msg)
|
||||
{
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// Pool — package source pool
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
enum Pool
|
||||
{
|
||||
binary,
|
||||
recipes,
|
||||
both,
|
||||
}
|
||||
|
||||
/// Parse a pool string value.
|
||||
@safe Pool fromPoolString(string s)
|
||||
{
|
||||
switch (s)
|
||||
{
|
||||
case "binary":
|
||||
return Pool.binary;
|
||||
case "recipes":
|
||||
return Pool.recipes;
|
||||
case "both":
|
||||
return Pool.both;
|
||||
default:
|
||||
throw new TypesException("invalid pool value");
|
||||
}
|
||||
}
|
||||
|
||||
/// Return canonical string representation of a Pool.
|
||||
@safe string poolToString(Pool p)
|
||||
{
|
||||
final switch (p)
|
||||
{
|
||||
case Pool.binary:
|
||||
return "binary";
|
||||
case Pool.recipes:
|
||||
return "recipes";
|
||||
case Pool.both:
|
||||
return "both";
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// BuildSystem — known build systems
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
enum BuildSystem
|
||||
{
|
||||
autotools,
|
||||
cmake,
|
||||
meson,
|
||||
make,
|
||||
cargo,
|
||||
custom,
|
||||
unknown,
|
||||
}
|
||||
|
||||
/// Map a build-system string to the enum. Unknown strings
|
||||
/// map to `BuildSystem.unknown` — callers decide how to handle it.
|
||||
@safe BuildSystem buildSystemFromString(string s)
|
||||
{
|
||||
switch (s)
|
||||
{
|
||||
case "autotools":
|
||||
return BuildSystem.autotools;
|
||||
case "cmake":
|
||||
return BuildSystem.cmake;
|
||||
case "meson":
|
||||
return BuildSystem.meson;
|
||||
case "make":
|
||||
return BuildSystem.make;
|
||||
case "cargo":
|
||||
return BuildSystem.cargo;
|
||||
case "custom":
|
||||
return BuildSystem.custom;
|
||||
default:
|
||||
return BuildSystem.unknown;
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// DepOp — dependency-constraint operators
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
enum DepOp
|
||||
{
|
||||
ge, // >=
|
||||
le, // <=
|
||||
eq, // == (and = normalised)
|
||||
ne, // ~=
|
||||
gt, // >
|
||||
lt, // <
|
||||
none, // unconstrained
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// Source — where a package comes from in a build-plan entry
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
enum Source
|
||||
{
|
||||
recipe,
|
||||
binary,
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// Character helpers for dep-spec scanning
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
private @safe bool isNameChar(char c)
|
||||
{
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|
||||
|| (c >= '0' && c <= '9') || c == '_' || c == '.'
|
||||
|| c == '+' || c == '-';
|
||||
}
|
||||
|
||||
private @safe bool isWhite(char c)
|
||||
{
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// PackageIndex — index.lua entry
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
struct PackageIndex
|
||||
{
|
||||
string name = "";
|
||||
string ver = "";
|
||||
string summary = "";
|
||||
Pool pool = Pool.both;
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// Recipe — .recipe file fields
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
struct Recipe
|
||||
{
|
||||
string name = "";
|
||||
string ver = "";
|
||||
string summary = "";
|
||||
string url = "";
|
||||
string sha256 = "";
|
||||
string[] deps; // raw dep specs e.g. ["libfoo", "libbar>=2.0"]
|
||||
BuildSystem buildSystem = BuildSystem.unknown;
|
||||
string[] configureArgs; // extra configure arguments
|
||||
string buildScript = ""; // required for custom build system
|
||||
string testCmd = ""; // e.g. "test -f ${DESTDIR}/usr/bin/pkg"
|
||||
string[] files; // committed file paths
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// DepConstraint — parsed dependency spec
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
/// A single parsed dependency constraint.
|
||||
///
|
||||
/// Examples:
|
||||
/// parse("libfoo") → name="libfoo", op=none, ver=""
|
||||
/// parse("libfoo>=2.0") → name="libfoo", op=ge, ver="2.0"
|
||||
/// parse("pcre2~=10.42") → name="pcre2", op=ne, ver="10.42"
|
||||
struct DepConstraint
|
||||
{
|
||||
string name = "";
|
||||
DepOp op = DepOp.none;
|
||||
string ver = "";
|
||||
|
||||
/// Parse a single dependency specification string.
|
||||
///
|
||||
/// Semantics ported from `vercmp.parse_dep` in ZETA/lib/vercmp.lua.
|
||||
/// - name : `[A-Za-z0-9_.+-]+`
|
||||
/// - ops : `>=` `<=` `==` `~=` `>` `<` `=` (2-char checked first)
|
||||
/// - `=` normalises to `==`
|
||||
/// - trailing garbage after version → `TypesException`
|
||||
/// - empty string / no name → `TypesException`
|
||||
@safe static DepConstraint parse(string spec)
|
||||
{
|
||||
size_t pos = 0;
|
||||
|
||||
// ── skip leading whitespace ──
|
||||
while (pos < spec.length && isWhite(spec[pos]))
|
||||
pos++;
|
||||
|
||||
// ── extract name ──
|
||||
size_t nameStart = pos;
|
||||
while (pos < spec.length && isNameChar(spec[pos]))
|
||||
pos++;
|
||||
|
||||
if (pos == nameStart)
|
||||
throw new TypesException("bad dependency \"" ~ spec ~ "\"");
|
||||
|
||||
DepConstraint result;
|
||||
result.name = spec[nameStart .. pos];
|
||||
|
||||
// ── skip whitespace after name ──
|
||||
while (pos < spec.length && isWhite(spec[pos]))
|
||||
pos++;
|
||||
|
||||
// ── unconstrained: no operator present ──
|
||||
if (pos >= spec.length)
|
||||
return result; // op=none, ver=""
|
||||
|
||||
// ── try matching an operator (2-char first, then 1-char) ──
|
||||
string opStr;
|
||||
|
||||
if (pos + 1 < spec.length)
|
||||
{
|
||||
string two = spec[pos .. pos + 2];
|
||||
if (two == ">=") { result.op = DepOp.ge; opStr = ">="; }
|
||||
else if (two == "<=") { result.op = DepOp.le; opStr = "<="; }
|
||||
else if (two == "==") { result.op = DepOp.eq; opStr = "=="; }
|
||||
else if (two == "~=") { result.op = DepOp.ne; opStr = "~="; }
|
||||
}
|
||||
|
||||
if (opStr.length == 0)
|
||||
{
|
||||
char c = spec[pos];
|
||||
if (c == '>') { result.op = DepOp.gt; opStr = ">"; }
|
||||
else if (c == '<') { result.op = DepOp.lt; opStr = "<"; }
|
||||
else if (c == '=') { result.op = DepOp.eq; opStr = "="; }
|
||||
}
|
||||
|
||||
if (opStr.length == 0)
|
||||
{
|
||||
throw new TypesException(
|
||||
"bad dependency constraint \"" ~ spec
|
||||
~ "\" (expected NAME OP VERSION)");
|
||||
}
|
||||
|
||||
pos += opStr.length;
|
||||
|
||||
// ── skip whitespace after operator ──
|
||||
while (pos < spec.length && isWhite(spec[pos]))
|
||||
pos++;
|
||||
|
||||
// ── extract version ──
|
||||
size_t verStart = pos;
|
||||
while (pos < spec.length && isNameChar(spec[pos]))
|
||||
pos++;
|
||||
size_t verEnd = pos;
|
||||
|
||||
if (verStart == verEnd)
|
||||
{
|
||||
throw new TypesException(
|
||||
"bad dependency constraint \"" ~ spec
|
||||
~ "\" (expected NAME OP VERSION)");
|
||||
}
|
||||
|
||||
result.ver = spec[verStart .. verEnd];
|
||||
|
||||
// ── check for trailing garbage ──
|
||||
while (pos < spec.length && isWhite(spec[pos]))
|
||||
pos++;
|
||||
if (pos < spec.length)
|
||||
{
|
||||
throw new TypesException(
|
||||
"bad dependency constraint \"" ~ spec
|
||||
~ "\" (expected NAME OP VERSION)");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// BuildPlanEntry / BuildPlan
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
struct BuildPlanEntry
|
||||
{
|
||||
string name = "";
|
||||
string recipePath = "";
|
||||
Source source = Source.recipe;
|
||||
}
|
||||
|
||||
/// Ordered set of packages to build. Sorting/deps-first ordering
|
||||
/// is guaranteed by the caller; this is a plain container.
|
||||
struct BuildPlan
|
||||
{
|
||||
BuildPlanEntry[] entries;
|
||||
|
||||
@safe void add(string name, string recipePath, Source source)
|
||||
{
|
||||
entries ~= BuildPlanEntry(name, recipePath, source);
|
||||
}
|
||||
|
||||
/// Return entries in the order they were added (caller
|
||||
/// must arrange deps-first ordering before calling this).
|
||||
@safe BuildPlanEntry[] order()
|
||||
{
|
||||
return entries;
|
||||
}
|
||||
|
||||
@safe bool isEmpty()
|
||||
{
|
||||
return entries.length == 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// CacheManifest — cached-package metadata
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
struct CacheManifest
|
||||
{
|
||||
string name = "";
|
||||
string ver = "";
|
||||
long fetchedAt = 0; // unix timestamp
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// BuildFailure — single build failure record
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
struct BuildFailure
|
||||
{
|
||||
string name = "";
|
||||
string reason = "";
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// BuildResult — build outcome tracker
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
struct BuildResult
|
||||
{
|
||||
string[] succeeded;
|
||||
BuildFailure[] failed;
|
||||
|
||||
/// Return just the names of the packages that failed.
|
||||
@safe string[] failedNames()
|
||||
{
|
||||
string[] names;
|
||||
foreach (f; failed)
|
||||
names ~= f.name;
|
||||
return names;
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// BinaryCheckResult — pre-built binary availability
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
struct BinaryCheckResult
|
||||
{
|
||||
bool exists = false;
|
||||
string ver = "";
|
||||
bool satisfies = false;
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// Unittests
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
// ── Pool parsing ──────────────────────────────────────
|
||||
assert(fromPoolString("binary") == Pool.binary);
|
||||
assert(fromPoolString("recipes") == Pool.recipes);
|
||||
assert(fromPoolString("both") == Pool.both);
|
||||
|
||||
assert(poolToString(Pool.binary) == "binary");
|
||||
assert(poolToString(Pool.recipes) == "recipes");
|
||||
assert(poolToString(Pool.both) == "both");
|
||||
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
fromPoolString("nope");
|
||||
assert(false, "expected exception");
|
||||
}
|
||||
catch (TypesException e)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
assert(caught, "invalid pool should throw");
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
// ── DepConstraint: unconstrained ─────────────────────
|
||||
{
|
||||
auto d = DepConstraint.parse("libfoo");
|
||||
assert(d.name == "libfoo");
|
||||
assert(d.op == DepOp.none);
|
||||
assert(d.ver == "");
|
||||
}
|
||||
{
|
||||
auto d = DepConstraint.parse("pcre2");
|
||||
assert(d.name == "pcre2");
|
||||
assert(d.op == DepOp.none);
|
||||
assert(d.ver == "");
|
||||
}
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
// ── DepConstraint: all operators ─────────────────────
|
||||
{
|
||||
auto d = DepConstraint.parse("libfoo>=2.0");
|
||||
assert(d.name == "libfoo");
|
||||
assert(d.op == DepOp.ge);
|
||||
assert(d.ver == "2.0");
|
||||
}
|
||||
{
|
||||
auto d = DepConstraint.parse("libfoo<=2.0");
|
||||
assert(d.name == "libfoo");
|
||||
assert(d.op == DepOp.le);
|
||||
assert(d.ver == "2.0");
|
||||
}
|
||||
{
|
||||
auto d = DepConstraint.parse("libfoo==2.0");
|
||||
assert(d.name == "libfoo");
|
||||
assert(d.op == DepOp.eq);
|
||||
assert(d.ver == "2.0");
|
||||
}
|
||||
{
|
||||
auto d = DepConstraint.parse("libfoo~=2.0");
|
||||
assert(d.name == "libfoo");
|
||||
assert(d.op == DepOp.ne);
|
||||
assert(d.ver == "2.0");
|
||||
}
|
||||
{
|
||||
auto d = DepConstraint.parse("libfoo>2.0");
|
||||
assert(d.name == "libfoo");
|
||||
assert(d.op == DepOp.gt);
|
||||
assert(d.ver == "2.0");
|
||||
}
|
||||
{
|
||||
auto d = DepConstraint.parse("libfoo<2.0");
|
||||
assert(d.name == "libfoo");
|
||||
assert(d.op == DepOp.lt);
|
||||
assert(d.ver == "2.0");
|
||||
}
|
||||
{
|
||||
// single = normalises to ==
|
||||
auto d = DepConstraint.parse("libfoo=2.0");
|
||||
assert(d.name == "libfoo");
|
||||
assert(d.op == DepOp.eq);
|
||||
assert(d.ver == "2.0");
|
||||
}
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
// ── DepConstraint: edge cases ────────────────────────
|
||||
// empty string → throw
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
DepConstraint.parse("");
|
||||
assert(false, "expected exception");
|
||||
}
|
||||
catch (TypesException)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
assert(caught, "empty string should throw");
|
||||
|
||||
// whitespace-only → throw
|
||||
caught = false;
|
||||
try
|
||||
{
|
||||
DepConstraint.parse(" ");
|
||||
assert(false, "expected exception");
|
||||
}
|
||||
catch (TypesException)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
assert(caught, "whitespace-only should throw");
|
||||
|
||||
// trailing garbage
|
||||
caught = false;
|
||||
try
|
||||
{
|
||||
DepConstraint.parse("libfoo>=2.0 extra");
|
||||
assert(false, "expected exception");
|
||||
}
|
||||
catch (TypesException)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
assert(caught, "trailing garbage should throw");
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
// ── BuildPlan ────────────────────────────────────────
|
||||
BuildPlan bp;
|
||||
assert(bp.isEmpty());
|
||||
|
||||
bp.add("mypkg", "path/to/mypkg.recipe", Source.recipe);
|
||||
assert(!bp.isEmpty());
|
||||
|
||||
bp.add("libbar", "path/to/libbar.recipe", Source.recipe);
|
||||
|
||||
auto ordered = bp.order();
|
||||
assert(ordered.length == 2);
|
||||
assert(ordered[0].name == "mypkg");
|
||||
assert(ordered[0].recipePath == "path/to/mypkg.recipe");
|
||||
assert(ordered[0].source == Source.recipe);
|
||||
assert(ordered[1].name == "libbar");
|
||||
assert(ordered[1].recipePath == "path/to/libbar.recipe");
|
||||
assert(ordered[1].source == Source.recipe);
|
||||
}
|
||||
|
||||
@safe unittest
|
||||
{
|
||||
// ── BuildResult.failedNames ──────────────────────────
|
||||
BuildResult br;
|
||||
assert(br.failedNames().length == 0);
|
||||
|
||||
br.failed ~= BuildFailure("foo", "compile error");
|
||||
br.failed ~= BuildFailure("bar", "link error");
|
||||
br.succeeded ~= "baz";
|
||||
|
||||
auto names = br.failedNames();
|
||||
assert(names.length == 2);
|
||||
assert(names[0] == "foo");
|
||||
assert(names[1] == "bar");
|
||||
}
|
||||
Reference in New Issue
Block a user