diff --git a/build.zig b/build.zig index fcebdd8..d7aa3fe 100644 --- a/build.zig +++ b/build.zig @@ -125,6 +125,7 @@ pub fn build(b: *Build) !void { run.addFileArg(winmd_text); run.addArg(metadata_version); run.addFileArg(b.path("ComOverloads.txt")); + run.addFileArg(b.path("everything-conflicts.txt")); const out_dir = run.addOutputDirectoryArg("."); gen_step.dependOn(&run.step); break :blk out_dir; diff --git a/everything-conflicts.txt b/everything-conflicts.txt new file mode 100644 index 0000000..3184afc --- /dev/null +++ b/everything-conflicts.txt @@ -0,0 +1,46 @@ +# Resolves everything.zig symbol collisions: one block per colliding symbol. +# +# api -> keeps the plain name +# api suffix -> exposed as +RESTRICTIONS + api UI.Shell + api NetworkManagement.NetworkPolicyServer suffix _Nps +ObjectContext + api System.ComponentServices + api System.Diagnostics.Debug suffix _Debug +Process + api System.ComponentServices + api inkobjcore suffix _InkObjCore +IResourceManager + api Media.DirectShow + api System.DistributedTransactionCoordinator suffix _Dtc +IID_IResourceManager + api Media.DirectShow + api System.DistributedTransactionCoordinator suffix _Dtc +IComponent + api Media.DirectShow + api System.Mmc suffix _Mmc +IID_IComponent + api Media.DirectShow + api System.Mmc suffix _Mmc +IImageList + api UI.Controls + api System.Mmc suffix _Mmc +IID_IImageList + api UI.Controls + api System.Mmc suffix _Mmc +IRangeException + api System.WindowsSync + api Web.MsHtml suffix _MsHtml +IID_IRangeException + api System.WindowsSync + api Web.MsHtml suffix _MsHtml +IDENTITY_TYPE + api Security.Authentication.Identity.Provider + api NetworkManagement.NetworkPolicyServer suffix _Nps +POLICY_ELEMENT + api Security.Cryptography + api NetworkManagement.QoS suffix _QoS +GetDeviceID + api dsound + api tbs suffix _Tbs diff --git a/src/genzig.zig b/src/genzig.zig index 732d531..c57c682 100644 --- a/src/genzig.zig +++ b/src/genzig.zig @@ -49,6 +49,7 @@ const Gen = struct { com_overload_map: *const StringPool.HashMapUnmanaged(ComTypeMap), pass1: *const pass1data.Root, type_index: *const TypeIndex, + everything_conflicts: *const EverythingConflicts, }; const autogen_header = "//! NOTE: this file is autogenerated, DO NOT MODIFY\n"; @@ -235,15 +236,16 @@ pub fn main() !u8 { // don't care about freeing args const cmd_args = all_args[1..]; - if (cmd_args.len != 5) { - std.log.err("expected 5 cmdline arguments but got {}", .{cmd_args.len}); + if (cmd_args.len != 6) { + std.log.err("expected 6 cmdline arguments but got {}", .{cmd_args.len}); return 1; } const extra_filename = cmd_args[0]; const text_path = cmd_args[1]; const version_string = cmd_args[2]; const com_overloads_filename = cmd_args[3]; - const zigwin32_out_path = stripDotDir(cmd_args[4]); + const everything_conflicts_filename = cmd_args[4]; + const zigwin32_out_path = stripDotDir(cmd_args[5]); const version = std.SemanticVersion.parse(version_string) catch fatal( "invalid version '{s}'", @@ -312,6 +314,7 @@ pub fn main() !u8 { // no need to free var com_overload_map: StringPool.HashMapUnmanaged(ComTypeMap) = .{}; try readComOverloads(api_set, &com_overload_map, com_overloads_filename); + const everything_conflicts = try readEverythingConflicts(everything_conflicts_filename); try cleanDir(std.fs.cwd(), zigwin32_out_path); var out_dir = try std.fs.cwd().openDir(zigwin32_out_path, .{}); @@ -344,6 +347,7 @@ pub fn main() !u8 { .com_overload_map = &com_overload_map, .pass1 = &pass1_root, .type_index = &type_index, + .everything_conflicts = &everything_conflicts, .mut = &mut, }; @@ -508,26 +512,97 @@ fn gatherSdkFiles(sdk_files: *std.array_list.Managed(*SdkFile), module: *Module) } } -const Export = struct { - kind: zigexports.Kind, - file: union(enum) { - sdk_file: *SdkFile, - zig: void, - }, +const ConflictEntry = struct { api: []const u8, suffix: []const u8 }; +const EverythingConflicts = struct { + entries: []const ConflictEntry, + by_symbol: StringPool.HashMapUnmanaged(Range), + const Range = struct { start: usize, len: usize }; + fn get(self: *const EverythingConflicts, symbol: StringPool.Val) ?[]const ConflictEntry { + const r = self.by_symbol.get(symbol) orelse return null; + return self.entries[r.start..][0..r.len]; + } +}; - pub fn fileAsSdk(self: Export) ?*SdkFile { - return switch (self.file) { - .sdk_file => |f| f, - .zig => null, - }; +fn readEverythingConflicts(filename: []const u8) !EverythingConflicts { + var file = try std.fs.cwd().openFile(filename, .{}); + defer file.close(); + const content = try file.readToEndAlloc(global.arena, std.math.maxInt(usize)); + // don't free, api/suffix slices point into content + + var entries: std.ArrayListUnmanaged(ConflictEntry) = .{}; + var by_symbol: StringPool.HashMapUnmanaged(EverythingConflicts.Range) = .{}; + var current: ?*EverythingConflicts.Range = null; + var lines = std.mem.splitScalar(u8, content, '\n'); + var line_number: u32 = 1; + while (lines.next()) |raw| : (line_number += 1) { + const line = std.mem.trimRight(u8, raw, "\r"); + if (line.len == 0 or line[0] == '#') continue; + if (line[0] == ' ') { + const range = current orelse fatal("{s} line {}: entry before any symbol", .{ filename, line_number }); + var it = std.mem.tokenizeScalar(u8, line, ' '); + const api_kw = it.next().?; + if (!std.mem.eql(u8, api_kw, "api")) fatal("{s} line {}: expected 'api', got '{s}'", .{ filename, line_number, api_kw }); + const api = it.next() orelse fatal("{s} line {}: missing api value", .{ filename, line_number }); + var suffix: []const u8 = ""; + if (it.next()) |suffix_kw| { + if (!std.mem.eql(u8, suffix_kw, "suffix")) fatal("{s} line {}: expected 'suffix', got '{s}'", .{ filename, line_number, suffix_kw }); + suffix = it.next() orelse fatal("{s} line {}: missing suffix value", .{ filename, line_number }); + if (it.next()) |f| fatal("{s} line {}: extra field '{s}'", .{ filename, line_number, f }); + } + try entries.append(global.arena, .{ .api = api, .suffix = suffix }); + range.len += 1; + } else { + var it = std.mem.tokenizeScalar(u8, line, ' '); + const symbol = it.next().?; + if (it.next()) |f| fatal("{s} line {}: symbol line has extra field '{s}'", .{ filename, line_number, f }); + const gop = try by_symbol.getOrPut(global.arena, try global.symbol_pool.add(symbol)); + if (gop.found_existing) fatal("{s} line {}: duplicate symbol '{s}'", .{ filename, line_number, symbol }); + gop.value_ptr.* = .{ .start = entries.items.len, .len = 0 }; + current = gop.value_ptr; + } } - pub fn fileZigName(self: Export) []const u8 { - return switch (self.file) { - .sdk_file => |f| f.zig_name, - .zig => "zig", - }; + return .{ .entries = try entries.toOwnedSlice(global.arena), .by_symbol = by_symbol }; +} + +fn conflictAlias(name: StringPool.Val, suffix: []const u8) !StringPool.Val { + if (suffix.len == 0) return name; + var buf: [256]u8 = undefined; + return global.symbol_pool.add(std.fmt.bufPrint(&buf, "{s}{s}", .{ name.slice, suffix }) catch unreachable); +} + +fn everythingEmit( + writer: *std.Io.Writer, + conflicts: *const EverythingConflicts, + emitted: *StringPool.HashMap([]const u8), + api: []const u8, + zig_name: ?[]const u8, + name: StringPool.Val, +) !void { + const alias: StringPool.Val = if (conflicts.get(name)) |entries| blk: { + for (entries) |e| { + if (std.mem.eql(u8, e.api, api)) break :blk try conflictAlias(name, e.suffix); + } + std.debug.panic( + "everything.zig: symbol '{f}' from api '{s}' collides but that api is not listed in the conflict config", + .{ name, api }, + ); + } else name; + + const gop = try emitted.getOrPut(alias); + if (gop.found_existing) { + if (std.mem.eql(u8, gop.value_ptr.*, api)) return; + std.debug.panic( + "everything.zig: symbol '{f}' from api '{s}' collides; add it to the conflict config", + .{ name, api }, + ); } -}; + gop.value_ptr.* = api; + + if (zig_name) |zn| + try writer.print("pub const {s} = @import(\"../win32.zig\").{s}.{s};\n", .{ alias.slice, zn, name.slice }) + else + try writer.print("pub const {s} = zig.{s};\n", .{ alias.slice, name.slice }); +} fn generateEverythingModule(gen: *const Gen, out_win32_dir: std.fs.Dir) !void { var everything_file = try out_win32_dir.createFile("everything.zig", .{}); @@ -566,94 +641,43 @@ fn generateEverythingModule(gen: *const Gen, out_win32_dir: std.fs.Dir) !void { try gatherSdkFiles(&sdk_files, gen.root_module); - // TODO: workaround issue where constants/functions are defined more than once, not sure what the right solution - // is for all these, maybe some modules are not compatible with each other. This could just be the permanent - // solution as well, if there are conflicts, we could just say the user has to import the specific module they want. - // TODO: I think the right way to reslve conflicts in everything.zig is to have a priority order for the apis. - // If I just sort the API's in the right order, more common apis go first, then my current logic will work. - var exports = StringPool.HashMap(Export).init(global.arena); - defer exports.deinit(); + var emitted = StringPool.HashMap([]const u8).init(global.arena); + defer emitted.deinit(); - // populate the exports with type names first - // because types can be referenced within other files (unlike consts/functions) - for (sdk_files.items) |sdk_file| { - var type_export_it = sdk_file.type_exports.iterator(); - while (type_export_it.next()) |kv| { - const type_name = kv.key_ptr.*; - const result = try exports.getOrPut(type_name); - if (!result.found_existing) { - result.value_ptr.* = .{ .kind = .type, .file = .{ .sdk_file = sdk_file } }; - } - } + inline for (zigexports.declarations) |decl| { + if (comptime redirects.has(decl.name)) continue; + try everythingEmit(writer, gen.everything_conflicts, &emitted, "zig", null, try global.symbol_pool.add(decl.name)); } - try addZigExports(writer, &exports); - for (sdk_files.items) |sdk_file| { try writer.print("// {s} exports {} constants:\n", .{ sdk_file.zig_name, sdk_file.const_exports.items.len }); - for (sdk_file.const_exports.items) |constant| { - const result = try exports.getOrPut(constant); - if (result.found_existing) { - const existing = result.value_ptr; - try writer.print( - "// omitting constant '{s}.{f}' in favor of {t} '{s}.{1f}'\n", - .{ sdk_file.zig_name, constant, existing.kind, existing.fileZigName() }, - ); - } else { - result.value_ptr.* = .{ .kind = .constant, .file = .{ .sdk_file = sdk_file } }; - try writer.print("pub const {f} = @import(\"../win32.zig\").{s}.{0f};\n", .{ constant, sdk_file.zig_name }); - } - } + for (sdk_file.const_exports.items) |name| + try everythingEmit(writer, gen.everything_conflicts, &emitted, sdk_file.api_name.slice, sdk_file.zig_name, name); try writer.print("// {s} exports {} types:\n", .{ sdk_file.zig_name, sdk_file.type_exports.count() }); - var export_it = sdk_file.type_exports.iterator(); - while (export_it.next()) |kv| { - const type_name = kv.key_ptr.*; - // guaranteed to exist since we added all the types above - const existing = exports.get(type_name) orelse unreachable; - std.debug.assert(existing.kind == .type); - if (existing.fileAsSdk() != sdk_file) { - try writer.print( - "// omitting type '{s}.{f}' in favor of {t} '{s}.{1f}'\n", - .{ sdk_file.zig_name, type_name, existing.kind, existing.fileZigName() }, - ); - } else { - try writer.print("pub const {f} = @import(\"../win32.zig\").{s}.{0f};\n", .{ type_name, sdk_file.zig_name }); - } - } + var type_it = sdk_file.type_exports.iterator(); + while (type_it.next()) |kv| + try everythingEmit(writer, gen.everything_conflicts, &emitted, sdk_file.api_name.slice, sdk_file.zig_name, kv.key_ptr.*); try writer.print("// {s} exports {} functions:\n", .{ sdk_file.zig_name, sdk_file.func_exports.count() }); var func_it = sdk_file.func_exports.iterator(); - while (func_it.next()) |kv| { - const func = kv.key_ptr.*; - if (exports.get(func)) |existing| { - try writer.print( - "// omitting function '{s}.{f}' in favor of {t} '{s}.{1f}'\n", - .{ sdk_file.zig_name, func, existing.kind, existing.fileZigName() }, - ); - } else { - try writer.print("pub const {f} = @import(\"../win32.zig\").{s}.{0f};\n", .{ func, sdk_file.zig_name }); - try exports.put(func, .{ .kind = .function, .file = .{ .sdk_file = sdk_file } }); - } + while (func_it.next()) |kv| + try everythingEmit(writer, gen.everything_conflicts, &emitted, sdk_file.api_name.slice, sdk_file.zig_name, kv.key_ptr.*); + } + + var conflict_it = gen.everything_conflicts.by_symbol.iterator(); + while (conflict_it.next()) |entry| { + const symbol = entry.key_ptr.*; + const range = entry.value_ptr.*; + for (gen.everything_conflicts.entries[range.start..][0..range.len]) |e| { + if (emitted.get(try conflictAlias(symbol, e.suffix)) == null) std.debug.panic( + "everything.zig conflict config: api '{s}' never exported '{f}' (stale?)", + .{ e.api, symbol }, + ); } } try writer.flush(); } -fn addZigExports(writer: *std.Io.Writer, exports: *StringPool.HashMap(Export)) !void { - inline for (zigexports.declarations) |decl| { - // redirected types are emitted by the redirects loop above - if (comptime redirects.has(decl.name)) continue; - const name = try global.symbol_pool.add(decl.name); - const result = try exports.getOrPut(name); - if (result.found_existing) std.debug.panic( - "zig.zig {t} export '{f}' conflicts with {t} from {s}", - .{ decl.kind, name, result.value_ptr.kind, result.value_ptr.fileZigName() }, - ); - result.value_ptr.* = .{ .kind = decl.kind, .file = .zig }; - try writer.print("pub const {f} = zig.{0f};\n", .{name}); - } -} - fn moduleLessThan(context: void, lhs: *Module, rhs: *Module) bool { _ = context; return std.ascii.lessThanIgnoreCase(lhs.name.slice, rhs.name.slice);