Package: nanobuild
NanobuildA very simple and customizable build system made in Python that generates Ninja.Installationpython3 -m pip install nanobuildWhy another build system?Modern build systems are cool, but for system programming where you need fine control over what gets compiled and how, a lot of build systems are inadequate and very difficult to control. CMake has dozens of variables that will change depending on the host configuration, and because of that it often feels like playing whack-a-mole whenever something about the build changes. Make is probably the best suited for that, but its syntax and features leave a lot to be desired. SCons is also a very good option, but you often feel like you're fighting the build system and it's not as fast as Ninja.All of these reasons combined made me want to build my own build system that is simple to use, is built upon a powerful scripting language like Python that allows an infinite degree of customization, and is very lightweight.Example usagebuild.py:#!/usr/bin/env python3importnanobuildasnbenv=nb.Environment(CCFLAGS=['-g'])sources=env.source_glob('**/*.cpp')objects=env.CXX(sources)binary=env.CXXLink(objects,'test')nb.run(binary)Usage:./build.pyEnvironmentsOne of the core concepts innanobuildis theEnvironment, which is essentially a collection of options and builders. Any number of environments can be created. Theclonemethod can be used to create copies of an environment. Theappendandreplacemethods are convenient ways of adding many variables using named arguments:env=nb.Environment(...)env.append(CFLAGS=['-g','-O3'],LIBS=['-lpng'])env.replace(CXX='clang')The source and build directories are also configured through the environment. The convenientsourceanddestmethods can be used to generate file paths to things in the source directory, or in the build directory.sources=[env.source('a.c'),env.source('b.c')]sources+=env.source_glob('**/*.cpp')# glob is also supportedbuild_file=env.dest('a.o')# relative to build directoryThe builders can also be accessed through the environment. Currently, these built-in builders exist:ASCCCPP(orCXX)LDLinkCCLinkCXXLinkStaticLinkPhony(currently broken)To invoke a builder, you just have to invokeenv.Builder_Name(inputs, output, **kwargs). The function will return aTargetobject (or a list of target objects) that can be used as inputs to other targets.Some builders (AS,CC,CPPandCXX) will generate their own output files, in these cases specifying the output isn't necessary. Currently this is the only way of calling these builders with multiple inputs; if you want to set your own target, you have to call the builder for each input file individually.Creating custom buildersTo create a custom builder, just derive from theBuilderclass.Here is an example of how a builder looks like:classCXXBuilder(Builder):multi_input=Falseautogenerate_output=Truedefdefault_vars(self):return{'CXX':'g++','CCFLAGS':[],'CXXFLAGS':[]}defgenerate(self)->Optional[str]:return"{CXX}{CCFLAGS}{CXXFLAGS}-c -o{OUT}{IN}"defgenerate_output_file(self,source:Path)->Optional[Path]:returnsource.with_suffix('.o')This is the C++ builder. As you can see, a builder will have some default options that will be added to the Environment (these can be overriden by the user). The generate function generates a command that will be used in the ninja script; this uses thestr.format_mapbuiltin function, and will replace all the{ }statements with variables from the environment.The{IN}and{OUT}variables are special (and reserved), and they will be replaced with the input and output files of the command.Some builders can only take in a single source file (like the CXX builder in this example), which is whymulti_input = False. Other builders, like the linker will take multiple inputs, so in that casemulti_inputshould be set to True.Theautogenerate_outputtells nanobuild if this builder can generate an output file path from a source file path (i.e. replace .cpp with .o). Builders that have this capability should implement thegenerate_output_filemethod. The way this mechanism works is that nanobuild can take a list of input files, and if it detects that the builder isn't multi-input but it can generate output files, it will do that instead.TODOsfind a more graceful way of handling batches (i.e. multiple input files to builders that can only take single inputs)more builtin buildersbetter error handlingstore variables inbuild.ninja