OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 8:10 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 3 posts ] 
Author Message
 Post subject: Capri, a script based build system
PostPosted: Mon Jun 23, 2014 6:37 am 
Offline
Member
Member
User avatar

Joined: Mon Mar 05, 2012 11:23 am
Posts: 616
Location: Germany
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:
Code:
import "SomeOtherScript.capri";

project Example default all {

    task all depends clean {
        Utils.println("Hello world!");
    }

    task clean {
        Utils.println("Doing cleanup...");
    }
}

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:
Code:
task first(par) depends second(par) { ...
task someTask(test, bla) depends anotherTask(test + "/bin", 43 * bla) { ...

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.
Code:
def myVariable;
for(i = 0; i < 100; i++) {
    myVariable = 25 + i;
}
// now you can still access myVariable

The script also allows using an expression within a string literal, what makes it much easier to concatenate strings:
Code:
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()}!");


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 :)

  • 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

Distinguishing architectures/systems/targets

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:
compiler = "gcc";

task prepare {
    architecture x86 {
        compiler = "arm-gcc"; // we want to cross-compile
    }
    system windows {
        flags += "-someSpecialWindowsFlag";
    }
}
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:
Code:
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;

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:
Code:
capri

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:
Code:
capri specialTask
// or use any kind of expression:
capri performMyTask("Some text!")

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):
Code:
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:
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 ...");
    }
}

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

_________________
Ghost OS - GitHub


Last edited by max on Thu Oct 02, 2014 1:03 pm, edited 2 times in total.

Top
 Profile  
 
 Post subject: Re: Capri, a script based build system
PostPosted: Mon Jun 23, 2014 9:53 am 
Offline
Member
Member

Joined: Sun Feb 01, 2009 6:11 am
Posts: 1070
Location: Germany
I don't want to sound negative, but... I missed the description of the problem that it solves. ;)

To me it looks mostly like Makefiles with a different syntax and with a tendency to use more of a procedural than a declarative style. Is it just that and you use it just because you can, or does it help you to overcome any limitations of make?

_________________
Developer of tyndur - community OS of Lowlevel (German)


Top
 Profile  
 
 Post subject: Re: Capri, a script based build system
PostPosted: Mon Jun 23, 2014 10:41 am 
Offline
Member
Member
User avatar

Joined: Mon Mar 05, 2012 11:23 am
Posts: 616
Location: Germany
Kevin wrote:
I don't want to sound negative, but... I missed the description of the problem that it solves. ;)

To me it looks mostly like Makefiles with a different syntax and with a tendency to use more of a procedural than a declarative style. Is it just that and you use it just because you can, or does it help you to overcome any limitations of make?

Hey Kevin, hehe you dont :)
I think that the syntax of Makefiles is much too complicated and hard to understand, especially if you have a really big one, with recursive source directories and the like (no, imho autotools dont make it beautiful in no way^^)
Also, Capri allows scripts to be platform-independent, a new platform can be added by just implementing the native stubs (and having a C++lib :P) and then you can use things like File.copy without worrying. I want to make this new, fresh and easy but still as reliable and flexible as Makefiles.

_________________
Ghost OS - GitHub


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 3 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: No registered users and 18 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group