Hacker News new | ask | show | jobs
by FlyingSnake 3125 days ago
C++, despite being a really powerful language, has a really primitive build system. Developers have to wade through a complex soup of Makefiles/CMake/Gyp/ninja etc just to get a project set up. There's conan for package management, but it pales when compared to Cargo or Gradle. Moreover setting up Conan is a full time job in itself, and it's not without weird quirks either. I've not tried Bazel yet, and would love to hear if it's as good as Xooglers say.

C++ on Jupyter looks amazing, and I really hope it makes starting with C++ easier.

9 comments

The conda package manager and it's subproject conda-build are fantastic tools to solve this problem. I write scientific software (primarily a mix of C++ and Python), and I use conda for literally every dependency I need, including pure C or C++ dependencies. Most of them are already available via the conda-forge project, and the ones that aren't are easily integrated by writing my own recipes to package with conda-build.
I write in a mix of python and C++ as well. As such, I've also gradually moved towards using conda. What's your dev workflow for the code that's not a dependency like? Specially if the code you're developing is C++. Do you make a conda package of it as you're developing? I've found that too much of a hassle to rebuild. I usually just use CMake with the conda environment dir as the CMAKE_INSTALL_PREFIX.
Yeah, that's what I do also. conda-build makes it easier to use your code downstream, but doesn't obviate the need for build scripts in general.

However, if you're using conda, you might be able to at least simplify your build scripts, even though you can't eliminate them.

If you are okay with requiring your users to have conda, then you can exploit that fact to simplify your CMakeLists.txt in some ways. For instance, find_library, etc.. can be replaced with hard-coded links to ${CONDA_PREFIX}/lib/...

(I'm not saying that's necessarily "best practice" for all projects, but it's a nice option to consider, especially in the early phases of development.)

Ah, that's an interesting shortcut.
I've found generally it's fine for most things but issues come from when the scientific libraries are built with/without certain options. The lack of OpenMP for FFTW is annoying, for example.
Yes, that's of course a potential issue with any distribution. The good news is that's relatively easy to tweak their recipe to build your own version.

  $ conda install conda-build # prerequisite...

  $ https://github.com/conda-forge/fftw-feedstock && cd fftw-feedstock
  $ emacs recipe/build.sh # Add --enable-openmp

  $ conda build recipe
  $ anaconda upload -u mychannel ${CONDA_PREFIX}/conda-bld/*-64/fftw-*.tar.bz2
Now you can install your custom-built version of fftw and easily share it with your friends by telling them the name of your channel on anaconda.org.
Bazel is what I really like (xoogler here, used it mainly with java+protos, minor python/c++, testing, other things). Similarly - gn (though different internals, easier platform switch, etc.), buck, pants, etc.

In all of these I like the single "BUILD" file per directory, and simple text file explaining dependencies.

Also these tools work awesome when you are in a company, and you have underneath - dozen or more libs, sdks, projects that need to run together.

> Also these tools work awesome when you are in a company, and you have underneath - dozen or more libs, sdks, projects that need to run together.

I'm pretty sure it works great then. But for the bigger C/C++ ecosystem the problem is that everyone uses another build system. From Makefiles to Automake, Cmake, Gyp, Scons, Bazel, etc. everything is included. As soon as you try to use dependencies that favor a different format you either have to rewrite their project definition in your format or at least build it as an external subproject and manually add the include directories.

Imho that's one of the biggest weaknesses of C++ that even outweighs language concern for me: It's often lots of work to integrate 3rd party libraries. And because of this there isn't a really great ecosystem of libraries (e.g. compared to Javascript and Java).

There is another big reason why there isn't a great ecosystem.

The C mentality of some devs dragged into C++ world.

Already in the 90's we had very nice high level C++ libraries, that could compete with what Java offered later.

Turbo Vision on MS-DOS, Object Windows Library and Visual Components Library on Windows. All from Borland.

PowerPlant on Mac OS, from Metrowerks.

From Microsoft, we had MFC, which started high level like those ones (originally named Afx), but then the beta testers requested for it to just be a thin layer over Win16 and it was reborn as MFC.

Many of the modern C++ patterns were already possible with those libraries, but the C wisdom made many not use them.

Another modern example is Android NDK, Google writes the code in nice C++ classes, that get exposed as low level C APIs, or have a Java JNI barrier (e.g. Skia).

> ...Google writes the code in nice C++ classes, that get exposed as low level C APIs...

I'm not on the android team, but in general it's easier to maintain reverse-compatibility with C ABIs than with C++ ABIs.

And the plethora of libraries and the modest size of the standard library are part of the problem. Let's say you're writing a C++ library that understands git and lets users develop more complex applications with your libgitxx as a building block. Whose filesystem primitives (file, directory, path, user, etc.) do you use? There's really not a satisfactory answer to that question.

Point being, C ABIs don't have this problem as much. They tend to just pass around ints and char. Maybe there are some structs to pass around, but they tend to be made up of ints, char, etc.

@ Google - most of the projects are linked statically, and all source code is present (possibly very, very few exceptions) at compile time (all open-source packages are recompiled too)...

As such, backward compatibility (binary-wise) is not needed.

Android, Chrome, etc. are another matter - former has an SDK, and I guess you may need that there, but I don't have much experience with it.

True, but if it's my understanding that Google doesn't pursue semantic versioning, at least with C++, partly because it's hard for a human being to understand what C++ changes affect an ABI and which ones don't.
Before C++17, boost::filesystem, now std::filesystem.

In Android's case, the ABI would be whatever g++, now clang, shipped with NDK support.

Additionally there are ways to implement compatible ABIs in C++.

In any case, they are wrapping the C++ classes already, either via C or Java, so they could offer them as well.

Instead, one needs to write unsafe C code, JNI boilerplate or wrap them again in C++.

The need to standardize things like std::filesystem was my point.

> Additionally there are ways to implement compatible ABIs in C++.

Eh... when even compilation flags affect ABIs, it's hard for a particlar C++ file to have a stable ABI. The same is true in C, of course, but the way C libraries are generally designed, it's simpler to be ABI compatible. In C++, arcane and unexpected things like noexcept specifications (affected by changes in underlying libraries!) can change your ABI.

> Instead, one needs to write unsafe C code, JNI boilerplate or wrap them again in C++.

That's a fair point. There might be reasons they'd do that (SLAs they can afford), but that's a presumption.

As update to my rant, I have to be faire to the Android team.

Apparently something related to C++ APIs on the NDK is planned to start appearing with NDR r17 onwards, as per Github issues.

Using another build system might seem like a problem, but at the end of the day - it's a list of .cpp, .h files, some defines and possibly some intermediate steps. These rules, list of files, etc. would tend to change much less in a well established library, hence a convert to another build system would not seem that big of a deal, even if two, or more a supported.

Today, Qt, for example has qmake, cmake, qbs? others?

The bazelment trunk, provides bazel rules for building ~30 libraries, among them: openssl, zlib, v8, folly, boost, and others: https://github.com/bazelment/trunk/tree/master/third_party

At work, I'm heavily vested in using Microsoft's VCPKG for new builds of open source libs, they internally use CMake, but then CMakeLists.txt were not even readily available for some of these projects, and the VCPKG maintainers did them.

At some point, or it may already exist, someone would make a converter from one system to another, lossy possibly. For example it's possible to parse everything you need, correctly from a .sln/.vcxproj usign the MSBuild framework, which is now available everywhere (except the actual .props, .target files, which are Visual Studio specific). But on a machine with the Community version of the compiler, these are free to use (should even work under wine). And since a lot of other build systems target .sln, .vcxproj files, this could in a way be a target for a lot of systems to converge, and from there extract what you want: - List of source, header files. - Separation between projects - Defines for each project. - etc.

It doesn't have to be perfect, but it's possible.

Or maybe from ninja generated files (though not meant to be used that way).

Luckily there is sane way to verify whether alternative build for a project works - simply compile it, and verify whether the produced artifacts are the same, or are close enough (list of produced header files, exported symbols, may go even deeper).

And yes, I do miss the Turbo/Borland Pascal's .TPU files, where no .h file was needed, or Borland C++ simple project file that was just listing line by line the .c files, but back then projects (which I was familiar with) were much smaller, even gcc, the linux kernel and others were much smaller then.

It really doesn't matter what build system other libraries use. If you use CMake, you just need to use `find_package` directives (https://cmake.org/cmake/help/v3.10/manual/cmake-packages.7.h...) to integrate third party libraries (which should be separately installed in the system).

It only seems difficult if you're stuck in the mentality of shipping all your dependencies with your source code and statically linking everything. C and C++ libraries actually care about maintaining API (and usually ABI) compatibility, so you don't have to ensure you have a specific version of dependencies.

Come on, you cannot even link C++ shared libraries from different version of standard library, potentially also compiler. ABI compatibility in C++ does not exist.

In C, you still had incompatibilities between compilers. (MS fastcall convention and declspec come to mind...)

There's better ABI compatibility than in the languages where you have to rebuild after every minor version bump. The C++ standard library version (and to some extent, the compiler) should be considered part of the platform.
C++ was the first programming language I picked up that made me feel like I was really making something (after VisualBASIC and HTML), when I was 13. Things were simpler back then. I used Borland C++ and it could produce .exe exports from .cpp sources, pulling in referenced #includes. Running it in Windows would launch a little DOS prompt that ran your program and then quit (unless you put a getch()!)

Like any teenager tinkering, I opened up some sample cpp file that came with the compiler and started just throwing in cout<< all over the place doing random stuff with variables. My older brother explained "loops", "functions" and later the basics of "arrays" and I completed my first game by 16, written purely in C++ -- a Tank clone that was a single long-ass cpp file using purely #include<graphics.h> calls, putting individual pixels on a 640x480 screen. I hadn't learned what classes were yet, so it was some form of "functional programming", just starting from a void main()

The point was, it was easy to tinker and learn and make stuff and explore in C++, the language isn't innately arcane. I didn't even have the internet back then to look stuff up. A teacher gave me a textbook for "graphics in C++" because my high school (which I wasn't in yet) computer science curriculum didn't cover graphics, but I hated copying code from the textbook (it seemed outdated) so I just opened up header files and tried to call functions I saw in there. I didn't need to use any fancy C++ syntax, just the basics my brother taught me. No make files, no build scripts, no gcc. Just an IDE that existed on a floppy disk and the most common OS available at the time (DOS).

It must be a sign I'm getting crotchety and old, but "back in my day" C++ wasn't such a confounding beast. And I'm sad to say I think modern-day python is going down a similar path, it started out intending to be the easiest language to tinker in, but it's slowly becoming arcane just to import a module and launch a program for a beginner.

I stepped back into C++ recently trying to make something "I can see" at from scratch, and it's damn nigh impossible without visiting 4-8 different instructional sites depending on how you define "something I can look at".

Picking up your example, that is exactly why ANSI C++ people want to have some kind of basic 2D support on the standard.

Yet the naysayers state that apparently we can have standard IO, filesystem and networking support, but not 2D.

The reason being that not all scenarios have graphical displays, as if all deployment scenarios always have filesystem and networking stacks.

it was some form of "functional programming", just starting from a void main()

'Procedural' is the term you were looking for.

Yeah I'm a bit sad about build systems.

I wanted to write a Postgres extension/fork, and was thinking "oh I'll just replace some bits with Rust".

Unfortunately everything is `make`, which is great when you're setting something up and have a huge process... but it's very hard to make some changes without figuring out the entire flow.

Someone that could somehow wrap make to make it easier to make incremental changes on existing projects could help fuel a lot more experimental changes.

>... wrap make to make it easier to make ...

I think there is a deep truth in (unintended?) pun: those who don't understand make are bound to reinvent it, poorly.

I feel like I "get" make, but when you have autotools in front of it you end up with stuff like [0], and Makefiles kicking off other Makefiles.

It's not the worst thing to ever grace the planet by a long shot, but I think it suffers from the same problem that bash scripts have. Everything is string-ly typed and you can only build up a thing to a certain level of complexity easily.

There's of course a lot of requirements in systems software, of course. And a lot of difficulty stems more from the C ecosystem's difficulties with packaging (where are the C equivalents to "environments" you find in Python/Ruby so you don't have to pass every lib in explicitly?)

I think there's some useful opportunities somewhere here.

[0]: https://github.com/postgres/postgres/blob/master/src/Makefil...

> Everything is string-ly typed and you can only build up a thing to a certain level of complexity easily.

Yes, make was designed for the 1970 world, where all you care about is building simple utilities on Unix. There was no today's complexity (out of tree builds, cross-compilation, etc) nor "alien" platforms like Windows where people have spaces in their paths.

What we need is to redesign make to handle today's requierements. And this is what we are trying to do with build2.

I'm a bit surprised to read this. It's been a long time since I've done C++ outside of the embedded space, but isn't this a problem that's solved by libraries and LTO? Compiling individual source units isn't terribly difficult either. There's a single tool that turns source files into object files, and a single tool to link them all up. It all feels very UNIX-y to me.
LTO is unrelated. Compiling individual units is only one of many build system responsibilities. There's also dependency management, cross-platform support, iterative builds, etc. And it all needs to be fast.
Cake https://github.com/dcasnowdon/Cake

Written by Matt Hermann who I believe is now at google - I've never met him. I worked somewhere that used it and have used it ever since for every greenfields C++ project I've done and just don't think about my build system anymore. For me this just works for everything I need, adding a linkflag for this .cpp file only? Dead simple, hard to imagine it being simpler. Build tests and run with valgrind equally so. I have a single top level makefile that literally just specifies targets, eg release, test, asan_test, clang_release etc. Each is a single cake line.

I highly recommend it for C++ on linux (haven't used it elsewhere to say), for me it's a thing of beauty and a joy forever. Not dealing with autotools or scons or complicated make based build systems makes C++ so much nicer to work with.

> Developers have to wade through a complex soup of Makefiles/CMake/Gyp/ninja etc

But you do this precisely once.

Once every week.
I don't see how that's possible unless you're just starting out. Do you start a new project every week?
- I have to start seperate small test and prototype projects every now and then.

- I have to include third party libraries occasionally, and sometimes weight if it wouldn't be just easier, faster and better for my mental health to reinvent the wheel instead.

- I have to check out, test or use third party projects every now and then. They all use different build systems, different ways to use different build systems and different work around to get around limitations of build systems.

I like C++11 and love C++17 but absolutely hate building and managing C++ projects. It's a mess.

I guess it also applies when you want to integrate a new third party library once a week, in cases where the libraries build system doesn't match yours.
Check build2, it has a sane build model and syntax and comes with integrated package manager/repository, similat to Cargo:

https://build2.org

>Gradle

Ugh