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

to be or not to be: CMake install() #38

Closed
adevress opened this issue Oct 23, 2017 · 58 comments
Closed

to be or not to be: CMake install() #38

adevress opened this issue Oct 23, 2017 · 58 comments
Labels
cmake related to CMake support discussion enhancement

Comments

@adevress
Copy link
Contributor

Issue following the discussions on #8 : should we support install() in the cmake build:

Pro:

  • required by people who do not use cmake in their project.
  • make the usage of package manager possible

Cons:

  • make easy for to rely on ABI and have problems
@rogeeff
Copy link
Contributor

rogeeff commented Oct 23, 2017

Another con:
bazel BUILD files will not have install rules. And we want the two build files to be almost identical. In fact I think we'll end up auto generating CMakefile(s) out of corresponding bazel files once dust is settled.

We can consider separate CMakefile, which is going to be provided as a courtesy and only present some install rules. This CMakefile will not be generated and can get out of sync.

@lilianmoraru
Copy link

Without the install step you can't use it in SDKs and Linux Distros.
It will be just a bunch of projects, everybody using their own version of Abseil(most probably, locked at one commit for the rest of the project's life)...

@adevress
Copy link
Contributor Author

adevress commented Oct 23, 2017

@rogeeff Bazel has a very specific workflow where everything is built inside a single workspace without any installation. This is not the case for almost every other built system and packaging system existing in this world that follow a configure / make / make install pattern.
I do not think it is a reasonable option to not be compatible with them, just to simplify Bazel build file maintainability.

Bazel might be omipresent at Google, but it is not the case (yet) in the open source world, and will probably never be the case some professional environment.

If maintainability of Bazel build file is an issue, why not "annotate" the target that are installable in the bazel recipe and generate the CMake Files accordingly ?

@Sarcasm
Copy link

Sarcasm commented Oct 23, 2017

Another pro:

"workspace build system", based on CMake or not (often not), often rely on the install output.
They offer way to copy files manually, but this is useful mostly when a project lacks an install target. The dependency between project is based on the install step.

Some tools I know of:

I don't know of such tools that don't depend on install actually.

I'm wondering if there any CMake user that does not want the install target and feel strongly about it?

@adevress
Copy link
Contributor Author

adevress commented Oct 23, 2017

@Sarcasm 👍 Yep, and you can add spack to the list for HPC environments

@lilianmoraru
Copy link

@Sarcasm Add to that Distros package managers(RPM for example, where it has a "buildroot" and takes the files from there after you run "make install" into it).

@a4z
Copy link

a4z commented Oct 24, 2017

I would like to follow the advice to live on head, have only workspaces and source builds, but in reality I have projects where I need go back in time.
When I need to fix a 5 year old version of a software, I am not allowed to change any part that is not directly affected from the problem.
The way to ensure this is, for every component the system is build with:
make install DESTDIR=/where/ever
pack the result, sing it and put a check sum int some archive.
The fixed version will have the same binary base system as the version 5 years ago, guaranteed.

Also, I need to deliver exactly a known binary package of today software to developers for today development. The way to ensure this is, the same.

If I can not do this I can not use a library at work, I am not allowed to.

Now, it is not that hard, just annoying, to write an install script yourself, but,
when I have to fear that after each update of an library my self made install script is incomplete/breaks, practically speaking a library will not be used.

And I would like to use abseil, because todays development should be on head, and the description for this lib and the concept behind sound just too good and fits exactly some of my needs.
But I have to live with both worlds, and I try to build a bridge. And therefore, I would need a make install DESTDIR.. because that's what is used to solve some practical problems I have to live with.

Could I please have it?

@lilianmoraru
Copy link

From the previous issue, it seems that people assume that make install means precompiled binaries by default.
It doesn’t need to be that way, you can install the source if you want and find_package can even do weird things like adding the project as a subdirectory(would not recommend)...

@gennadiycivil
Copy link
Contributor

In the spirit of the documentation - it clearly says "Do not depend on a compiled representation of Abseil. We do not promise any ABI compatibility — we intend for Abseil to be built from source, hopefully from head"
So.. if people would like to include install() that is fine of course , however it clearly goes against the spirit here and therefore should not be a part of master.

@Sarcasm
Copy link

Sarcasm commented Oct 24, 2017

In the spirit of the documentation - it clearly says "Do not depend on a compiled representation of Abseil. We do not promise any ABI compatibility — we intend for Abseil to be built from source, hopefully from head"

Please, consider one moment that most persons in this issue understand that.

So.. if people would like to include install() that is fine of course , however it clearly goes against the spirit here and therefore should not be a part of master.

It's not so black and white:

  • install() does not mean binary distribution
  • people who build from source, often do so by using the install targets

The build tools I mentioned earlier, #38 (comment), and spack mentioned by @adevress, #38 (comment), all relies on install().

And for the subset I actually used: bitbake, buildroot, buildroot-like, catkin, ament,
and by the look of it, nix and spack too.
All of these, build workspaces from sources.

They all respect this contract:

In your project build, ensure there is exactly one (current) copy of Abseil.
All of the dependencies that require Abseil will work on that. Build it from source.
-- @tituswinters: #8 (comment)

@gennadiycivil
Copy link
Contributor

Interesting argument @Sarcasm, thank you.

install() does not mean binary distribution

I tend to think about install() as a binary distribution, copy the build artifacts within the file systems accessible from where this build is running.

It would perhaps be useful to consider this from a different point of view, could you please elaborate, how you see what install() does?

Thank you again.
G

@a4z
Copy link

a4z commented Oct 25, 2017

Interesting quote from tituswinters, @Sarcasm , and true what you write
For some, they way to ensure that there is only one copy of a component is that developers have only access to one version distributed via a package manager -or as part of the SDK- used for the current product development.
The package hast to be generated via make install DESTDIR, of course.

In terms of ABI compatibility , since it is normal to rebuild always everything that depends on top a component when a component is updated, this is not an issue at all. (as long as not the MS way of doing things is chosen to break ABI between Debug/Release builds. Abseil is not planning to do this?)

Seriously, in some working environment, when you say every workspace (team) needs to have a source version from Abseil, this is the guarantee for non equal Abseil version used in a product because some team will update today, others tomorrow ....
But when all have to use the same SDK, they will have the same version guaranteed.

I find it very interesting that there are 2 such different point of views and requirements to reach same target, what is, the same version, hopefully the latest, of a component in a product development environment.

Well, its up to the repo owner/developer to decide, I provided a different point of view, everything else could only be repeat my self, so my 2 cents are donated with this and my previous post. ;-)

@adevress
Copy link
Contributor Author

adevress commented Oct 25, 2017

I tend to think about install() as a binary distribution, copy the build artifacts within the file systems accessible from where this build is running.

@gennadiycivil That's the source of the mis-understanding I think.

For many package manager and build system, install() is just an intermediate step in the build pipeline what can be understood as "please, put the public interface of component X of revision Y under path Z for reuse by the next component in the build".

For package manager like Nix or Spack, install() of a intermediate component (like abseil) is just a step in the build process before continuing the build for the other component that depend on it.

Nix detects the changes in both the revision and the build configuration, and determine a unique, immutable install() path based on a cryptographic hash(recipe, source, dependencies) where the component (abseil) will be installed.

This has several advantages:

  • You definitively solve all ABI problems, because a change in the abseil source will generate a different output, under a different prefix, and trigger recompilation of every component depending on it.
  • You have a very clear separation between every component: each component can have its own build system ( cmake, autottools, meson, scons ), as long as they all provider an "install under prefix" command, everything interoperate nicely.
  • Every tuple <component, configuration, dependencies> is built once and only once. Avoiding useless recompilation.

Spack and some others works the same way.

It seems to me that you guys are afraid of install() phase because you suffered in the past of the limitations of classical Linux distro package manager (rpm, dpkg).
With RPM/DPKG, every component is shipped into a global namespace, under the same prefix and relying on strong assumption on ABI and API compatibility.
The consequence is that it triggers happily nuclear explosion each time a non-ABI-compatible update (of googletest) is deployed.
And the second consequence (side effect) of that, is that everybody use outdated package because they can not get updated without explosion.

This is not what many of us rely on.

@lilianmoraru
Copy link

lilianmoraru commented Oct 25, 2017

@adevress It's not an issue with RPM/DPKG per se but more with how Linux distros traditionally work.
We have at work projects that use BitBake but also RPM for dependency management(so we don't have to reinvent a build-system and port everything to it).
Example of RPM-based project that lives at HEAD:

new version of Abseil ---- project1 ------------ project2 ---------- etc...
            \ install         / use abseil          / use abseil
SDK -------------------------------------------------------------------------------                                           
                                         [abseil-HEAD]

After each merged commit, the SDK is updated in Git(LFS) and when somebody does repo sync -j $(nproc), he gets the latest SDK(with the latest interface, libraries, etc... modifications) and his code update.

A team can basically have just an SDK that is always at HEAD and only the source code of their own project(a team can have their own manifest, the CI needs to import them all with its own manifest, make the build and update the SDK with libraries, headers, RPM database, etc...).

@adevress
Copy link
Contributor Author

@lilianmoraru I totally agree. The problem is not so much RPM / DEB itself, more how it is traditionally used.

@Sarcasm
Copy link

Sarcasm commented Oct 25, 2017

@gennadiycivil

I have tried to produce a "simple" CMake file that ressemble what some of the mentioned tools do.
I picked a method that uses CMake external projects, not because this is the best tool, but because the example is self contained and hopefully most people have CMake installed somewhere.

The examples build cctz and its dependencies, Google Mock and Google/benchmark.
All of the sources are fetched from the master branch on Github.
None of these projects "include" the other, which is convenient because it's possible to reuse them.

First create the project:

mkdir ep-cctz
cd ep-cctz
cat <<'EOF' > CMakeLists.txt
cmake_minimum_required(VERSION 2.8.12)

include(ExternalProject)

set_property(DIRECTORY PROPERTY EP_BASE ep)

ExternalProject_Add(ep_benchmark
  URL https://github.com/google/benchmark/archive/master.zip
  CMAKE_ARGS -DBENCHMARK_ENABLE_TESTING=OFF -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>)
ExternalProject_Get_Property(ep_benchmark install_dir)
set(ep_benchmark_install_dir "${install_dir}")

ExternalProject_Add(ep_gmock
  URL https://github.com/google/googletest/archive/master.zip
  CMAKE_ARGS
      -DBUILD_GMOCK=ON
      -Dgmock_build_tests=OFF
      -Dgtest_build_samples=OFF
      -Dgtest_build_tests=OFF
      -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>)
ExternalProject_Get_Property(ep_gmock install_dir)
set(ep_gmock_install_dir "${install_dir}")

ExternalProject_Add(ep_cctz
  URL https://github.com/Sarcasm/cctz/archive/master.zip
  LIST_SEPARATOR ::
  CMAKE_ARGS
      -DBUILD_EXAMPLES=ON
      -DBUILD_TESTING=ON
      "-DCMAKE_PREFIX_PATH=${ep_benchmark_install_dir}::${ep_gmock_install_dir}"
      -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
  DEPENDS ep_benchmark ep_gmock)

# # Here, for example, Abseil could reuse ep_cctz and ep_gmock to be built.
# # However the current proposed CMake, which IMPOSE cctz to be in a subdirectory,
# # does not work.
# # I really wish the CMake integration was "classic".
# # How can cctz be embedded and be used by other projects?
# # This works only if abseil is the only user of cctz,
# # this does not compose well.
# ExternalProject_Add(ep_abseil
#   # URL https://github.com/adevress/abseil-cpp/archive/master.zip
#   URL https://github.com/adevress/abseil-cpp/archive/b5ce9df6243286729bb5c23f848c92c43bcd9ff0.zip
#   LIST_SEPARATOR ::
#   CMAKE_ARGS
#       -DBUILD_EXAMPLES=ON
#       -DBUILD_TESTING=ON
#       "-DCMAKE_PREFIX_PATH=${ep_cctz_install_dir}::${ep_gmock_install_dir}"
#       -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
#   DEPENDS ep_cctz ep_gmock)
EOF

Then build it with CMake:

mkdir build
cd build
cmake ..
make

Now, to understand what is going on:

  • we have a "workspace" description in the CMakeLists.txt
  • we build projects from sources
  • yet, we use the install() provided by these projects as a dependency between projects. Please note that, all other Google projects provide an install target, and this is nice of them, if only Abseil was doing the same.

If we take a look at the build layout, in the build/ep/ directory, we have this:

../ep-cctz/build$ ls ep
Build  Download  Install  Source  Stamp  tmp

This is similar to some of the tools I mentioned.

Here is the content of the directories,
which matches the different stages of the build that most of these build tools uses.

  • Download/: the directory where the sources are downloaded: wget ...

    $ tree ep/Download
    ep/Download
    ├── ep_benchmark
    │   └── master.zip
    ├── ep_cctz
    │   └── master.zip
    └── ep_gmock
        └── master.zip
    
  • Source/: where they are extracted: unzip ...

    $ tree -L 2 ep/Source
    ep/Source
    ├── ep_benchmark
    │   ├── appveyor.yml
    │   ├── AUTHORS
    │   ├── cmake
    │   ├── CMakeLists.txt
    <snip...>
    │   ├── src
    │   ├── test
    │   └── tools
    ├── ep_cctz
    │   ├── BUILD
    │   ├── cmake
    │   ├── CMakeLists.txt
    <snip...>
    │   ├── README.md
    │   ├── src
    │   └── WORKSPACE
    └── ep_gmock
        ├── appveyor.yml
        ├── BUILD.bazel
        ├── CMakeLists.txt
        ├── googlemock
        ├── googletest
        ├── README.md
        ├── travis.sh
        └── WORKSPACE
    
  • Build/: the build directory, using "out-of-source" build: cmake .. && make ...

    $ tree -C -L 2 ep/Build
    ep/Build
    ├── ep_benchmark
    │   ├── CMakeCache.txt
    │   ├── CMakeFiles
    │   ├── cmake_install.cmake
    │   ├── install_manifest.txt
    │   ├── Makefile
    │   └── src
    ├── ep_cctz
    <snip...>
    │   ├── install_manifest.txt
    │   ├── libcctz.a
    │   ├── Makefile
    │   ├── Testing
    │   ├── time_tool
    │   ├── time_zone_format_test
    │   └── time_zone_lookup_test
    └── ep_gmock
        ├── CMakeCache.txt
        ├── CMakeFiles
        ├── cmake_install.cmake
    <snip...>
        ├── install_manifest.txt
        └── Makefile
    
    
  • Install/: packages are installed in the build tree, so that other built package can depend on it:
    make install

    $ tree -C -L 3 ep/Install
    ep/Install
    ├── ep_benchmark
    │   ├── include
    │   │   └── benchmark
    │   └── lib
    │       ├── cmake
    │       └── libbenchmark.a
    ├── ep_cctz
    │   ├── include
    │   │   └── cctz
    │   └── lib64
    │       ├── cmake
    │       └── libcctz.a
    └── ep_gmock
        ├── include
        │   ├── gmock
        │   └── gtest
        └── lib64
            ├── libgmock.a
            ├── libgmock_main.a
            ├── libgtest.a
            ├── libgtest_main.a
            └── pkgconfig
    

The build happens in sequence of dependencies:

  • gmock and benchmark first
  • then cctz which depends on them

Also, for each of these projects, the following stages can happen, in the following order:

  • download
  • extract
  • patch
  • configure
  • build
  • install

When a package like cctz depends on other projects, we point it to the earlier projects install tree,
so that it can find the headers and library.

You can see this for the cctz project, where we have:

ExternalProject_Add(ep_cctz
  <snip...>
  CMAKE_ARGS
      <snip...>
      "-DCMAKE_PREFIX_PATH=${ep_benchmark_install_dir}::${ep_gmock_install_dir}"
      <snip...>
  DEPENDS ep_benchmark ep_gmock)

This means cctz will look in the install tree of benchmark and gmock,
when calling the find_package() command from CMake:

find_package(benchmark REQUIRED)
find_package(GMock REQUIRED)

-- https://github.com/Sarcasm/cctz/blob/44ddb7563e46e23ecd32a67896570b4b4d81edeb/CMakeLists.txt#L101-L102

CMake external projects is not the best example, tools like nix, spack,
catkin, ament or bitbake offer more control and scale better for big projects,
but this example demonstrates how projects can rely on each other install target
when building a workspace from source.

It also demonstrates that in the current state, without install() targets,
and by embedding it's dependencies in the CMakeLists.txt,
the Abseil CMake is not usable at all in these scenarios, which is very unfortunate IMHO.

@Orphis
Copy link

Orphis commented Oct 26, 2017

@Sarcasm This is potentially using different ABIs. If your top level project uses -std=c++14 and wants to propagate it, you will have to pass it manually to each dependency. This version of ExternalProject is broken in that regard, and you should have a look at the GoogleTest documentation for a better usage.

Abseil internally changes depending on what is available in the standard library. If you can have different standards used when you compile it and when you use it, you will have issues linking, or worse, difficult to debug crashes at runtime.

@Sarcasm
Copy link

Sarcasm commented Oct 26, 2017

It's true that this toy example is not perfect.
I did not mean to have a "top level" project here, I assumed the CMakeLists.txt was the whole, final workspace.

To be more correct, a toolchain file may be needed, e.g.:

set(CMAKE_CXX_FLAGS "-std=c++14")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

the projects may need to be patched when they don't honor the toolchain, ...
All of these, the tools can do it, and often do.
Maybe not so often for the reasons of the Abseil compatibility requirements.

Actually, I'm wondering if the question of whether or not having an install() is the right question.
What if we assume that it is "yes", and that we want Abseil to be a good citizen in the CMake world.
What could be done to make Abseil safer in this scenario?
What if Abseil wasn't "Zero configuration" as advertised here:

Without giving it too much thoughts, a configuration could reasonably be made at build time, and installed, to either:

  • have a frozen configuration from Abseil build time
  • or assert the configuration is compatible/identical

Since Abseil is not a header only, I personally don't see any strong benefits of being configuration-free.
I expect I may be proven wrong here.

I understand Bazel may not need configuration as there is only one possible toolchain and build settings at anytime, or something along these lines.
CMake is just not like that, but the project can surely be accommodated to avoid misuse of the library.

Also, I think this is a goal of modern CMake versions to support this kind of scenario, for example, looking at this recent discussion:

Ideally people could specify the minimum standard required in a CMakeLists.txt, but the user can request a specific version from outside.

@ghost
Copy link

ghost commented Oct 27, 2017

Just dropping in here (I don't have a stake in this library, but some experience with CMake).. The typical workflow with CMake is to build everything from source. Especially on windows, you need to download all of your dependencies and then build them. Everything uses the same compiler. On OSX, you have homebrew, which uses an ABI-compatible method of building (everything is build with the same compiler, with the same options).

I don't think being obstinate about using install is going to fix any problems or make anyone more likely to use HEAD. It's just going to make other people have to write their own CMakeLists.txt to get around your obstinance.

People don't read the documentation and then complain. They may file issues when they shouldn't. The way that homebrew deals with that problem is to ban people who don't read the documentation. If you're that concerned about extra work, then you should take the same approach.

@junyer
Copy link
Contributor

junyer commented Nov 2, 2017

In order for RE2 to depend on Abseil without abandoning or alienating our external users, I believe that it will be necessary for Abseil to accommodate traditional use cases in some way. Perhaps the "long-term support" branches could serve as a basis for packaging? And then Debian could handle Abseil much like they handle Boost, I imagine, and likewise for other distributions.

@ghost
Copy link

ghost commented Nov 2, 2017

Perhaps the "long-term support" branches could serve as a basis for packaging?

This is exactly what the maintainers are afraid of: people conflating install with long-term support for an outdated version. I completely understand this and I personally have little sympathy for users of outdated software.

The problem is that linux distributions have a completely different packaging model than OSX or windows. In the latter two, you just use the latest version of the library, or something close to it. But in the case of linux, the packages are frozen in time for years.

One solution could be to only offer install if the OS is OSX or windows, making it intentionally a PITA to package abseil on linux distributions. That could offer all of the advantages and none of the disadvantages (well, except for linux packagers, which aren't going to be supported anyway).

@lilianmoraru
Copy link

@xoviat Now you've made it very clear why the install target is not desired...

I thought initially that the main argument against the install target was because of the ABI issues when compiling/linking libraries with different flags for example(I was trying to explain that it does not have to be precompiled, but it does not address the "love for old, buggy, slow code" issue).

I understand that the idea is to not leave developers any other option than to directly use abseil(because distros could not "make install" abseil, for example :) ), potentially living at "HEAD".

Looking at other open-source projects, the reality is that projects usually pin the submodule or copy the code at one point in time and leave it there for "stability" or because nobody considers it important to have the code up-to-date(even when there are critical security fixes :( ).

What I'm trying to say is that this still won't ensure that the projects live at "HEAD"(but does improve the chance of it, because it might exclude support for abseil in Linux distros), it's just that it makes it considerably harder for the SDK, big projects(many smaller components/repos depending on abseil), etc... use-case to happen.

I am not sure yet which solution is better or which issue is actually more important(did not yet take time to thoroughly think about this), but I can propose a somewhat weird compromise.

I don't remember which programming language had this(I thought Ruby, but can't find it) but there was a very specific problem in the language that you could not solve easily with the usual API, but also you could not apply the obvious "workaround" because it was awful(for performance or something like this).
In the end the language provided the solution, for that exceptional case where you need it, and it was called somewhat like this: i_am_a_horrible_person_do_stuff.

Maybe we can still have the install target but hidden under a cumbersome and obviously "bad" flag(which you will want to read about)?
Something like: -DI_SHOULD_NOT_USE_THIS_AND_INCLUDE_ABSEIL_AS_A_SUBDIRECTORY_INSTEAD=TRUE? 😄

@junyer
Copy link
Contributor

junyer commented Nov 3, 2017

This is exactly what the maintainers are afraid of: people conflating install with long-term support for an outdated version. I completely understand this and I personally have little sympathy for users of outdated software.

https://abseil.io/about/philosophy states:

We will periodically mark a tag as “supported” and branch it — if we discover security issues or major performance problems, we’ll update those branches. Our expectation is to do this every 6 months and support those branches for 2 years.

I understand that Abseil folks don't wish to endorse or even encourage packaging, but considering their promise to maintain such branches, I don't understand why they would obstruct packaging.

There will be users of RE2, for example, who won't know or care about Abseil and its philosophy. In many cases, they will be using language bindings, so they won't know or care about C++ problems. In all cases, they will want new versions of RE2 to work just as easily as old versions of RE2. I can't condemn them to using outdated software simply because they had the temerity to use RE2 before Abseil existed.

@JonathanDCohen
Copy link
Contributor

@xoviat can you please go on about the linux packaging model? I thought apt-get largely worked similarly to homebrew et al.

@a4z
Copy link

a4z commented Nov 3, 2017

some difference between Windows and Linux packaging:
(sorry for dropping in to this topic, but I feel it could not harm to add some info)

on Windows MSVC break ABI from version to version and even between debug and release build.

This is why in Windows project often are huge monoliths, with external dependency in a subdirectory, 3rdParty or so.
The software, if it is source code, becomes old over time also, or, some project have even binaries in such folders.
If they have only binaries and no source, than they can not update the compiler, also not uncommon in reality.

On Linux ABI does not break that often, I think I had it only once, some when during gcc 3.x
This is why it is not a problem to run multiple gcc versions, or also clang, and compile programs with them that use the same binary versions of libraries in the system.

on RHEL you use for example software collections to get a new gcc-63 or whatever, the latest python or ruby, .....
and these latest version work great together with existing components, build with a different compiler.
you add them to your system and the system itself stays stable as it is.

(I totally understand why abseil would break in such a scenario, I have no understanding why a lib like google test should not be shipped via a package, but this just as a site note)

Of course RHEL, Suse, or debian do not update basic packages to the latest version, they add latest version in parallel, this is for a reason.
If you want always the latest in your base system, you run Arch or Slackware current or something similar.
If you look at this distros, what xoviat wrote is not true.
But, these distros, and Debian, do not rebuild world for releases, they are rolling releases, they add and replace.
In contrast to RHEL or SUSE who can rebuild everything, if they want or mean it is required.
Or, when you build for embedded via yocto, where you also rebuild everything.

So there is no one story about package managers on Linux.

On Windows such a scenarios, any of them, are impossible with MSVC, therefore you need those monolithic workspaces with insane build times and everything included.

I have no idea about Mac.

@ghost
Copy link

ghost commented Nov 3, 2017

@xoviat can you please go on about the linux packaging model? I thought apt-get largely worked similarly to homebrew et al.

No, it doesn't. Because apt-get is integrated into the operating system, the software available is restricted to a curated subset that was present at the time of the OS release (for example, gcc 6 is not present by default in the Ubuntu repository). This is especially painful for something like RHEL, where the software is years old.

In contrast, homebrew usually stays a few months behind HEAD at most. Specifically, let's compare the effort to update a package in both package managers.

  1. In something like Ubuntu, you need to go through a curation process, the result of which will be included in the next OS release. This can take at least a few weeks of testing and days of effort.
  2. In homebrew, if you want to update the version, you just file a PR against homebrew-core. The CI automatically tests the latest version of the package and the PR is accepted within a week at most. In addition homebrew forces users to use the latest version of the package. See this comment:

Most projects have a specific version of Node that they require so as part of each projects bootstrap we install Node to get npm, then use npm to install a thing called "n", an easy NodeJS version switcher.

We specifically don't want you to pin Node to a particular version because then you don't get any security updates.

@Sarcasm
Copy link

Sarcasm commented Mar 16, 2018

To me, it feels like you want the compiler to do its job correctly, that what it compiles and what it uses is not just compatible, but really the same.
But you don't seem to care about using the CMake build system correctly.

Another funny thing, is to promote "live at head", but require ancient time CMake (2.8.12).

You want to force people to have a coherent/correct workspace built with CMake, but CMake is not good to deal with multiple dependencies.
It's good to build one isolated set of related targets: libs, tests and executables.

add_subdirectory or External projects are just hacks, is for quick and dirty dependency management, not something to build upon. It's actually CMake antipatterns.
Seeing how it's difficult to have proper CMakeLists.txt, it's an horrible idea to recommend anyone to include someone else CMakeLists.txt, and hope he is a good citizen.
We should just trust the generated dist: lib/, bin/, include/, etc.

Real tools for dependency management, rely on install targets, and expertise.

Personnally, I'm thinking that "Configure-time configuration" is possible for Abseil, and it would make it easier to integrate while still being correct.
Unfortunately, easy build system integration is not a primary goal:

I hope the tone is not too harsh.
I'd just like to user both Abseil and CMake correctly.

@isaachier
Copy link

@Sarcasm the only part I disagree with is the way end. CMake does have inherent support for building external projects. That is the basis of one of my favorite projects Hunter (https://github.com/ruslo/hunter). I really believe Hunter is exactly what this project wants, but for some reason or another the motive to keep using classic CMake is too powerful. For the record, Hunter uses backwards compatible extensions so the install works even without downloading. I highly recommend building something with it. I guarantee you'll like it.

@gennadiycivil
Copy link
Contributor

@Sarcasm , re: CMake 2.8.12 - good probability that Abseil will require much newer CMake soon.

@JonathanDCohen
Copy link
Contributor

@Sarcasm I'm a little confused at your response. I'm basically proposing doing https://github.com/google/googletest/blob/master/googletest/CMakeLists.txt#L114 for Abseil, allowing it to be used in the same way as googletest. I'm not sure what I'm missing.

@isaachier
Copy link

I hate to use reddit as a source, but this is a pretty good summary of the issue:

"I've used both pretty extensively at work, and use Bazel for my hobby projects. They're great to work with, so long as you buy into the monorepo pitch. Hermetic, reproducible builds...
The big problem is that they're a little bit all or nothing -- whatever third party project you want to depend on probably assumes an autotools deployment strategy: you download, make, and install your dependencies in a way your project doesn't need to understand, and then find them in whatever directory you installed them to within your project. There's a lot of problems with this way of doing things, but the reality is that you can't rewrite the world, and will end up either making a build target that just globs together all your dependency's sources, or else invokes its build system from within yours."

https://www.reddit.com/r/cpp/comments/6euc7b/_/die6g1y?context=1000

@Sarcasm
Copy link

Sarcasm commented Mar 21, 2018

@JonathanDCohen

The GoogleTest CMake snippet you linked to, installs the binary targets:

cxx_library(gtest "${cxx_strict}" src/gtest-all.cc)
cxx_library(gtest_main "${cxx_strict}" src/gtest_main.cc)
target_link_libraries(gtest_main gtest)

...

install(TARGETS gtest gtest_main
  RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
  ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
  LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}")
install(DIRECTORY "${gtest_SOURCE_DIR}/include/gtest"
  DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")

https://github.com/google/googletest/blob/dccc2d67547a5a3a97e4f211f39df931c6fbd5d5/googletest/CMakeLists.txt#L114

If that's what you propose, then I'm all for it, and I'm sorry for the misunderstanding.

Sadly, I don't think that's what you proposed by rereading this (emphasis mine):

My understanding is that to support this, we need to install(EXPORT) to allow referencing abseil targets, and install(DIRECTORY) to isntal; the actual abseil source.
Explicitly, we are not supporting installation of binaries or even add_subdirectory with a binary destination.
-- #38 (comment)

So, if your actual plan, is to do like GoogleTest except that you don't install the binary targets, then I maintain my comment, and additionally the comments I made here, against add_subdirectory and ExternalProject, also applies to GoogleTest.

@JonathanDCohen
Copy link
Contributor

I'm wondering if there isn't a miscommunication here, after talking with some other people internally.

Is the concern that you'd like to do something like apt-get install abseil and then find_package(Abseil)?

@coryan
Copy link

coryan commented Mar 22, 2018

While I do not expect the Abseil developers to provide packages for every platform out there (apt-get install abseil, and vcpkg install abseil, and brew install abseil, and dnf install abseil, etc. etc.) I would like them to make it easy for package maintainers to create such packages. Part of making that easy is that make install should work and install the necessary headers, libraries, pkg-config files, and CMake config files, in the (more or less) standard places.

Otherwise each package maintainer has to reimplement the install target themselves, and mistakes are more likely to happen.

@Sarcasm
Copy link

Sarcasm commented Mar 22, 2018

I firmly believe the clean way to use packages with CMake is to use find_package.
I'm not interested in system-wide installation of Abseil, I install locally and use -DCMAKE_PREFIX_PATH=<path/to/local/abseil/install>.
I don't want my project to rely on add_subdirectory, and to break at any unexpected change from the Abseil CMakeLists.txt after an upgrade.

I'd like the install rule because a lots of existing tools can relies on it correctly:

Sure, these tools can be misused, but they can also be used perfectly well, and when a package like Abseil just add difficulties because of "fear of misuse", it's seems wrong.
I prefer things to be simple. You declare targets, you install them. I find_package Abseil, use the exported targets.

The CMake documentation documents how dependencies are to be used:

I don't want the Abseil team to teach me how I should use CMake in a different way than recommended by the CMake documentation itself. Either you adopt the build system and its quirks, or you give up and just say "use Bazel".

Adopting CMake and propagating the unconventional usage is not doing any good IMHO.

Disclaimer: I understand the complications of system wide installation, the ABI issues you can have as a system installation cannot guarantee every use of the library will use the correct settings. However, for projects at my companies, we don't do system wide installations, we have "workspaces" where we build the library, and make sure we have a set of correct build settings. We rely on autotools projects, CMake projects, Boost.Build, ...All these tools provide installation targets and we rely on them.

@JonathanDCohen
Copy link
Contributor

Okay great, I think I understand the issues here. Let me make my goals clear from my end, and hopefully we can move forward with this:

What I'm aiming to do right now is support live-at-head use of Abseil with CMake. This necessarily involves the configuration-time dance that googletest does, or else users have a superbuild and use ExternalPRoject

In the near future, abseil will start to cut long-term support branches about every 6 months, likely with versioned inline namespaces. I and other members of the team are pushing that we should work with package manager maintainers such that all of their packages can, as much as possible, use the same long term support version of Abseil.

Now to your point, @Sarcasm

I'm not interested in system-wide installation of Abseil, I install locally and use -DCMAKE_PREFIX_PATH=<path/to/local/abseil/install>.

I follow you here, but allow me to ask a stupid question -- how are you installing Abseil in this situation without a package manager?

@coryan

make install should work and install the necessary headers, libraries, pkg-config files, and CMake config files, in the (more or less) standard places.

For headers, we may be able to cobble together some kind of "includes" subdirectory of an install destination. What we probably can't do is actually structure abseil's code in this way -- it would be way too much work to restructure our code at this point.

As for libraries, abseil is actively shedding our cctz dependency, at which point we will have no non-test dependencies. This seems to me like then there are no libraries to install.

I have to admit I don't know what pkg-config is, I'll have to research that.

For CMake config files, what I got out of our training with Kitware is largely that absl/base/config.h does what would otherwise be done with a cmake config file, unless I'm misunderstanding you.

Again, apologies if I'm being super dense here. I'm coming at this with the best intentions and learning on the fly.

@isaachier
Copy link

Thank you @JonathanDCohen for your dedication here. First, I'm not sure how absl/base/config.h does anything like a CMake config. I think you might be confusing a compiler config with a CMake config. So let me give a brief explanation.

A CMake config (and pkg-config for that matter) solve a pretty basic problem. If I have library B that depends on library A, how do I determine the necessary header directories, libraries, and flags to compile with? CMake developed two solutions to the problem.

  1. Have each package write a FindA.cmake file dedicated to resolving all requirements for linking/compiling against A.
  2. Have a global configuration installed in <prefix>/lib/cmake/AConfig.cmake that resolves these requirements for any dependency.

The problem with the first solution should be clear. The package requirements should be dictated by the package itself. The burden of determining required flags should not be placed on the dependent library. Unfortunately, most CMake projects to this day still rely almost entirely on this solution.

The second solution is much more sane, and is compounded by the fact that CMake can do all the work for you. If library A has a CMake build, CMake can easily create a config for consumption by library B (see https://cmake.org/cmake/help/v3.1/module/CMakePackageConfigHelpers.html for details). The primary issue here is the inability to apply this to non-CMake projects.

Now that we have that sorted out, you can see why I strongly suggest you take the second, more sensible approach.

@coryan
Copy link

coryan commented Mar 23, 2018

Thank you for clarifying the short-term goals. You may want to open a separate bug or something called "Partial improvements to CMake support" or show folks a roadmap (without dates) where you explain how this fits into the plan to have releases with versioned namespaces and so forth.

make install should work and install the necessary headers, libraries, pkg-config files,
and CMake config files, in the (more or less) standard places.

For headers, we may be able to cobble together some kind of "includes" subdirectory of an install
destination. What we probably can't do is actually structure abseil's code in this way -- it would be way
too much work to restructure our code at this point.

Agree, /usr/include/absl/*.h (and deeper subdirectories as needed), is what I clumsily trying to say. Or more generally ${prefix}/include/absl/**/*.h in case the users decide to install in /opt, or /usr/local, or some other directory. What would be surprising and I dare say unacceptable, would be to install in ${prefix}/share/doc or ${prefix}/src.

As for libraries, abseil is actively shedding our cctz dependency, at which point we will have no
non-test dependencies. This seems to me like then there are no libraries to install.

I was skipping steps and thinking about your longer term goals. In that case you do create libabsl_base.a, right? Those should get installed to ${prefix}/lib (typically, it is different on Windows, and different on some flavors of Linux, but that is Too Much Detail).

@nolange
Copy link

nolange commented Mar 27, 2018

I follow you here, but allow me to ask a stupid question -- how are you installing Abseil in this situation without a package manager?

In a way - thats the whole point why we nag about an install target. Normally you would do this:

# thats your workspace with all your libraries in whatever version you want.
MYBUILD_DIR=<someplace>
# git clone / unpack your sources to /tmp/abseil/src

cd /tmp/abseil/build
cmake -DCMAKE_PREFIX_PATH=$MYBUILD_DIR ../src
make
DESTDIR=$MYBUILD_DIR make install

# repeat for all dependencies

cd <my_project>/build
cmake -DCMAKE_PREFIX_PATH=$MYBUILD_DIR ../src
make
DESTDIR=/tmp/my_project_package make install
# now I can pack up /tmp/my_project_package, and unpack this to its final destination

it should not matter whether you use a package manager or build from source, atleast for your project files. I don't know how the part before can be solved easily in an automated foolproof flexible configuration (I am leaning toward an extra CMakeList.txt thats independent from the my_project build).

but the key point is that you have an directory that's your build environment and you dont want to leak out details from individual builds, and you dont want to burden the users of your library by having to manually extract all files and configuration. You do so by providing an install target which can be redirected (CMake mostly does this for you, as do Automake and everything else).
After that you can delete the build directory (/tmp/abseil/).

Or you could use some Projects like Buildroot / Yocto to create this build environment for you. At any rate you will need a way to install your libs/headers/source and a way to use them in depended projects.

@Sarcasm
Copy link

Sarcasm commented Mar 28, 2018

Now to your point, @Sarcasm

I'm not interested in system-wide installation of Abseil, I install locally and use
-DCMAKE_PREFIX_PATH=<path/to/local/abseil/install>.

I follow you here, but allow me to ask a stupid question -- how are you installing Abseil in this situation without a package manager?

If I want to try a new library, like Abseil, I proceed as follow:

First, I have an existing workspace with just my app:

$ pwd
/my/workspace
$ ls
my_app/

I will first get the Abseil cctz dependency:

mkdir third_party
cd third_party
git clone https://github.com/google/cctz.git
mkdir cctz/build
cd cctz/build
cmake -G Ninja -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/my/workspace/third_party-install ..
ninja -v        # check compile options are ok (e.g. -std=c++11), adjust if necessary
ninja install   # (1) sadly this does not work yet because they want to do like Abseil: https://github.com/google/cctz/pull/68#issuecomment-364585683

Then Abseil (notice how this could be the same steps as cctz,
that's the advantage of using conventional CMake):

cd /my/workspace/third_party
git clone https://github.com/abseil/abseil-cpp.git
mkdir abseil-cpp/build
cd abseil-cpp/build
# (2) this cmake call does't work, because Abseil does not consume cctz with `find_package`
cmake -G Ninja -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/my/workspace/third_party-install ..
ninja -v         # check compile options are ok, adjust if necessary
ninja install    # (3) one can always hope!

(1), (2) and (3) are all the reasons why departing from standard CMake is really painful,
none of them work right now because of this issue.

(2) the following patch was sufficient:

+find_package(cctz REQUIRED)
+set(ABSL_CCTZ_TARGET cctz::cctz)

- if(NOT ABSL_CCTZ_TARGET)
-   set(ABSL_CCTZ_TARGET cctz)
- endif()

Now I have the following workspace:

$ pwd
/my/workspace
$ ls
my_app/
third_party/
third_party-install/
$ ls third_party-install/*
third_party-install/include:
absl   cctz

third_party-install/lib:
cmake  libabsl_base.a  ... libcctz.a

And to test it in my_app/, I can do the following steps:

Edit my_app/CMakeLists.txt:

+find_package(absl REQUIRED)
+target_link_libraries(my_app absl::base)

Reconfigure CMake to specify where to find the dependencies:

cmake -DCMAKE_PREFIX_PATH=/my/workspace/third_party-install .

What's really important here is that you build on existing knowledge/practices.
Even beyond CMake, this method works mostly the same with autotools, and probably things like Meson.

You can see this is already a bit painful this way, not providing the install targets makes it even more so.

As @nolange says, tools like Buildroot/Yocto really works with this kind of workflow.
Just an interesting example, see this Yocto best practices for CMake, the second item is:

  1. do all install operations in CMake if possible (vs do_install in the OE recipe)
    -- http://bec-systems.com/site/1128/best-practices-for-using-cmake-in-openembeddedyocto

@JonathanDCohen says:

I have to admit I don't know what pkg-config is, I'll have to research that.

Yes, please read about it.
Some thinks a successful package manager for C++ needs something like pkg-config.
Autotools, CMake, Meson and probably others, support pkg-config.
CMake supports it but it also has it's own specific config file format.

For CMake config files, what I got out of our training with Kitware is largely that absl/base/config.h does what would otherwise be done with a cmake config file, unless I'm misunderstanding you.

For me a CMake config file is xxx-config.cmake, is not the same as absl/base/config.

If you can, you should say hi to the #cmake channel on cpplang' Slack: https://cpplang.now.sh/
People there have really good feedbacks on using CMake correctly and using modern idioms.

@zhangxy988 zhangxy988 added the cmake related to CMake support label Mar 30, 2018
@Quincunx271
Copy link
Contributor

Quincunx271 commented Apr 1, 2018

Could the FindFoo.cmake or foo-config.cmake be used to do a git pull then build/test if necessary? That would allow for the nice find_package(absl) syntax, while also keeping abseil up to date. Seems like abuse of those files, though.

@isaachier
Copy link

@Quincunx271 I do not believe so. Those files are always used to find precompiled content, not source files.

@nolange
Copy link

nolange commented Apr 2, 2018

downloading anything during the configure stage is a anti-pattern to me, but no one forces the libraries that are found with find_package to be precompiled.

There are object libraries in CMake which could be used for that.

find_package(Abseil)

# add abseil source files
add_executable(my_project
  $<TARGET_OBJECTS:Abseil::Sources> 
)
# add usage requirements: include path, macros, depended libraries...
target_link_libraries(my_project
  Abseil::Common
)

@isaachier
Copy link

Well as I've said before, if that is what you are looking for, use ExternalProject or Hunter (https://github.com/ruslo/hunter).

@JonathanDCohen
Copy link
Contributor

This is now tracked in #111

@lilianmoraru
Copy link

Finally. I can attempt now to push Abseil at work :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cmake related to CMake support discussion enhancement
Projects
None yet
Development

No branches or pull requests