diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 0f28dac378..d278ebf092 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -415,7 +415,7 @@ pub fn evalZigProcess( .Exited => { // Note that the exit code may be 0 in this case due to the // compiler server protocol. - if (compile.expect_errors.len != 0 and s.result_error_bundle.errorMessageCount() > 0) { + if (compile.expect_errors != null and s.result_error_bundle.errorMessageCount() > 0) { return error.NeedCompileErrorCheck; } }, diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index fa5d66780e..9ede31c917 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -204,10 +204,10 @@ use_llvm: ?bool, use_lld: ?bool, /// This is an advanced setting that can change the intent of this Compile step. -/// If this slice has nonzero length, it means that this Compile step exists to +/// If this value is non-null, it means that this Compile step exists to /// check for compile errors and return *success* if they match, and failure /// otherwise. -expect_errors: []const []const u8 = &.{}, +expect_errors: ?ExpectedCompileErrors = null, emit_directory: ?*GeneratedFile, @@ -220,6 +220,11 @@ generated_llvm_bc: ?*GeneratedFile, generated_llvm_ir: ?*GeneratedFile, generated_h: ?*GeneratedFile, +pub const ExpectedCompileErrors = union(enum) { + contains: []const u8, + exact: []const []const u8, +}; + pub const CSourceFiles = struct { dependency: ?*std.Build.Dependency, /// If `dependency` is not null relative to it, @@ -2131,7 +2136,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const maybe_output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) { error.NeedCompileErrorCheck => { - assert(self.expect_errors.len != 0); + assert(self.expect_errors != null); try checkCompileErrors(self); return; }, @@ -2390,39 +2395,70 @@ fn checkCompileErrors(self: *Compile) !void { // Render the expected lines into a string that we can compare verbatim. var expected_generated = std.ArrayList(u8).init(arena); + const expect_errors = self.expect_errors.?; var actual_line_it = mem.splitScalar(u8, actual_stderr, '\n'); - for (self.expect_errors) |expect_line| { - const actual_line = actual_line_it.next() orelse { - try expected_generated.appendSlice(expect_line); - try expected_generated.append('\n'); - continue; - }; - if (mem.endsWith(u8, actual_line, expect_line)) { - try expected_generated.appendSlice(actual_line); - try expected_generated.append('\n'); - continue; - } - if (mem.startsWith(u8, expect_line, ":?:?: ")) { - if (mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) { - try expected_generated.appendSlice(actual_line); - try expected_generated.append('\n'); - continue; - } - } - try expected_generated.appendSlice(expect_line); - try expected_generated.append('\n'); - } - - if (mem.eql(u8, expected_generated.items, actual_stderr)) return; // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile - return self.step.fail( - \\ - \\========= expected: ===================== - \\{s} - \\========= but found: ==================== - \\{s} - \\========================================= - , .{ expected_generated.items, actual_stderr }); + switch (expect_errors) { + .contains => |expect_line| { + while (actual_line_it.next()) |actual_line| { + if (!matchCompileError(actual_line, expect_line)) continue; + return; + } + + return self.step.fail( + \\ + \\========= should contain: =============== + \\{s} + \\========= but not found: ================ + \\{s} + \\========================================= + , .{ expect_line, actual_stderr }); + }, + .exact => |expect_lines| { + for (expect_lines) |expect_line| { + const actual_line = actual_line_it.next() orelse { + try expected_generated.appendSlice(expect_line); + try expected_generated.append('\n'); + continue; + }; + if (matchCompileError(actual_line, expect_line)) { + try expected_generated.appendSlice(actual_line); + try expected_generated.append('\n'); + continue; + } + try expected_generated.appendSlice(expect_line); + try expected_generated.append('\n'); + } + + if (mem.eql(u8, expected_generated.items, actual_stderr)) return; + + return self.step.fail( + \\ + \\========= expected: ===================== + \\{s} + \\========= but found: ==================== + \\{s} + \\========================================= + , .{ expected_generated.items, actual_stderr }); + }, + } +} + +fn matchCompileError(actual: []const u8, expected: []const u8) bool { + if (mem.endsWith(u8, actual, expected)) return true; + if (mem.startsWith(u8, expected, ":?:?: ")) { + if (mem.endsWith(u8, actual, expected[":?:?: ".len..])) return true; + } + // We scan for /?/ in expected line and if there is a match, we match everything + // up to and after /?/. + const expected_trim = mem.trim(u8, expected, " "); + if (mem.indexOf(u8, expected_trim, "/?/")) |index| { + const actual_trim = mem.trim(u8, actual, " "); + const lhs = expected_trim[0..index]; + const rhs = expected_trim[index + "/?/".len ..]; + if (mem.startsWith(u8, actual_trim, lhs) and mem.endsWith(u8, actual_trim, rhs)) return true; + } + return false; } diff --git a/test/link/elf.zig b/test/link/elf.zig index d5a62db4cd..dad3604c31 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -76,6 +76,8 @@ pub fn build(b: *Build) void { elf_step.dependOn(testLargeBss(b, .{ .target = glibc_target })); elf_step.dependOn(testLinkOrder(b, .{ .target = glibc_target })); elf_step.dependOn(testLdScript(b, .{ .target = glibc_target })); + elf_step.dependOn(testLdScriptPathError(b, .{ .target = glibc_target })); + elf_step.dependOn(testMismatchedCpuArchitectureError(b, .{ .target = glibc_target })); // https://github.com/ziglang/zig/issues/17451 // elf_step.dependOn(testNoEhFrameHdr(b, .{ .target = glibc_target })); elf_step.dependOn(testPie(b, .{ .target = glibc_target })); @@ -99,6 +101,8 @@ pub fn build(b: *Build) void { elf_step.dependOn(testTlsOffsetAlignment(b, .{ .target = glibc_target })); elf_step.dependOn(testTlsPic(b, .{ .target = glibc_target })); elf_step.dependOn(testTlsSmallAlignment(b, .{ .target = glibc_target })); + elf_step.dependOn(testUnknownFileTypeError(b, .{ .target = glibc_target })); + elf_step.dependOn(testUnresolvedError(b, .{ .target = glibc_target })); elf_step.dependOn(testWeakExports(b, .{ .target = glibc_target })); elf_step.dependOn(testWeakUndefsDso(b, .{ .target = glibc_target })); elf_step.dependOn(testZNow(b, .{ .target = glibc_target })); @@ -1601,6 +1605,56 @@ fn testLdScript(b: *Build, opts: Options) *Step { return test_step; } +fn testLdScriptPathError(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "ld-script-path-error", opts); + + const scripts = WriteFile.create(b); + _ = scripts.add("liba.so", "INPUT(libfoo.so)"); + + const exe = addExecutable(b, "main", opts); + addCSourceBytes(exe, "int main() { return 0; }", &.{}); + exe.linkSystemLibrary2("a", .{}); + exe.addLibraryPath(scripts.getDirectory()); + exe.linkLibC(); + + expectLinkErrors( + exe, + test_step, + .{ + .contains = "error: missing library dependency: GNU ld script '/?/liba.so' requires 'libfoo.so', but file not found", + }, + ); + + return test_step; +} + +fn testMismatchedCpuArchitectureError(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "mismatched-cpu-architecture-error", opts); + + const obj = addObject(b, "a", .{ + .target = .{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .gnu }, + }); + addCSourceBytes(obj, "int foo;", &.{}); + obj.strip = true; + + const exe = addExecutable(b, "main", opts); + addCSourceBytes(exe, + \\extern int foo; + \\int main() { + \\ return foo; + \\} + , &.{}); + exe.addObject(obj); + exe.linkLibC(); + + expectLinkErrors(exe, test_step, .{ .exact = &.{ + "invalid cpu architecture: expected 'x86_64', but found 'aarch64'", + "note: while parsing /?/a.o", + } }); + + return test_step; +} + fn testLinkingC(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "linking-c", opts); @@ -2783,6 +2837,72 @@ fn testTlsStatic(b: *Build, opts: Options) *Step { return test_step; } +fn testUnknownFileTypeError(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "unknown-file-type-error", opts); + + const dylib = addSharedLibrary(b, "a", .{ + .target = .{ .cpu_arch = .x86_64, .os_tag = .macos }, + }); + addZigSourceBytes(dylib, "export var foo: i32 = 0;"); + + const exe = addExecutable(b, "main", opts); + addCSourceBytes(exe, + \\extern int foo; + \\int main() { + \\ return foo; + \\} + , &.{}); + exe.linkLibrary(dylib); + exe.linkLibC(); + + expectLinkErrors(exe, test_step, .{ .exact = &.{ + "unknown file type", + "note: while parsing /?/liba.dylib", + "undefined symbol: foo", + "note: referenced by /?/a.o:.text", + } }); + + return test_step; +} + +fn testUnresolvedError(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "unresolved-error", opts); + + const obj1 = addObject(b, "a", opts); + addCSourceBytes(obj1, + \\#include + \\int foo(); + \\int bar() { + \\ return foo() + 1; + \\} + , &.{"-ffunction-sections"}); + obj1.linkLibC(); + + const obj2 = addObject(b, "b", opts); + addCSourceBytes(obj2, + \\#include + \\int foo(); + \\int bar(); + \\int main() { + \\ return foo() + bar(); + \\} + , &.{"-ffunction-sections"}); + obj2.linkLibC(); + + const exe = addExecutable(b, "main", opts); + exe.addObject(obj1); + exe.addObject(obj2); + exe.linkLibC(); + + expectLinkErrors(exe, test_step, .{ .exact = &.{ + "error: undefined symbol: foo", + "note: referenced by /?/a.o:.text.bar", + "note: referenced by /?/b.o:.text.main", + } }); + + return test_step; +} + fn testWeakExports(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "weak-exports", opts); @@ -3081,6 +3201,12 @@ fn addAsmSourceBytes(comp: *Compile, bytes: []const u8) void { comp.addAssemblyFile(file); } +fn expectLinkErrors(comp: *Compile, test_step: *Step, expected_errors: Compile.ExpectedCompileErrors) void { + comp.expect_errors = expected_errors; + const bin_file = comp.getEmittedBin(); + bin_file.addStepDependencies(test_step); +} + const std = @import("std"); const Build = std.Build; diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 0ef584ccec..c8265a4165 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -640,7 +640,7 @@ pub fn lowerToBuildSteps( }, .Error => |expected_msgs| { assert(expected_msgs.len != 0); - artifact.expect_errors = expected_msgs; + artifact.expect_errors = .{ .exact = expected_msgs }; parent_step.dependOn(&artifact.step); }, .Execution => |expected_stdout| no_exec: {