Skip to content

Commit

Permalink
work on getting started
Browse files Browse the repository at this point in the history
  • Loading branch information
sisteczko committed Mar 7, 2019
1 parent d056129 commit 5b3c478
Show file tree
Hide file tree
Showing 12 changed files with 185 additions and 33 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/*
Binary file removed build/doctrees/about.doctree
Binary file not shown.
Binary file removed build/doctrees/environment.pickle
Binary file not shown.
Binary file removed build/doctrees/faq.doctree
Binary file not shown.
Binary file removed build/doctrees/features.doctree
Binary file not shown.
Binary file removed build/doctrees/index.doctree
Binary file not shown.
Binary file removed build/doctrees/manual.doctree
Binary file not shown.
Binary file removed build/doctrees/readme.doctree
Binary file not shown.
Binary file removed build/doctrees/start.doctree
Binary file not shown.
4 changes: 2 additions & 2 deletions docs/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ List of the most visible features of the Beetroot
===========================================================

Targets are defined inside the function ``generate_target()`` in target definition file
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

In CMake targets (in contrast to variables) are internally represented by the object which lives in a global namespace, even if defined inside the function. Putting target definitions inside a function prevents leaking of temporary variables and pollution of the variable namespace.

Dependencies of targets are set inside the function ``declare_dependencies()`` in target definition file
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Target dependencies are handled by function rather than data structure, which allows for maximum flexibility (dependencies can depend in complicated way on the target parameters/features). Because Beetroot structure needs dependencies to be resolved *before* target definition (and possibly be called multiple times on the same target), the only place to put them is in a dedicated user-supplied function. Code inside this function should be omnipotent, because it can be executed multiple times in a single run. The code will be executed only during the target declaration phase.

Expand Down
54 changes: 24 additions & 30 deletions docs/manual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ All those functions are parametrized by the three categories of variables, which

If you find yourself defining a large list of `TARGET_PARAMETERS` over and over again for different targets (for instance to pass targets from a library to a consumer), you can spare yourself a time and call a function `include_target_parameters_of(TEMPLATE_NAME)` in the targets body (i.e. outside of any functions defined therein) to include all the target parameters defined in that file. The file will be processed in the context just after parsing the targets file, including all declared and non-declared variables defined in this file.

#### include_target_parameters_of()
#### include_target_parameters_of()::


```
include_target_parameters_of(<TEMPLATE_NAME> [ALL_EXCEPT <VAR_NAME>... | INCLUDE_ONLY <VAR_NAME>...])
```


Use this function to import `TARGET_PARAMETERS` from another template file. Only the variable definitions will be imported, just like if you would paste them into the own `TARGET_PARAMETERS`, not other variables and it will not set a build dependency. If the included file itself includes target parameters, they can be included too.

Expand Down Expand Up @@ -133,20 +133,20 @@ Parameters that can be passed to the template (e.g. target) are distinguished by

##### `OPTION`

Container `OPTION` can hold only variables of the type `BOOL`, and hence the only type allowed for it is either `BOOL` or empty string. It behaves diffently from `BOOL` `SCALAR` only when passed as a parameter via function call. Just like in base CMake, `OPTION` parsing is implemented using `cmake_parse_arguments`, so it does not require a value. E.g. if
Container `OPTION` can hold only variables of the type `BOOL`, and hence the only type allowed for it is either `BOOL` or empty string. It behaves diffently from `BOOL` `SCALAR` only when passed as a parameter via function call. Just like in base CMake, `OPTION` parsing is implemented using `cmake_parse_arguments`, so it does not require a value. E.g. if::

```
set(TARGET_PARAMETERS
USE_GPU OPTION "" 0
USE_CPU SCALAR BOOL 0
)
```

we use it like this:
set(TARGET_PARAMETERS
USE_GPU OPTION "" 0
USE_CPU SCALAR BOOL 0
)


we use it like this::


get_target(<template name> USE_GPU USE_CPU 1)

```
get_target(<template name> USE_GPU USE_CPU 1)
```
to set both values and
```
get_target(<template name> USE_CPU 0)
Expand All @@ -172,33 +172,27 @@ If `SCALAR` is used for feature, the rules for overriding depend on the type:
* If the type is `BOOL`, `CHOICE`, `STRING` and `PATH` then the non-default value overrides the default. Two non-default values cannot override each other and will result in different target (if that is allowd) or the error will be generated (for static targets).
* If the type is `INTEGER` then bigger value overrides smaller.

For example, suppose we have a template with the following features:
For example, suppose we have a template with the following features::

```
set(TARGET_FEATURES
F_VERSION SCALAR INTEGER "14"
F_FLAG SCALAR BOOL "YES"
F_SOME_PATH SCALAR PATH ""
F_COMPILER CHOICE(clang:gnu:intel) STRING clang
F_FLAVOUR SCALAR STRING "debian"
)

```

```
set(TARGET_FEATURES
F_VERSION SCALAR INTEGER "14"
F_FLAG SCALAR BOOL "YES"
F_SOME_PATH SCALAR PATH ""
F_COMPILER CHOICE(clang:gnu:intel) STRING clang
F_FLAVOUR SCALAR STRING "debian"
)
get_target(<template_name> F_FLAVOUR "git")
get_target(<template_name> F_FLAVOUR "custom")
get_target(<template_name> F_FLAG "NO")
```

First two lines will instantiate two targets, one with `F_FLAVOUR` set to "git" and the other with `F_FLAVOUR` set to "custom", because there neither set of features overrides the other.
The third line will generate an error, because of ambivalency, since there is more than one target that is eligible to apply the feature `F_FLAG`.
The third line will generate an error, because of ambivalency, since there is more than one target that is eligible to apply the feature `F_FLAG`.::

```
get_target(<template_name> F_VERSION 15)
get_target(<template_name> F_VERSION 23)
get_target(<template_name> F_FLAG "NO")
```

Integer 23 overrides both 15 and the default value of 14 so, there is no conflict and no need to instantiate separate targets. Likewise `NO` overrides the default (here `YES`). That's why all the lines will instantiate exactly one target, with `${F_VERSION}` equal to 23 and `${F_FLAG}` equal to `NO`.

Expand Down
159 changes: 158 additions & 1 deletion docs/start.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,161 @@
Getting started
===============

Plan: work through all cmake-test cases, adding features as you go.

The Hello World
^^^^^^^^^^^^^^^

We will to start small, with the very simple C++ CMake build.

Suppose this is our ``source.cpp``::

// 'Hello World!' program
#include <iostream>
#define STRINGIFY2(X) #X
#define STRINGIFY(X) STRINGIFY2(X)
#ifndef WHO
#define WHO World
#endif

int main() {
std::cout << "Hello " STRINGIFY(WHO) "!" << std::endl;
return 0;
}

For reference, to build it plain CMake, this would be our ``CMakeLists.txt``:

cmake_minimum_required(VERSION 3.5)
project(hello_simple)
add_executable(hello_simple source.cpp)

Our file structure would like this::

project_folder/
|
+- build/
+- source.cpp
+- CMakeLists.txt

To be able to compile this code using Beetroot, we need first to make beetroot accessible to our build. If you use git, the preferred way to do that is to put the beetroot repository as a (shallow) submodule of your project, in folder ``cmake/beetroot``.

Then we put the following content in the ``targets.cmake``::

set(ENUM_TEMPLATES HELLO_SIMPLE)
function(generate_targets TEMPLATE_NAME)
add_executable(${TARGET_NAME} "${CMAKE_CURRENT_SOURCE_DIR}/test.cpp")
endfunction()

We also need to adjust the ``CMakeLists.txt`` to have the Beetroot actually executed::

cmake_minimum_required(VERSION 3.13)
include(../cmake/beetroot/beetroot_bootstrap)
project(hello_simple)
build_target(HELLO_SIMPLE)
finalize()

Our final folder structure should look like this:


project_folder/
+- cmake
| +- beetroot (beetroot clone)
| +- root.cmake
+- source.cpp
+- targets.cmake
+- CMakeLists.txt

And finally we are set. Keep in mind, that Beetroot is designed to work best with middle and large size projects, so the amount of work to get the simplest C++ code compile is offset by the time we save when the project grows.

We compile it as usual::

$ cd project_folder
$ mkdir build
$ cd build
$ cmake .. && make
DECLARING DEPENDENCIES AND DECIDING WHETHER TO USE SUPERBUILD
No languages in project bootstrapped_hello_simple
-- Discovering dependencies for HELLO_SIMPLE (HELLO_SIMPLE_f9fc6118c955867490b6f80bce90dc5b)...
DEFINING TARGETS IN PROJECT BUILD
TESTS disabled
-- The CXX compiler identification is GNU 7.3.0
-- Check for working CXX compiler: /home/adam/spack/opt/spack/linux-ubuntu16.04-x86_64/gcc-8.1.0/gcc-7.3.0-zclb4ttmy53mjkahiocmsqozhu6veriz/bin/g++
-- Check for working CXX compiler: /home/adam/spack/opt/spack/linux-ubuntu16.04-x86_64/gcc-8.1.0/gcc-7.3.0-zclb4ttmy53mjkahiocmsqozhu6veriz/bin/g++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/adam/beetroot-examples/hello_simple/build
Scanning dependencies of target bootstrapped_hello_simple
[ 50%] Building CXX object CMakeFiles/bootstrapped_hello_simple.dir/source.cpp.o
[100%] Linking CXX executable bootstrapped_hello_simple
[100%] Built target bootstrapped_hello_simple
$ ls
hello_simple CMakeCache.txt CMakeFiles cmake_install.cmake Makefile
$ ./hello_simple
Hello World!

Now let's start complicating things. You may have noticed, that we have a macro parameter ``WHO`` in our C++ file, that can be used to change the program output. Let's do just that. After all, handling target parameters is one of the strongest sides of Beetroot. Let's modify our ``targets.cmake`` and insert definition of the parameter, which we will also call ``WHO``::

set(ENUM_TEMPLATES HELLO_SIMPLE)
set(TARGET_PARAMETERS
WHO SCALAR STRING "Beetroot"
)
function(generate_targets TEMPLATE_NAME)
add_executable(${TARGET_NAME} "${CMAKE_CURRENT_SOURCE_DIR}/source.cpp")
target_compile_definitions(${TARGET_NAME} PRIVATE "WHO=${WHO}")
endfunction()

The name of the parameter does not need to match the name of the preprocessor macro. The formal syntax is this: ``TARGET_PARAMETERS`` is an array organized into 4-element tuples.

#. First element of the tuple is the name of the parameter, then
#. container type. There are three container types: ``OPTIONAL``, ``SCALAR`` and ``VECTOR``, and they correspond to the CMake options, scalars and lists.
#. Element type. At the moment the are 5 possible types: ``BOOL``, ``INTEGER``, ``PATH``, ``STRING`` and ``CHOICE(<colon-separated list of possible values>)``.
#. Default value.

In the function body we need to tie the parameter with the target, and we do that in the usual CMake way, by using ``target_compile_definitions()``. All target parameters are always implicitely available in the function ``generate_targets``, so we can simply use them.

If we compile the program and run we get::

$./hello_simple
Hello Beetroot!

Let's say, that this file is our unit test and we need to compile three of them, one for the default string, and the other for a special string "Mars" and "Venus". It is easy with Beetroot, and by doing this we will demonstrate two ways of passing variables to targets. Let's re-write the ``CMakeLists.txt``::

cmake_minimum_required(VERSION 3.13)
include(../cmake/beetroot/beetroot_bootstrap)
project(hello_simple)
build_target(HELLO_SIMPLE)
set(WHO "Venus")
build_target(HELLO_SIMPLE)
build_target(HELLO_SIMPLE WHO Mars)
finalize()


After we build, we should get three executables: ``hello_simple1``, ``hello_simple2`` and ``hello_simple3``.

$./hello_simple1
Hello Beetroot!
$./hello_simple2
Hello Venus!
$./hello_simple3
Hello Mars!


0 comments on commit 5b3c478

Please sign in to comment.