- CMakeSL User guide
- Builtin types
- User types
- Type deduction
- Designated initializers
- Import/export
- Mixing CMakeSL with 'old' CMake
- Examples
- Declarative format
CMake binary expects that CMakeSL scripts are named CMakeLists.cmsl
. Based on extension (cmsl
not txt
) it knows that the scripts are written in CMakeSL, not the 'old' CMake language.
Every CMakeLists.cmsl
script has to have a main function. It's the entry point of script execution.
The main function of the top-level script has to have signature taking no parameters and returning an int:
int main() { ... }
Main function of scripts in subdirectories are allowed to have any signature, but the function has to be named main
. E.g.:
cmake::library main(cmake::project& top_lvl_project, string some_additional_parameter)
{
...
}
Script is allowed to have one, and exactly one, main function.
Here's a list of currently implemented builtin types:
- void
- bool
- int
- double
- string
- extern
- list
- cmake::option
- cmake::version
- cmake::project
- cmake::executable
- cmake::library
All types are classes, even the primitives (bool, int, double).
For detailed documentation of the types please refer to files under doc/builtin.
For the list of builtin functions, please see the cmake namespace doc file.
Well known from C/C++ (and others). Used to indicate that a function doesn't return anything.
Not so much to say. A bool type that does what you expect from bool.
It's a class, so you can e.g. call to_string()
method:
bool foo = true;
string bar = foo.to_string();
A signed 64 bit int. Has to_string
, as well:
int foo = 42;
string bar = foo.to_string();
A typical double. Not much to say.
A string type implementing basic operations from C++' std::string
and some from the Python's one.
A type used to get variables passed to cmake command line. E.g. for a such call:
cmake .. -DFOO="some string"
You can get the FOO
value like this:
auto foo = extern<string>("FOO");
if(foo.has_value()) {
cmake::message(foo.value());
}
If FOO
value was not passed, foo.has_value()
returns false
.
A generic list.
list<string> some_list;
some_list += "foo";
some_list.push_back("bar");
string qux = "qux";
list<string> other_list = { "baz", qux };
some_list += other_list;
List is generic, so you can have a list of lists:
list<list<int>> matrix = {
{ 1, 2, 3 },
{ 4, 42, 6 },
{ 7, 8, 9 }
};
matrix.at(1).at(1) = 5;
An array subscript operator (matrix[1][1]
) is not supported yet.
An equivalent of CMake's option()
command:
auto with_tests = option("MY_PROJECT_WITH_TESTS", "When ON, tests will be built", false);
if(with_tests.value()) {
add_subdirectory("test");
}
A type representing version. You passes it to cmake_minimum_required
:
cmake::version ver = cmake::version(3, 14);
cmake::minimum_required(ver);
string str_version = ver.to_string();
Registers a project in the build system. It registers it only at project("some name")
constructor, so you can copy the variable around, without duplicating more projects.
cmake::project foo = cmake::project("The Foo");
foo.add_executable(...);
Represents library. project::add_library
returns an instance of it, so other libraries and executables can link to it.
cmake::project foo = cmake::projecr("The Foo");
cmake::library bar_lib = foo.add_library("bar_lib", { "bar.cpp" });
cmake::library baz_lib = foo.add_library("baz_lib, { "baz.cpp" });
baz_lib.link_to(bar_lib);
Similar to library but other executables and libraries can't link to it (it's actually ensured by type safety - library/executable::link_to
accepts only instances of the library
type):
cmake::project foo = cmake::projecr("The Foo");
cmake::library bar_lib = foo.add_library("bar_lib", { "bar.cpp" });
cmake::library baz_exec = foo.add_executable("baz_exec", { "baz.cpp" });
baz_exec.link_to(bar_lib);
A class with members and methods can be defined by user:
class foo
{
int bar;
auto multiplied_bar(int mul)
{
return bar * mul;
}
};
The auto
is not an actual type, but it can be used in a context where the type can be deduced:
auto foo = 42; // Deduced to int.
// Deduced to void.
auto bar()
{}
// Deduced to int.
auto baz()
{
return foo;
}
// The deduction can be nested. Deduced to int.
auto qux(bool flag)
{
if(flag)
{
return baz();
}
return 42;
}
// Error, the function which return type should be deduced,
// can not be called recursively.
auto top()
{
return top();
}
// Error, the return type can not be deduced because
// the return expressions evaluate to different types.
auto kek(bool flag)
{
if(flag)
{
return 42;
}
return "some string";
}
When the expected type of a variable is known, designated initializers can be used to initialize it:
class foo
{
int bar;
string baz;
};
// Not all members need to be initialized.
foo qux = { .bar = 42 };
void top(foo kek)
{}
int main()
{
top({ .baz = "some string" });
reutrn 0;
}
Besides an obvious division of CMakeSL scripts into CMakeLists.cmsl
in subdirectories, user can create a script that e.g. exports some functionality or variables.
An example file that provides util to create desired library name libs_utils.cmsl
:
// The 'export' indicates that this function should be importable.
namespace utils
{
export string create_library_name(string base_name)
{
return "my_project_" + base_name;
}
}
And an example usage, the root CMakeLists.cmsl
:
// Import the file with libs utils.
import "libs_utils.cmsl";
int main()
{
cmake::minimum_required(cmake::version(3, 14));
auto project = cmake::project("My Project");
auto lib_name = utils::create_library_name("my_lib");
project.add_library(lib_name, { /* ... */ };
return 0;
}
Currently, functions, variables and classes can be exported:
export int foo = 42;
export int bar()
{
return foo;
}
export class baz
{
auto qux()
{
return bar();
}
};
CMakeSL can be mixed with CMake at directories level. In short, in a CMakeLists.cmsl
you can call add_subdirectory()
with a subdirectory that contains 'old' CMakeLists.txt
. It works (well, not exactly, it'll fully work soon) also the other way - in 'old' CMakeLists.txt
you can call add_subdirectory()
with a CMakeLists.cmsl
script.
Please see the root CMakeLists.cmsl. There is a call add_subdirectory("external/googletest")
which adds the whole googletest library that is later on used in the CMakeSL tests.
A simple CMakeSL script:
int main()
{
cmake::message("Hello World!");
return 0;
}
Here's one that actually does something useful:
int main()
{
cmake::minimum_required(cmake::version(3, 14, 3))
cmake::project hello_world = cmake::project("Hello world");
list<string> hw_sources = { "main.cpp" };
hello_world.add_executable("hw_exec", hw_sources);
return 0;
}
Here, we ensure that we run a cmake version that supports CMakeSL.
Then, we create a hello_world
variable of type project
. In the project
's constructor,
a project with name "Hello world"
is registered.
Then, a list with source files is created and passed to project::add_executable
method.
This method registers an executable with name hw_exec
and the list of sources, containing only one file: main.cpp
.
One more thing that add_executable
does is that it returns a variable of type executable
.
We don't need it, so we just don't assign it to anything.
The last thing that is done, is returning 0
from main.
Now, let's create an executable that links to a library which was created in a subdirectory. Files structure:
├── CMakeLists.cmsl
├── lib
│ ├── CMakeLists.cmsl
│ └── lib.cpp
└── main.cpp
CMakeLists.cmsl
:
int main()
{
cmake::minimum_required(cmake::version(3, 14));
auto hello_world = cmake::project("Hello world");
auto lib = add_subdirectory("lib", hello_world);
auto sources = { "main.cpp" };
auto exec = hello_world.add_executable("hw_exec", sources);
exec.link_to(lib);
return 0;
}
lib/CMakeLists.cmsl
:
cmake::library main(cmake::project& top_lvl_project)
{
auto sources = { "lib.cpp" };
return top_lvl_project.add_library("hw_lib", sources);
}
Declarative format can be used as leaf nodes of you build system.
There are four builtin component types that you should be aware of:
static_library
shared_library
executable
test_executable
All of them derive from product
component type. You can find available properties in the docs.
You've probably spotted an usage of property like files.public
. At this point you probably figured out what does it mean.
In CMake a lot of properties can be PUBLIC
, PRIVATE
or INTERFACE
. You can add directories to an include path of a target, with PRIVATE
keyword. That means they won't be forwarded.
The same functionality comes with properties of product
component, that are of forwarding_lists
type. forwarding_lists
has three properties:
public
private
interface
and you can access them and assign to them like you saw earlier, e.g.:
static_library {
name = "foo"
files.public = [
"foo.cpp"
]
include_dirs.public = [
"public/include/dir"
]
inculde_dirs.private = [
"private/include/dir"
]
dependencies.interface = [
"bar_dependency"
]
}
And so on..
(Check out a complete example: custom_component)
There are of course cases, when you would want to create a custom component. E.g. you'd want to add a suffix to all of your libraries. You could, of course, add the suffix manually in every static_library
declaration, but that's ugly. Instead, you can create a custom component that accumulates common functionality and properties. Later on it can be used to declare stuff.
Let's say that we want all of our libraries to have suffix "_my_fancy_lib" and include directory "my/fancy/dir". Let's declare a custom component out of it, that derives from the builtin static_library
component:
component my_fancy_lib : static_library {
name_suffix = "_my_fancy_lib"
include_dirs.public = [
"my/fancy/dir"
]
}
And now it can be used to declare our fancy lib:
my_fancy_lib {
name = "foo"
include_dirs.public += [
"another/fancy/dir"
]
}
Mind the +=
while adding include directory. Thanks to that, the list with another/fancy/dir
will be appended to the list in component declaration. If you'd use a plain =
, the list would be overriden.
(Check out a complete example: module_import)
Imperative CMakeSL as well as the declarative one has modules support. With the example above, you most likely would want to declare my_fance_lib
component in a commonly accessible file and use it wherever you need.
Just create the file, let's name it my_fancy_lib.dcmsl
and in import it the file in whchich you want to use it:
import "my_fancy_lib.dcmsl";
my_fancy_lib {
name = "foo"
include_dirs.public += [
"another/fancy/dir"
]
}
(Check out a complete example: cmake_variables_accessor)
In a lot of cases you'll need to get a value of an old-style CMake variable. You can get it from an accessor named cmake_variables
. CMakeSL is statically typed, so you need to provide information how the variable should be treated, using as_bool
, as_int
, as_double
, as_string
or as_list
.
For example, let's say that you have a root CMakeLists written in the vanilla CMake:
CMakeLists.txt
:
cmake_minimum_required(VERSION 3.14.3)
project("MyFancyProject")
set(MY_FANCY_SUFFIX "_my_fancy_suffix")
add_subdirectory(my_fancy_lib)
In the my_fancy_lib
dir, you declare a library that gets its name suffix from the accessor:
my_fancy_lib/CMakeLists.dcmsl
:
static_library {
name = "my_fancy_lib"
name_suffix = cmake_variables.MY_FANCY_SUFFIX.as_string
...
}
(Check out a complete example: declarative_root_cmakelists)
Declarative file can be used as the root CMakeLists. So, if you don't need to do anything fancy in the project, or just want to quickly check something out, that's the way.