Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CMake: Somewhat simplify separate compiler and runtime builds, incl. cross-compiling LDC itself #4872

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

kinke
Copy link
Member

@kinke kinke commented Mar 9, 2025

Which allows building the all target to cross-compile all LDC executables. And to install those executables, as well as the LTO linker plugin and compiler-rt libraries from cross-LLVM.

@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

The config file is generated in the runtime CMakeLists.txt but installed in the root CMakeLists.txt, leading to install error if the root cmake doesn't descend in runtime. Keeping the generation+install steps in the same cmake file was one of the things that I wanted to accomplish in my series of changes.

@kinke kinke force-pushed the cmake_cross_excl_rt branch 2 times, most recently from 863c355 to b6a2ec4 Compare March 9, 2025 14:04
Which allows building the `all` target to cross-compile all LDC executables.
And to install those executables, as well as the LTO linker plugin and
compiler-rt libraries from cross-LLVM.
@kinke kinke force-pushed the cmake_cross_excl_rt branch 2 times, most recently from 392e78c to 276cda4 Compare March 9, 2025 15:17
@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

In my separate compiler + manual multilib build I use this patch. Maybe it can help you?

@kinke kinke force-pushed the cmake_cross_excl_rt branch from 276cda4 to e93ed29 Compare March 9, 2025 15:31
@kinke
Copy link
Member Author

kinke commented Mar 9, 2025

Instead of including (incomplete) imports when installing a standalone library build, I think it makes much more sense to generate matching ldc2.conf files whenever building the runtime. AFAICT, the final configured files only depend on the runtime build config; the only exception being LDC_WITH_LLD, which controls -link-internally as default wasm switch (and that can be propagated from the compiler in ldc-build-runtime).

This e.g. allows using the etc/ldc2_install.conf from the cross-compiled default libs here as base for the bundled ldc2.conf of cross-compiled packages (so not just the libs, but libs + ldc2.conf).

And in #4870, we could go a bit further, converting the default section of the new libs' ldc2.conf to an appended section for the existing compiler ldc2.conf.

In a `etc/` directory. When included from the LDC parent build,
switch from `../bin/` to `../etc/`.
@kinke kinke force-pushed the cmake_cross_excl_rt branch from e93ed29 to 9c34ddd Compare March 9, 2025 15:57
@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

Instead of including (incomplete) imports when installing a standalone library build, I think it makes much more sense to generate matching ldc2.conf files whenever building the runtime.

I'm not sure what you mean by incomplete imports. As far as I see it, currently there are:

  1. import/{std,core,stc}. These are platform-independent and can be safely installed, in native and in cross builds, and don't depend on anything else
  2. The jit ldc/dynamic_compile.d. runtime/CMakeLists.txt doesn't currently go and look for LLVM so jit-rt is never installed in a cross-build
  3. The gccbuiltins. These ones depend on the LLVM ldc2 is linked against and aren't built in cross

So, the only headers the runtime should install are phobos and druntime, which don't require any special handling.

AFAICT, the final configured files only depend on the runtime build config; the only exception being LDC_WITH_LLD, which controls -link-internally as default wasm switch (and that can be propagated from the compiler in ldc-build-runtime).

True, it mostly depends on the runtime. But that's the issue, it isn't completely independent from the compiler. Even if there are a few switches that the compiler builds cares about: LDC_WITH_LLD and COMPILER_RT_LIBDIR (in the case of the compiler-rt libs not being copied to ldc2's libdir); we can't simply ignore one of the 2 builds.

What you're trying to do with ldc-build-runtime can work, i.e. embedding those switches from the compiler build, if you only take into account one aspect of cross-compiling.

I think both of us are thinking of different things when we say cross-builds. When I say a cross-build I mean building the runtime (and possible the compiler) for another architecture and installing the result on a system running that architecture. When you say it I'm getting the impression that you mean building the runtime for another architecture, but installing that result on the build machine, not on something else.

I did think about the ldc2.conf situation and I think that the easiest way of supporting both mine and (what I assume is) your use case is having it be a directory. I'm pretty sure that this would be a lot easier than going around and adding special cases to the cmake file but it won't be too hard to warrant dropping one of the two possible use cases

@kinke
Copy link
Member Author

kinke commented Mar 9, 2025

I'm not sure what you mean by incomplete imports.

Yeah, missing gccbuiltins.

When I say a cross-build I mean building the runtime (and possible the compiler) for another architecture and installing the result on a system running that architecture. When you say it I'm getting the impression that you mean building the runtime for another architecture, but installing that result on the build machine, not on something else.

If we are talking about CMake-installing (make/ninja install), then yeah, I only focus on that being run on the host which (cross)compiled the runtime.

But those installed artifacts (libraries and object file) then work on every other box using another prebuilt LDC with matching version (and matching target C cross-toolchain), for cross-linking. They just need to be copied to that other box, and the ldc2.conf extended (e.g., based on the now-produced ldc2.conf as part of ninja install). That's why https://wiki.dlang.org/Cross-compiling_with_LDC suggests using the prebuilt official packages from GitHub, and which could be automated via a tool like ldc-add-target.

I'm still not sure what you wanna do for Fedora. I guess you could enable cross-compiling to the other archs supported by Fedora, out of the box or whatever. But you'll most likely never be able to allow your users to easily cross-compile to Windows, Mac or Android, if you don't wanna depend on the official LDC binaries from GitHub.

E.g., with the existing prebuilt multilib Linux package, I can easily cross-compile & -link a x86_64-linux-gnu executable on Windows; all I need is a cross-gcc toolchain (tested myself years ago). I guess the binary would run on Fedora too. Similar for Android; the libs in the prebuilt packages work when using the same NDK version, regardless of whether the host is Linux or Windows. And cross-compiling to Windows on a Posix box is the simplest option of all, due to the bundled MinGW-based libraries in the official Windows packages - with builtin LLD, these Posix users don't need any C toolchain for targeting Windows.

@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

Let me think about this for a little bit and let's try to keep the conversation in one issue, either this or #4870, or some new one, since my fingers are getting dizzy from switching tabs and some of the points we're making are relevant to both discussions

@kinke
Copy link
Member Author

kinke commented Mar 9, 2025

When I say a cross-build I mean building the runtime (and possible the compiler) for another architecture and installing the result on a system running that architecture.

Cross-compiling the compiler itself isn't trivial as you probably know by now :) - but that should be an exotic use case, mainly as workaround for missing native CI runners or when trying to conquer a new platform for which there's no usable D compiler yet. If GDC is available and working for a new platform (which LDC doesn't completely support yet), using that as host D (cross-)compiler is strongly encouraged - for LDC to work, D and C++ interop needs to work, and while GDC doesn't have to deal with platform-specific ABI quirks, LDC does, so LDC can only build a working version of itself as soon as its ABI is fully working for the desired target.

Cross-compiling the runtime libs only is much simpler, and ldc-build-runtime intended to make that (and runtime customizations) as simple as possible, for both package managers and power users. - Are you specifically referring to building the 32-bit x86 libs on x86_64 hosts only, or which other targets do you plan on building the runtimes for, on which hosts? Just to get an idea. - The existing ldc-build-runtime usages for CI should already show some expected use cases:

  • adding libs for another OS (variant) - macOS packages bundling iOS libs, for the same arch
  • adding libs for another arch - Android ARM(64) packages bundling the corresponding x86(_64) simulator libraries, and the Windows multilib package additionally bundling ARM64 ones
  • cross-compiling the default libraries as prerequisite for the cross-compiled compiler (which is linked against those pre-cross-compiled druntime/Phobos)

@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

Ok, I've thought about how to word this.

Let's say that our goal is strictly being able to cross-compile the runtime libs and install them on the build machine, so your use case. You've implemented this in #4870 so let's start with that solution, with ldc-build-runtime calling ninja install and installing the artifacts to lib-cross but no header files.

You also said that you could augment this solution with the ~= operator and had ldc-build-runtime also append a config section to save users from doing that manually. You could have ldc-build-runtime hard-code the paths and write the values manually or you could generate a stub-config during the cmake build and use that by copy-pasting it at the end of the current config.

Let's go with the cmake approach since there's already code in there that generated the switches.


I am proposing that you can augment this solution in a way that benefits both my and your use case.
In the cmake file you would have something like:

if(NOT CMAKE_CROSS_COMPILING) # OR (NOT LDC_EXE)
  list(APPEND LDC_DEFAULT_SWITCHES "-defaultlib=..." "-Iruntime/import")
  configure_file(../ldc2.conf.in etc/ldc2.conf)
  configure_file(../ldc2_install.conf.in etc/ldc2_install.conf)
endif()

But you can change that to:

list(APPEND LDC_DEFAULT_SWITCHES "-defaultlib=..." "-Iruntime/import")
configure_file(../ldc2.conf.in etc/ldc2.conf)
configure_file(../ldc2_install.conf.in etc/ldc2_install.conf)

and move the runtime-independent switches to the root CMakeLists.txt, dropping the if:

list(APPEND LDC_DEFAULT_SWITCHES .....)
list(APPEND WASM_DEFAULT_SWITCHES .....)
if(LDC_WITH_LLD)
    list(APPEND WASM_DEFAULT_SWITCHES ...)
endif()
configure_file(ldc2.conf.in etc/ldc2.conf)
configure_file(../ldc2_install.conf.in etc/ldc2_install.conf)

You would get the same result when cross-compiling the runtime but, of course, the native compiler build is now broken. And here's my main point, let's just have the compiler and the runtime generate separate config files. This is very important but I'm not suggesting the compiler being able to support ldc2.conf being a directory, like my other PR, we can actually use a post build command to aggregate those 2 files back into the expected ldc2.conf and ldc2_install.conf.

The end result would look like:

  • CMakeLists.txt
    # generate(build/ldc2.conf.d/compiler)
    # generate(build/ldc2_isntall.conf.d/compiler)
    ....
    foreach(conf ldc2.conf.d ldc2_install.conf.d)
        add_custom_command(${LDC_EXE} POST_BUILD COMMAND tools/cat-all-files-from.sh build/etc/${conf})
    endforeach()
  • runtime/CMakeLists.txt
    # generate(build/ldc2.conf.d/runtime)
    # generate(build/ldc2_install.conf.d/runtime)

Here generated could be the same configure_file or file(WRITE ...) but it can also be a specific function like in my other PR:

makeConfSection(NAME "wasm"
    SECTION "^wasm(32|64)-"
    SWITCHES -defaultlib= -L-z -Lstack-size=1048576 -L--stack-first -L--export-dynamic
)

The end result is the same as what you originally had, but you spent:

  • moving the code around a bit
  • adding a POST_BUILD command

and got:

  • losing the conditional in runtime/CMakeLists.txt
  • losing the dependencies on the config file (the multilib section) in the runtime cmake
  • simpler code because the 2 cmake files no longer fight for the config file
  • almost supporting my use case by having compiler specific switches be configured when the compiler is built
  • file parity between a native and a cross build (if you unconditionally install the headers like I suggested) which is also something I need.

@kinke kinke force-pushed the cmake_cross_excl_rt branch from 8ae3946 to 5b317d2 Compare March 9, 2025 18:52
@kinke
Copy link
Member Author

kinke commented Mar 9, 2025

The workflow here for gathering the package contents for a cross-compiled compiler+runtime was indeed simplified by keeping on letting the runtime install the imports, and copying the gccbuiltins manually - that allows skipping the bootstrap compiler installation again (#4871). So I'll change that in #4870.

@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

They just need to be copied to that other box, and the ldc2.conf extended (e.g., based on the now-produced ldc2.conf as part of ninja install).

For me copying to another box means letting the package manager install them and that implies that a cross-build would install the same files (and the same contents) as a native build. I can't touch files that are already built so I need the cross-build to be functional when the build machine is done with it, not when the host machine installs it. (Note that technically I can change post-installed files but that feature is not meant for this, it is meant for updating cache files like the XDG application database)

I'm still not sure what you wanna do for Fedora. I guess you could enable cross-compiling to the other archs supported by Fedora, out of the box or whatever. But you'll most likely never be able to allow your users to easily cross-compile to Windows, Mac or Android

I'm on Gentoo and the situation here is a bit different. You can technically install it on Windows (in cygwin) and on Android (in tmux) so, technically, I could target those systems. Of course, this is more theoretical because the user base is so little there that you would encounter a lot of bugs that you would have to solve yourself but Gentoo does provide the tools to accomplish this, at least on paper

Cross-compiling the compiler itself isn't trivial as you probably know by now :) - but that should be an exotic use case,

I agree with it being more rare in normal circumstances but on a source-based distribution like Gentoo it's not that to build (or cross-build) things. With it being non-trivial I don't really agree. I do have an implementation that works mostly but that takes advantage of some assumptions that only hold true for Gentoo. Regardless, my argument is that this exotic use case doesn't require that much more work here to support.

Cross-compiling the runtime libs only is much simpler, and ldc-build-runtime intended to make that (and runtime customizations) as simple as possible

As a sidenote, I prefer using the cmake wrappers made available by Gentoo since they already handle the C-toolchain side of cross-compiling and I expect that it will be less work to reimplement the missing features from ldc-build-runtime than use ldc-build-runtime and implement a whole separate toolchain. But you can assume that if ldc-build-runtime can do something I can do it myself, I just need to write the shell code for it.

Are you specifically referring to building the 32-bit x86 libs on x86_64 hosts only, or which other targets do you plan on building the runtimes for, on which hosts?

Multilib builds are somewhat related by not that required. I already can do this, with the caveat that I use the config file from the runtime build, since, like you said, it contains everything needed to get the compiler working. Having proper support for cross-build would simplify my work a little as well as improve the ldc2.conf short-commings but the priority for me right now is making sure that cross compiling works fine (for both my and your use case)

In terms of other architectures I probably won't target more than x86_64-linux-gnu and maybe musl and aarch64-linux-gnu simply because I don't have any more hardware to test this on and I don't expect many people to be using other arches since the D portion of Gentoo is not that populated.

A more technical perspective is being able to have self-hosted compilers for niche-arches like sparc and mips which Gentoo technically supports but that run very slowly. It would be much nicer to be able to build a sparc ldc2 (compiler and runtime) on a amd64 host since it would be way faster and the simply installing the products on other systems. This is all just theory and I don't know if D would ever get there.

Anyway I've done some recent work also on gdc and, with the help on someone more experienced, we got that package to work when cross-compiled so I can rely on that being the bootstrap-compiler on other arches, if need may arise.

@kinke kinke changed the title CMake: Exclude runtime libraries when cross-compiling LDC CMake: Somewhat simplify cross-compiling LDC itself Mar 9, 2025
@kinke kinke changed the title CMake: Somewhat simplify cross-compiling LDC itself CMake: Somewhat simplify separate compiler and runtime builds, incl. cross-compiling LDC itself Mar 10, 2025
@kinke kinke force-pushed the cmake_cross_excl_rt branch from 999f471 to 2244172 Compare March 10, 2025 21:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants