diff --git a/CMakeLists.txt b/CMakeLists.txt index b9a81575be..72035496fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,34 @@ set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX project(zig C CXX) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) +set(ZIG_VERSION_MAJOR 0) +set(ZIG_VERSION_MINOR 7) +set(ZIG_VERSION_PATCH 0) +set(ZIG_VERSION "" CACHE STRING "Override Zig version string. Default is to find out with git.") + +if("${ZIG_VERSION}" STREQUAL "") + set(ZIG_VERSION "${ZIG_VERSION_MAJOR}.${ZIG_VERSION_MINOR}.${ZIG_VERSION_PATCH}") + find_program(GIT_EXE NAMES git) + if(GIT_EXE) + execute_process( + COMMAND ${GIT_EXE} -C ${CMAKE_SOURCE_DIR} name-rev HEAD --tags --name-only --no-undefined --always + RESULT_VARIABLE EXIT_STATUS + OUTPUT_VARIABLE ZIG_GIT_REV + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + if(EXIT_STATUS EQUAL "0") + if(ZIG_GIT_REV MATCHES "\\^0$") + if(NOT("${ZIG_GIT_REV}" STREQUAL "${ZIG_VERSION}^0")) + message("WARNING: Tag does not match configured Zig version") + endif() + else() + set(ZIG_VERSION "${ZIG_VERSION}+${ZIG_GIT_REV}") + endif() + endif() + endif() +endif() +message("Configuring zig version ${ZIG_VERSION}") + set(ZIG_STATIC off CACHE BOOL "Attempt to build a static zig executable (not compatible with glibc)") set(ZIG_STATIC_LLVM off CACHE BOOL "Prefer linking against static LLVM libraries") set(ZIG_PREFER_CLANG_CPP_DYLIB off CACHE BOOL "Try to link against -lclang-cpp") @@ -233,6 +261,8 @@ set(LIBC_FILES_DEST "${ZIG_LIB_DIR}/libc") set(LIBUNWIND_FILES_DEST "${ZIG_LIB_DIR}/libunwind") set(LIBCXX_FILES_DEST "${ZIG_LIB_DIR}/libcxx") set(ZIG_STD_DEST "${ZIG_LIB_DIR}/std") +set(ZIG_CONFIG_H_OUT "${CMAKE_BINARY_DIR}/config.h") +set(ZIG_CONFIG_ZIG_OUT "${CMAKE_BINARY_DIR}/config.zig") # This is our shim which will be replaced by stage1.zig. set(ZIG0_SOURCES @@ -280,6 +310,7 @@ set(ZIG_CPP_SOURCES # then manually running the build-obj command (see BUILD_ZIG1_ARGS), and then looking # in the zig-cache directory for the compiler-generated list of zig file dependencies. set(ZIG_STAGE2_SOURCES + "${ZIG_CONFIG_ZIG_OUT}" "${CMAKE_SOURCE_DIR}/lib/std/array_hash_map.zig" "${CMAKE_SOURCE_DIR}/lib/std/array_list.zig" "${CMAKE_SOURCE_DIR}/lib/std/ascii.zig" @@ -519,7 +550,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/print_env.zig" "${CMAKE_SOURCE_DIR}/src/print_targets.zig" "${CMAKE_SOURCE_DIR}/src/stage1.zig" - "${CMAKE_SOURCE_DIR}/src/stage1_config.zig" "${CMAKE_SOURCE_DIR}/src/target.zig" "${CMAKE_SOURCE_DIR}/src/tracy.zig" "${CMAKE_SOURCE_DIR}/src/translate_c.zig" @@ -538,6 +568,15 @@ if(MSVC) endif() endif() +configure_file ( + "${CMAKE_SOURCE_DIR}/src/stage1/config.h.in" + "${ZIG_CONFIG_H_OUT}" +) +configure_file ( + "${CMAKE_SOURCE_DIR}/src/config.zig.in" + "${ZIG_CONFIG_ZIG_OUT}" +) + include_directories( ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} @@ -684,7 +723,7 @@ set(BUILD_ZIG1_ARGS "-femit-bin=${ZIG1_OBJECT}" "${ZIG1_RELEASE_ARG}" -lc - --pkg-begin build_options "${CMAKE_SOURCE_DIR}/src/stage1_config.zig" + --pkg-begin build_options "${ZIG_CONFIG_ZIG_OUT}" --pkg-end --pkg-begin compiler_rt "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt.zig" --pkg-end @@ -733,6 +772,7 @@ set(ZIG_INSTALL_ARGS "build" --override-lib-dir "${CMAKE_SOURCE_DIR}/lib" "-Dlib-files-only" --prefix "${CMAKE_INSTALL_PREFIX}" + "-Dconfig_h=${ZIG_CONFIG_H_OUT}" install ) diff --git a/build.zig b/build.zig index 37ee924e1c..abf59fa007 100644 --- a/build.zig +++ b/build.zig @@ -54,7 +54,8 @@ pub fn build(b: *Builder) !void { const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false; const is_stage1 = b.option(bool, "stage1", "Build the stage1 compiler, put stage2 behind a feature flag") orelse false; - const enable_llvm = b.option(bool, "enable-llvm", "Build self-hosted compiler with LLVM backend enabled") orelse is_stage1; + const static_llvm = b.option(bool, "static-llvm", "Disable integration with system-installed LLVM, Clang, LLD, and libc++") orelse false; + const enable_llvm = b.option(bool, "enable-llvm", "Build self-hosted compiler with LLVM backend enabled") orelse (is_stage1 or static_llvm); const config_h_path_option = b.option([]const u8, "config_h", "Path to the generated config.h"); b.installDirectory(InstallDirectoryOptions{ @@ -89,6 +90,7 @@ pub fn build(b: *Builder) !void { exe.addBuildOption(bool, "skip_non_native", skip_non_native); exe.addBuildOption(bool, "have_llvm", enable_llvm); if (enable_llvm) { + const cmake_cfg = if (static_llvm) null else findAndParseConfigH(b, config_h_path_option); if (is_stage1) { exe.addIncludeDir("src"); exe.addIncludeDir("deps/SoftFloat-3e/source/include"); @@ -96,6 +98,7 @@ pub fn build(b: *Builder) !void { // of being built by cmake. But when built by zig it's gonna get a compiler_rt so that // is pointless. exe.addPackagePath("compiler_rt", "src/empty.zig"); + exe.defineCMacro("ZIG_LINK_MODE=Static"); const softfloat = b.addStaticLibrary("softfloat", null); softfloat.setBuildMode(.ReleaseFast); @@ -121,31 +124,91 @@ pub fn build(b: *Builder) !void { }; exe.addCSourceFiles(&stage1_sources, &exe_cflags); exe.addCSourceFiles(&optimized_c_sources, &[_][]const u8{ "-std=c99", "-O3" }); - // We need this because otherwise zig_clang_cc1_main.cpp ends up pulling - // in a dependency on llvm::cfg::Update::dump() which is - // unavailable when LLVM is compiled in Release mode. - const zig_cpp_cflags = exe_cflags ++ [_][]const u8{"-DNDEBUG=1"}; - exe.addCSourceFiles(&zig_cpp_sources, &zig_cpp_cflags); + if (cmake_cfg == null) { + // We need this because otherwise zig_clang_cc1_main.cpp ends up pulling + // in a dependency on llvm::cfg::Update::dump() which is + // unavailable when LLVM is compiled in Release mode. + const zig_cpp_cflags = exe_cflags ++ [_][]const u8{"-DNDEBUG=1"}; + exe.addCSourceFiles(&zig_cpp_sources, &zig_cpp_cflags); + } } + if (cmake_cfg) |cfg| { + // Inside this code path, we have to coordinate with system packaged LLVM, Clang, and LLD. + // That means we also have to rely on stage1 compiled c++ files. We parse config.h to find + // the information passed on to us from cmake. + if (cfg.cmake_prefix_path.len > 0) { + b.addSearchPrefix(cfg.cmake_prefix_path); + } + exe.addObjectFile(fs.path.join(b.allocator, &[_][]const u8{ + cfg.cmake_binary_dir, + "zigcpp", + b.fmt("{s}{s}{s}", .{ exe.target.libPrefix(), "zigcpp", exe.target.staticLibSuffix() }), + }) catch unreachable); + assert(cfg.lld_include_dir.len != 0); + exe.addIncludeDir(cfg.lld_include_dir); + addCMakeLibraryList(exe, cfg.clang_libraries); + addCMakeLibraryList(exe, cfg.lld_libraries); + addCMakeLibraryList(exe, cfg.llvm_libraries); - for (clang_libs) |lib_name| { - exe.linkSystemLibrary(lib_name); - } + const need_cpp_includes = tracy != null; - for (lld_libs) |lib_name| { - exe.linkSystemLibrary(lib_name); - } + // System -lc++ must be used because in this code path we are attempting to link + // against system-provided LLVM, Clang, LLD. + if (exe.target.getOsTag() == .linux) { + // First we try to static link against gcc libstdc++. If that doesn't work, + // we fall back to -lc++ and cross our fingers. + addCxxKnownPath(b, cfg, exe, "libstdc++.a", "", need_cpp_includes) catch |err| switch (err) { + error.RequiredLibraryNotFound => { + exe.linkSystemLibrary("c++"); + }, + else => |e| return e, + }; - for (llvm_libs) |lib_name| { - exe.linkSystemLibrary(lib_name); - } + exe.linkSystemLibrary("pthread"); + } else if (exe.target.isFreeBSD()) { + try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes); + exe.linkSystemLibrary("pthread"); + } else if (exe.target.isDarwin()) { + if (addCxxKnownPath(b, cfg, exe, "libgcc_eh.a", "", need_cpp_includes)) { + // Compiler is GCC. + try addCxxKnownPath(b, cfg, exe, "libstdc++.a", null, need_cpp_includes); + exe.linkSystemLibrary("pthread"); + // TODO LLD cannot perform this link. + // Set ZIG_SYSTEM_LINKER_HACK env var to use system linker ld instead. + // See https://github.com/ziglang/zig/issues/1535 + } else |err| switch (err) { + error.RequiredLibraryNotFound => { + // System compiler, not gcc. + exe.linkSystemLibrary("c++"); + }, + else => |e| return e, + } + } - // This means we rely on clang-or-zig-built LLVM, Clang, LLD libraries. - exe.linkSystemLibrary("c++"); + if (cfg.dia_guids_lib.len != 0) { + exe.addObjectFile(cfg.dia_guids_lib); + } + } else { + // Here we are -Denable-llvm but no cmake integration. + for (clang_libs) |lib_name| { + exe.linkSystemLibrary(lib_name); + } - if (target.getOs().tag == .windows) { - exe.linkSystemLibrary("version"); - exe.linkSystemLibrary("uuid"); + for (lld_libs) |lib_name| { + exe.linkSystemLibrary(lib_name); + } + + for (llvm_libs) |lib_name| { + exe.linkSystemLibrary(lib_name); + } + + // This means we rely on clang-or-zig-built LLVM, Clang, LLD libraries. + exe.linkSystemLibrary("c++"); + + if (target.getOs().tag == .windows) { + exe.linkSystemLibrary("version"); + exe.linkSystemLibrary("uuid"); + } } } if (link_libc) { @@ -281,6 +344,158 @@ pub fn build(b: *Builder) !void { test_step.dependOn(docs_step); } +fn addCxxKnownPath( + b: *Builder, + ctx: CMakeConfig, + exe: *std.build.LibExeObjStep, + objname: []const u8, + errtxt: ?[]const u8, + need_cpp_includes: bool, +) !void { + const path_padded = try b.exec(&[_][]const u8{ + ctx.cxx_compiler, + b.fmt("-print-file-name={}", .{objname}), + }); + const path_unpadded = mem.tokenize(path_padded, "\r\n").next().?; + if (mem.eql(u8, path_unpadded, objname)) { + if (errtxt) |msg| { + warn("{}", .{msg}); + } else { + warn("Unable to determine path to {}\n", .{objname}); + } + return error.RequiredLibraryNotFound; + } + exe.addObjectFile(path_unpadded); + + // TODO a way to integrate with system c++ include files here + // cc -E -Wp,-v -xc++ /dev/null + if (need_cpp_includes) { + // I used these temporarily for testing something but we obviously need a + // more general purpose solution here. + //exe.addIncludeDir("/nix/store/b3zsk4ihlpiimv3vff86bb5bxghgdzb9-gcc-9.2.0/lib/gcc/x86_64-unknown-linux-gnu/9.2.0/../../../../include/c++/9.2.0"); + //exe.addIncludeDir("/nix/store/b3zsk4ihlpiimv3vff86bb5bxghgdzb9-gcc-9.2.0/lib/gcc/x86_64-unknown-linux-gnu/9.2.0/../../../../include/c++/9.2.0/x86_64-unknown-linux-gnu"); + //exe.addIncludeDir("/nix/store/b3zsk4ihlpiimv3vff86bb5bxghgdzb9-gcc-9.2.0/lib/gcc/x86_64-unknown-linux-gnu/9.2.0/../../../../include/c++/9.2.0/backward"); + } +} + +fn addCMakeLibraryList(exe: *std.build.LibExeObjStep, list: []const u8) void { + var it = mem.tokenize(list, ";"); + while (it.next()) |lib| { + if (mem.startsWith(u8, lib, "-l")) { + exe.linkSystemLibrary(lib["-l".len..]); + } else { + exe.addObjectFile(lib); + } + } +} + +const CMakeConfig = struct { + cmake_binary_dir: []const u8, + cmake_prefix_path: []const u8, + cxx_compiler: []const u8, + lld_include_dir: []const u8, + lld_libraries: []const u8, + clang_libraries: []const u8, + llvm_libraries: []const u8, + dia_guids_lib: []const u8, +}; + +const max_config_h_bytes = 1 * 1024 * 1024; + +fn findAndParseConfigH(b: *Builder, config_h_path_option: ?[]const u8) ?CMakeConfig { + const config_h_text: []const u8 = if (config_h_path_option) |config_h_path| blk: { + break :blk fs.cwd().readFileAlloc(b.allocator, config_h_path, max_config_h_bytes) catch unreachable; + } else blk: { + // TODO this should stop looking for config.h once it detects we hit the + // zig source root directory. + var check_dir = fs.path.dirname(b.zig_exe).?; + while (true) { + var dir = fs.cwd().openDir(check_dir, .{}) catch unreachable; + defer dir.close(); + + break :blk dir.readFileAlloc(b.allocator, "config.h", max_config_h_bytes) catch |err| switch (err) { + error.FileNotFound => { + const new_check_dir = fs.path.dirname(check_dir); + if (new_check_dir == null or mem.eql(u8, new_check_dir.?, check_dir)) { + return null; + } + check_dir = new_check_dir.?; + continue; + }, + else => unreachable, + }; + } else unreachable; // TODO should not need `else unreachable`. + }; + + var ctx: CMakeConfig = .{ + .cmake_binary_dir = undefined, + .cmake_prefix_path = undefined, + .cxx_compiler = undefined, + .lld_include_dir = undefined, + .lld_libraries = undefined, + .clang_libraries = undefined, + .llvm_libraries = undefined, + .dia_guids_lib = undefined, + }; + + const mappings = [_]struct { prefix: []const u8, field: []const u8 }{ + .{ + .prefix = "#define ZIG_CMAKE_BINARY_DIR ", + .field = "cmake_binary_dir", + }, + .{ + .prefix = "#define ZIG_CMAKE_PREFIX_PATH ", + .field = "cmake_prefix_path", + }, + .{ + .prefix = "#define ZIG_CXX_COMPILER ", + .field = "cxx_compiler", + }, + .{ + .prefix = "#define ZIG_LLD_INCLUDE_PATH ", + .field = "lld_include_dir", + }, + .{ + .prefix = "#define ZIG_LLD_LIBRARIES ", + .field = "lld_libraries", + }, + .{ + .prefix = "#define ZIG_CLANG_LIBRARIES ", + .field = "clang_libraries", + }, + .{ + .prefix = "#define ZIG_LLVM_LIBRARIES ", + .field = "llvm_libraries", + }, + .{ + .prefix = "#define ZIG_DIA_GUIDS_LIB ", + .field = "dia_guids_lib", + }, + }; + + var lines_it = mem.tokenize(config_h_text, "\r\n"); + while (lines_it.next()) |line| { + inline for (mappings) |mapping| { + if (mem.startsWith(u8, line, mapping.prefix)) { + var it = mem.split(line, "\""); + _ = it.next().?; // skip the stuff before the quote + const quoted = it.next().?; // the stuff inside the quote + @field(ctx, mapping.field) = toNativePathSep(b, quoted); + } + } + } + return ctx; +} + +fn toNativePathSep(b: *Builder, s: []const u8) []u8 { + const duplicated = mem.dupe(b.allocator, u8, s) catch unreachable; + for (duplicated) |*byte| switch (byte.*) { + '/' => byte.* = fs.path.sep, + else => {}, + }; + return duplicated; +} + const softfloat_sources = [_][]const u8{ "deps/SoftFloat-3e/source/8086/f128M_isSignalingNaN.c", "deps/SoftFloat-3e/source/8086/s_commonNaNToF128M.c", diff --git a/src/stage1_config.zig b/src/config.zig.in similarity index 62% rename from src/stage1_config.zig rename to src/config.zig.in index 3410540d5b..871c6e9abf 100644 --- a/src/stage1_config.zig +++ b/src/config.zig.in @@ -1,10 +1,10 @@ pub const have_llvm = true; -pub const version: [:0]const u8 = "0.0.0+zig0"; +pub const version: [:0]const u8 = "@ZIG_VERSION@"; pub const semver: @import("std").SemanticVersion = .{ - .major = 0, - .minor = 0, - .patch = 0, - .build = "zig0", + .major = @ZIG_VERSION_MAJOR@, + .minor = @ZIG_VERSION_MINOR@, + .patch = @ZIG_VERSION_PATCH@, + .build = "@ZIG_GIT_REV@", }; pub const log_scopes: []const []const u8 = &[_][]const u8{}; pub const zir_dumps: []const []const u8 = &[_][]const u8{}; diff --git a/src/stage1/config.h.in b/src/stage1/config.h.in new file mode 100644 index 0000000000..4d375652ff --- /dev/null +++ b/src/stage1/config.h.in @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016 Andrew Kelley + * + * This file is part of zig, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef ZIG_CONFIG_H +#define ZIG_CONFIG_H + +// Used by zig0.cpp +#define ZIG_VERSION_MAJOR @ZIG_VERSION_MAJOR@ +#define ZIG_VERSION_MINOR @ZIG_VERSION_MINOR@ +#define ZIG_VERSION_PATCH @ZIG_VERSION_PATCH@ +#define ZIG_VERSION_STRING "@ZIG_VERSION@" + +// Used by build.zig for communicating build information to self hosted build. +#define ZIG_CMAKE_BINARY_DIR "@CMAKE_BINARY_DIR@" +#define ZIG_CMAKE_PREFIX_PATH "@CMAKE_PREFIX_PATH@" +#define ZIG_CXX_COMPILER "@CMAKE_CXX_COMPILER@" +#define ZIG_LLD_INCLUDE_PATH "@LLD_INCLUDE_DIRS@" +#define ZIG_LLD_LIBRARIES "@LLD_LIBRARIES@" +#define ZIG_CLANG_LIBRARIES "@CLANG_LIBRARIES@" +#define ZIG_LLVM_LIBRARIES "@LLVM_LIBRARIES@" +#define ZIG_DIA_GUIDS_LIB "@ZIG_DIA_GUIDS_LIB_ESCAPED@" + +#endif diff --git a/src/stage1/zig0.cpp b/src/stage1/zig0.cpp index ffe5ebe244..73f5b4f685 100644 --- a/src/stage1/zig0.cpp +++ b/src/stage1/zig0.cpp @@ -18,6 +18,11 @@ #include "buffer.hpp" #include "os.hpp" +// This is the only file allowed to include config.h because config.h is +// only produced when building with cmake. When using the zig build system, +// zig0.cpp is never touched. +#include "config.h" + #include #include @@ -539,9 +544,9 @@ const char *stage2_add_link_lib(struct ZigStage1 *stage1, } const char *stage2_version_string(void) { - return "0.0.0+zig0"; + return ZIG_VERSION_STRING; } struct Stage2SemVer stage2_version(void) { - return {0, 0, 0}; + return {ZIG_VERSION_MAJOR, ZIG_VERSION_MINOR, ZIG_VERSION_PATCH}; }