Skip to content

Commit

Permalink
std.Build: add an option to CSourceFile to override the language de…
Browse files Browse the repository at this point in the history
…tection.

and change `addAssemblyFile()` to use a `AsmSourceFile` option struct as well. (breaking change)

Language is inferred from the file extension, however:
- it can be ambiguous. for instance,
    ".h" is often used for c headers or c++ headers.
    ".s" (instead of ".S") assembly files may still need the c preprocessor. (ziglang#20655)

- a singular file may be interpreted with different languages depending on the context.
    in "single-file libraries", the source.h file can be both a c-header to include, or compiled as a C file (with a #define as toggle)  (ziglang#19423)
  • Loading branch information
xxxbxxx committed Dec 19, 2024
1 parent e2e3633 commit 0f87959
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 20 deletions.
5 changes: 2 additions & 3 deletions lib/compiler/build_runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1504,9 +1504,8 @@ fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void {
.special => {},
};
for (mod.link_objects.items) |link_object| switch (link_object) {
.static_path,
.assembly_file,
=> |lp| lp.addStepDependencies(step),
.static_path => |lp| lp.addStepDependencies(step),
.assembly_file => |source| source.file.addStepDependencies(step),
.other_step => |other| step.dependOn(&other.step),
.system_lib => {},
.c_source_file => |source| source.file.addStepDependencies(step),
Expand Down
85 changes: 82 additions & 3 deletions lib/std/Build/Module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,39 @@ pub const RPath = union(enum) {
special: []const u8,
};

// subset of Compilation.FileExt
pub const AsmSourceLang = enum {
assembly,
assembly_with_cpp,

pub fn getName(lang: @This()) []const u8 {
return switch (lang) {
.assembly => "assembler",
.assembly_with_cpp => "assembler-with-cpp",
};
}
};

pub const AsmSourceFile = struct {
file: LazyPath,
/// if null, deduce the language from the file extension
lang: ?AsmSourceLang = null,
flags: []const []const u8 = &.{},

pub fn dupe(file: AsmSourceFile, b: *std.Build) AsmSourceFile {
return .{
.file = file.file.dupe(b),
.flags = b.dupeStrings(file.flags),
.lang = file.lang,
};
}
};

pub const LinkObject = union(enum) {
static_path: LazyPath,
other_step: *Step.Compile,
system_lib: SystemLib,
assembly_file: LazyPath,
assembly_file: *AsmSourceFile,
c_source_file: *CSourceFile,
c_source_files: *CSourceFiles,
win32_resource_file: *RcSourceFile,
Expand Down Expand Up @@ -78,21 +106,68 @@ pub const SystemLib = struct {
pub const SearchStrategy = enum { paths_first, mode_first, no_fallback };
};

/// Supported languages for "zig clang -x <lang>".
// subset of Compilation.FileExt
pub const CSourceLang = enum {
/// "c"
c,
/// "c-header"
h,
/// "c++"
cpp,
/// "c++-header"
hpp,
/// "objective-c"
m,
/// "objective-c-header"
hm,
/// "objective-c++"
mm,
/// "objective-c++-header"
hmm,
/// "assembler"
assembly,
/// "assembler-with-cpp"
assembly_with_cpp,
/// "cuda"
cu,

pub fn getName(lang: @This()) []const u8 {
return switch (lang) {
.assembly => "assembler",
.assembly_with_cpp => "assembler-with-cpp",
.c => "c",
.cpp => "c++",
.h => "c-header",
.hpp => "c++-header",
.hm => "objective-c-header",
.hmm => "objective-c++-header",
.cu => "cuda",
.m => "objective-c",
.mm => "objective-c++",
};
}
};

pub const CSourceFiles = struct {
root: LazyPath,
/// `files` is relative to `root`, which is
/// the build root by default
files: []const []const u8,
/// if null, deduce the language from the file extension
lang: ?CSourceLang = null,
flags: []const []const u8,
};

pub const CSourceFile = struct {
file: LazyPath,
lang: ?CSourceLang = null,
flags: []const []const u8 = &.{},

pub fn dupe(file: CSourceFile, b: *std.Build) CSourceFile {
return .{
.file = file.file.dupe(b),
.lang = file.lang,
.flags = b.dupeStrings(file.flags),
};
}
Expand Down Expand Up @@ -377,6 +452,7 @@ pub const AddCSourceFilesOptions = struct {
/// package that owns the `Compile` step.
root: ?LazyPath = null,
files: []const []const u8,
lang: ?CSourceLang = null,
flags: []const []const u8 = &.{},
};

Expand All @@ -398,6 +474,7 @@ pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void {
c_source_files.* = .{
.root = options.root orelse b.path(""),
.files = b.dupeStrings(options.files),
.lang = options.lang,
.flags = b.dupeStrings(options.flags),
};
m.link_objects.append(allocator, .{ .c_source_files = c_source_files }) catch @panic("OOM");
Expand Down Expand Up @@ -427,9 +504,11 @@ pub fn addWin32ResourceFile(m: *Module, source: RcSourceFile) void {
m.link_objects.append(allocator, .{ .win32_resource_file = rc_source_file }) catch @panic("OOM");
}

pub fn addAssemblyFile(m: *Module, source: LazyPath) void {
pub fn addAssemblyFile(m: *Module, source: AsmSourceFile) void {
const b = m.owner;
m.link_objects.append(b.allocator, .{ .assembly_file = source.dupe(b) }) catch @panic("OOM");
const source_file = b.allocator.create(AsmSourceFile) catch @panic("OOM");
source_file.* = source.dupe(b);
m.link_objects.append(b.allocator, .{ .assembly_file = source_file }) catch @panic("OOM");
}

pub fn addObjectFile(m: *Module, object: LazyPath) void {
Expand Down
53 changes: 49 additions & 4 deletions lib/std/Build/Step/Compile.zig
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@ pub fn getEmittedLlvmBc(compile: *Compile) LazyPath {
return compile.getEmittedFileGeneric(&compile.generated_llvm_bc);
}

pub fn addAssemblyFile(compile: *Compile, source: LazyPath) void {
pub fn addAssemblyFile(compile: *Compile, source: Module.AsmSourceFile) void {
compile.root_module.addAssemblyFile(source);
}

Expand Down Expand Up @@ -1072,6 +1072,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {

var prev_has_cflags = false;
var prev_has_rcflags = false;
var prev_has_xflag = false;
var prev_search_strategy: Module.SystemLib.SearchStrategy = .paths_first;
var prev_preferred_link_mode: std.builtin.LinkMode = .dynamic;
// Track the number of positional arguments so that a nice error can be
Expand Down Expand Up @@ -1108,6 +1109,12 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {

// Inherit dependencies on system libraries and static libraries.
for (mod.link_objects.items) |link_object| {
if (prev_has_xflag and link_object != .c_source_file and link_object != .c_source_files and link_object != .assembly_file) {
try zig_args.append("-x");
try zig_args.append("none");
prev_has_xflag = false;
}

switch (link_object) {
.static_path => |static_path| {
if (my_responsibility) {
Expand Down Expand Up @@ -1233,12 +1240,31 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
.assembly_file => |asm_file| l: {
if (!my_responsibility) break :l;

if (prev_has_cflags) {
if (asm_file.flags.len == 0) {
if (prev_has_cflags) {
try zig_args.append("-cflags");
try zig_args.append("--");
prev_has_cflags = false;
}
} else {
try zig_args.append("-cflags");
for (asm_file.flags) |arg| {
try zig_args.append(arg);
}
try zig_args.append("--");
prev_has_cflags = false;
prev_has_cflags = true;
}
if (asm_file.lang) |lang| {
try zig_args.append("-x");
try zig_args.append(lang.getName());
prev_has_xflag = true;
} else if (prev_has_xflag) {
try zig_args.append("-x");
try zig_args.append("none");
prev_has_xflag = false;
}
try zig_args.append(asm_file.getPath2(mod.owner, step));

try zig_args.append(asm_file.file.getPath2(mod.owner, step));
total_linker_objects += 1;
},

Expand All @@ -1259,6 +1285,16 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
try zig_args.append("--");
prev_has_cflags = true;
}
if (c_source_file.lang) |lang| {
try zig_args.append("-x");
try zig_args.append(lang.getName());
prev_has_xflag = true;
} else if (prev_has_xflag) {
try zig_args.append("-x");
try zig_args.append("none");
prev_has_xflag = false;
}

try zig_args.append(c_source_file.file.getPath2(mod.owner, step));
total_linker_objects += 1;
},
Expand All @@ -1280,6 +1316,15 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
try zig_args.append("--");
prev_has_cflags = true;
}
if (c_source_files.lang) |lang| {
try zig_args.append("-x");
try zig_args.append(lang.getName());
prev_has_xflag = true;
} else if (prev_has_xflag) {
try zig_args.append("-x");
try zig_args.append("none");
prev_has_xflag = false;
}

const root_path = c_source_files.root.getPath2(mod.owner, step);
for (c_source_files.files) |file| {
Expand Down
4 changes: 2 additions & 2 deletions test/link/elf.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2918,7 +2918,7 @@ fn testSharedAbsSymbol(b: *Build, opts: Options) *Step {
addAsmSourceBytes(dso,
\\.globl foo
\\foo = 3;
);
, &.{});

const obj = addObject(b, opts, .{
.name = "obj",
Expand Down Expand Up @@ -3533,7 +3533,7 @@ fn testTlsLargeTbss(b: *Build, opts: Options) *Step {
\\.section .tcommon,"awT",@nobits
\\y:
\\.zero 1024
);
, &.{});
addCSourceBytes(exe,
\\#include <stdio.h>
\\extern _Thread_local char x[1024000];
Expand Down
13 changes: 8 additions & 5 deletions test/link/link.zig
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub fn addTestStep(b: *Build, prefix: []const u8, opts: Options) *Step {
const OverlayOptions = struct {
name: []const u8,
asm_source_bytes: ?[]const u8 = null,
asm_source_flags: []const []const u8 = &.{},
c_source_bytes: ?[]const u8 = null,
c_source_flags: []const []const u8 = &.{},
cpp_source_bytes: ?[]const u8 = null,
Expand Down Expand Up @@ -122,7 +123,10 @@ fn createModule(b: *Build, base: Options, overlay: OverlayOptions) *Build.Module
});
}
if (overlay.asm_source_bytes) |bytes| {
mod.addAssemblyFile(write_files.add("a.s", bytes));
mod.addAssemblyFile(.{
.file = write_files.add("a.s", bytes),
.flags = overlay.asm_source_flags,
});
}

return mod;
Expand All @@ -147,11 +151,10 @@ pub fn addCppSourceBytes(comp: *Compile, bytes: []const u8, flags: []const []con
comp.addCSourceFile(.{ .file = file, .flags = flags });
}

pub fn addAsmSourceBytes(comp: *Compile, bytes: []const u8) void {
pub fn addAsmSourceBytes(comp: *Compile, bytes: []const u8, flags: []const []const u8) void {
const b = comp.step.owner;
const actual_bytes = std.fmt.allocPrint(b.allocator, "{s}\n", .{bytes}) catch @panic("OOM");
const file = WriteFile.create(b).add("a.s", actual_bytes);
comp.addAssemblyFile(file);
const file = WriteFile.create(b).add("a.s", bytes);
comp.addAssemblyFile(.{ .file = file, .flags = flags });
}

pub fn expectLinkErrors(comp: *Compile, test_step: *Step, expected_errors: Compile.ExpectedCompileErrors) void {
Expand Down
4 changes: 2 additions & 2 deletions test/link/macho.zig
Original file line number Diff line number Diff line change
Expand Up @@ -630,13 +630,13 @@ fn testHeaderWeakFlags(b: *Build, opts: Options) *Step {
\\_main:
\\ bl _x
\\ ret
),
, &.{}),
.x86_64 => addAsmSourceBytes(obj,
\\.globl _main
\\_main:
\\ callq _x
\\ ret
),
, &.{}),
else => unreachable,
}

Expand Down
2 changes: 1 addition & 1 deletion test/src/CompareOutput.zig
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ pub fn addCase(self: *CompareOutput, case: TestCase) void {
.optimize = .Debug,
}),
});
exe.root_module.addAssemblyFile(first_file);
exe.root_module.addAssemblyFile(.{ .file = first_file });

const run = b.addRunArtifact(exe);
run.setName(annotated_case_name);
Expand Down
3 changes: 3 additions & 0 deletions test/standalone/build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@
.c_compiler = .{
.path = "c_compiler",
},
.c_header = .{
.path = "c_header",
},
.pie = .{
.path = "pie",
},
Expand Down
29 changes: 29 additions & 0 deletions test/standalone/c_header/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const std = @import("std");

pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;

const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

// single-file c-header library
{
const exe = b.addExecutable(.{
.name = "single-file-library",
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
});

exe.linkLibC();
exe.addIncludePath(b.path("."));
exe.addCSourceFile(.{
.file = b.path("single_file_library.h"),
.lang = .c,
.flags = &.{"-DTSTLIB_IMPLEMENTATION"},
});

test_step.dependOn(&b.addRunArtifact(exe).step);
}
}
11 changes: 11 additions & 0 deletions test/standalone/c_header/main.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const std = @import("std");
const C = @cImport({
@cInclude("single_file_library.h");
});

pub fn main() !void {
const msg = "hello";
const val = C.tstlib_len(msg);
if (val != msg.len)
std.process.exit(1);
}
14 changes: 14 additions & 0 deletions test/standalone/c_header/single_file_library.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// library header:
extern unsigned tstlib_len(const char* msg);

// library implementation:
#ifdef TSTLIB_IMPLEMENTATION

#include <string.h>

unsigned tstlib_len(const char* msg)
{
return strlen(msg);
}

#endif

0 comments on commit 0f87959

Please sign in to comment.