-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Build system: Add support for custom exports #19859
Comments
I thought it was already possible to Why doesn't something like the following work in status-quo? What benefit does customExport bring?
//user
const mzb = @import("microzig-build");
const board_providers = mzb.default_providers; //could hold anything you need
const board_provider_config = ...; //could hold anything, even another @import
const result: mzb.Target = mzb.getTarget( //can freely return any type you need
"board/raspberrypi/pico",
board_providers,
board_provider_config,
).?;
//microzig-build
pub const Target = struct{
name: []const u8,
vendor: []const u8,
cpu: ...,
dependencies: []const ...,
};
const board_providers = ...;
pub fn getTarget(name: []const u8, board_providers: anytype, board_provider_config: anytype) ?Target {
return for (board_providers) |board_provider| {
if (board_provider.getTarget(name, board_provider_config)) |t| break t;
} else null;
}
//board_provider.zig
const mzb = @import("microzig-build");
pub fn getTarget(name: []const u8, board_provider_config: anytype) ?Target {
_ = board_provider_config; //could be anything, even the type of another user-side @import
if (@import("std").mem.eql(name, "board/raspberrypi/pico")) return .{
.name = "RaspberryPi Pico",
.vendor = "RaspberryPi",
.cpu = mzb.cpus.cortex_m0,
.dependencies = &.{
.{ .name = "libusb", .module = libusb_mod },
},
};
return null;
}
Can you elaborate, roughly, on what sort of "hackery" is currently happening? I'm also not clear on which part of this would require a runtime-type-id concept - all dependencies are present at build time and compiled together AFAIU. |
Except you cannot access the dependencies of that Assume you want to pass a module inside a custom command that imports from another dependency. You can't model that right now, because there's no clean way to obtain a handle to the dependency tree
How would you store a
We do pass a pointer via |
I don't see why the requirement of calling I assume that passing the user's
What do you mean by
I'm confused whether |
Some more elaboration: The follow use case isn't possible with the proposed solution of "just using Library Code
|
I've read the issue description and the discussion a few times over and I'm not sure I understand exactly what is being asked for, but I believe the functionality you want can already cleanly implemented in user space today using
// main/build.zig.zon
.{
.name = "main",
.version = "0.0.0",
.dependencies = .{
.microzig = .{
.path = "../microzig",
},
.raspberrypi = .{
.path = "../raspberrypi",
},
},
.paths = .{""},
} // main/build.zig
const std = @import("std");
const microzig = @import("microzig");
const raspberrypi = @import("raspberrypi");
pub fn build(b: *std.Build) void {
const rp_pico_target: microzig.Target = raspberrypi.getTarget(b, "pico").?;
std.debug.print("{s}\n", .{rp_pico_target.name});
const rp_400_target: microzig.Target = raspberrypi.getTarget(b, "400").?;
std.debug.print("{s}\n", .{rp_400_target.name});
b.installDirectory(.{
.source_dir = rp_pico_target.include_path.?,
.install_dir = .prefix,
.install_subdir = "",
});
} // microzig/build.zig.zon
.{
.name = "microzig",
.version = "0.0.0",
.paths = .{""},
} // microzig/build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
_ = b;
}
pub const Target = struct {
name: []const u8,
include_path: ?std.Build.LazyPath = null,
}; // raspberrypi/build.zig.zon
.{
.name = "raspberrypi",
.version = "0.0.0",
.dependencies = .{
.microzig = .{
.path = "../microzig",
},
.@"pico-sdk" = .{
.path = "../pico-sdk",
},
},
.paths = .{""},
} // raspberrypi/build.zig
const std = @import("std");
const microzig = @import("microzig");
pub fn build(b: *std.Build) void {
_ = b;
}
pub fn getTarget(b: *std.Build, name: []const u8) ?microzig.Target {
if (std.mem.eql(u8, name, "pico")) {
const this_dep = b.dependencyFromBuildZig(@This(), .{});
const pico_sdk_dep = this_dep.builder.dependency("pico-sdk", .{});
const include_path = pico_sdk_dep.path("foo/bar/include");
return .{ .name = "RaspberryPi Pico", .include_path = include_path };
}
if (std.mem.eql(u8, name, "400")) {
return .{ .name = "RaspberryPi 400" };
}
return null;
} Here, the Obviously it is a bit more involved if you also want to pass along targets and build options, but that's mainly a question of designing your exported APIs in a clever and intuitive way and not something the build system itself will restrict you from doing. |
That's indeed an interesting solution. I didn't know about I still think it's a hack and the passing of custom types is definitly a valid use case for the build system, as it would streamline and simplify a lot of stuff |
Here's a hacky way to do custom exports currently: const AnyPtr = struct {
id: [*]const u8,
val: *const anyopaque,
};
fn exposeArbitrary(b: *std.Build, name: []const u8, comptime ty: type, val: *const ty) void {
const valv = b.allocator.create(AnyPtr) catch @panic("oom");
valv.* = .{
.id = @typeName(ty),
.val = val,
};
const name_fmt = b.fmt("__exposearbitrary_{s}", .{name});
const mod = b.addModule(name_fmt, .{});
// HACKHACKHACK
mod.* = undefined;
mod.owner = @ptrCast(@alignCast(@constCast(valv)));
}
fn findArbitrary(dep: *std.Build.Dependency, comptime ty: type, name: []const u8) *const ty {
const name_fmt = dep.builder.fmt("__exposearbitrary_{s}", .{name});
const modv = dep.module(name_fmt);
// HACKHACKHACK
const anyptr: *const AnyPtr = @ptrCast(@alignCast(modv.owner));
std.debug.assert(anyptr.id == @typeName(ty));
return @ptrCast(@alignCast(anyptr.val));
} |
I think what it comes down to at the moment is wrapping functions that provide custom functionality to expose them and have an internal implementation function, since fn build(b: *std.Build) void {
const custom_type = CustomBuildType.initInternal( b, .{});
// do something with the CustombuildType
}
pub const CustomBuildType = struct{
pub const Options = struct{};
// This can be used from an @import() of this dependency
pub fn init(b: *std.Build, opt: Options, args: anytype) CustomBuildType {
return initInternal( b.dependencyFromBuildZig(@This(), args).builder, opt);
}
// b is "guaranteed" to be the current std.Build in private scope
fn initInternal(b: *std.Build, opt: Options) CustomBuildType {
_ = b;
_ = opt;
return .{};
}
}; |
Right now, the build system allows passing two types of information between dependency edges:
*std.Build.Step.Compile
*std.Build.Module
I propose that we should add support to pass arbitrary types between dependency edges, so more
advanced projects like Mach or MicroZig can improve the user experience by passing around types of their build automation.
A potential implementation could look like this:
Usage Example
In MicroZig, we have our own
Target
type that encodes relevant information for embedded targets including post processing steps, output format and so on.It would be nice to have board support packages just expose
MicroZig.Target
instead of doing the hackery we have right now.With this proposal, the user-facing build script could look like this:
While the board support package might look something like this:
Remarks
This feature most likely depends on the
@typeId
proposal: #19858The text was updated successfully, but these errors were encountered: