-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
zig cc: support reading from stdin via "-x LANG -" #14462
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
Conversation
Edit: fixed. |
c872d08
to
9de2ef5
Compare
Have you considered the alternate implementation using I don't want to support the use case of stdin as a root source file in any case other than C/C++ compiler compatibility mode. |
I ruled it out for a reason I can no longer remember. This is how I understand it: if the input file is I will look at this again.
I agree. |
I started poking at this and remember better now. I did indeed consider the clang passthrough mode; we had this discussion before (around Oct 2022) and you advised me to slurp the file and pass it on to the remaining compilation chain as a regular file. :) Here was my argument: everything in
For a taste on how many things will need to change to adjust this, here is a start: diff --git a/src/Compilation.zig b/src/Compilation.zig
index 7d42d3b61..8b351270e 100644
--- a/src/Compilation.zig
+++ b/src/Compilation.zig
@@ -210,7 +210,10 @@ pub const LangToExt = std.ComptimeStringMap(FileExt, .{
/// For passing to a C compiler.
pub const CSourceFile = struct {
- src_path: []const u8,
+ src: union(enum) {
+ stdin,
+ path: []const u8,
+ },
extra_flags: []const []const u8 = &.{},
/// Same as extra_flags except they are not added to the Cache hash.
cache_exempt_flags: []const []const u8 = &.{}, There are many places in code that will change as a result of this. I can do it, but:
Let me know. |
I can reduce the surface area of this feature this way: allow only |
This is useful for tests that want to `execve` zig directly. The string is already null-terminated, so this will just expose it as such, removing an extra allocation from the test. Will be used in ziglang#14462
Rebased, rewrote the test for the "build system v2". Also, simplified the change to only account for |
This is useful for tests that want to `execve` zig directly. The string is already null-terminated, so this will just expose it as such, removing an extra allocation from the test. Will be used in #14462
eb6f34d
to
90e6657
Compare
This is useful for tests that want to `execve` zig directly. The string is already null-terminated, so this will just expose it as such, removing an extra allocation from the test. Will be used in ziglang#14462
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding this. I have some requested changes. Let me know if you want any help with them.
src/main.zig
Outdated
// if a c_source_file is "-", dump it to a tempdir and replace the src_name | ||
// with the newly created file. translate-c works fine with streams; other | ||
// actions don't. | ||
if (arg_mode != .translate_c) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do you get the idea that translate-c handles -
arguments? I don't see that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does handle /dev/stdin
well without the intermediate files.
The previous commits have support for any streams (so /dev/stdin
would work with anything), but later I decided to drop support for everything but -
. That simplifies things a lot.
This comment is a left-over from "those times".
test/tests.zig
Outdated
fn exec( | ||
allocator: std.mem.Allocator, | ||
cwd: []const u8, | ||
expect_code: u8, | ||
argv: []const []const u8, | ||
) !std.ChildProcess.ExecResult { | ||
const max_output_size = 100 * 1024; | ||
const result = std.ChildProcess.exec(.{ | ||
.allocator = allocator, | ||
.argv = argv, | ||
.cwd = cwd, | ||
.max_output_bytes = max_output_size, | ||
}) catch |err| { | ||
std.debug.print("The following command failed:\n", .{}); | ||
printCmd(cwd, argv); | ||
return err; | ||
}; | ||
switch (result.term) { | ||
.Exited => |code| { | ||
if (code != expect_code) { | ||
std.debug.print( | ||
"The following command exited with error code {}, expected {}:\n", | ||
.{ code, expect_code }, | ||
); | ||
printCmd(cwd, argv); | ||
std.debug.print("stderr:\n{s}\n", .{result.stderr}); | ||
return error.CommandFailed; | ||
} | ||
}, | ||
else => { | ||
std.debug.print("The following command terminated unexpectedly:\n", .{}); | ||
printCmd(cwd, argv); | ||
std.debug.print("stderr:\n{s}\n", .{result.stderr}); | ||
return error.CommandFailed; | ||
}, | ||
} | ||
return result; | ||
} | ||
|
||
fn printCmd(cwd: []const u8, argv: []const []const u8) void { | ||
std.debug.print("cd {s} && ", .{cwd}); | ||
for (argv) |arg| { | ||
std.debug.print("{s} ", .{arg}); | ||
} | ||
std.debug.print("\n", .{}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should not be added
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which section(s) are you referring to? It's not quite clear from the comment.
test/tests.zig
Outdated
if (builtin.os.tag != .windows) { | ||
const tmp_path = b.makeTempPath(); | ||
var dir = std.fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled"); | ||
dir.writeFile("truth.c", "int main() { return 42; }") catch @panic("unhandled"); | ||
var infile = dir.openFile("truth.c", .{}) catch @panic("unhandled"); | ||
|
||
const outfile = std.fs.path.joinZ( | ||
b.allocator, | ||
&[_][]const u8{ tmp_path, "truth" }, | ||
) catch @panic("unhandled"); | ||
const pid_result = std.os.fork() catch @panic("unhandled"); | ||
if (pid_result == 0) { // child | ||
std.os.dup2(infile.handle, std.os.STDIN_FILENO) catch @panic("unhandled"); | ||
const argv = &[_:null]?[*:0]const u8{ | ||
b.zig_exe, "cc", | ||
"-o", outfile, | ||
"-x", "c", | ||
"-", | ||
}; | ||
const envp = &[_:null]?[*:0]const u8{ | ||
std.fmt.allocPrintZ(b.allocator, "ZIG_GLOBAL_CACHE_DIR={s}", .{tmp_path}) catch @panic("unhandled"), | ||
}; | ||
const err = std.os.execveZ(b.zig_exe, argv, envp); | ||
std.debug.print("execve error: {any}\n", .{err}); | ||
std.os.exit(1); | ||
} | ||
|
||
const res = std.os.waitpid(pid_result, 0); | ||
assert(0 == res.status); | ||
|
||
// run the compiled executable and check if it's telling the truth. | ||
_ = exec(b.allocator, tmp_path, 42, &[_][]const u8{outfile}) catch @panic("unhandled"); | ||
|
||
const cleanup = b.addRemoveDirTree(tmp_path); | ||
step.dependOn(&cleanup.step); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test as is does the following things directly in the configure phase rather than using build steps like it must do in order to play nicely with the other build steps:
- making a temp dir and writing a file
- calling fork, execve, and wait
- calling exec
If you look at the Test Godbolt API code block above, it shows how to use the build system API for testing. It will be a combination of WriteFileStep, and RunStep.
I'm happy to provide assistance in writing this test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally did everything in the configure phase, because I couldn't find how to execute a command and change it's stdin file descriptor.
After rebasing this test breaks zig build
altogether, so I commented the full test.
I would really appreciate help here. Not asking to write the full code, rather a sequence of how Step
, RunStep
should be structured, keeping in mind the "need to pass this fd to stdin, which was acquired earlier by opening that temporary file".
Note to myself: handle this case: #15338 (comment) |
I have addressed all the review comments for the code, but not for the tests. Need some help there. |
echo 'some C program' | $CC -x c - Is a common pattern to test for compiler or linker features. This patch adds support for reading from non-regular files. This will make at least one more Go test to pass.
* no need to move `tmpFilePath` around * no need for calculating max length of `FileExt` tag name * provide a canonical file extension name for `FileExt` so that, e.g. the file will be named `stdin.S` instead of `stdin.assembly_with_cpp`. * move temp file cleanup to a function to reduce defer bloat in a large function. * fix bug caused by mixing relative and absolute paths in the cleanup logic. * remove commented out test and dead code
In one of the happy paths, execve() is used to switch to clang in which case any cleanup logic that exists for this temporary file will not run and this temp file will be leaked. Oh well. It's a minor punishment for using `-x c` which nobody should be doing. Therefore, we make no effort to clean up. Using `-` for stdin as a source file always leaks a temp file. Note that the standard `zig build-exe` CLI does not support stdin as an input file. This is only for `zig cc` C compiler compatibility.
Alright, I have made some executive decisions and finished this patch. The final diff ended up being quite small and tidy. I removed the dead code added to tests.zig; adding test coverage for this CLI usage can be a separate enhancement. Here is a clue should you wish to pursue this: you'll want to have a Run step with the I have tested this locally and force-pushed to your branch, with auto-merge enabled. |
// Note that in one of the happy paths, execve() is used to switch | ||
// to clang in which case any cleanup logic that exists for this | ||
// temporary file will not run and this temp file will be leaked. | ||
// Oh well. It's a minor punishment for using `-x c` which nobody | ||
// should be doing. Therefore, we make no effort to clean up. Using | ||
// `-` for stdin as a source file always leaks a temp file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could minimize the damage by:
- hashing the file contents (while writing to the file, so no additional allocations)
- renaming the filename to the hash.
That way, we avoid a small chance of hash collision (I know) and multiple invocations of echo | zig cc <...>
produce only one garbage-file.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, sounds reasonable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is a common pattern to test for compiler or linker features. This PR adds support for reading from non-regular files.
This will make at least one more Go test to pass.