Skip to content

Conversation

willcl-ark
Copy link
Member

@willcl-ark willcl-ark commented May 22, 2025

Fixes: #32428

This PR adds a dependency provider to depends builds.

Currently the depends toolchain appears to rely on certain assumptions [citation needed] to find packages correctly. It does largely do this correctly, however NixOS sets some environment variables which interfere with this and cause builds from depends to fail, due to attempting to link system packages.

We can reproduce this interferrence by running a NixOS container and loading system packages using a flake:

docker run --pull=always -it nixos/nix

# In the container:
git clone --depth=1 https://github.com/bitcoin/bitcoin && cd bitcoin

# Install system packages for a non-depends build from https://github.com/bitcoin-dev-tools/bix
nix develop github:bitcoin-dev-tools/bix --extra-experimental-features flakes --extra-experimental-features nix-command --no-write-lock-file

# Attempt depends build
make -C depends -j$(nproc)
cmake -B build --toolchain /bitcoin/depends/<host-platform-triplet>/toolchain.cmake

# Find possibly-interferring env vars:
env | grep -E 'CMAKE|NIX'

A dependency provider allows the overriding of cmake's find_package() therefore giving total control over where dependencies come from in a build.

This achieves two things:

  1. Provides stronger guarantees about where dependencies come from during a (depends) build; not permitting environment flags to override these locations [citation needed].
  2. Fixes issues like a non-standard CMAKE_PREFIX_PATH breaking builds (i.e. 32428)

This provider must be specified along with the toolchain, with an invocation of the type:

make -C depends
cmake -B build --toolchain depends/x86_64-pc-linux-gnu/toolchain.cmake -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=depends/x86_64-pc-linux-gnu/dependency_provider.cmake
cmake --build build

To-do

@DrahtBot
Copy link
Contributor

DrahtBot commented May 22, 2025

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32595.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept NACK fanquake, purpleKarrot

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

No conflicts as of last run.

@willcl-ark
Copy link
Member Author

Opened in draft for feedback on approach.

There is also a bypass for the boost package (in cmake/module/AddBoostIfNeeded.cmake) which I'd like to get working with the dependency provider ideally.

Note that this also bumps the minimum cmake version to 3.24.

@willcl-ark willcl-ark requested a review from josibake May 22, 2025 21:28
@hebasto
Copy link
Member

hebasto commented May 22, 2025

cc @purpleKarrot

@fanquake
Copy link
Member

Provides stronger guarantees about where dependencies come from during a (depends) build; i.e. only from depends.

Can you elaborate on this? Currently, CMake configured with a depends toolchain, should never look outside depends (doing so would be a bug). What are the stronger guarantees?

@willcl-ark
Copy link
Member Author

Provides stronger guarantees about where dependencies come from during a (depends) build; i.e. only from depends.

Can you elaborate on this? Currently, CMake configured with a depends toolchain, should never look outside depends (doing so would be a bug). What are the stronger guarantees?

Right, perhaps I didn't use the best wording here.

When using the toolchain I have not found us pulling in extraneous deps from outside of depends, but have encountered us missing dependencies from inside of depends, e.g. in my original issue.

By using a dependency provider and intercepting calls to find_package(), as I understand it, we are getting even more control over which packages can be found than when using the toolchain.

This is what I termed "stronger guarantees", perhaps misleadingly!

@willcl-ark
Copy link
Member Author

The windows job fails as currently we activate the dependency provider automatically based on whether a(ny) toolchain is in use.

This works fine for depends, but doesn't make sense on windows where the vcpkg toolchain is used, without depends.

The idea for doing it this way was to try and avoid users having to pass two flags in order to build from depends -- a toolchain and a dependency-provider -- but perhaps this is necessary after all...

I do recall reading somewhere in cmake docs that choice of dependency provider should always be left to the user too, so I suppose doing it this way would fit with that.

@willcl-ark willcl-ark force-pushed the cmake-dependency-provider branch from b1a0b66 to 7bdf1af Compare May 23, 2025 12:23
@willcl-ark
Copy link
Member Author

Pushed an update which now only activates the dependency provider when using the depends toolchain.

This will hopefully appease the windows CI job, as well as making more sense in general and being more expected behaviour.

@purpleKarrot
Copy link
Contributor

Thank you for working on a dependency provider!

I think core should provide two dependency providers, which will give users three different ways to build:

  1. Not using a dependency provider: CMake will produce an error when dependencies are not found on the system. This will be the preferred way for distro packagers.
  2. A fallback provider: This will use system packages when they are found and fall back to compiling vendored depends. Using this provider, find_package will always succeed, but it will not compile any "unnecessary" code. This approach may be preferred by developers or for quick experiments.
  3. A depends provider: This will ignore system packages and always use vendored depends. This approach will be used for reproducible builds.

At the moment, the dependency provider points to prebuilt depends. Alternatively, the dependency provider could take the responsibility of building depends at the moment they are requested. This would make the current, Makefile based depends system an implementation detail that can be ported piece by piece to CMake.

@willcl-ark
Copy link
Member Author

Thanks for the feedback @purpleKarrot.

I like the sound of your approach here myself and will be happy to adapt to it. I think i'll start off by targetting 3.:

A depends provider: This will ignore system packages and always use vendored depends. This approach will be used for reproducible builds.

...without the optionl "auto-build depends" functionality. This is close to what I have here already (and solves my immediate personal itch regarding building using depends on NixOS).

I would appreciate your input though on one more aspect of this; namely the (dev) UX involved with the 3 options. Option 1 would not see any UX changes.

Option 2 would require dev to specify the "fallback provider" file with a -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES flag.

Option 3 would require the dev to specify both a --toolchain and a -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES.

Option 3 in particular feels a little clunky to me (as a cmake novice), as I'd expect the toolchain to "contain everything needed to build". But I am guessing this can be abstracted away with a CMakePreset handling passing of the toolchain and dep. provider. This would become even more seamless in a version which auto-built depends for you too.

Does my understanding above sound roughly logical to you? If so I can make the changes later this week.

@fanquake
Copy link
Member

A fallback provider: This will use system packages when they are found and fall back to compiling vendored depends. Using this provider, find_package will always succeed, but it will not compile any "unnecessary" code. This approach may be preferred by developers or for quick experiments.

I can't tell from this if you are saying to use depends to "fill in" any missing system packages, or, if all the required system packages aren't available, we would switch to using depends (unclear if that is actually desirable). Certainly a NACK on mixing and matching the two.

@purpleKarrot
Copy link
Contributor

I can't tell from this if you are saying to use depends to "fill in" any missing system packages,

Yes. The idea is to provide an approach to build core with minimal effort. If you NACK mixing system packages with vendored dependencies, are you saying that nobody will ever want that, or that nobody should ever want that?

@fanquake
Copy link
Member

fanquake commented May 30, 2025

Yes. The idea is to provide an approach to build core with minimal effort.

I think make -C depends && cmake -B --toolchain and apt install your_dependencies && cmake -B are both already pretty minimal effort. It's not really clear why a developer would apt install x, but not apt install x y, and then would need a behaviour to fallback to depends for y?

I'm also not currently aware of a platform that we support, where the dependencies needed to compile Core, aren't available via the system package manager; one used to be our specific version of BDB 4.x, but that dependency has now been removed. So this fallback usecase mostly seems like additional build complexity, which creates a larger matrix of "supported" builds to test / debug, for not much gain. We used to have a similar kind of option in our depends system (ALLOW_HOST_PACKAGES), but allowing the use of system packages, with depends, defeats the point of a self-contained dependency builder, so it was removed.

There's also some nuance around how this would function, are you imaging it all integrated into into CMake, so depends is no-longer a stand alone system? Otherwise, we need logic in CMake, to detect if the depends requirements (curl, compilers, patch, binutils etc) are actually installed / usable (i.e otherwise the fallback would fail because curl isn't installed, probably leaving the user wondering why it's trying to download stuff), as well as making sure that build flags / compiler selection are all passed through to depends (i.e if the user is trying to build with Clang & libc++, then any tools / flags need to be forwarded to the depends compilation).

I'm also just not sure that if a dependency is missing, downloading and compiling something is even the best behaviour. Maybe the user just forgot to apt install it, and will do so when CMake fails to configure?

@willcl-ark willcl-ark force-pushed the cmake-dependency-provider branch 3 times, most recently from a20f62c to 4d568f9 Compare June 2, 2025 12:34
@willcl-ark
Copy link
Member Author

OK I reworked this a little so that it works as described in top comment.

The overview is that the provider must be manually specified with -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES, thus keeping full control for the user.

The toolchain remains usable (as today) without the provider being specified.

@fanquake
Copy link
Member

fanquake commented Jun 2, 2025

https://cirrus-ci.com/task/5352790488252416?logs=ci#L1675:

[09:31:48.288] Extracting freetype...
[09:31:48.316] /ci_container_base/depends/sources/freetype-2.11.0.tar.xz: OK
[09:31:48.562] Preprocessing freetype...
[09:31:48.575] Configuring freetype...
[09:31:48.656] CMake Error at CMakeLists.txt:100 (cmake_minimum_required):
[09:31:48.656]   Compatibility with CMake < 3.5 has been removed from CMake.
[09:31:48.656] 
[09:31:48.656]   Update the VERSION argument <min> value.  Or, use the <min>...<max> syntax
[09:31:48.656]   to tell CMake that the project requires at least <min> but has been updated
[09:31:48.656]   to work with policies introduced by <max> or earlier.
[09:31:48.656] 
[09:31:48.656]   Or, add -DCMAKE_POLICY_VERSION_MINIMUM=3.5 to try configuring anyway.
[09:31:48.656] 
[09:31:48.656] 
[09:31:48.656] -- Configuring incomplete, errors occurred!

@@ -32,6 +32,18 @@ if [ -n "${APT_LLVM_V}" ]; then
)
fi

# Fetch newer cmake on Ubuntu 22.04
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure. The CI in this repo is probably the wrong place for fragile nightly tests. At a minimum, it would be good to pin the version. My preference would be to just use bookworm, see https://github.com/bitcoin/bitcoin/pull/32595/files#r2106854007

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear, you mean switching the native_previous_releases job from ubuntu:22.04 to debian:bookworm?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, on bookworm (+ gcc12) we seem to fail on a new error: https://github.com/bitcoin/bitcoin/pull/32595/checks?check_run_id=44008178986 which I haven't looked into much yet

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fail on a new error:

This looks similar to what we are already working around in the native fuzz valgrind task, so it seems fine to use the same workaround here (-Wno-error=array-bounds).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My preference would be to keep at least one CI with the min required gcc version. Albeit I am not aware of any changes that would affect us, compared to gcc-12, it seems inconsistent to bump it early before bumping the minimum required.

@willcl-ark willcl-ark force-pushed the cmake-dependency-provider branch from 05b9e17 to 62525d0 Compare June 13, 2025 10:10
@willcl-ark willcl-ark force-pushed the cmake-dependency-provider branch from 62525d0 to 3ac4ac9 Compare June 13, 2025 13:40
theuni and others added 2 commits June 16, 2025 09:55
This has a few advantages over the old method of simply copying headers:
- Installs proper cmake files which can be picked up by our buildsystem
- Only installs necessary headers, not all of boost

Co-authored-by: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com>
The `FindBoost` module has been removed by policy CMP0167.
@willcl-ark willcl-ark force-pushed the cmake-dependency-provider branch from 3ac4ac9 to 2e2d658 Compare June 16, 2025 13:53
Ubuntu 22.04 supplies cmake 3.22 which is below our minimum version.

- Switch to bookworm for cmake 3.25.
- Set FORTIFY_SOURCE=2, as level 3 creates a wraning on this platform.
@willcl-ark willcl-ark force-pushed the cmake-dependency-provider branch from 2e2d658 to e350162 Compare June 17, 2025 09:16
After depends have been built, print out the required flags needed to
use the dependency provider along with the existing --toolchain message.

Include configure output showing active toolchain and dependency
provider information.
Copy link
Member

@josibake josibake left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this, @willcl-ark ! I got nerd sniped on this and ended up learning a bunch about CMake, toolchains, and dependency providers.

As written now, I don't think this PR actually solves #32428, so per #32428 (comment), I do think for nix / nixos specifically we'd be better off going the simple route for now, and then continuing to work on this PR as a more comprehensive solution.

set(${package_name}_INCLUDE_DIR "${DEPENDS_DIR}/include" CACHE PATH "" FORCE)
set(${package_name}_INCLUDEDIR "${DEPENDS_DIR}/include" CACHE PATH "" FORCE)
set(${package_name}_LIBRARY_DIR "${DEPENDS_DIR}/lib" CACHE PATH "" FORCE)
find_package(${package_name} ${ARGN} BYPASS_PROVIDER)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT, this doesn't quite do what we want. Ideally, we'd want to completely override find_package's behaviour with our own custom logic, but this just sets some variables before calling the built-in find package (hence the BYPASS_PROVIDER argument).

I noticed this because Qt was failing to build inside depends due to it still reaching out to the system installed Qt in my nix environment. To get around this, I was able to patch qt.mk with:

--- a/depends/packages/qt.mk
+++ b/depends/packages/qt.mk
@@ -158,6 +158,11 @@ $(package)_config_env_darwin := OBJC="$$($(package)_cc)"
 $(package)_config_env_darwin += OBJCXX="$$($(package)_cxx)"

 $(package)_cmake_opts := -DCMAKE_PREFIX_PATH=$(host_prefix)
+$(package)_cmake_opts += -DCMAKE_FIND_ROOT_PATH=$(host_prefix)
+$(package)_cmake_opts += -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY
+$(package)_cmake_opts += -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY
+$(package)_cmake_opts += -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY
+$(package)_cmake_opts += -DCMAKE_SYSTEM_PREFIX_PATH=$(host_prefix)
 $(package)_cmake_opts += -DQT_FEATURE_cxx20=ON
 $(package)_cmake_opts += -DQT_ENABLE_CXX_EXTENSIONS=OFF
 ifneq ($(V),)
 

.. but that at least indicated to me that this approach is not doing what we'd expect. What we want is to fully restrict CMake to only use what is in depends, even if system packages are installed. To confirm this, I tried a nix build with libevent installed in the system packages, and the did a depends build with NO_LIBEVENT=1 and the final build of bitcoind did not fail and instead mixed the system libevent with the vendored dependencies.

I'm still reading up on dependency providers and find_package, so I don't have a concrete solution to propose, but my intuition so far says we'd be better of completely overriding find_package with our own logic, instead of using find_package with BYPASS_PROVIDER.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Currently this provider is only configuring the correct directories dynamically on find_package() calls, then "bypassing" fully over-riding find_package(). So essentially it's more of a "configuration & passthrough provider".

I'm going to close this for now (could mark as up for grabs?) as a) this doesn't achieve any increased hermetic properties as it stands, and b) I agree with the comments in #32428 that we should implement a simple fix for Nix users first, with the aim of introducing a more full-featured provider in the future.

(This was actually my preferred approach, hence me including just such a patch in the original issue comment in #32428 (comment)

@@ -63,6 +63,10 @@ created. To use it during configuring Bitcoin Core:

cmake -B build --toolchain depends/x86_64-pc-linux-gnu/toolchain.cmake

To additionally use the depends [dependency provider](https://cmake.org/cmake/help/v3.24/guide/using-dependencies/index.html):

cmake -B build --toolchain depends/x86_64-pc-linux-gnu/toolchain.cmake -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=depends/x86_64-pc-linux-gnu/dependency_provider.cmake
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of my depth here, but one question I had while reading up on this was if we have a dependency provider, do we still need a toolchain?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Depends toolchain doesn't contain enough info to build from depends on a fresh NixOS install
8 participants