Capri, a script based build system
Posted: Mon Jun 23, 2014 6:37 am
Note: this description is quite outdated, I will add a link to the project website once it's online.
Heyho:)
I'd like to announce a project I've been developing lately. It's a script based build system named Capri written in C++. Initially I just made it to have something for my kernel, but I think it could be useful for other people, too. I'll explain a little about what you can do with it here. By now it doesn't have a project website, but if I see enough interest I'll put the effort in it and make one (with an extensive documentation, for sure). Until then, you will find news and more in this thread.
It's quite too much to tell about all the features in a single post, but I want to give you an idea and maybe wake your interest. Feel free to ask any questions. So, let's dive right in with some examples!
Projects & Tasks
First things first, the main thing that you write when using Capri is a project. A build script can contain multiple projects, and each project can contain multiple tasks. A task can have dependencies, which are executed before the task itself is run. This is an example for a minimum "Hello world" with a dependency:
A dependency can not only be the name of a task, but any kind of expression: you can for example call a dependency with a parameter of the task:
This allows writing dependencies chains and reusing tasks by passing parameters.
Script & syntax
The language itself has a quite easy syntax (similar to Java/C++/JavaScript). You don't have to define variables, you just assign them. If you want to define a variable in a context higher than where you're assigning it, you must use the keyword def.
The script also allows using an expression within a string literal, what makes it much easier to concatenate strings:
To define an array, you can simply write expressions inside {"curly", "brackets"}.
Note: anything you pass to a function or assign is copied, there is not pass-by-reference.
Native functions
So, we have a fancy script now, but we also want to do things with it. The script by now provides the following native functions (which can easily be extended, see the "Main.cpp"), I think you can figure out what most of them do
This is the way to distinguish between operating systems, architectures or later targets (not implemented yet). The values for these switches are set in the "Main.cpp".This allows for example to create a global variable and then assign something special if you're on a specific architecture.
Concurrency
The script is also able to perform things concurrently. You can do that like this:
You can put the concurrent keyword in front of any statement, but note that if you put it in front of for example a while-loop, the entire loop is run in a thread, and not each execution of the body. This is something I'll add in the near future, but maybe with a different keyword.
Launching it
Once you have compiled Capri and added the binary to your PATH, you can easily run it like this:
Simple, isn't it? It will automatically look for a file named "Build.capri" and execute it's default task. If you want to run a specific task, or any other expression, you can do this:
The expressions you pass there will then be interpreted within the context of the first project found in "Build.capri".
If you want to load a different file than the main script, you must do it like this (this isn't very comfortable yet, because you can't use the default task, comes on the TODO-list):
How can I use this for my project?
By now it's still in an early stage, however you can play around and already create a fully functional buildscript. This is a little example file that you can use:
To get some inspiration, you can find the buildscript of my kernel here.
Getting the source
It might not have the very best code yet and gives some weird error messages if you mess up the syntax. ^^ But its only a work-in-progress by now.
// Out of date, sorry
Feel free to ask questions! If you are interested I would be glad to hear some feedback on this, considerations, critics and suggestions are highly appreciated.
Thank you!
Greets, Max
Heyho:)
I'd like to announce a project I've been developing lately. It's a script based build system named Capri written in C++. Initially I just made it to have something for my kernel, but I think it could be useful for other people, too. I'll explain a little about what you can do with it here. By now it doesn't have a project website, but if I see enough interest I'll put the effort in it and make one (with an extensive documentation, for sure). Until then, you will find news and more in this thread.
It's quite too much to tell about all the features in a single post, but I want to give you an idea and maybe wake your interest. Feel free to ask any questions. So, let's dive right in with some examples!
Projects & Tasks
First things first, the main thing that you write when using Capri is a project. A build script can contain multiple projects, and each project can contain multiple tasks. A task can have dependencies, which are executed before the task itself is run. This is an example for a minimum "Hello world" with a dependency:
Code: Select all
import "SomeOtherScript.capri";
project Example default all {
task all depends clean {
Utils.println("Hello world!");
}
task clean {
Utils.println("Doing cleanup...");
}
}
Code: Select all
task first(par) depends second(par) { ...
task someTask(test, bla) depends anotherTask(test + "/bin", 43 * bla) { ...
Script & syntax
The language itself has a quite easy syntax (similar to Java/C++/JavaScript). You don't have to define variables, you just assign them. If you want to define a variable in a context higher than where you're assigning it, you must use the keyword def.
Code: Select all
def myVariable;
for(i = 0; i < 100; i++) {
myVariable = 25 + i;
}
// now you can still access myVariable
Code: Select all
myFlags = "-std=c++11";
foreach(source : sources) {
System.execute("gcc {myFlags} {source} -o {File.getFileName(source)}.o");
}
// You could also call a function from within that:
Utils.println("Hello {getName()}!");
Note: anything you pass to a function or assign is copied, there is not pass-by-reference.
Native functions
So, we have a fancy script now, but we also want to do things with it. The script by now provides the following native functions (which can easily be extended, see the "Main.cpp"), I think you can figure out what most of them do
- System.execute(command)
- String.endsWith(str, suffix)
- File.isFolder(path)
- File.isFile(path)
- File.cleanFolder(path)
- File.listFolder(path)
- File.listTree(path) - recursive file system listing
- File.createFolder(path)
- File.deleteFile(path)
- File.deleteFolder(path)
- File.getFileName(path)
- File.getAbsolutePath(path)
- File.changeDirectory(path)
- File.getCurrentDirectory()
- Utils.print(message)
- Utils.println(message)
- Utils.length(obj) - returns the length of the array or string you give to it
This is the way to distinguish between operating systems, architectures or later targets (not implemented yet). The values for these switches are set in the "Main.cpp".
Code: Select all
compiler = "gcc";
task prepare {
architecture x86 {
compiler = "arm-gcc"; // we want to cross-compile
}
system windows {
flags += "-someSpecialWindowsFlag";
}
}
Concurrency
The script is also able to perform things concurrently. You can do that like this:
Code: Select all
executionA = concurrent someFunction(withSomeParameter);
executionB = concurrent someOtherFunction();
// These two functions will now execute simultaneously.
// Before the current context exits, they will automatically be joined,
// but you can also join them manually to get the result:
resultA = join executionA;
resultB = join executionB;
Launching it
Once you have compiled Capri and added the binary to your PATH, you can easily run it like this:
Code: Select all
capri
Code: Select all
capri specialTask
// or use any kind of expression:
capri performMyTask("Some text!")
If you want to load a different file than the main script, you must do it like this (this isn't very comfortable yet, because you can't use the default task, comes on the TODO-list):
Code: Select all
capri all MyScript.capri
How can I use this for my project?
By now it's still in an early stage, however you can play around and already create a fully functional buildscript. This is a little example file that you can use:
Code: Select all
project MyProject default all {
isoPath = "image.iso";
srcPath = "src";
binPath = "bin";
task all depends compile, link {
Utils.println("Finished, running...");
System.execute("qemu-system-i386 -cdrom {isoPath}");
}
task compile {
allFiles = File.listTree(srcPath);
foreach(file : allFiles) {
System.execute("i386-elf-gcc {file} -o {binPath}/{File.getFileName(file)}.o ...");
}
}
task link {
System.execute("i386-elf-ld {binPath}/*.o ...");
}
}
Getting the source
It might not have the very best code yet and gives some weird error messages if you mess up the syntax. ^^ But its only a work-in-progress by now.
// Out of date, sorry
Feel free to ask questions! If you are interested I would be glad to hear some feedback on this, considerations, critics and suggestions are highly appreciated.
Thank you!
Greets, Max