zig
zig copied to clipboard
`std.Build.Step.ConfigHeader`: `#cmakedefine X @X@` causes false error when `X` isn't specified
Zig Version
0.14.0-dev.1002+71a27ebd8
Steps to Reproduce and Observed Behavior
In a fresh zig init project:
const std = @import("std");
pub fn build(b: *std.Build) void {
const config_header = b.addConfigHeader(.{
.style = .{ .cmake = b.path("src/config_header.h.cmake") },
}, .{});
b.getInstallStep().dependOn(&config_header.step);
}
With this src/config_header.h.cmake:
#cmakedefine X @X@
zig build causes this error:
error: (...)\src\config_header.h.cmake:1: unable to substitute variable: error: MissingValue
Expected Behavior
Lines using the pattern #cmakedefine X @X@ shouldn't cause an error when X isn't specified.
This is caused by ConfigHeader's cmake logic is first scanning for names within @ @. When a name is found without a corresponding value, the error is immediately raised, regardless of whether it is used on a #cmakedefine line that depends on its definition.
The following config_header.h.cmake builds without issue whether X is specified or not:
#cmakedefine X
Wouldn't the best and most foolproof behavior be to always require the identifier to be specified, regardless of if takes on the form #cmakedefine FOO or #define FOO @FOO@ or any combination of the two? When I need to configure config headers for my own projects, I usually want to make sure that I have complete coverage of all identifiers present in the input file (using .FOO_BAR = null to explicitly undef) and would prefer a build error if I've missed any.
Then the question is whether we want to mirror CMake's behavior, or enforce stricter rules and require values even for #cmakedefine lines.
Ran into this behavior while helping Mach with their Zig version nomination. For context, the behavior was added in https://github.com/ziglang/zig/pull/20234 and reported in https://github.com/ziglang/zig/issues/20230
Seems the options are:
- Following CMake's behavior
- Or enforcing stricter more explicit rules
I lean towards option 1, because assigning values for 100+ variable definitions is a tedious endeavor.
Or, perhaps the best of both worlds? Allow the developer to choose the behavior.
I would agree with option 1. Although I've just spent a little-bit-of-time to get a few hundred configuration options setup for a project, having to have them all set to null places a lot of noise in the way of the signal.
One other little issue (I can raise a separate issue if people think this is a problem) - when you pass in a string as a value in the values input, this gets silently changed to an .ident. I'm not sure that this is the correct behaviour, as the generated cmake configuration file surrounds the strings with "s.
Update: I've made the changes necessary for this in my local zig std library, and it now produces exactly the same configuration file as cmake does. (Quoted strings and simply ignored missing values).
Then the question is whether we want to mirror CMake's behavior, or enforce stricter rules and require values even for
#cmakedefinelines.
If we want to implement cmakedefine then we mirror cmake and not do our own thing because then we no longer implement cmakedefine but our own version of it.
I haven't looked at the other config header code but if they are more restrictive as well this would be a huge problem.