org-howto/articles/2023/10/nix-calling-cmake.org

109 lines
3.7 KiB
Org Mode

#+title: [7oct2023] transitive library dependencies
# ----------------------------------------------------------------
#+tags: @cmake @nix @pybind11 @transitive
# ----------------------------------------------------------------
#+description: transitive library dependencies in cmake
#
# org-publish options
#
# ^:{} require a_{b} before assuming that b should be subscripted.
# without this option a_b will automatically subscript b.
#+options: ^:{}
#
# emacs-specific options
#+startup: showall
#
# html exporter options
#+language: en
#+keywords: c++ gcc preprocessor
#+setupfile: ../../../ext/fniessen/theme-readtheorg.setup
#
#+html_head: <link rel="shortcut icon" type="image/x-icon" href="/web/img/favicon.ico" />
#+html_link_home: ../../../index.html
#
# not using: prefer theme-readtheorg
# +infojs_opt: view:showall mouse:#ffc0c0 toc:nil ltoc:nil path:/web/ext/orginfo/org-info.js
# +html_head: <link rel="stylesheet" type="text/css" href="/web/css/primary.css" />
* Problem
=pybind11= link difficulties with transitive library dependencies,
revealed by invoking cmake from within a nix build.
** Setup
- cmake version =3.25.3=
- pybind11 version ???
- nix build (see https://github.com:rconybea/xo-nix2)
Consequences of nix build:
- Each package installed to a separate directory -- no "common swimming pool" like =/usr/lib=
- Implies install directory always distinct from any directory containing build inputs
- Tends to reveal oversights in toolchain, as we'll see below
- pybind library (=xo-pyreflect=) with dependency on a separate library (=xo-reflect=),
that in turn has secondary dependencies (=xo-refcnt=, =xo-indentlog=).
Note that =xo-indentlog= is header-only.
- Expect this cmake script to work:
#+begin_src cmake
find_package(pybind11)
pybind11_add_module(pyreflect pyreflect.cpp)
find_package(reflect CONFIG REQUIRED)
target_link_libraries(pyreflect PUBLIC reflect)
#+end_src
** Problem
- Instead, link fails. Link line something like:
#+begin_src sh
g++ -fPIC ... -o pyreflect.cpython-311-x86_64-linux-gnu.so /path/to/libreflect.so -lrefcnt -lindentlog
#+end_src
Two problems here:
1. directory containing =librefcnt.so= isn't on the link line (no =-L/path/to/refcnt/dir= for example).
2. =libindentlog.so= does not exist, since =indentlog= is header-only
- Looked into intermediate outputs like =lib/cmake/reflectTargets.cmake=, excerpt:
#+begin_src cmake
set_target_properties(reflect PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
INTERFACE_LINK_LIBRARIES "indentlog;refcnt"
)
#+end_src
It's not obvious how =xo_pyreflect= can know that =indentlog= is header-only, while =refcnt= isn't
(though could presumably extract the relevant libdir from =find_package()= with some work).
** Workaround
- Recognize that =pyreflect= link shouldn't need =refcnt= on the link line,
since =libreflect.so= has a =DT_NEEDED= entry for it.
#+begin_example
$ readelf -d /path/to/libreflect.so
Dynamic section at offset 0x17860 contains 34 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [librefcnt.so.1]
...
#+end_example
- When building =pyreflect=, suppress transitive dependencies
For example:
#+begin_src cmake
# xo_cxx.cmake
macro(xo_pybind11_dependency target dep)
find_package(${dep} CONFIG REQUIRED)
set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "")
target_link_libraries(${target} PUBLIC ${dep})
endmacro()
#+end_src
Then in =.cmake= for =pyreflect=, something equivalent to:
#+begin_src cmake
pybind11_add_module(pyreflect pyreflect.cpp)
xo_pybind11_dependency(pyreflect reflect)
#+end_src