A "model" is a complete source file which the Vesta evaluator can execute.
A Vesta model is made up of three parts in sequence:
The files and import clauses bring in external data. The block of statements is the body of the model. Every model is really a program, or more precisely a function, which is why they end with a return statement. The return statement specifies the result value which will be produced when the model is evaluated.
A files clause turns files and directories into variables in your model. For example, in a model in the same directory with a file named "foo.c" this files clause:
files foo = foo.c; |
Would put the contents of foo.c into a variable named foo as a text value. There's a shortcut for naming the variable created after the file or directory being referenced, which works just like the self-assignment shortcut for bindings. A shorter way to write the above would be:
files foo.c; |
With the difference being that the variable introduced is named "foo.c" rather than just "foo". (The SDL identifier syntax rules allow periods in variable names.)
Entire directories can also be turned into variables. If there was a sub-directory named "sources" in the same directory as a model then this:
files sources; |
Would introduce a variable named sources with the contents of the directory represented as a binding as its value. Each file or directory within sources would become a name/value pair of the binding. Files would get text values with their contents, and directories would get nested binding values.
This brings up a key feature of the Vesta SDL:
As is the first example, you can use any variable name you want to store the contents of the directory:
files c_files = sources; |
It's also possible to create a binding in-line in a files clause, like this:
files c_files = [ utils.c, main.c ]; |
This creates a variable named c_files whose value is a two-element binding. Within it, the names "utils.c" and "main.c" are bound the to contents of those files as text values.
You can even create a binding with files from sub-directories:
files c_files = [ sources/utils.c, sources/main.c ]; |
The files and directories referenced in a files clause are almost always named by relative paths, which are interpreted relative to the location of the model containing the files clause. Absolute paths are permitted, but they are considered poor form and only immutable files and directories within the Vesta repository can be referenced. (So, for example, you couldn't turn "/vesta" into a variable, because it's appendable not immutable, and you couldn't turn "/usr/lib/libc.a" into a variable because it's outside the repository.)
Note that you can't use "." or ".." in a files clause.
files foo = ./file1.txt; // evaluation stops with "Not found" error bar = ../file2.c; // evaluation stops with "Not found" error |
If you reference a nested file/directory without specifying a variable name, the first arc of the path will be used as the variable name.
files dir1/dir2/file.txt; // creates variable named "dir1" file.txt = dir1/dir2/file.txt; // probably more useful |
Note that the "=" in a files clause isn't really an assignment statement. You can't use operators on the right-hand side.
files files = sources ++ headers; // syntax error |
If you need to reference a file or directory which has a name that is neither a legal identifier nor an integer constant or is a reserved word, you can quote it (just like a text string):
files files = [ "file-with-dashes.c", "sub-dir"/foo.txt ]; dir = "sub-dir"/another_dir; |
Take care when referencing files/directories not to try assign them to variables which aren't proper identifiers.
files "file-with-dashes.c"; // "not an identifier" error 1234/foo.txt; // "not an identifier" error |
The import clause allows one Vesta SDL model to use another. For example, this is how std_env references a version of the C++ compiler bridge, how a model to build an executable references the libraries the program is linked against, and how a release model references particular versions of each sub-component. In other words, imports are used to make modular builds.
Imported models become functions with no formal arguments. Just as the the files clause assigns files and directories to variables, the import clause creates variables which hold models. They can be called in the body of the model, just like a function defined within the model.
The simplest way to import another model is with a relative path.
import model1 = tests.ves; // creates the variable "model1" from tests.ves model2 = src/lib.ves; // creates the variable "model2" from src/lib.ves { foo = model1(); // calls tests.ves, storing its result in "foo" bar = model2(); // calls src/lib.ves, storing its result in "bar" // ... } |
This is often used to separate different components of a build in the same package. For example, separating the instructions for building a library from the instructions for building associated test programs, or separating the description of how to build a program from the specification of which target platform it should be built for.
Note that unlike in a files clause, you must provide an explicit variable name for models imported with relative paths.
import tests.ves; // parse error src/lib.ves; // also a parse error |
As in a files clause, you can import several models into a binding.
import b = [ tests = tests.ves, lib = src/lib.ves ]; { foo = b/tests(); // calls tests.ves, storing its result in "foo" bar = b/lib(); // calls src/lib.ves, storing its result in "bar" // ... } |
Unlike in a files clause, importing a directory will not create a binding. Instead, it is equivalent to importing the model named "build.ves" within that directory.
import foo = src; // If "src" is a directory this is equivalent // to "foo = src/build.ves". |
As in a files clause, any path component that is neither a legal identifier nor an integer constant or is a reserved word must be quoted:
import foo = "subdir-with-dashes"/foo.ves; bar = "binding"/bar.ves; |
Also note that models must end in ".ves". If you try to import something which is not a directory and doesn't end in ".ves", the evaluator automatically adds the ".ves" for you. (It does this even if the file without ".ves" exists, which can be a little counter-intuitive.)
import foo = src; // If "src" is not a directory (even if it is a file) // this is equivalent to "foo = src.ves"! |
The from keyword can be used to import models relative to a different directory. This is used to import components from specific versions of other packages.
from /vesta/vestasys.org/vesta import progs = eval/4/src/progs.ves; // progs = /vesta/vestasys.org/vesta/eval/4/src/progs.ves |
The shortcut for "build.ves" and the requirement that model files end in ".ves" also apply within the scope of a from.
from /vesta/vestasys.org/vesta import eval = eval/4; progs = eval/4/src/progs; // eval = /vesta/vestasys.org/vesta/eval/4/build.ves // progs = /vesta/vestasys.org/vesta/eval/4/src/progs.ves |
Unlike with local imports, within a from you can omit the variable name and "=" and have the first path component be used as the variable name.
from /vesta/vestasys.org/vesta import eval/4; weeder/1/src/progs.ves; // eval = /vesta/vestasys.org/vesta/eval/4/build.ves // weeder = /vesta/vestasys.org/vesta/weeder/1/src/progs.ves |
As with local imports, a binding can be created with several imports.
from /vesta/vestasys.org/vesta import b = [ eval/4, weeder/1, cache/5 ]; // b/eval = /vesta/vestasys.org/vesta/eval/4/build.ves // b/weeder = /vesta/vestasys.org/vesta/weeder/1/build.ves // b/cache = /vesta/vestasys.org/vesta/cache/5/build.ves |
Note that a from changes the path of all imports up to the next from. Therefore, you must place all local imports before the first from.
from /vesta/vestasys.org/vesta import eval/4; import local = local.ves; // error, tries to import: // /vesta/vestasys.org/vesta/local.ves |
Use of symbolic links (such as the latest link in packages) is not allowed in an import clause. (To do so would allow the possibility of a model's imports changing without the model's contents changing, which would lead to irreproducible builds.)
from /vesta/vestasys.org/vesta import eval/latest/build.ves; // "Not a directory" error |
The vimports command parses the import clause of a model and displays the models imported. The vupdate command provides a method of automatically editing the import clause of a model.
There is a special variable named "_self" which is essentially an implicit import of the model in which it is used.
A block of statements, ending with a return/value statement. The return statement specifies the result value which will be produced when the model is evaluated.
{ return [ foo/bar = 2 ]; } |
{ ovs = [ Cxx/switches/program = [ shared_libs = "-static" ]] ++ [ Cxx/switches/compile = [ optimize = "-O2", debug = "-g3" ]]; libs = <./libs/c/clib_umb>; return ./Cxx/program("foo", foo_c, foo_h, libs, ovs) ++ ./Cxx/program("bar", bar_c, bar_h, libs, ovs); } |