Welcome to BuildGen, in this tutorial we will cover how to both create the required files and use BuildGen to build your project. The two will be intertwined as we will be changing the building as we add new features to the configuration files.
This tutorial was intended to be read in order and we will be building upon we was discussed earlier.
To start off we are going to create a sample project. The project is going to use C and python because they are well known and cross-platform. It is also useful to have a compiled example. I am also going to assume that your project is located in "/src" and you are building in "/src/build".
This is also available in an archive.
-> build # Where we are going to build our project -> [Empty Directory] -> printer # a library for printing things. -> printer.h -> printer.c -> Hello World.py # The main program.
--- Hello World.py --- #! /usr/bin/env python import os os.system("printer Hello World!")
--- printer/printer.c ---
#include <stdio.h>
int main ( int argc, char **argv )
{
int i;
for ( i = 1; i < argc; i++ )
printf("%s ", argv[i]);
putchar('\n');
return 0;
}
The program is a two part program that prints "Hello World!" to standard output. Nothing too fancy.
BuildGen generates the project info by reading a select set of files. These files contain information that describe the project. In order to keep BuildGen simple for anyone to run these files have pre-determined names. There are two different files used to describe the project.
There is one Buildroot
file in each project. It is located in the root directory of the project folder.
This Buildroot
file is loaded before every other file. It describes project wide settings such as version numbers and compiler flags.
Buildinfo
The Buildinfo
files are the files that do all of the work. They tell BuidGen what to compile and where to put it. It is good practice to have one Buildinfo file for each section of your project. This allows sections of the project to be build (along with all of their dependencies) with the same build configuration you would have if you built the whole project.
Now that we have learned about the theory we will move into how it actually works. Since there is only one Buildroot
per project and it is used by all files it seems like a logical place to start.
Since or project is very simple there is not much we need to set up in the Buildroot
. We could very likely get away with an empty one but, in the interest of learning we will put a little info in there now.
All we are going to put in our Buildroot
(for now) is some project info so that whenever we need to use it from inside our Buildinfo
's we will have the same thing. So create a Buildinfo
file with these contents:
projectName = "Sample Project" -- These are not necessary but keep the project version = "1.0.0" -- neat.
Pretty self-explanatory. Some keen observers have noticed the comment syntax and some might have thought of Lua. This is not a coincidence as all of BuildGen's configuration files are Lua scripts.
While you can do a lot with Lua scripting, it is strongly advised not to generate any files from BuildGen configuration files. This is because BuildGen is supposed to generate build scripts and the actual building of the project will be done afterwords. It is a much better practice to put your generation scripts and other programs inside another file and tell BuildGen about that.
The names "projectName" and "version" are almost completely arbitrary. The only thing to consider when choosing names is to ensure you names don't collide with BuildGen's commands. To make this easy for you BuildGen uses a simple naming scheme so that you can be sure that the next version of BuildGen will not use names that would collide with the ones you use in your project.
BuildGen organizes all of it's functions an variable names into namespaces. These namespaces (Lua tables) are all one capital letter. The current namespaces are; C
the Core namespace, D
the Definitions namespace, S
the Standard library namespace and L the user Libraries namespace. There is also a namespace called P
for libraries to store data between Buildinfo files. You should not use this namespace as anything that you need to remember should be kept in your Buildroot.
Because of the naming scheme you know that anything that is not a one capital letter name will not interfere with BuildGen's framework and we will not interfere with your application in the future.
There is one more thing to remember. Once you import BuildGen's base standard library Penlight and LuaFileSystem are also loaded into the global namespace. So it would be wise to try to avoid their names also.
Now that we have our Buildroot
set up we are going to start on our Buildinfo
's. We will use one for our main program (Hello World.py) and one for our helper application (printer). We will start with our main program because we can fully install it without error. (except for the runtime error)
We will go step by step through the procedure. First, we will import the standard library. It contains an install function that makes installing files nice, easy and tidy. (It also allows us to install new programs without rebooting on UNIX)
S.import("stdlib") -- or -- S.import "stdlib"
Now we have the standard library loaded into S. Wait! you might say. We just called something from S, and you are absolutely right. Some basic functionality is loaded into S
(and L
) when the core (C
) namespace is initialized so that they can load themselves.
The second way of writing it is just syntactic sugar via Lua's constructor call syntax.
Next we will tell BuildGen what to install where.
S.install("Hello World.py", "bin/")
Notice that there is not a full path to bin. The install prefix is added to the path. If you want to specify a full path just make sure it starts with a '/'. In BuildGen all path seperators will be represented as '/' and the correction will be preformed for other operating systems.
That is it for installing your main program. Your Buildinfo
should now look like this:
S.import "stdlib" S.install("Hello World.py", "bin/")
and your entire setup should look like this.
Now that you have got your project set up it is time to build it.
cd build # Move into your build directory gen .. # call BuildGen
If you look at your build directory you have probably noticed that there is a new file (or files). If you are running UNIX it is probably a Makefile. This is because BuildGen will automatically pipe it's output into a default generator. If you want to see just BuildGen's output you can use:
gen -o build.xml ..
Then you can look at "build.xml" to see what BuildGen did. It should look something like this:
<?xml version="1.0" encoding="utf-8"?> <BuildGen version="0.1.0"> <meta> <projectroot>/data/Desktop/buildgen-tutorial/</projectroot> <outroot>/data/Desktop/buildgen-tutorial/build/</outroot> <buildgenroot>/usr/lib/buildgen</buildgenroot> </meta> <targets> <target> <out>/data/Desktop/buildgen-tutorial/build/install</out> <depends>/usr/local/bin/Hello World.py</depends> </target> <target> <out>/usr/local/bin/Hello World.py</out> <generator> <arg pos="0">/bin/install</arg> <arg pos="1">-D</arg> <arg pos="2">/data/Desktop/buildgen-tutorial/Hello World.py</arg> <arg pos="3">/usr/local/bin/Hello World.py</arg> </generator> <depends>/data/Desktop/buildgen-tutorial/Hello World.py</depends> </target> </targets> </BuildGen>
Then, if you so desire, you can manually run a generator. The generators have the prefix gen- then their name. For example:
gen-makefile build.xml
Then you get your Makefile again. Then you can build. With make that looks like:
make # Build your project. (In this example does nothing) make install # Install it.
Now you can run your program:
"Hello World.py"
And you will get an error telling you that printer isn't found. So that is what we will do next.
Python is really easy to install and a simple Python script is enough of an installer for a Python program. But C and other compiled languages present more of a challenge. First we will show you the hard way. (doing it manually) then the easy way using the C standard library.
Before you start either way we are going to add a dependency from "Hello World.py" to "printer". To do this you add the following line to your Buildinfo
. (the one we already wrote)
C.addDir "printer" -- Tells BuildGen to look for the Buildinfo for more info
This simply tells BuildGen that there is a section of the project in the "printer" directory and to look at it.
We are going to show you the manual way for many reasons. The first being the least important.
If you really don't want to know you can skip ahead BUT make sure you read the section called "BuildGen Paths" because it relates to everything that you will ever do in BuildGen. Other than that section you can skip it and look at this when you need it. That being said, feel free to skip ahead.
Create "printer/Buildinfo" and put the following into it:
S.import "stdlib"
Easy enough so far, right? Now comes the hard part:
C.addGenerator( {"@printer"}, {"printer.c"}, {"*gcc", "-o", C.path"@printer", C.path"printer.c"} )
Woah! That's a lot of new information. What this actually is is the core command of BuildGen C.addGenerator
. Almost everything filters down to this command. It takes three arguments, 3 tables acting like lists. (Tables with numerical keys starting at 1) The three arguments are, in order; outputs that this generator creates, input files that are used, the command to make it all happen.
So if you look at the command again you can start to see what is happening. It creates the executable printer, using *gcc -o ${something} printer.c*
. So what are these funny characters? and what is C.path?
BuildGen uses short-forms embedded in it's paths to make it quick and easy for you to identify files.
Many functions know that you are going to pass them a path and will call C.path for you. But, in the odd case, like above, that we need to convert it we can call C.path manually and it will return the absolute path.
Some examples if you are running "/src/thing/Buildinfo": (assuming your project is in "/src" and you are building in "/src/build")
S.path("prog.c") --> "/src/thing/prog.c" S.path("<prog.c") --> "/src/prog.c" S.path(">prog") --> "/src/build/prog" S.path("@prog") --> "/src/build/thing/prog" S.path("!<prog.c") --> "/src/thing/<prog.c" S.path("!!!prog.c") --> "/src/thing/!!prog.c" S.path("*ls") --> "/bin/ls" -- Assuming these are actually S.path("*python") --> "/usr/bin/python" -- present on the system.
C.addGenerator( {"@printer"}, {"printer.c"}, {"*gcc", "-o", S.path"@printer", S.path"printer.c"} )
So we got this and we understand it. Lets build it.
gen .. make
But make did nothing! make install won't eiter (except install "Hello World.py" if you deleted it.). Why is that? It is because you didn't tell it to. "Hello World.py" doesn't directly need printer so it wasn't built. And BuildGen doesn't know how or where to install it.
So lets add that on:
C.addDependancy(">all", "@printer") C.addGenerator( {"/usr/local/bin/printer"}, {"@printer"}, {"*install", "-D", C.path"@printer", "/usr/local/bin/printer"} ) C.addDependancy(">install", "/usr/local/bin/printer")
Now we are using C.addDependancy()
. This tells BuildGen that one program needs another. In this case that all needs "@printer". But what is all? Nothing really. It is the default target. The build-script generator will try to make this happen by default. For example the makefile generator will run all be default. And to build all it must build everything that depends on it first. For example "@printer". The second one is telling BuildGen how and where to install the program. It uses another pseudo-target install. So if you type make install it will install it.
If you try that it now works. Now you have a good understanding about how BuildGen works low down and it is time to learn the easy (and preferred) way. You may have noticed that the last version would only work on a Posix environment. You would essentially need another version for each system.
Modify "printer/Buildinfo" to have the following in it:
S.import "c" S.c.compile("@printer", {"printer.c"}); S.addToDefault "@printer" S.install("@printer", "bin/")
We have seen two of these lines before. The import statement loads the C and standard libraries. Please remember that. Whenever a library is loaded from S.import the standard library is loaded first if it is not already. There is no harm in trying to load libraries more than once as S.import quietly won't let you. The C library will now be available at S.c. This is how all of the libraries work. They are loaded into their own namespace that matches their name.
The second (functional) line is completely new. It is calling the compile function from S.c
. This function takes an output file and a table (list like) of files as sources. You should also add your header files so that your program gets recompiled if they change.
This function is completely cross-system as it looks through a list of compilers until it finds one. The C library also lets you set compiler flags in a compiler independent way, but we'll get to that later.
The last line we have seen too it just installs the program.
Run gen ..
and build the project. Congratulations! You now have "Hello World.py" installed with its helper application.
Your final setup should look something like this.
I hope you liked BuildGen. For more advanced features please read our [How To's](UserDocs_HowTo].
Wiki: UserDocs
Wiki: UserDocs_HowTos_CGenerateHeaderFile
Anonymous