static and dynamic libraries, single compile

I am very new to cmake.

I am not new to build systems.
I have maintained signficant ones.

I have a very custom build system.
Very declarative, and embeds a little custom scripting language as an escape hatch.

Nice but design, but that I would rather stop maintaining. At least the underlying
platform support. I envision keeping the declarative stuff
and generating cmake files from it. Or maybe translating
once to idiomatic maintainable cmake.

However it has at least one very nice simple useful
feature that I have seen rarely or never elsewhere,
and I would like to know how to achieve it in cmake (or automake/libtool,
for better or worse, I want to delegate the work to others).

We compile everything just once, with -fPIC.
I find the approach of compiling things twice quite absurd, really.

For any “library” by default we make a static library and
link a shared library. We name the static libraries like foo.a.sa
or foo.lib.sa. (I forget what we do on HP-UX, where .sa means shared archive;
we have very broad platform support also).

If a library however is marked as “build_standalone”, we only build
the .sa form.

When we link an executable, by default, we probe around for the libraries
it requests. If there is dynamic we use that, else if there is static we use that.

If an executable is marked “standalone”, we look only for static,
which always succeeds.

The standalone libraries have another use, I only just realized.
The system is its own language. We have to write “bindings”, like rewriting
the X headers in our language, and such. Then we have like:

source(xwhateverbindings)
if not HasX11
build_standalone()
end
library(“x11bindings”)

The result of this, the same, compile, make static archive, and do not
attempt shared link. In this fashion, we can still compile our bindings
to check them somewhat, w/o error due to lack of underlying X11.
i.e. because we’d have unresolved symbols, depending (I know shared
libraries can sometimes have unresolved symbols).

The users of the library, granted:
if HasX11
use_library(“x11bindings”)
program(“guiapp”)
end

so no checking on executable source we cannot link.

Does my explanation make sense?

Is this possible in cmake? How?
If no way, maybe a reasonable feature request?

I do find the cmake documenation a bit obtuse, I’m sorry to say.
It speaks of higher level concepts.

Thank you very much,

  • Jay

I do not understand right.

Modern cmake use the concept of build targets.
One target may depend on other targets.

see It’s Time To Do CMake Right | Pablo Arias

That is informative and shines a very positive light on CMake, thank you.

I had some typos, I apologize, but my explanation wasn’t far off.

We do like this:

By default, make library foo with source foo.c bar.c

OK, a simple modern CMakeLists.txt:

cmake_minimum_required(VERSION 3.14...3.20)

# ---- Project ----

# Note: update this to your new project's name and version
project(
  Observe
  VERSION 3.0
  LANGUAGES CXX
)

# ---- Include guards ----

if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
  message(
    FATAL_ERROR
      "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there."
  )
endif()

# --- Import tools ----

include(cmake/tools.cmake)

# ---- Add dependencies via CPM ----
# see https://github.com/TheLartians/CPM.cmake for more info

include(cmake/CPM.cmake)

# PackageProject.cmake will be used to make our target installable
CPMAddPackage(
  NAME PackageProject.cmake
  GITHUB_REPOSITORY TheLartians/PackageProject.cmake
  VERSION 1.5.1
)

# ---- Add source files ----
# Note: globbing sources is considered bad practice as CMake's generators may not detect new files
# automatically. Keep that in mind when changing files, or explicitly mention them here.
file(GLOB_RECURSE _headers CONFIGURE_DEPENDS
     "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h"
)

# ---- Create library ----

add_library(Observe INTERFACE)
target_compile_features(Observe INTERFACE cxx_std_17)

# beeing a cross-platform target, we enforce enforce standards conformance on MSVC
target_compile_options(
  Observe INTERFACE "$<$<COMPILE_LANG_AND_ID:CXX,MSVC>:/permissive>"
)

target_include_directories(
  Observe
  INTERFACE "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
            "$<INSTALL_INTERFACE:include/${PROJECT_NAME}-${PROJECT_VERSION}>"
)

# for run-clang-tidy
add_executable(example example/example.cpp)
target_link_libraries(example Observe)

# ---- Create an installable target ----
# this allows users to install and find the library via `find_package()`.

packageProject(
  NAME ${PROJECT_NAME}
  VERSION ${PROJECT_VERSION}
  BINARY_DIR ${PROJECT_BINARY_DIR}
  INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include
  INCLUDE_DESTINATION include/${PROJECT_NAME}-${PROJECT_VERSION}
  DEPENDENCIES ""
)

This creates the following after make install:

tree stage/
stage/
├── include
│   └── Observe-3.0
│       └── observe
│           ├── event.h
│           ├── observer.h
│           └── value.h
└── lib
    └── cmake
        └── Observe-3.0
            ├── ObserveConfig.cmake
            ├── ObserveConfigVersion.cmake
            └── ObserveTargets.cmake

6 directories, 6 files

clone the project from here GitHub - ClausKlein/Observe: 📣 Hey listen! A simple general-purpose event-listener system for C++17.

A sample how to build and install shared libraries you may try this project:

That doesn’t answer my question.

But ok I looked into things some. I hadn’t looked in years I guess.

Apparently this is a faq: build static and dynamic libraries.
It isn’t fully automated, but compile once is easy enough:

add_library(l1-standalone STATIC a.cpp b.cpp)
add_library(l1-shared SHARED $<TARGET_OBJECTS:l1-standalone>)
set_property(TARGET l1-standalone PROPERTY POSITION_INDEPENDENT_CODE 1)

And then if I chose the names right, building selectively
just standalone and letting probing happen, should work.

i.e. default:
add_library(l1-standalone STATIC a.cpp b.cpp)
add_library(l1 SHARED $<TARGET_OBJECTS:l1-standalone>)
set_property(TARGET l1-standalone PROPERTY POSITION_INDEPENDENT_CODE 1)

standalone:
add_library(l1 STATIC a.cpp b.cpp)
set_property(TARGET l1 PROPERTY POSITION_INDEPENDENT_CODE 1)

But this doesn’t offer though is “standalone” executable
that favor standalone libraries. Perhaps we can drop that feature.
rpath=$origin/…/lib offers “similar” behavior. Though we also use standalone
so we can update libraries used by the actual build system implementation,
i.e. to avoid overwriting in-use files. Maybe this isn’t really needed.
And we use it to run uninstalled executables. A bit of an inefficient hack, but it is useful.
Like for executables used during the build (we ignore the cross build problem there, granted).
Maybe cmake creates wrapper scripts that set LD_LIBRARY_PATH or such for that?
Maybe I’ll just try and see.

I had forgotten, other aspects of our build system that will need
translation or alteration or abandonment.

We create multi-arch package stores, like:

/install/pkg1/amd64-linux/libpkg1.a
/install/pkg1/amd64-linux/libpkg1.so
/install/pkg1/arm64-darwin/libpkg1.a
/install/pkg1/arm64-darwin/libpkg1.dylib
/install/pkg2/amd64-linux/libpkg2.a
/install/pkg2/amd64-linux/libpkg2.so
/install/pkg2/arm64-darwin/libpkg2.a
/install/pkg2/arm64-darwin/libpkg2.dylib

and then sym or hardlinks into bin and lib directories.

Kinda like Debian’s /lib/ but predating it by a long time.

We’ll have to think about what we need and what is worth customization.

Thank you,

  • Jay

I think you should not say, that the cmake document is obtuse!

Think about of your questions, that is obtuse.

I seems time to forget what you dit in the past.

Make it new, clear, and simple!

There is more than one way to do it.

I can’t help you, sorry.

From my understanding I think you’d rather use OBJECT library:
https://cmake.org/cmake/help/latest/command/add_library.html#object-libraries

add_library(objlib OBJECT  a.cpp b.cpp)
set_property(TARGET objlib PROPERTY POSITION_INDEPENDENT_CODE 1)
add_library(l1-static STATIC)
target_link_libraries(l1-static objlib)
add_library(l1-shared SHARED)
target_link_libraries(l1-shared objlib)
add_executable(standalone-exec)
target_link_libraries(standalone-exec objlib)

cf:
https://cmake.org/cmake/help/latest/command/target_link_libraries.html#linking-object-libraries

I think you should separate the subject of your question. I understand that your current build system has various feature but:

  1. as @ClausKlein said you should try the cmake-way of doing thing first not trying to translate line-by-line
  2. ask relatively standalone question for each feature you seek for your build system so that it’s easier to answer without having to cope with the whole complexity.

I saw object libraries, but on Windows I prefer .libs over .objs, so that the linker
will resolve and perhaps retain fewer symbols.

On Unix I guess slightly preference for object libs to avoid the cost to write the lib.

Very very similar though.

  • Jay

You should not assume what CMake will use for OBJECT libraries on various platform. The point of CMake is to abstract away as far as possible from platform specifics. With CMake you usually give functional requirements on your build.

If you want to explain what CMake should do on Windows vs Linux vs MacOS etc… then you’ll end up with a CMake build system with a lot of if(WIN32) etc… which seems awkward when you seek cross-platform build.

I would just avoid object libraries everywhere, not per-platform,
because of their varying semantic, at least on Windows (possibly everywhere?)

And sure, but what is the high level difference then?
What are the intended observed behaviors of an object library vs. a static library?
They sound like exact low level ideas, and do in fact map directly to the next level.

The caveat I am avoiding others have documented:

Benefits of CMake Object Libraries | Scientific Computing | SciVision.

"
Caveats
Using object libraries means that duplicate symbols can cause conflicts that need to be
manually resolved. When linking non-object libraries, the linker typically just uses
the first instance seen of a symbol.
"

  • Jay

How would you do this on Windows to compile only once for shared and static libraries? Is that even possible due to the need for dllexport?

1 Like

There is no need for declspec(dllexport).
Plenty of systems do not use it.
Use a .def file instead.

Declspec(dllimport) is also not needed.
(Unless you import data, or compare function pointers for equality, both are rare.)

  • Jay