Skip to content

Commit

Permalink
Improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
adamryczkowski committed May 29, 2019
1 parent 5bc5bf7 commit 7a7201d
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 10 deletions.
32 changes: 22 additions & 10 deletions docs/manual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ Use any of these functions to import any kind of parameters as any other kind of

### apply_dependency_to_target()

Optional function that is called in Project build everytime, when there is an internal dependee that requires this template. The file is called after both targets are defined. Function gets two positional arguments: first is the dependee target name (the target that needs us), and the second is the name of our target, even if our template does not define targets (in case the file we describe allows only one singleton target, this second argument is always fixed to our target name).
Optional function that is called in Project build everytime, when there is an internal dependee that requires this template. The file is called after both targets are defined. Function gets two positional arguments: first is the dependee target name (the target that needs us), and the second is the name of our target, even if our template does not define targets (in case the file we describe allows only one singleton target, this second argument is always fixed to our target name).

When user decides to implement this function and there are targets defined in `targets.cmake` (usually there are), he is required to put either `LINK_TO_DEPENDEE` or `DONT_LINK_TO_DEPENDEE` to specify whether he wishes Beetroot to link or not the target to the dependee.

The function role is to apply extra modifications to the dependee, and finaly to call (or not) `target_link_libraries(${DEPENDEE_TARGET_NAME} ${KEYWORD} ${TARGET_NAME})`, where `DEPENDEE_TARGET_NAME` and `TARGET_NAME` are respectively first and second parameters to the function and `KEYWORD` is defined as either `INTERFACE` or `PRIVATE` depending on whether the `${DEPENDEE_TARGET_NAME}`'s type is `INTERFACE_LIBRARY` or not.

Expand All @@ -49,37 +51,47 @@ If the function is not defined, and if the `${DEPENDEE_TARGET_NAME}` is not of t

### Template global options

All options affect the way all targets defined in the `targets.cmake` get intepreted.
All options affect the way all targets defined in the `targets.cmake` get intepreted. Effectively treats targets as all-or-nothing. Does not affect anything if there is only one template/target define in the file.

#### SINGLETON_TARGETS
#### `SINGLETON_TARGETS`

Option. By setting this option we declare that all targets defined in this file can only be defined in one go, all using the same set of target parameters and features. It also forces only one instance of each target and forces the user to declare the targets using `ENUM_TARGETS` rather than `ENUM_TEMPLATES`.

If there is only one target defined in this file, the only impact of this option is to force use `ENUM_TARGETS` rather than `ENUM_TEMPLATES`.

#### NO_TARGETS
The most obvious place to use this option is in `targets.cmake` which describe external project.

#### `NO_TARGETS`

Option. By setting this option we declare that this file does not define any targets at all. Such "no targets" templates still are internally named by their template/target name for resolving the dependencies. The targets will never be built - in fact defining `generate_targets()` function is illegal with this option set, and user must define `apply_dependency_to_target(DEPENDEE_TARGET_NAME OUR_TARGET_NAME)` function instead, that applies whathever there is to apply to already existing dependee, (which will be guaranteed to be the actual target).
By setting this option we declare that this file does not define any targets at all. Such "no targets" templates still are internally named by their template/target name for resolving the dependencies. The targets will never be built - in fact defining `generate_targets()` function is illegal with this option set, and user must define `apply_dependency_to_target(DEPENDEE_TARGET_NAME OUR_TARGET_NAME)` function instead, that applies whathever there is to apply to already existing dependee, (which will be guaranteed to be the actual target).

Targets declared with `NO_TARGETS` cannot have their own dependency, because it is unknown how to force CMake to respect dependencies with something that is not a target.

Such option is usefull for old-style header only libraries (that do not use `INTERFACE` targets) or pieces of CMake code that only produce CMake variables; in such case the relevant code that produces the variables must be placed either in global scope of the `targets.cmake` or inside the `apply_dependency_to_target`. Remember, that only declared variables will be passed to the `apply_dependency_to_target`.

Note that for `NO_TARGETS` files, there is no difference whether a parameter gets declared inside `TARGET_PARAMETERS`, `LINK_PARAMETERS` or `TARGET_FEATURES` block.

#### LANGUAGES
#### `LANGUAGES`

List of languages required by the targets. User cannot set the languages himself, because `enable_language()` function requires to be run in the global context, while none of the user code is run, except for the CMakeLists.txt. CMake 3.13 supports the following languages: `CXX`, `C`, `CUDA`, `Fortran`, and `ASM`. This option can depend on the parameters.

#### EXPORTED_VARIABLES
#### `EXPORTED_VARIABLES`

List of variables (`TARGET_PARAMETER`, `LINK_PARAMETER` or `FEATURE`) that can be embedded into the set of variables available when calling `generate_targets()`. These variables and their values will not participate in the definition of the targets' identities and will get instantiated only when calling those two functions. In order to actually use the variable, the dependee must explicitely declare then when defining dependencies.

#### LINK_TO_DEPENDEE
#### `LINK_TO_DEPENDEE`

Flag makes sense only if the tempalate generates targets and they are not of the type `UTILITY`. If the flag is set, the Beetroot will always call `target_link_libraries()`, even if the function `apply_dependency_to_target()` is defined. The call to `target_link_libraries()` will be placed _after_ the call of the `apply_dependency_to_target()`.
If the flag is set, the Beetroot will always internally call `target_link_libraries()` to link this target with the dependee, even if the function `apply_dependency_to_target()` is defined. Specifying this flag when user did not write `apply_dependency_to_target()` is not required, because in this case calling `target_link_libraries()` is the default behavior. This flag excludes `NO_TARGETS`.

The call to `target_link_libraries()` will be executed _after_ the call of the `apply_dependency_to_target()`.

This option has exactly the same meaning as the option of the same name in the external project options, so there is no point in setting them in both places.

#### `DONT_LINK_TO_DEPENDEE`

Don't call `target_link_libraries()`, even if `apply_dependency_to_target()` is not defined. This flag is in opposition to LINK_TO_DEPENDEE and excludes it.

This option has exactly the same meaning as the option of the same name in the external project set, so there is no point in setting them in both places.
If user wrote `apply_dependency_to_target()` Beetroot requires him to either set LINK_TO_DEPENDEE or DONT_LINK_TO_DEPENDEE to make sure user understands the linking behavior.

#### GENERATE_TARGETS_INCLUDE_LINKPARS

Expand Down
52 changes: 52 additions & 0 deletions presenation_draft
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
tags: cmake, project building, compiling, tooling, dsl, external projects, superbuild, best practices

Audience: Developers in need to write and maintain complex CMake build system

Abstract:

Open source DSL based on CMake that targets medium to large CMake deployments and developers less experienced in writing CMake code. It helps by nudging users to put their targets definitions and dependency declarations inside CMake functions with clear API interface, so it is clear on what information and components each target depends. In return for these restraints

it does a great deal of semantic checks on the user code,
allows a lot of flexibility of where and how to put the user CMake code,
allows building any part of the project from any directory,
can automatically turn a project into a superbuild if any of the targets are external.

I will explain the most important concepts of the tool, demo the project and answer questions.

Documentation (still work in progress) https://beetroot.readthedocs.io/en/latest/
Source code http://github.com/beetroot-project/beetroot

Outline:

1. A quick reminder about the problems with CMake:
a) Most variables are global
b) It is difficult to define function formal parameters (and this system can break in presence of array arguments)
c) Usually impossible to build only a part of our code unless we specifically program for functionality
d) superbuilds are not trivial to pull off.
e) handling information passing between the various parts of the build system can be messy

2. The original idea about the solution
Let's try to build a system where the user defines the targets in separate functions with formal and well-defined parameters and declared dependencies (functions have a local scope).

3. The system grew over several complete re-writes to arrive with the following main design decisions. Understanding those decisions is key for understanding the tool:

a) Leave most of the action out of CMakeLists.txt - let them only name the targets to build with their arguments (needed, because we need to parse the code twice and CMakeLists are not suited for re-parsing)
b) Instead, describe the targets in the separate CMake files, that can be unambiguously found either by name (`targets.cmake`) or by the directory (`targets`), inside user-provided function `generate_targets()`.
c) Identify the user code describing the targets by the targets' names rather than by path. (so it is much easier to refactor directory structure without breaking the build. Scanning for targets' location turned out to be surprisingly fast)
d) Instantiating two targets with different sets of parameters results in two separate build targets with unique names assigned by the library. This implies that the target name is not known for the user unless he opts out of this behavior (see: singleton targets). By default the user-provided `define_targets()` is a template for creating targets and perhaps `targets.cmake` should be called `templates.cmake`.
e) Discovering dependencies between the targets is done separately and before actually parsing functions that define them. This allows for really nice handling of singleton templates, i.e. templates that instantiate only one target (e.g. external projects). In short, this functionality allows us to say something in effect of "our target depends on the library foo with-whatever-options-the-other-part-of-my-project-wants, but it must have feature A". CMake will automatically scan all the declared targets of our project in search of foo, check if the foo is compatible with feature A, and if it is - makes sure the feature is set.

4. Show the basic syntax of the basic block of the system - the `targets.cmake` file

(show one of the examples from the documentation)

5. Incrementally demo growing the project, adding external dependencies, all three kinds of parameters, how to handle code generators, etc. - if time permits.


Comments:

1. At this moment the project in the alpha stage in a sense that I am not confident its API, i.e. the keywords and function names will stay as they are now.
2. Since the presentation is not going to be a workshop, I would like your help on what use cases I should focus (and perhaps even skip some key features). By default, I will focus on our use case, which may not be representative to your community.
3. I believe that experienced developers are less likely to like this tool because they most probably have their own habits of using the CMake, so perhaps I should limit the expertise level of the target audience? OTOH I would really like to have feedback from the CMake experts.
4. I am open to longer session lengths if you think the audience will be interested in more details.
5. What sort of biography do you think I should write? What it will be used for? I never cared about the biography of speakers unless it is somehow relevant to the work they present. In the case of my presentation, I believe the only relevant information about me is my affiliation, which is already included.

0 comments on commit 7a7201d

Please sign in to comment.