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

ldc-build-runtime: Add --installWithLibSuffix option #4870

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

kinke
Copy link
Member

@kinke kinke commented Mar 9, 2025

To simplify copying the libraries to an existing LDC installation.

@kinke kinke force-pushed the build_runtime_install branch 2 times, most recently from a2a00b6 to 074ed6d Compare March 9, 2025 11:33
To simplify copying the libraries to an existing LDC installation.
@kinke kinke force-pushed the build_runtime_install branch from 074ed6d to 9be3b9f Compare March 9, 2025 12:51
@kinke kinke marked this pull request as ready for review March 9, 2025 12:52
@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

This somewhat breaks me relying on the runtime installing the include files. Is there any reason to stop installing them when the runtime is built separately? There shouldn't be an issue if they overwrite the files belonging to the host ldc2.

@kinke
Copy link
Member Author

kinke commented Mar 9, 2025

I don't think there's a good reason to duplicate the imports with library bundles; the bundled imports don't depend on the target. And the runtime build in isolation wouldn't have them all; the ldc/gccbuiltins_*.di modules are auto-generated via LLVM when building the compiler (and depend on the enabled codegen backends for that linked LLVM, as well as the generated gen_gccbuiltins tool being runnable on the host in order to generate them).

@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

I didn't mean to duplicate the imports. I meant leaving ninja install to also install the headers alongside lib-cross. Like:

  1. ninja -C host-build install
    • installs bin/*
    • installs lib/*
    • installs import/*
  2. ninja -C runtime-cross-build install
    • installs lib-cross/*
    • installs import/*, overwriting the ones in step 1 but given that they are the same files there won't be any issues

This way the runtime cmake stays a little bit more independent from the compiler, which I think will help with maintenance in the future. Also my use case keeps working.

@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

the ldc/gccbuiltins_*.di modules are auto-generated via LLVM when building the compiler (and depend on the enabled codegen backends for that linked LLVM

They also depend on the specific version LLVM. But I have a question, would it be correct to have these files generated and stored in the repo and then, in cmake, rather than handling the generator program as well as cross-compilation we would simply install the files we already have?

@kinke
Copy link
Member Author

kinke commented Mar 9, 2025

I guess we could, but as we support multiple LLVM versions, I don't think it would simplify things, rather just one more extra step when adding support for a new LLVM version.

@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

It would be a 1-time cost when bumping LLVM compared to always paying the cost of compiling gen_gccbuiltins as well as manually copying the files when cross-compiling, which can lead to errors because, like you said, those files depend on the enabled LLVM targets alongside its major version.

It would also slightly clean up the cmake files as there wouldn't be any more need to add_custom_command and add_custom_target, as well as carrying the GCCBUILTINS dependency downstream on everything that is compiled by LDC_EXE. The latter, though, can be fixed right now by making bin/ldc2 depend on GCCBUILTINS.

@kinke
Copy link
Member Author

kinke commented Mar 9, 2025

It would be a 1-time cost when bumping LLVM

Twice a year or so. And would most likely need to be CI-automated or at least verified, to make sure that process is robust, not depend on how the guy doing it had configured his LLVM.

compared to always paying the cost of compiling gen_gccbuiltins

I think that's negligible; chances are high this tool is built & run while the compiler frontend is being compiled and waited for.

as well as manually copying the files when cross-compiling, which can lead to errors because, like you said, those files depend on the enabled LLVM targets alongside its major version.

If you have a prebuilt ldc-build-runtime, you have the compiler and its imports already. No need to copy anything. - If you don't use ldc-build-runtime, but CMake directly for a standalone runtime build, and need the imports, then please elaborate or link to some code.

@kinke
Copy link
Member Author

kinke commented Mar 9, 2025

Oh and if we had multiple gccbuiltins_<arch>.di versions (one per major LLVM version), then it's not a matter of simply putting them in the druntime src tree, but would need to be CMake-configured depending on the LLVM version.

@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

If you have a prebuilt ldc-build-runtime, you have the compiler and its imports already. No need to copy anything. - If you don't use ldc-build-runtime, but CMake directly for a standalone runtime build, and need the imports, then please elaborate or link to some code.

Alright, let me show you how I would need to express the runtime correctly embedding the compiler-rt libdir and gccbuiltins if I were to package it in Gentoo.

1. Build the compiler

LLVM_SLOT=18 emerge dev-lang/ldc2

This builds and installs the compiler. The files that would be installing (assuming we don't build gccbuiltns in this step) would be:

/usr/bin/ldmd2-1.40
/usr/bin/ldc2-1.40
/usr/share/doc/ldc2-1.40.0/README.md.bz2
/usr/lib/ldc2/1.40/usr/share/bash-completion/completions/ldc2
/usr/lib/ldc2/1.40/bin/ldc-build-plugin
/usr/lib/ldc2/1.40/bin/timetrace2txt
/usr/lib/ldc2/1.40/bin/ldc-prune-cache
/usr/lib/ldc2/1.40/bin/ldc-build-runtime
/usr/lib/ldc2/1.40/bin/ldmd2
/usr/lib/ldc2/1.40/bin/ldc2

2. Build the runtime

emerge dev-libs/ldc2-runtime

This would install, amongst the standard headers and libs:

/usr/lib/ldc2/1.40/etc/ldc2.conf
/usr/lib/ldc2/1.40/include/d/ldc/gccbuiltins_x86.di
/usr/lib/ldc2/1.40/include/d/ldc/gccbuiltins_s390.di
/usr/lib/ldc2/1.40/include/d/ldc/gccbuiltins_riscv.di

Now, those gccbuiltins and ldc2.conf file need to be generated from a LLVM version. Let's say that I called ldc2 -v and checked for what version of LLVM it was compiled, and take the LLVM major version from there. Everything fine so far.

3. Rebuild the compiler

LLVM_SLOT=15 emerge ldc2

Now we end up in a situation in which the generated gccbuiltins are built for LLVM 18 but ldc2 is built against LLVM 15. This is broken as trying to use one of the intrinsics that is not understood by LLVM 15 would result in your program giving liking errors.


If I wanted to fix this I would then need to make ldc2-runtime depend on a LLVM version. So:

2 Building the runtime (fixed)

LLVM_SLOT=18 emerge ldc2-runtime

I can specify in the ebuild file that the LLVM version for ldc2-runtime must match the one for ldc2 and the build system will take care of making sure that happens. This, unfortunately has a consequence.

3 Rebuilding the compiler (downside)

LLVM_SLOT=15 emerge ldc2

If I did that portage would correctly enforce that the LLVM_SLOT for the compiler and the runtime must match and, alongside the compiler, it would also rebuild the runtime and this is the issue.

What you are suggesting makes the runtime build be dependent on a LLVM version (excluding jit-rt which actually depends on LLVM) which is not reflecting the source code.

You may argue that this is a consequence of me doing separate compiler and runtime builds but the exact same issues appears if I introduce cross-compiling, and there I must do the builds separately.

The only way to have the code function like it should would be:

  1. Installing gccbuiltsins with the compiler
  2. Having ldc2.conf be a directory (for the compiler-rt libdir, but I consider that a add-in bonus so not a requirement right now)

@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

My main concern right now is not whether we can manage to get a runtime cross-build to play nicely with an already installed ldc2, it's putting in the work to add all these special cases for doing out-of-compiler and all-at-once builds, where the a way better result can be achieved by doing some smaller fundamental changes

@kinke
Copy link
Member Author

kinke commented Mar 9, 2025

Alright, let me show you how I would need to express the runtime correctly embedding the compiler-rt libdir and gccbuiltins if I were to package it in Gentoo.

1. Build the compiler

LLVM_SLOT=18 emerge dev-lang/ldc2

This builds and installs the compiler. The files that would be installing (assuming we don't build gccbuiltns in this step) would be:

/usr/bin/ldmd2-1.40
/usr/bin/ldc2-1.40
/usr/share/doc/ldc2-1.40.0/README.md.bz2
/usr/lib/ldc2/1.40/usr/share/bash-completion/completions/ldc2
/usr/lib/ldc2/1.40/bin/ldc-build-plugin
/usr/lib/ldc2/1.40/bin/timetrace2txt
/usr/lib/ldc2/1.40/bin/ldc-prune-cache
/usr/lib/ldc2/1.40/bin/ldc-build-runtime
/usr/lib/ldc2/1.40/bin/ldmd2
/usr/lib/ldc2/1.40/bin/ldc2

2. Build the runtime

emerge dev-libs/ldc2-runtime

This would install, amongst the standard headers and libs:

/usr/lib/ldc2/1.40/etc/ldc2.conf
/usr/lib/ldc2/1.40/include/d/ldc/gccbuiltins_x86.di
/usr/lib/ldc2/1.40/include/d/ldc/gccbuiltins_s390.di
/usr/lib/ldc2/1.40/include/d/ldc/gccbuiltins_riscv.di

AFAICT, that's the problem right there. IMHO, the imports belong in the compiler package, not the runtime package - they don't depend on target specifics or other runtime-package parameters, but do depend on the LLVM version the compiler was linked against - for ldc/gccbuiltins_*.di. [And the compiler is implicitly tied to its druntime and Phobos versions, so there's no way to couple a v1.40 LDC with a v1.41 LDC runtime.]

Now you can of course bundle the imports with the runtime packages - but not the gccbuiltins. Those definitely need to be bundled with the compiler, because of the LLVM dependency. But having two import roots (normal imports from runtime package, and gccbuiltins dir from compiler package) means ldc2.conf trouble.

So to me, the natural solution would be to bundle all imports with the compiler package. And to restrict the runtime packages to prebuilt artifacts, possibly in hardcoded locations expected by an ldc2.conf bundled with the compiler. Then you could e.g. have separate runtime packages for static/dynamic linking, only providing the prebuilt libs and object files, not each version bundling its own set of identical imports (the same goes for potential cross-runtime packages, or 32/64-bit package versions etc.). E.g., the Alpine LDC runtime packages provide the library artifacts exclusively:

The compiler package provides imports and ldc2.conf:

@kinke kinke marked this pull request as draft March 9, 2025 20:25
@the-horo
Copy link
Contributor

the-horo commented Mar 9, 2025

AFAICT, that's the problem right there. IMHO, the imports belong in the compiler package, not the runtime package - they don't depend on target specifics or other runtime-package parameters, but do depend on the LLVM version the compiler was linked against - for ldc/gccbuiltins_*.di. [And the compiler is implicitly tied to its druntime and Phobos versions, so there's no way to couple a v1.40 LDC with a v1.41 LDC runtime.]

Yes! This is what I ended up with and it was part of my changes that in submitted in regards to ldc2.conf. Putting all the gccbuiltins bits into the root CMakeLists.txt and not have the runtime bother with them.

So to me, the natural solution would be to bundle all imports with the compiler package.

it makes sense for the gccbuiltins to be built & installed as part of the compiler but the stdlib headers is another thing entirely. If my goal is getting separate compiler and runtime builds then isn't it weird that I would want to mix the 2 even more? In my world if I rm -rf runtime in the ldc source tree and then built what was left I would expect that I would get the portions of the compiler, so executables + gccbuilts, or at least that's what I want to achieve.

Note that the arch-family packages don't install the headers as part of the libphobos because, probably, they don't handle static-libs & multilib as cleanly as Gentoo. In Gentoo the multilib package is a different configuration of the ldc2-runtime, so the plain package installs the headers and lib64 but the multilib would install the headers, lib64 and lib32. For Arch you couldn't do that because the x86 and x86_64 libphobos are different packages (I think) so you couldn't have both of them install the headers.

Additionally, their packages seems to build the ldc2 package once (compiler + runtime) and the split their install, and that's not what I need https://gitlab.archlinux.org/archlinux/packaging/packages/ldc/-/blob/main/PKGBUILD?ref_type=heads#L77

@kinke
Copy link
Member Author

kinke commented Mar 9, 2025

How about we install the imports as part of installing the compiler, not the runtime? It may sound weird at first, but as they are ideally bundled with the compiler package, it IMO makes sense to install/copy them when installing the compiler - plus they'd be complete with the gccbuiltins headers that way.

In that case, a pure compiler build/install without runtime would still need the runtime/ subdir, incl. Phobos submodule. We could add an extra BUILD_RUNTIME=OFF option to NOT include runtime/CMakeLists.txt for such pure-compiler builds.

@the-horo
Copy link
Contributor

It may sound weird at first, but as they are ideally bundled with the compiler package

I think what you mean is that bundling the headers with the compiler would mark the separation:

  • install the compiler package if you intend to build D applications (so you get the ldc2 binary + the stdlib headers)
  • install the runtime package if you intend to run already-built applications (so you get the lib*-ldc files)

This matches what some distributions do:

  • fedora

    • ldc
      • the stdlib + gccbuiltins headers
      • the compiler binaries
      • the config file
    • ldc-libs
      • the actual library files (both debug and release)
  • alpine

But not others:

  • arch

    • ldc
      • the compiler binaries
      • the config file
    • liblphobos
      • the libs (all shared, static, debug, release, lto variants)
      • the headers (including gccbuiltins)
  • artix
    Same as arch

  • debian

  • gentoo
    (Open to changes)

    • dev-lang/ldc2
      • the compiler binaries
      • gccbuiltins
    • dev-libs/ldc2-runtime
      • the stdlib headers
      • the lib* files
      • the config file

But, in all those cases (with the exception of Gentoo) the ldc package is actually built as a whole, and then split off into different tarballs for distribution. This is not what I'm asking for as I need a way to build the different "tarballs" independently from each other. We could go and try to take an approach from the above but choosing simply because somebody else does it is not enough.


However, the argument of installing the phobos & druntime headers as part of the compiler build is based on the premise that those files are architecturally (more correctly runtime-build) independent, which they aren't fully.

ldc/dynamic_compile.d

This file is installed only if ldc2 has been compiled with -DLDC_DYNAMIC_COMPILE=ON. Credit where credit is due, there is an argument for installing this file with the compiler because it would make sure that if you build the compiler with dynamic compile support you will have that file installed, regardless of how you build the runtime. The issue is that the runtime build is still not free from LDC_DYNAMIC_COMPILE.

If installing this file with the compile prevented the runtime build from depending on LDC_DYNAMIC_COMPILE I would be much more sympathetic to your point, but since there will still be needed duplication and the possibility of "getting it wrong" (different values for LDC_DYNAMIC_COMPILE between the compiler and the runtime build) I don't see any clear benefit of installing this file with the compiler, it would be basically the same.

core/sys/linux/* et friends.

Those files contain the D declarations for some system functions. The headers got a pretty long way using version(TRG_SYSTEM). The issue is that this is again not enough. I've already run into this in dlang/dmd#17485 but the gist is currently there is no way to say that a function exists since some glibc version. The current approach is "glibc-version independent" by making sure that all the functions have been added in an ancient glibc version. This is a bug not a feature.

It's not fine to rely on this broken behavior and say that those files are independent of the built target. Technically those declarations should be inferred from the glibc headers when building a D application but that sounds like a lot of work so it's OK not to fully support this use case and resort to the version of glibc druntime & phobos were built against. This would imply having those files or, more cleanly, some separate config.d file which is generated when building the runtime, live in a separate directory (much like lib<arch>) that is included when targeting that triple. Something like:

.
├── include
│   └── d
│       ├── core
│       │   └── sys
│       │       └── linux
│       │           └── unistd.d
│       ├── etc
│       └── std
├── lib
│   ├── include
│   │   └── d
│   │       └── core
│   │           └── sys
│   │               └── linux
│   │                   └── config.d
│   ├── libdruntime-ldc-shared.so
│   └── libphobos-ldc-shared.so
└── lib-cross
    ├── include
    │   └── d
    │       └── core
    │           └── sys
    │               └── linux
    │                   └── config.d
    ├── libdruntime-ldc-shared.so
    └── libphobos-ldc-shared.so

Of course, this isn't argument for what to do with the std and etc packages, those can still be installed with the compiler and my solution would still work. All I wanted to point out is that the runtime still needs to install some of the headers.


The final argument is simply based on how the implementation would look. Does it sound better to have 6 if(BUILD_RUNTIME), that may change in the future, across the runtime/CMakeLists.txt and the cmake files of the other modules or would it be cleaner to have one if(BUILD_RUNTIME) in the ldc CMakeLists.txt that simply skipped going into the runtime subdirectory?

In that case, a pure compiler build/install without runtime would still need the runtime/ subdir, incl. Phobos submodule. We could add an extra BUILD_RUNTIME=OFF option to NOT include runtime/CMakeLists.txt for such pure-compiler builds.

I don't understand what you mean. Would your solution have two file(GLOB PHOBOS2_STD ) in both the root CMakeLists.txt and runtime/CMakeLists.txt, the former for installing them and the latter for compiling them?

@kinke
Copy link
Member Author

kinke commented Mar 10, 2025

Thx for the distros comparison! Yeah, I take it hardly any other distro goes the way of building compiler and runtime separately, so haven't hit the issues you have with the Gentoo source packages - where suddenly it becomes a question of installing which project yields which files. And e.g. the ugly asymmetry that the ldc2.conf is only needed by the compiler, but (almost exclusively) depends on the runtime config. And similarly, imports/headers only required by the compiler, but the source files obviously belonging to the runtime project.

In that case, a pure compiler build/install without runtime would still need the runtime/ subdir, incl. Phobos submodule. We could add an extra BUILD_RUNTIME=OFF option to NOT include runtime/CMakeLists.txt for such pure-compiler builds.

I don't understand what you mean. Would your solution have two file(GLOB PHOBOS2_STD ) in both the root CMakeLists.txt and runtime/CMakeLists.txt, the former for installing them and the latter for compiling them?

Basically moving this to the top-level (compiler) CMakeLists.txt (edit: note, no PHOBOS2_STD etc. - those lists are used for compiling, not for installing):

ldc/runtime/CMakeLists.txt

Lines 893 to 904 in 9f4184e

set(DRUNTIME_PACKAGES core etc ldc)
install(FILES ${RUNTIME_DIR}/src/object.d ${RUNTIME_DIR}/src/__importc_builtins.di ${RUNTIME_DIR}/src/importc.h DESTINATION ${INCLUDE_INSTALL_DIR})
foreach(p ${DRUNTIME_PACKAGES})
install(DIRECTORY ${RUNTIME_DIR}/src/${p} DESTINATION ${INCLUDE_INSTALL_DIR} FILES_MATCHING PATTERN "*.d")
install(DIRECTORY ${RUNTIME_DIR}/src/${p} DESTINATION ${INCLUDE_INSTALL_DIR} FILES_MATCHING PATTERN "*.di")
endforeach()
if(PHOBOS2_DIR)
install(DIRECTORY ${PHOBOS2_DIR}/std DESTINATION ${INCLUDE_INSTALL_DIR} FILES_MATCHING PATTERN "*.d")
install(DIRECTORY ${PHOBOS2_DIR}/etc DESTINATION ${INCLUDE_INSTALL_DIR} FILES_MATCHING PATTERN "*.d")
endif()
install(FILES ${GCCBUILTINS} DESTINATION ${INCLUDE_INSTALL_DIR}/ldc)

I wanna avoid including runtime/CMakeLists.txt for a compiler-only build, not hackily disabling the build-runtime pieces in that file.


ldc/dynamic_compile.d

I think this was an oversight, and think this should be always installed, regardless of whether the jitrt library has been built or not. It's the only violation of the every-compiler-package-bundles-the-same-imports rule (for the same compiler version obviously), modulo incomplete gccbuiltins headers (depending on enabled LLVM backends as well as LLVM version).

core/sys/linux/* et friends.

I don't see glibc versioning as a blocker for the bundled imports. FreeBSD is similar and needs a predefined version to select the proper symbols; this predefined version (FreeBSD_8 to FreeBSD_15) is set according to the used triple. So I guess we can at one point handle versioned glibc symbols similarly if the need really arises.

As long as I keep watch, there aren't going to be any target-dependent imports, i.e., different trees. In druntime and Phobos, target specifics are handled via version / static if etc. I really like these complete imports, not just because they make cross-compiling so easy.

@the-horo
Copy link
Contributor

Alright, we seem to be getting closer to a common conclusion:

I agree with you on:

  • install the gccbuiltins as part of the compiler
  • always install dynamic_compile.d
  • don't add_directory(runtime) for the compiler-only builds
  • ldc2.conf is a pain

I would still prefer to install the stdlib headers as part of the runtime/CMakeLists.txt since that, to me, feels more natural but I'm willing to compromise since you insisted, so add:

  • install phobos and druntime headers as part of the compiler build

So I guess we can at one point handle versioned glibc symbols similarly if the need really arises.

I can live with this.

As long as I keep watch, there aren't going to be any target-dependent imports, i.e., different trees.

What I presented would result in the same import tree for all targets, non-glibc would simply have a dummy enum GLIBC_VERSION = -1 or some other placeholder value, symbol that would already be behind a version(GLIBC) so its value wouldn't even matter for those targets. What I did was duplicating some of the files and splitting them in different directories, but the final available imports would have stayed the same.

Anyways, I already said that I'm fine with going with the triple approach like the BSDs, if the need arises.


I hope that I didn't forget any details but this feels like all that we've discussed about

@kinke
Copy link
Member Author

kinke commented Mar 10, 2025

Awesome, looks like we have a compromise! 👍

Please note that this is all v1.41 stuff due to the breaking changes; v1.40.1 is coming up these days (beta1 first, then the final hopefully 1-2 weeks later). Incl. the ~= .conf array-append change, as e.g. it might break existing redub tool versions if people already used it - the only reason why I haven't merged that PR already.

@the-horo
Copy link
Contributor

Please note that this is all v1.41 stuff due to the breaking changes;

That's fine, I do prefer waiting for an implementation if we need the time to make sure that it's as good as we can get it, so long as progress doesn't stall

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