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
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
46 changes: 46 additions & 0 deletions everything-conflicts.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Resolves everything.zig symbol collisions: one block per colliding symbol.
# <SYMBOL>
# api <API> -> keeps the plain name
# api <API> suffix <SUFFIX> -> exposed as <SYMBOL><SUFFIX>
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
212 changes: 118 additions & 94 deletions src/genzig.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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}'",
Expand Down Expand Up @@ -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, .{});
Expand Down Expand Up @@ -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,
};

Expand Down Expand Up @@ -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", .{});
Expand Down Expand Up @@ -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);
Expand Down
Loading