#Writing C/C++ Build System

129 messages ยท Page 1 of 1 (latest)

cursive jacinth
#

Hello fellow C++ community. I decided to write my own build system for myself (and maybe for others who will find it usefull) which should be as simple to use and as powerful as possible. I want everything be defined in single build file and right now need to sum up other people opinion on this. Right now I came up to this:

(predefined statements)
compiler, standart, output_dir, obj_dir, src_dir, include_dir, lib_dirs

Example config using these:

compiler = "clang++" //if it is in the path, otherwire privide path to executable
standard = "c++20"
output_dir = "build/bin"
obj_dir = "build/obj"
src_dirs = {  "../src" }
include_dirs = { }
lib_dirs = { "SDLmain.lib" "SDL.lib" }

(User defined variables)

Example:

path_to_sdl = "path/to/sdl"

include_dirs = { ${path_to_sdl}/include }

Right now I wrote file lexer and until it is not too late I want to know

What are your thoughts on this design? What is missing, what can be improved? I will glad to hear and implement you ideas and suggestions! Thanks!

jade fiber
#

Is this is a toy project, or something you want to be actually useful?

#

If you want it to be useful, you need a selling point. What other build systems do wrong that yours does right?

cursive jacinth
#

Oooh I got you! Never thought of it! Thats a really good idea I will look into it. Thanks!

jade fiber
#

Or if you don't make the config file C++, at least make it a common format such as JSON

jade fiber
cursive jacinth
#
#include "../src/makepp.hpp"

int main() {
  Maker build;
  build.SetCompiler("clang++").SetStandard("c++20")
       .SetBuildDir("../build");

  build.AddExecutable("main1")
       .AddSources({"hello_world.cpp"});

  //build.AddLibrary("libmylib.a");
  build.Make();
}

Going as is. Working on cross-compile rn.

cursive jacinth
#

wdym?

#

I already use them here

#

you pass sources as vector

#

or you meant smth else?

#
#pragma once

#include <iostream>
#include <vector>
#include <filesystem>

#include "target.hpp"

class Maker {
  private:
    std::vector<Target> _targets;
    std::string _compiler = "clang+";
    std::string _build_dir = "build";
    std::string _standard = "c++17";
    std::string _output_extension_exe = "";
    std::string _output_extension_lib = ".a";

  public:
    Maker() {
      DetectPlatform();
    }

    Target& AddExecutable(const std::string& name) {
      this->_targets.push_back(Target(name));
      return this->_targets.back();
    }

    Target& AddLibrary(const std::string& name) {

    }

    Maker& SetCompiler(const std::string& compiler) {
      this->_compiler = compiler;
      return *this;
    }

    Maker& SetStandard(const std::string& standard) {
      this->_standard = standard;
      return *this;
    }

    Maker& SetBuildDir(const std::string& build_dir) {
      this->_build_dir = build_dir;
      return *this;
    }

    void Make() const {
      if (!this->_build_dir.empty())
        std::filesystem::create_directories(this->_build_dir);
      
      for (const auto& target : this->_targets) {
        std::string output_path = this->_build_dir + (this->_build_dir.empty() ? "" : "/") + target.GetName();
        std::string command = this->_compiler + " -std=" + this->_standard + " -o " + output_path;

        for (const auto& src : target.GetSources()) {
          command += " " + src;
        } 

        for (const auto& include_dir : target.GetIncludeDirectories()) {
          command += " -I" + include_dir;
        }

        for (const auto& dep : target.GetDependencies()) {
          command += " " + dep;
        }

        std::system(command.c_str());
      }
    }

    void DetectPlatform() {
      
    }
};
#

I want to make it full headers. So people can compile build file very simple with just 1 command without links

jade fiber
#

Another thing that would be nice to have is respecting the standard environment variables (CC, CXX, CFLAGS, CXXFLAGS, etc)

cursive jacinth
forest saddle
#

works

#

that will be cool

#

...writing c++

jade fiber
#

I definitely don't want to edit its build script to change the flags

cursive jacinth
jade fiber
#

.SetCompiler should probably be ignored if the env variable is set

#

The flags env variables should be appended to the end of flags you have hardcoded. I think

cursive jacinth
#

Will work on it aswell thanks!

cursive jacinth
#

So constructor of Maker class will automatically set all flags

hidden owl
cursive jacinth
jade fiber
#

IMO non-turing-complete build systems are uncool. You'll inevitably want to do something non-trivial, and then you have to resort to shell scripts or whatever

cursive jacinth
hidden owl
# cursive jacinth <@185713939353567232> yes you will need to do such as ```bash clang++ main.cpp -...

The main advantage of build systems is basically to care less about the compiler and platform and let almost everything automated, of course you are still needing to care about edge cases but they are rare.

If you need to compile the build system you are going to need to care about the compiler, platform and etc all the time, because the build system itself is a binary file. also it makes CI more complicated to use.

cursive jacinth
#

If you are writing it for sure you know at least how to compile a single file into exec

jade fiber
hidden owl
jade fiber
#

Being cross-platform isn't hard, you just cl /EHsc main.cpp or g++ main.cpp on MSVC and everywhere else respectively

hidden owl
cursive jacinth
#

Cross-compile handled in library itself

cursive jacinth
hidden owl
#

how are you going to distribute the headers of your build system? Also would they need to be put together with the project in the repository?

#

What if there is a bug in the build system, how would I need to update it?

cursive jacinth
hidden owl
#

if there's a share lib, wouldn't it be better to have a bootstrapped build system where the build system compiles and setup itself?

jade fiber
#

I'd make it header-only, personally

#

It's not like you'd need to rebuilt it often

hidden owl
#

I bet people would put in the include folder and then the build system will build system together with the app

jade fiber
#

๐Ÿคทโ€โ™‚๏ธ

cursive jacinth
hidden owl
jade fiber
#

My hot take is that nobody will ever need anything other than header-only for this

#

But that's me

hidden owl
#

Use modules

#

So people struggle to find a way to install gcc-13

cursive jacinth
cursive jacinth
jade fiber
#

Btw, the reason why I have opinions on all this is because I've been contemplating making a build system similar to this one

cursive jacinth
cursive jacinth
willow echo
#

you're going to need to be able to execute C and C++ compile commands, using different compilers, and link the objects

cursive jacinth
#

Right now I am targeting c++ only. I will later think how to implement C support but I can just make C headers

willow echo
#

but before i say anything else: this looks really interesting. you're underestimating the huge number of corner cases and weird situations a build system will run into, but that's completely OK. in fact you should ignore much of the what-ifs now if you feel like exploring the design

willow echo
cursive jacinth
willow echo
#

i'll show you a pet build system of mine, implemented in GNU make, not as a suggestion in any way but just to show how some projects will mix C and C++ translation units

willow echo
#

unfortunately the verbose build option is off in that run, let me start one for you with the compile commands visible

cursive jacinth
willow echo
#

i have a compiler feature introspection thingy in my build system. overall it's highly cursed, it uses extremely horrible GNU make syntax and features

willow echo
cursive jacinth
willow echo
#

i do a test build with each one of these compilers: gcc-7, gcc-8, gcc-9, gcc-10, gcc-11, gcc-12, gcc-13, gcc-14, clang-6, clang-7, clang-8, clang-9, clang-10, clang-11, clang-12, clang-13, clang-14, clang-15, clang-16, clang-17, clang-18, clang-19, clang-20

#

so a few of them

#

the compiler flags with all the different warning settings etc. aren't any representation of some absolute "correct" set of flags for the compiler versions. they're based on what language standards and features i'm using

#

btw, i'm explicitly only using the C23 standard of C, or as close to it as I can, so everything in my build only really applies to this specific choice of language standard

#

the C++ side isn't really used in any proper way so I haven't spent as much time on it

willow echo
#

but don't look too long, don't get exhausted ๐Ÿ™‚

#

focus on the compiler flags you use now and work with those

cursive jacinth
#

it seems to be usefull

willow echo
#

I think I do have those in every build config as a baseline

cursive jacinth
#

especially as I can see for clang

willow echo
#

clang has -Weverything but it literally enables every useful and useless warning, a lot of them need to be overridden with -Wno- flags to make the result reasonably useful

#

it's probably not a good idea to use the -Weverything -Wno-... approach with clang though, it takes fiddling

#

-Wall -Wextra is good

cursive jacinth
#

okay

#

right now I think I will focus on clang mainly untill most of the usefull features will be done

willow echo
#

sounds like a good strategy

cursive jacinth
#

gonna test build system on my sdl2 + opengl + imgui project

willow echo
#

a build system literally based on C++ classes is a really interesting concept imho

cursive jacinth
cursive jacinth
willow echo
#

testing a build system is just doing all the permutations of configurable parameters and seeing what happens ๐Ÿ˜„

willow echo
#

i still don't do that btw, i need to add that to my CI. current number of builds in ubuntu-24.04 is "only" like 46

#

it'll grow with a multiple of jesuschrist when i permute all the things

cursive jacinth
#

last CI i wrote was back ago in october. Ig I already forgot everything

willow echo
#

my background is in bash scripting so i fall back on writing scripts usually, i'm going to slowly move more stuff into a collection of bash utility functions and make the actual github workflow file much smaller

#

now there's a lot of inline bash there for no other reason than not bothering to dedup it yet

cursive jacinth
#

oh

#

one day the time will come ๐Ÿ˜…

cursive jacinth
jade fiber
#

Do you build them from source? I consistently have problems with linux distros not updating the compilers fast enough. Or not letting you install different versions of one (ubuntu doesn't let you have different libc++'s, I think?)

willow echo
#

The installatiom of all of them

jade fiber
#

Ah, you don't test libc++, and the rest is simple ๐Ÿ˜

#

Dunno what I expected

willow echo
#

Just adding repos and running apt basically

#

To an extent yes

cursive jacinth
#

Added preset default toolchains for corresponding systems

  struct ToolchainConfig {
    std::string compiler;
    std::string archiver;
    std::string linker_flags;
    std::string output_extension_exe;
    std::string output_extension_lib;
  };

std::unordered_map<std::string, ToolchainConfig> _toolchains;

this->_toolchains["windows-x86_64"] = {"x86_64-w64-mingw32-g++", "x86_64-w64-mingw32-ar", "", ".exe", ".lib"};
      this->_toolchains["windows-x86"] = {"i686-w64-mingw32-g++", "i686-w64-mingw32-ar", "", ".exe", ".lib"};

      this->_toolchains["macos-x86_64"] = {"clang++", "ar", "", "", ".dylib"};
      this->_toolchains["macos-arm64"] = {"clang++", "ar", "", "", ".dylib"};

      this->_toolchains["linux-x86_64"] = {"g++", "ar", "-L/usr/lib/x86_64-linux-gnu", "", ".a"};
      this->_toolchains["linux-arm64"] = {"aarch64-linux-gnu-g++", "aarch64-linux-gnu-ar", "-L/usr/lib/aarch64-linux-gnu", "", ".a"};

Also now it automatically parse env variables as @jade fiber suggested and automatically detects current platform and architecture trying to build with preset toolchains if user didn't set compiler manually.

#

I think now its time to add some sort of cross compile. I think each Maker class should be set to needed arch

#

Yep. I can use it for testing