diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..e3220ba13 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,14 @@ +[target.'cfg(true)'] +rustflags = [ + # rustc will pass `-nodefaultlibs` without this, but we need the C++ standard library. + '-Cdefault-linker-libraries=yes', +] + +[target.'cfg(target_env = "musl")'] +rustflags = [ + '-Cdefault-linker-libraries=yes', + # musl, at least in Nixpkgs, is not compiled with -fPIE. + # XXX: nevermind? as of Nixpkgs 26.05?? + # Oh gods do we need to gate this?? + #'-Crelocation-model=static', +] diff --git a/Cargo.lock b/Cargo.lock index 3ed416d82..f51301c6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,13 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "lix" +version = "0.0.0" +dependencies = [ + "lix-doc", +] + [[package]] name = "lix-doc" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index 0fbb1db51..34c865e62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,17 @@ [workspace] resolver = "2" -members = ["lix/lix-doc"] +members = [ + "lix/lix-doc", + "lix/lix-rs", +] [workspace.package] edition = "2021" + +[profile.dev] +opt-level = 1 + +[profile.release] +debug = "full" +debug-assertions = true +overflow-checks = true diff --git a/lix/libutil/meson.build b/lix/libutil/meson.build index 1892f6c91..31d53df4d 100644 --- a/lix/libutil/meson.build +++ b/lix/libutil/meson.build @@ -261,6 +261,7 @@ archive_setting_definitions = files( 'archive-settings/use-case-hack.md', ) liblix_generated_headers += custom_target( + 'archive-settings.gen.inc', command : [ python.full_path(), '@SOURCE_ROOT@/lix/code-generation/build_settings.py', @@ -278,6 +279,7 @@ feature_setting_definitions = files( 'feature-settings/experimental-features.md', ) liblix_generated_headers += custom_target( + 'feature-settings.gen.inc', command : [ python.full_path(), '@SOURCE_ROOT@/lix/code-generation/build_settings.py', @@ -299,6 +301,7 @@ logging_setting_definitions = files( # keep-sorted end ) liblix_generated_headers += custom_target( + 'logging-settings.gen.inc', command : [ python.full_path(), '@SOURCE_ROOT@/lix/code-generation/build_settings.py', diff --git a/lix/lix-base.pc.in b/lix/lix-base.pc.in index 9b3470ab2..fc6b2618d 100644 --- a/lix/lix-base.pc.in +++ b/lix/lix-base.pc.in @@ -7,4 +7,4 @@ Description: Lix Package Manager (base include dir) Version: @PACKAGE_VERSION@ Requires: kj-async Cflags: -I${includedir} -Libs: -L${libdir} -lrust_monocrate +Libs: -L${libdir} diff --git a/lix/lix-doc/meson.build b/lix/lix-doc/meson.build index 5006c695e..eda13619c 100644 --- a/lix/lix-doc/meson.build +++ b/lix/lix-doc/meson.build @@ -1,36 +1,5 @@ -rnix = dependency('rnix-0.12-rs') -rowan = dependency('rowan-0.15-rs') - -rust = import('rust') - -# This hack is required by the wombo combo of meson bugs: -# Meson does not set the soname for us: https://github.com/mesonbuild/meson/issues/13537 -# Meson ignores link_args for Rust targets: https://github.com/mesonbuild/meson/issues/13538 -lix_doc_rust_args = [ - # link-arg=-soname has no effect when building a static lib, - # since the linker is never actually invoked. - '-C', f'link-arg=-Wl,@soname_arg@,liblix_doc.@dylib_suffix@', -] - -lix_doc = static_library( - 'lix_doc', - sources : files('src/lib.rs'), - dependencies : [ - rowan, - rnix, - ], - rust_args : [ - lix_doc_rust_args, - ], -) - -# NOTE: this dependency object is only used for `rust_monocrate` -liblix_doc_internal = declare_dependency( - link_with : lix_doc, -) - -rust.test( - 'lix_doc_test', - lix_doc, - suite : 'check' +lix_rs_files += files( + 'Cargo.toml', + 'src/lib.rs', + 'src/pprint.rs', ) diff --git a/lix/lix-rs/Cargo.toml b/lix/lix-rs/Cargo.toml new file mode 100644 index 000000000..f6dae1a3f --- /dev/null +++ b/lix/lix-rs/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "lix" +edition = "2021" + +[lib] +name = "lixrs" +path = "src/lib.rs" +crate-type = ["staticlib"] + +[features] +default = [] + +[dependencies] +lix-doc.path = "../lix-doc" diff --git a/lix/lix-rs/meson.build b/lix/lix-rs/meson.build new file mode 100644 index 000000000..8ba2cb755 --- /dev/null +++ b/lix/lix-rs/meson.build @@ -0,0 +1,4 @@ +lix_rs_files += files( + 'Cargo.toml', + 'src/lib.rs', +) diff --git a/lix/lix-rs/src/lib.rs b/lix/lix-rs/src/lib.rs new file mode 100644 index 000000000..09b1b0b91 --- /dev/null +++ b/lix/lix-rs/src/lib.rs @@ -0,0 +1 @@ +extern crate lix_doc; diff --git a/lix/lix.pc.in b/lix/lix.pc.in index 9019382b5..061d8805a 100644 --- a/lix/lix.pc.in +++ b/lix/lix.pc.in @@ -9,4 +9,4 @@ Version: @PACKAGE_VERSION@ Requires: lix-base kj-async @BOEHM_IF_FOUND@ Requires.private: @AWS_SDK_IF_FOUND@ @BOEHM_IF_FOUND@ @CPUID_IF_FOUND@ @SECCOMP_IF_FOUND@ libeditline lowdown ncurses libbrotlidec libbrotlienc libarchive libcrypto capnp-rpc libcurl sqlite3 Cflags: -I${includedir} -Libs: -L${libdir} -lrust_monocrate -llix +Libs: -L${libdir} -llix diff --git a/lix/meson.build b/lix/meson.build index 7b46fdc0e..afcdd5a1c 100644 --- a/lix/meson.build +++ b/lix/meson.build @@ -89,15 +89,15 @@ config_h = configure_file( install_headers(config_h, subdir : 'lix') +lix_rs_files = files( + '../Cargo.toml', + '../Cargo.lock', +) +subdir('lix-rs') + # Subcomponents: these link into artifacts themselves, and have interdependencies. subdir('lix-doc') -subdir('rust-monocrate') - -liblixutil_rs = declare_dependency( - link_with : rust_monocrate, -) - # we only collect sources and generated headers here. real headers are collected and # installed by each subdir due to how meson generates paths for headers it installs. liblix_sources = [] @@ -150,36 +150,109 @@ dependencies = [ seccomp, sqlite, toml11, - # NOTE: this is secretly rust-monocrate in a trenchcoat. - # See that directory's README for more details. - liblixutil_rs, ] if host_machine.system() == 'freebsd' dependencies += [ libprocstat ] endif -liblix_all = library( - 'lix', - liblix_sources, - liblix_generated_headers, - dependencies : dependencies, - # `..` makes the `lix/` subtree of the repo available for includes, - # mirroring our header directory structure once they are installed. - include_directories : [ '..' ], - cpp_args : cpp_args, - cpp_pch : cpp_pch, - install : true, - # FIXME(Qyriad): is this right? - install_rpath : libdir, +lixrs = custom_target( + 'liblixrs', + input : lix_rs_files, + output : 'liblixrs.a', + command : [ + cargo, + '-Z', 'unstable-options', + 'build', + '--artifact-dir=@0@'.format(meson.current_build_dir()), + '-p', + 'lix', + f'--target=@host_triple@', + cargo_jobs_args, + ], + console : true, + env : { + 'MESON_BUILD_DIR': meson.project_build_root(), + }, ) -liblix = declare_dependency( - include_directories : include_directories('..'), - dependencies : dependencies, - link_with : liblix_all, +if meson.can_run_host_binaries() + test( + 'lix-rs-tests', + cargo, + args : [ + 'test', + '--all', '--all-targets', + '--no-fail-fast', + cargo_jobs_args, + ], + suite : 'check', + ) +endif + +liblix_cpp_static = static_library( + 'lix_cpp_static', + liblix_sources, + liblix_generated_headers, + dependencies : [ + dependencies, + ], + include_directories : '..', + cpp_args : cpp_args, + cpp_pch : cpp_pch, ) +if is_darwin + lixrs_link_args = [ '-Wl,-force_load,lix/liblixrs.a' ] +else + lixrs_link_args = [ '-Wl,-L,lix,--whole-archive,-l,lixrs,--no-whole-archive' ] +endif + +if is_static + liblix = declare_dependency( + include_directories : include_directories('..'), + dependencies : dependencies, + link_whole : [ liblix_cpp_static ], + link_args : lixrs_link_args, + sources : [ + custom_target( + input : [ lixrs ], + output : 'lixrs-dep-propagator.gen.hh', + command : [ 'bash', '-c', 'touch "$1"', 'touch', '@OUTPUT@' ], + ), + ], + ) +else + liblix_all_shared = shared_library( + 'lix', + custom_target( + input : [ lixrs ], + output : 'lixrs-dep-propagator.gen.cc', + command : [ 'bash', '-c', 'touch "$1"', 'touch', '@OUTPUT@' ], + ), + dependencies : [ + dependencies, + require_dylib_rtti, + ], + link_whole : [ liblix_cpp_static ], + link_args : lixrs_link_args, + # `..` makes the `lix/` subtree of the repo available for includes, + # mirroring our header directory structure once they are installed. + include_directories : [ '..' ], + cpp_args : cpp_args, + cpp_pch : cpp_pch, + install : true, + # FIXME(Qyriad): is this right? + install_rpath : libdir, + ) + + liblix = declare_dependency( + include_directories : include_directories('..'), + dependencies : dependencies, + link_with : [ liblix_all_shared ], + ) +endif + meson.override_dependency('lix', liblix) # The rest of the subdirectories aren't separate components, diff --git a/lix/nix/meson.build b/lix/nix/meson.build index 01d2c9e6e..1e25ea143 100644 --- a/lix/nix/meson.build +++ b/lix/nix/meson.build @@ -155,9 +155,9 @@ nix_headers = files( # keep-sorted end ) -# You know. The thing you link against with `-liblix`. -iblix = library( - 'iblix', +nix = executable( + 'nix', + files('main.cc'), nix_sources, legacy_sources, nix_settings_headers, @@ -166,6 +166,9 @@ iblix = library( legacy_headers, legacy_generated_headers, include_directories : legacy_include_directories, + cpp_pch : cpp_pch, + install : true, + install_rpath : libdir, dependencies : [ liblix, boehm, @@ -173,41 +176,9 @@ iblix = library( nlohmann_json, kj, libatomic, - ], - cpp_pch : cpp_pch, - install : true, - # FIXME(Qyriad): is this right? - install_rpath : libdir, -) - -libiblix = declare_dependency( - link_with : [iblix], - dependencies : [ - liblix, - ], - include_directories : [ - include_directories('.'), - legacy_include_directories - ], - sources : [ - nix_settings_headers, - nix_generated_headers, - nix_headers, - legacy_headers, - legacy_generated_headers, - ], -) - -nix = executable( - 'nix', - 'main.cc', - cpp_pch : cpp_pch, - install : true, - install_rpath : libdir, - dependencies : [ - libiblix, libasanoptions, ], + cpp_pch : cpp_pch, ) nix_symlinks = [ diff --git a/lix/rust-monocrate/Cargo.toml b/lix/rust-monocrate/Cargo.toml deleted file mode 100644 index 53c2f5745..000000000 --- a/lix/rust-monocrate/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -# This may not actually build with Cargo, but exists to help Meson and to manage lockfiles. -[package] -name = "rust-monocrate" -edition.workspace = true - -[lib] -crate-type = ["staticlib"] -lix-doc = { path = "../lix-doc" } diff --git a/lix/rust-monocrate/README.md b/lix/rust-monocrate/README.md deleted file mode 100644 index 1cbcb552a..000000000 --- a/lix/rust-monocrate/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# What the hell is this? - -This is a cursed solution to the intersection of the following problems: - -- Lix is separated into multiple libraries, which are intended to be buildable as dynamic/shared libraries *or* statlic libraries. -- Those libraries are linked into the Lix executable binary. -- Static libraries also statically link their library dependencies. -- We want to be able to use Rust code in multiple places in the codebase. -- Meson has poor support for Rust and non-Rust sources in the same target, with [internal errors](https://github.com/mesonbuild/meson/issues/15435) preventing mixed targets from linking against non-mixed targets. -- Linking more than one Rust `crate-type = staticlib` is [unsupported](https://github.com/rust-lang/rust/issues/44322) with Rust's standard library. -- Meson [does not support dynamically linking the Rust standard library for `crate-type = staticlib`](https://mesonbuild.com/Release-notes-for-1-9-0.html#new-experimental-option-rust_dynamic_std). -- Meson [ignores `link_args` for Rust targets](https://github.com/mesonbuild/meson/issues/13538) -- Meson [does not set soname of Rust cdylibs](https://github.com/mesonbuild/meson/issues/13537) - -So! What do we do? For any Rust code that Lix wants to use, we always link to a single crate, which will reƫxport Rust code from any crate (in-tree or out-of-tree) we wish to use. -This "monocrate" is *always* built as a static library, and anything in Lix that depends on Rust code will statically link it in. -When the Lix executable is linked against Lix libraries that are built statically, we do *not* `link_whole` them. -As far as we::Qyriad can tell, this is sufficient to not break everything. -But long-term, we should probably solve this problem by migrating to Rust code at top-level, and having Rustc do the final link. diff --git a/lix/rust-monocrate/meson.build b/lix/rust-monocrate/meson.build deleted file mode 100644 index 71a94726c..000000000 --- a/lix/rust-monocrate/meson.build +++ /dev/null @@ -1,19 +0,0 @@ -# This crate is what ultimately links to Lix. All other Rust crates Lix uses must go through this. - -rust_monocrate_sources = files( - 'src/lib.rs', -) - -rust_monocrate = static_library( - 'rust_monocrate', - rust_abi : 'c', - sources : rust_monocrate_sources, - dependencies : [ - liblix_doc_internal, - rnix, - rowan, - ], - # If any installed target links against a static library, - # the static library must also be installed. - install : true, -) diff --git a/lix/rust-monocrate/src/lib.rs b/lix/rust-monocrate/src/lib.rs deleted file mode 100644 index 480f7504c..000000000 --- a/lix/rust-monocrate/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! This is a hack that forces Rust to link all the Lix libs as a single static -//! library for usage when building Lix itself in static mode. -//! -//! The reason for this is that Rust does not support linking multiple -//! staticlibs into one executable, as it will jam a libstd into every single -//! one of them. This is ridiculously goofy because it would be trivially -//! solved by linking libstd separately. -//! -//! https://github.com/rust-lang/rust/issues/44322 -//! -//! It re-exports all the symbols that should be exported to C++. -pub use lix_doc::*; diff --git a/meson.build b/meson.build index 7ad10b9c2..438bb3152 100644 --- a/meson.build +++ b/meson.build @@ -102,6 +102,7 @@ enable_internal_api_docs = get_option('internal-api-docs') doxygen = find_program('doxygen', required : enable_internal_api_docs, native : true) bash = find_program('bash', native : true) +cargo = find_program('cargo') rapidcheck_meson = dependency('rapidcheck', required : enable_internal_api_docs) @@ -128,8 +129,99 @@ if get_option('tests-brief') tests_args += '--gtest_brief=1' endif +cargo_jobs = get_option('cargo-jobs') +cargo_jobs_args = cargo_jobs != 0 ? [ f'-j@cargo_jobs@' ] : [] cxx = meson.get_compiler('cpp') +rustc = meson.get_compiler('rust') +rustc_for_build = meson.get_compiler('rust', native : true) + +# NOT the same thing as is_static. +# The default library can be static and this still be true. +# But on musl, linking a shared library that uses C++ RTTI gives: +# `relocation R_X86_64_32S against symbol `_ZTVN10__cxxabiv117__class_type_infoE' +# can not be used when making a shared object; recompile with -fPIC` +# We don't control Nixpkgs' Musl's position-independent-ness, so instead we just +# disable shared libraries that use RTTI (like liblix) when this is the case. +can_dylib_rtti = cxx.links(''' + #include + void foo() { + std::println(""); + } + ''', + args : '-shared', +) +require_dylib_rtti = declare_dependency( + #'dylib-rtti-disabler', + dependencies : can_dylib_rtti ? [] : disabler(), +) +summary('C++ RTTI in dylibs:', can_dylib_rtti, bool_yn : true) + + +# HACK: Meson really does not tell us the build and host machine triples... +# But we need these to provide Cargo configuration (lix/lix-rs/meson.build). +# We can't even infer them from host_machine/build_machine.foo() methods! +# Look! https://mesonbuild.com/Reference-manual_builtin_host_machine.html +# In `---`, we can query Arch and OS, and vendor is w/e, +# but we can't differentiate `-gnu` and `-musl`! +# Okay okay, so we just ask our compilers. Meson does tell us those, and surely +# the compilers know their own triples right? +# Well... +# +# We can't use `cc -dumpmachine`, because that will also give us things like +# `arm64-apple-darwin24.6.0`, which rustc won't accept for --target. Clang +# does have `-print-effective-triple`, but that does exactly the same thing. +# There aren't compiler builtin `#define`s that we can extract this information +# from either, and there's also no `__MUSL__` macro: +# https://wiki.musl-libc.org/faq#Q:-Why-is-there-no-%3Ccode%3E__MUSL__%3C/code%3E-macro? +# +# Okay... +# rustc has `--print=host-tuple`, but that only prints the the triple for the +# build machine. +# +# NOTE: Yes! Remember! Rust has only "host" and "target", which are what +# everyone else (including Meson and Nixpkgs) calls the "build" and "host" +# machines respectively. +# +# Anyway. There's no way to print the value of `--target`, since, well, +# supposedly if you're passing it you already know it: +# https://github.com/rust-lang/rust/issues/105588#issuecomment-1500285344 +# +# So! Either we use `RUSTC_BOOTSTRAP=1` and use `--print=target-spec-json`: +# https://github.com/rust-lang/rust/issues/38338 +# +# ...or we string-parse the parse the arguments from `rustc.cmd_array()`. +# That seems the least error-prone to me. +# +# FIXME: we can get rid of this whole thing when we have Meson 1.11: +# https://mesonbuild.com/Rust-module.html#compiler_target +host_triple = '' +foreach i : range(rustc.cmd_array().length()) + arg = rustc.cmd_array()[i] + if arg == '--target' + host_triple = rustc.cmd_array()[i + 1] + break + endif + if arg.startswith('--target=') + host_triple = arg.split('=')[1] + break + endif + unset_variable('arg') +endforeach + +# Thank GODS we can at least get this one reasonably. +build_triple = run_command( + # See the above comment about Rustc vs Meson/Nixpkgs terminology on host/build/target. + rustc_for_build.cmd_array(), '--print=host-tuple', + capture : true, + check : true, +).stdout().strip() +# If there was no `--target` argument, assume it's the same as the "host tuple", ig. +if host_triple == '' + host_triple = build_triple +endif +summary('TRIPLE', host_triple) +summary('TRIPLE_FOR_BUILD', build_triple) # tag define to tell us we are compiling lix itself and can thus make # non-namespaced aliases of a utility defines visible. this spares us @@ -228,14 +320,6 @@ is_darwin = host_machine.system() == 'darwin' is_freebsd = host_machine.system() == 'freebsd' is_x64 = host_machine.cpu_family() == 'x86_64' -# Meson knows these, but won't tell us. We need them to hack around some linker shenanigans. -# See the 'Rust Support' section below. -if is_darwin - dylib_suffix = 'dylib' -elif is_linux - dylib_suffix = 'so' -endif - # Per-platform arguments that you should probably pass to shared_module() invocations. # Something like add_project_arguments() can't be scoped on only shared modules, so this # variable is here instead. @@ -254,30 +338,6 @@ elif is_linux endif configdata = configuration_data() -# -# Rust Support -# - -if is_static - rust_dynamic_args = [] -else - rust_dynamic_args = ['-C', 'prefer-dynamic'] -endif - -# This hack is required by the wombo combo of meson bugs: -# Meson does not set the soname for us: https://github.com/mesonbuild/meson/issues/13537 -# Meson ignores link_args for Rust targets: https://github.com/mesonbuild/meson/issues/13538 -# Thus we have to, when the build is dynamic, pass these in rust_args to make it link properly. -if cxx.get_linker_id().startswith('ld64') - # Rust actually sets the -install_name if you pass -C rpath=yes on macOS targets (?!): - # https://github.com/rust-lang/rust/blob/2a8af4f7c8262a90b886bc063fe0c271d2b51a45/compiler/rustc_codegen_ssa/src/back/linker.rs#L441-L445 - # https://rust-lang.zulipchat.com/#narrow/channel/182449-t-compiler.2Fhelp/topic/libstd.2C.20rpath.2C.20dylibs.20and.20packaging - # This seems like a bug, so let's keep passing it deliberately. - soname_arg = '-install_name' -elif cxx.get_linker_id().startswith('ld') - soname_arg = '-soname' -endif - # # Dependencies # @@ -435,7 +495,6 @@ endif # Build-time tools # dot = find_program('dot', required : false, native : true) -cbindgen = find_program('cbindgen', native : true) pymod = import('python') python = pymod.find_installation('python3') @@ -659,6 +718,17 @@ if cxx.get_linker_id() in ['ld.bfd', 'ld.gold'] add_project_link_arguments('-Wl,--no-copy-dt-needed-entries', language : 'cpp') endif +# musl + GNU ld has a complaint: "warning: arch_x86_64_setcontext.S.o: +# missing .note.GNU-stack section implies executable stack. This behaviour +# is deprecated and will be removed in a future version of the linker". +# lld defaults to `-z noexecstack` in the first place: +# https://github.com/llvm/llvm-project/issues/57009 +# So we can just add that to normalize the behavior. +add_project_link_arguments( + cxx.get_supported_link_arguments('-Wl,-z,noexecstack'), + language : 'cpp', +) + if is_freebsd # FreeBSD's `environ` is defined in `crt1.o`, not `libc.so`, # so the linker thinks it's undefined diff --git a/meson.options b/meson.options index 28fa7c85a..c4a941622 100644 --- a/meson.options +++ b/meson.options @@ -108,3 +108,7 @@ option('disable-fibers', type : 'boolean', value : false, option('builtin-dep-closure', type : 'array', description : 'dependency closure used for builtin builder sandboxes. the install paths are included automatically.', ) + +option('cargo-jobs', type : 'integer', value : 0, + description : 'Value to pass for Cargo -j. Use 0 to omit', +) diff --git a/meson/clang-tidy/build_required_targets.py b/meson/clang-tidy/build_required_targets.py index 7da86b667..1516255d6 100755 --- a/meson/clang-tidy/build_required_targets.py +++ b/meson/clang-tidy/build_required_targets.py @@ -24,6 +24,11 @@ def main(): targets = ( [t for t in get_targets_of_rule(args.build_root, "CUSTOM_COMMAND") if t.endswith(".gen.hh")] + + [ + t + for t in get_targets_of_rule(args.build_root, "CUSTOM_COMMAND") + if t.endswith(".gen.cc") + ] + [ t for t in get_targets_of_rule(args.build_root, "CUSTOM_COMMAND_DEP") diff --git a/package.nix b/package.nix index aa5971917..694af9c4e 100644 --- a/package.nix +++ b/package.nix @@ -54,8 +54,8 @@ rapidcheck, removeReferencesTo, rustPlatform, - rust-cbindgen, rustc, + cargo, sqlite, systemtap-lix ? __forDefaults.systemtap-lix, toml11, @@ -289,6 +289,8 @@ let ./subprojects/aws_sdk # Required for meson to generate Cargo wraps ./Cargo.lock + ./Cargo.toml + ./.cargo/config.toml ]); functionalTestFiles = fileset.unions [ @@ -303,6 +305,23 @@ let lldBintools = wrapBintoolsWith { bintools = llvmPackages.bintools; }; + + # Rustc will use `cc` as the linker by default, but we need to link the C++ standard library. + # However the C++ compiler is not unprefixed `c++` in Nixpkgs when cross compiling. + # This is a fair amount of logic to encode in a Cargo config.toml, so the most reasonable + # options are to have meson `configure_file()` a config.toml, + # or just use these environment variables. + cxxLinkerFor = stdenv: lib.getExe' stdenv.cc "${stdenv.cc.targetPrefix}c++"; + hostCargoEnvVar = hostPlatform.rust.cargoEnvVarTarget; + buildCargoEnvVar = buildPlatform.rust.cargoEnvVarTarget; + + # Environment variables that set Cargo config values. + cargoConfigEnv = { + "CARGO_TARGET_${hostCargoEnvVar}_LINKER" = cxxLinkerFor stdenv; + } + // lib.optionalAttrs (hostCargoEnvVar != buildCargoEnvVar) { + "CARGO_TARGET_${buildCargoEnvVar}_LINKER" = cxxLinkerFor buildPackages.clangStdenv; + }; in assert (lintInsteadOfBuild -> lix-clang-tidy != null); stdenv.mkDerivation (finalAttrs: { @@ -427,13 +446,23 @@ stdenv.mkDerivation (finalAttrs: { # We only include CMake so that Meson can locate toml11, which only ships CMake dependency metadata. dontUseCmakeConfigure = true; + # depsBuildBuild is put in PATH before nativeBuildInputs, but clangStdenv.cc's version + # of clang-tidy doesn't seem to work. Thankfully, if we're linting, we don't need to + # compile things for the build platform either. + depsBuildBuild = lib.optionals (!lintInsteadOfBuild) [ + # clangStdenv is spliced but clangStdenv.cc is not: + # https://github.com/NixOS/nixpkgs/issues/211340 + buildPackages.clangStdenv.cc + ]; + nativeBuildInputs = [ + rustPlatform.cargoSetupHook finalAttrs.lixPythonForBuild meson ninja cmake - rust-cbindgen rustc + cargo capnproto # Required for libstd++ assertions that leaks inside of the final binary. removeReferencesTo @@ -572,6 +601,8 @@ stdenv.mkDerivation (finalAttrs: { VERSION_SUFFIX = versionSuffix; } + # Putting these here means they'll also work in the dev shell. + // cargoConfigEnv // lib.optionalAttrs (finalAttrs.buildTestEnv != null) { BUILD_TEST_ENV = finalAttrs.buildTestEnv; } @@ -590,7 +621,7 @@ stdenv.mkDerivation (finalAttrs: { # what the correct `wrapCCWith`/`stdenv.cc.override` incantation is. # But just putting that libstdc++ in `-L` seems to make the driver find # the right things. - NIX_CFLAGS_LINK = "-L${lib.makeLibraryPath [ pkgsStatic.gcc.cc ]}"; + NIX_CFLAGS_LINK = "-L${lib.getLib pkgsStatic.gcc.cc}/lib"; }; cargoDeps = rustPlatform.importCargoLock { lockFile = ./Cargo.lock; }; @@ -773,6 +804,21 @@ stdenv.mkDerivation (finalAttrs: { # we make precompiled C++ stdlib conditional on using Clang. # https://git.lix.systems/lix-project/lix/issues/374 ++ [ (lib.mesonBool "enable-pch-std" stdenv.cc.isClang) ]; + depsBuildBuild = [ + # From a strict deps point-of-view, I'm pretty sure this is literally incorrect. + # The clang-tidy from clang-tools is for checking source files that are compiled + # for the host machine. + # However, we need a clang-tools version of clang-tidy in PATH before + # clangStdenv.cc, and it seems like all depsBuildBuild things come before + # "packages" things. + # I also tried just putting these in `packages` with the correct ordering, + # but that did not work. + # I guess the platform offsets really do matter here, even in the devshell. + buildPackages.llvmPackages.clang-tools + + # This *has* clang-tidy, but it doesn't (seem to?) work. + buildPackages.clangStdenv.cc + ]; packages = lib.optional (stdenv.cc.isClang && hostPlatform == buildPlatform) llvmPackages.clang-tools