Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions docs/design/recipe_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ The `recipe` node defines package metadata. All properties are specified as chil
| `url` | string | "" | Project homepage URL | `url "http://busybox.net"` |
| `licenses` | string(s) | [] | License identifiers (multiple allowed) | `licenses "GPL" "MIT"` |
| `archs` | string(s) | ["x86_64"] | Supported architectures | `archs "aarch64" "x86_64"` |
| `depends` | string(s) | [] | Build dependencies | `depends "make" "llvm"` |
| `depends` | string(s) | [] | Build dependencies; repeatable entries may use `arch` to select a target architecture | `depends "make" "llvm"` |
| `env` | properties | {} | Environment variables for all phases | `env CC="clang"` |

**Example:**
Expand All @@ -136,11 +136,29 @@ recipe {
licenses "BSD"
archs "x86_64" "aarch64"
depends "openssl-dev" "pcre2-dev" "zlib-ng-dev"
depends "nasm" arch="x86_64"
env CC="clang" CFLAGS="-O2"
}
```

### `vars` Node (Optional)
### Architecture-specific build dependencies

A `depends` entry without an `arch` property applies to every architecture supported by the recipe. Add `arch` when a build dependency is needed only for the package target architecture:

```kdl
recipe {
name "libjpeg-turbo"
version "3.1.0"
release 1
archs "x86_64" "aarch64"

depends "cmake" "ninja"
depends "nasm" arch="x86_64"
}
```

Here `cmake` and `ninja` are selected for both targets. `nasm` is selected only for the x86_64 package and is omitted for aarch64, where it is unavailable. `arch` always means the package target architecture, not the build host architecture.


Define custom variables for use in string interpolation.

Expand Down
9 changes: 8 additions & 1 deletion src/kdl_schema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,14 @@ const recipe_meta_children = [_]NodeSpec{
.{ .name = "url", .args = .{ .kind = .string, .min = 1, .max = 1, .label = "value" } },
.{ .name = "licenses", .args = .{ .kind = .string, .min = 1, .label = "value" } },
.{ .name = "archs", .args = .{ .kind = .string, .min = 1, .label = "value" } },
.{ .name = "depends", .args = .{ .kind = .string, .min = 1, .label = "value" } },
.{
.name = "depends",
.repeatable = true,
.args = .{ .kind = .string, .min = 1, .label = "value" },
.properties = .{ .fixed = &[_]PropertySpec{
.{ .name = "arch", .kind = .string },
} },
},
.{ .name = "needs-root", .args = .{ .kind = .boolean, .min = 1, .max = 1, .label = "value" } },
recipe_env_node,
};
Expand Down
72 changes: 63 additions & 9 deletions src/recipe.zig
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ pub fn parse(ctx: *mere.Context, recipe_buf: []const u8) !Recipe {
};
}

// Resolve dependencies after architecture selection so an optional
// depends arch="..." selector applies to the recipe target.
for (nodes.items) |*node| {
if (std.mem.eql(u8, node.name, "recipe")) {
try parseKdlDepends(allocator, node, &recipe);
}
}

// Parse remaining sections that can use interpolation.
var found_package = false;
for (nodes.items) |*node| {
Expand Down Expand Up @@ -221,22 +229,68 @@ fn parseKdlRecipeNode(allocator: std.mem.Allocator, node: *const kdl.Node, recip
}
}

// depends "cmake" "ninja"
if (node.findChild("depends")) |depends_node| {
for (depends_node.arguments.items) |arg| {
if (arg.getString()) |s| {
const dup = try allocator.dupe(u8, s);
try recipe.depends.append(allocator, dup);
}
}
}

// env CC="clang" CXX="clang++" (properties on an env child node)
if (node.findChild("env")) |env_node| {
try parseKdlEnvProperties(allocator, env_node, &recipe.env);
}
}

/// Parse dependencies whose optional arch selector matches the recipe target.
fn parseKdlDepends(allocator: std.mem.Allocator, node: *const kdl.Node, recipe: *Recipe) !void {
for (node.children.items) |*child| {
if (!std.mem.eql(u8, child.name, "depends")) continue;
const selector = if (child.getProperty("arch")) |property|
property.getString() orelse return RecipeError.NonStringValue
else
null;
if (!dependencyAppliesToArch(selector, recipe.arch)) continue;
for (child.arguments.items) |arg| {
if (arg.getString()) |dependency| {
try recipe.depends.append(allocator, try allocator.dupe(u8, dependency));
}
}
}
}

fn dependencyAppliesToArch(selector: ?[]const u8, recipe_arch: ?[]const u8) bool {
if (selector == null) return true;
return recipe_arch != null and std.mem.eql(u8, selector.?, recipe_arch.?);
}

test "parseKdlDepends selects target-specific dependencies" {
const th = @import("test_helpers.zig");
var test_env = try th.createTestEnv();
defer {
test_env.cleanup();
std.testing.allocator.destroy(test_env);
}

const ctx = &test_env.ctx;
var nodes = try kdl.parseDocument(
ctx.allocator,
"recipe {\n depends \"cmake\"\n depends \"nasm\" arch=\"x86_64\"\n}",
);
defer {
for (nodes.items) |*node| node.deinit();
nodes.deinit(ctx.allocator);
}

var x86_recipe = try Recipe.init(ctx.allocator, ctx);
defer x86_recipe.deinit();
x86_recipe.arch = "x86_64";
try parseKdlDepends(ctx.allocator, &nodes.items[0], &x86_recipe);
try std.testing.expectEqualStrings("cmake", x86_recipe.depends.items[0]);
try std.testing.expectEqualStrings("nasm", x86_recipe.depends.items[1]);

var arm_recipe = try Recipe.init(ctx.allocator, ctx);
defer arm_recipe.deinit();
arm_recipe.arch = "aarch64";
try parseKdlDepends(ctx.allocator, &nodes.items[0], &arm_recipe);
try std.testing.expectEqual(@as(usize, 1), arm_recipe.depends.items.len);
try std.testing.expectEqualStrings("cmake", arm_recipe.depends.items[0]);
}

/// Parse a vars {} node
fn parseKdlVarsNode(allocator: std.mem.Allocator, node: *const kdl.Node, vars: *std.ArrayList(KV)) !void {
// Each child node is a var: major "20"
Expand Down