Skip to content

Commit f349be9

Browse files
committed
cmake: add ZIG_STATIC_CXX
This controls whether or not system c++ libraries are statically linked. Note this does not effect whether or not zig-bundled libcxx is used.
1 parent 156f06d commit f349be9

File tree

7 files changed

+139
-34
lines changed

7 files changed

+139
-34
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ set(ZIG_SHARED_LLVM off CACHE BOOL "Prefer linking against shared LLVM libraries
9292
set(ZIG_STATIC_LLVM off CACHE BOOL "Prefer linking against static LLVM libraries")
9393
set(ZIG_STATIC_ZLIB off CACHE BOOL "Prefer linking against static zlib")
9494
set(ZIG_STATIC_ZSTD off CACHE BOOL "Prefer linking against static zstd")
95+
set(ZIG_STATIC_CXX off CACHE BOOL "Prefer linking against static standard c++ library")
9596
set(ZIG_USE_CCACHE off CACHE BOOL "Use ccache")
9697

9798
if(ZIG_USE_CCACHE)
@@ -107,6 +108,7 @@ if(ZIG_STATIC)
107108
set(ZIG_STATIC_LLVM ON)
108109
set(ZIG_STATIC_ZLIB ON)
109110
set(ZIG_STATIC_ZSTD ON)
111+
set(ZIG_STATIC_CXX ON)
110112
endif()
111113

112114
if (ZIG_SHARED_LLVM AND ZIG_STATIC_LLVM)

build.zig

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -552,32 +552,56 @@ fn addCmakeCfgOptionsToExe(
552552
if (use_zig_libcxx) {
553553
exe.linkLibCpp();
554554
} else {
555-
const need_cpp_includes = true;
556-
const lib_suffix = switch (cfg.llvm_linkage) {
557-
.static => exe.target.staticLibSuffix()[1..],
558-
.dynamic => exe.target.dynamicLibSuffix()[1..],
559-
};
560-
561555
// System -lc++ must be used because in this code path we are attempting to link
562-
// against system-provided LLVM, Clang, LLD.
563-
if (exe.target.getOsTag() == .linux) {
564-
// First we try to link against gcc libstdc++. If that doesn't work, we fall
565-
// back to -lc++ and cross our fingers.
566-
addCxxKnownPath(b, cfg, exe, b.fmt("libstdc++.{s}", .{lib_suffix}), "", need_cpp_includes) catch |err| switch (err) {
567-
error.RequiredLibraryNotFound => {
556+
// against system-provided or system-dependent LLVM, Clang, LLD.
557+
try exe.bundle.append(.{ .name = "libcxx", .allow = false });
558+
if (cfg.zig_static_cxx) {
559+
const lib_suffix = exe.target.staticLibSuffix()[1..];
560+
const need_cpp_includes = true;
561+
switch (exe.target.getOsTag()) {
562+
.linux => {
563+
// First we try to link against gcc libstdc++. If that doesn't work, we fall
564+
// back to -lc++ and cross our fingers.
565+
addCxxKnownPath(b, cfg, exe, b.fmt("libstdc++.{s}", .{lib_suffix}), "", need_cpp_includes) catch |err| switch (err) {
566+
error.RequiredLibraryNotFound => {
567+
exe.linkSystemLibrary("c++");
568+
},
569+
else => |e| return e,
570+
};
571+
exe.linkSystemLibrary("unwind");
572+
},
573+
.ios, .macos, .watchos, .tvos => {
574+
exe.linkSystemLibrary("c++");
575+
},
576+
.freebsd => {
577+
try addCxxKnownPath(b, cfg, exe, b.fmt("libc++.{s}", .{lib_suffix}), null, need_cpp_includes);
578+
try addCxxKnownPath(b, cfg, exe, b.fmt("libgcc_eh.{s}", .{lib_suffix}), null, need_cpp_includes);
579+
@panic("WIP: mike");
580+
},
581+
.openbsd => {
582+
try addCxxKnownPath(b, cfg, exe, b.fmt("libc++.{s}", .{lib_suffix}), null, need_cpp_includes);
583+
try addCxxKnownPath(b, cfg, exe, b.fmt("libc++abi.{s}", .{lib_suffix}), null, need_cpp_includes);
584+
@panic("WIP: mike");
585+
},
586+
.netbsd => {
587+
@panic("WIP: mike");
588+
},
589+
.dragonfly => {
590+
@panic("WIP: mike");
591+
},
592+
else => {
593+
@panic("WIP: mike");
594+
},
595+
}
596+
} else {
597+
switch (exe.target.getOsTag()) {
598+
.ios, .macos, .watchos, .tvos, .freebsd, .openbsd => {
568599
exe.linkSystemLibrary("c++");
569600
},
570-
else => |e| return e,
571-
};
572-
exe.linkSystemLibrary("unwind");
573-
} else if (exe.target.isFreeBSD()) {
574-
try addCxxKnownPath(b, cfg, exe, b.fmt("libc++.{s}", .{lib_suffix}), null, need_cpp_includes);
575-
exe.linkSystemLibrary("pthread");
576-
} else if (exe.target.getOsTag() == .openbsd) {
577-
try addCxxKnownPath(b, cfg, exe, b.fmt("libc++.{s}", .{lib_suffix}), null, need_cpp_includes);
578-
try addCxxKnownPath(b, cfg, exe, b.fmt("libc++abi.{s}", .{lib_suffix}), null, need_cpp_includes);
579-
} else if (exe.target.isDarwin()) {
580-
exe.linkSystemLibrary("c++");
601+
else => {
602+
exe.linkSystemLibrary("stdc++");
603+
},
604+
}
581605
}
582606
}
583607

@@ -675,6 +699,7 @@ const CMakeConfig = struct {
675699
llvm_include_dir: []const u8,
676700
llvm_libraries: []const u8,
677701
dia_guids_lib: []const u8,
702+
zig_static_cxx: bool,
678703
};
679704

680705
const max_config_h_bytes = 1 * 1024 * 1024;
@@ -740,6 +765,7 @@ fn parseConfigH(b: *Builder, config_h_text: []const u8) ?CMakeConfig {
740765
.llvm_include_dir = undefined,
741766
.llvm_libraries = undefined,
742767
.dia_guids_lib = undefined,
768+
.zig_static_cxx = false,
743769
};
744770

745771
const mappings = [_]struct { prefix: []const u8, field: []const u8 }{
@@ -792,6 +818,7 @@ fn parseConfigH(b: *Builder, config_h_text: []const u8) ?CMakeConfig {
792818
.field = "llvm_lib_dir",
793819
},
794820
// .prefix = ZIG_LLVM_LINK_MODE parsed manually below
821+
// .prefix = ZIG_STATIC_CXX parsed manually below
795822
};
796823

797824
var lines_it = mem.tokenize(u8, config_h_text, "\r\n");
@@ -809,6 +836,8 @@ fn parseConfigH(b: *Builder, config_h_text: []const u8) ?CMakeConfig {
809836
_ = it.next().?; // skip the stuff before the quote
810837
const quoted = it.next().?; // the stuff inside the quote
811838
ctx.llvm_linkage = if (mem.eql(u8, quoted, "shared")) .dynamic else .static;
839+
} else if (mem.startsWith(u8, line, "#define ZIG_STATIC_CXX ")) {
840+
ctx.zig_static_cxx = true;
812841
}
813842
}
814843
return ctx;

lib/std/build/LibExeObjStep.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ install_step: ?*InstallArtifactStep,
117117
/// Base address for an executable image.
118118
image_base: ?u64 = null,
119119

120+
bundle: ArrayList(BundleOption),
121+
120122
libc_file: ?FileSource = null,
121123

122124
valgrind_support: ?bool = null,
@@ -290,6 +292,11 @@ pub const EmitOption = union(enum) {
290292
}
291293
};
292294

295+
pub const BundleOption = struct {
296+
name: []const u8,
297+
allow: bool,
298+
};
299+
293300
pub fn createSharedLibrary(builder: *Builder, name: []const u8, root_src: ?FileSource, kind: SharedLibKind) *LibExeObjStep {
294301
return initExtraArgs(builder, name, root_src, .lib, .dynamic, switch (kind) {
295302
.versioned => |ver| ver,
@@ -375,6 +382,7 @@ fn initExtraArgs(
375382
.override_dest_dir = null,
376383
.installed_path = null,
377384
.install_step = null,
385+
.bundle = ArrayList(BundleOption).init(builder.allocator),
378386

379387
.output_path_source = GeneratedFile{ .step = &self.step },
380388
.output_lib_path_source = GeneratedFile{ .step = &self.step },
@@ -1242,6 +1250,16 @@ fn make(step: *Step) !void {
12421250
try zig_args.append(builder.fmt("0x{x}", .{image_base}));
12431251
}
12441252

1253+
for (self.bundle.items) |option| {
1254+
if (option.allow) {
1255+
try zig_args.append("--bundle");
1256+
try zig_args.append(option.name);
1257+
} else {
1258+
try zig_args.append("--no-bundle");
1259+
try zig_args.append(option.name);
1260+
}
1261+
}
1262+
12451263
if (self.filter) |filter| {
12461264
try zig_args.append("--test-filter");
12471265
try zig_args.append(filter);

src/Compilation.zig

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ local_cache_directory: Directory,
120120
global_cache_directory: Directory,
121121
libc_include_dir_list: []const []const u8,
122122
thread_pool: *ThreadPool,
123+
allow_bundle: AllowBundle,
123124

124125
/// Populated when we build the libc++ static library. A Job to build this is placed in the queue
125126
/// and resolved before calling linker.flush().
@@ -307,6 +308,31 @@ pub const CObject = struct {
307308
}
308309
};
309310

311+
/// This structure determines if a bundled library vs. system library is used.
312+
/// It does not have effect whether or not the library is actually enabled.
313+
pub const AllowBundle = struct {
314+
libc: bool = true,
315+
libcxx: bool = true,
316+
317+
/// Return null on success.
318+
/// Otherwise return invalid name.
319+
pub fn parse(self: *AllowBundle, csv: []const u8, allow: bool) ?[]const u8 {
320+
var it = mem.split(u8, csv, ",");
321+
while (it.next()) |name| {
322+
if (name.len == 0) {
323+
continue;
324+
} else if (mem.eql(u8, name, "libc")) {
325+
self.libc = allow;
326+
} else if (mem.eql(u8, name, "libcxx")) {
327+
self.libcxx = allow;
328+
} else {
329+
return name;
330+
}
331+
}
332+
return null;
333+
}
334+
};
335+
310336
pub const MiscTask = enum {
311337
write_builtin_zig,
312338
glibc_crt_file,
@@ -914,6 +940,7 @@ pub const InitOptions = struct {
914940
link_libc: bool = false,
915941
link_libcpp: bool = false,
916942
link_libunwind: bool = false,
943+
allow_bundle: AllowBundle = .{},
917944
want_pic: ?bool = null,
918945
/// This means that if the output mode is an executable it will be a
919946
/// Position Independent Executable. If the output mode is not an
@@ -1913,6 +1940,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
19131940
.libc_include_dir_list = libc_dirs.libc_include_dir_list,
19141941
.sanitize_c = sanitize_c,
19151942
.thread_pool = options.thread_pool,
1943+
.allow_bundle = options.allow_bundle,
19161944
.clang_passthrough_mode = options.clang_passthrough_mode,
19171945
.clang_preprocessor_mode = options.clang_preprocessor_mode,
19181946
.verbose_cc = options.verbose_cc,
@@ -2049,7 +2077,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
20492077
if (comp.wantBuildLibUnwindFromSource()) {
20502078
try comp.work_queue.writeItem(.{ .libunwind = {} });
20512079
}
2052-
if (build_options.have_llvm and is_exe_or_dyn_lib and comp.bin_file.options.link_libcpp) {
2080+
if (comp.allow_bundle.libcxx and build_options.have_llvm and is_exe_or_dyn_lib and comp.bin_file.options.link_libcpp) {
20532081
try comp.work_queue.writeItem(.libcxx);
20542082
try comp.work_queue.writeItem(.libcxxabi);
20552083
}
@@ -4945,7 +4973,8 @@ fn wantBuildLibCFromSource(comp: Compilation) bool {
49454973
};
49464974
return comp.bin_file.options.link_libc and is_exe_or_dyn_lib and
49474975
comp.bin_file.options.libc_installation == null and
4948-
comp.bin_file.options.target.ofmt != .c;
4976+
comp.bin_file.options.target.ofmt != .c and
4977+
comp.allow_bundle.libc;
49494978
}
49504979

49514980
fn wantBuildGLibCFromSource(comp: Compilation) bool {

src/link/MachO/zld.zig

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3734,7 +3734,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
37343734
}
37353735

37363736
// libc++ dep
3737-
if (options.link_libcpp) {
3737+
if (options.link_libcpp and comp.allow_bundle.libcxx) {
37383738
try positionals.append(comp.libcxxabi_static_lib.?.full_object_path);
37393739
try positionals.append(comp.libcxx_static_lib.?.full_object_path);
37403740
}
@@ -3930,16 +3930,19 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
39303930
try argv.append(lib.full_object_path);
39313931
}
39323932

3933-
if (options.link_libcpp) {
3934-
try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
3935-
try argv.append(comp.libcxx_static_lib.?.full_object_path);
3936-
}
3937-
39383933
try argv.append("-o");
39393934
try argv.append(full_out_path);
39403935

3936+
if (options.link_libcpp) {
3937+
if (comp.allow_bundle.libcxx) {
3938+
try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
3939+
try argv.append(comp.libcxx_static_lib.?.full_object_path);
3940+
} else {
3941+
try argv.append("-lc++");
3942+
}
3943+
}
3944+
39413945
try argv.append("-lSystem");
3942-
try argv.append("-lc");
39433946

39443947
for (options.system_libs.keys()) |l_name| {
39453948
const info = options.system_libs.get(l_name).?;

src/main.zig

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,8 @@ const usage_build_generic =
500500
\\ --subsystem [subsystem] (Windows) /SUBSYSTEM:<subsystem> to the linker
501501
\\ --stack [size] Override default stack size
502502
\\ --image-base [addr] Set base address for executable image
503+
\\ --bundle=[names] Allow bundling of one or more { libc,libcxx }
504+
\\ --no-bundle=[names] Disallow bundling of one or more { libc,libcxx }
503505
\\ -weak-l[lib] (Darwin) link against system library and mark it and all referenced symbols as weak
504506
\\ -weak_library [lib]
505507
\\ -framework [name] (Darwin) link against framework
@@ -695,6 +697,7 @@ fn buildOutputType(
695697
var link_libc = false;
696698
var link_libcpp = false;
697699
var link_libunwind = false;
700+
var allow_bundle: Compilation.AllowBundle = .{};
698701
var want_native_include_dirs = false;
699702
var enable_cache: ?bool = null;
700703
var want_pic: ?bool = null;
@@ -999,6 +1002,22 @@ fn buildOutputType(
9991002
};
10001003
} else if (mem.eql(u8, arg, "--compress-debug-sections")) {
10011004
linker_compress_debug_sections = link.CompressDebugSections.zlib;
1005+
} else if (mem.eql(u8, arg, "--bundle")) {
1006+
if (allow_bundle.parse(args_iter.nextOrFatal(), true)) |name| {
1007+
fatal("expected --bundle [csv] with one or more values of {{ c,c++ }}, found '{s}'", .{name});
1008+
}
1009+
} else if (mem.eql(u8, arg, "--no-bundle")) {
1010+
if (allow_bundle.parse(args_iter.nextOrFatal(), false)) |name| {
1011+
fatal("expected --no-bundle [csv] with one or more values of {{ c,c++ }}, found '{s}'", .{name});
1012+
}
1013+
} else if (mem.startsWith(u8, arg, "--bundle=")) {
1014+
if (allow_bundle.parse(arg["--bundle=".len..], true)) |name| {
1015+
fatal("expected --bundle=[csv] with one or more values of {{ c,c++ }}, found '{s}'", .{name});
1016+
}
1017+
} else if (mem.startsWith(u8, arg, "--no-bundle=")) {
1018+
if (allow_bundle.parse(arg["--no-bundle=".len..], false)) |name| {
1019+
fatal("expected --no-bundle=[csv] with one or more values of {{ c,c++ }}, found '{s}'", .{name});
1020+
}
10021021
} else if (mem.eql(u8, arg, "-pagezero_size")) {
10031022
const next_arg = args_iter.nextOrFatal();
10041023
pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| {
@@ -2321,8 +2340,10 @@ fn buildOutputType(
23212340
}
23222341
if (target_util.is_libcpp_lib_name(target_info.target, lib_name)) {
23232342
link_libcpp = true;
2324-
system_libs.orderedRemoveAt(i);
2325-
continue;
2343+
if (allow_bundle.libcxx) {
2344+
system_libs.orderedRemoveAt(i);
2345+
continue;
2346+
}
23262347
}
23272348
switch (target_util.classifyCompilerRtLibName(target_info.target, lib_name)) {
23282349
.none => {},
@@ -2955,6 +2976,7 @@ fn buildOutputType(
29552976
.link_libc = link_libc,
29562977
.link_libcpp = link_libcpp,
29572978
.link_libunwind = link_libunwind,
2979+
.allow_bundle = allow_bundle,
29582980
.want_pic = want_pic,
29592981
.want_pie = want_pie,
29602982
.want_lto = want_lto,

stage1/config.h.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@
2929
#define ZIG_LLVM_LIB_PATH "@LLVM_LIBDIRS@"
3030
#define ZIG_LLVM_LINK_MODE "@LLVM_LINK_MODE@"
3131

32+
#cmakedefine ZIG_STATIC_CXX
33+
3234
#endif

0 commit comments

Comments
 (0)