Introduction
Contributions are very welcome!
Do not hesitate to create a PR with no change to start the conversation about something you’d be interested in developing.
What to do?
If you have no specific idea in mind, you can have a look at the Roadmap or the Known issues.
Of course, much remains to be done to make Golem the best build system!
Among many things, Golem needs more tests.
Pull requests
Do not hesitate to create a PR with no change to start the conversation about something you’d be interested in developing.
That said, avoid PRs addressing differents issues.
For example, renaming a lot of variables for a cleaner code and fixing a bug are 2 different matters, and therefore requires independant PRs.
But refactoring of functions and renaming variables pursue the same objective and can be submitted under the same PR.
The point is to be able to focus the discussion on one matter at a time.
How is it designed?
Golem is powered by Waf, but provides a completely different API. It’s a sophisticated frontend to Waf that adds many features and simplifies for the users how to define their project.
Quick overview of how it works
Flow of execution
Calling a command on golem calls a wrapper script (main.py).
This script adapts the arguments needed to call the waflib scripting entry point. Mainly because by default Waf wouldn’t leave the project directory untouched. Golem fixes this behavior by forcing Waf to work from the build directory.
When the waflib scripting entry point gets called, the execution flow continues in a templated wscript file and gets redirected into context.py
Calling golem configure calls def configure(self): in builder.py, which prepares a Context object and calls def configure(self): on it in context.py
From here, Golem reads the project file, the environment variables, the options to properly set how to call waflib primitives. That is, detecting Qt, determining the cache configuration, etc.
Waf is designed around a task system. To build a program, tasks are generated by Golem with all the needed compilation and linking options and Waf runs them.
Remains some cases where Golem isn’t using yet the task system (but should), such as when resolving, cloning, configuring, building dependencies. In these cases, Golem calls Git directly, or calls itself directly.
Resolving a dependency
Resolving a dependency (golem resolve) consists of 5 steps:
- Resolving the version (commit hash) of the dependency the project refers to
- Cloning the dependency in the right cache directory (if doesn’t exist yet)
- Configuring the dependency before build
- Resolving the dependencies of the dependency if any (recursion)
Resolving the version consists of interpreting version in the dependency definition found in the project file. This version can be a commit hash (no resolution needed), a branch name, a tag, or a Node SemVer to search a SemVer tag.
To do so, Golem uses git ls-remote --tags and git ls-remote --heads to search for a corresponding commit hash. See dependency.py.
Cloning the dependency in the cache consists of:
- Generating a recipe ID corresponding to the dependency and adding to it the short commit hash (8 first characters). See
def generate_recipe_id(url):anddef make_dep_base(dep):in helpers.py. - Determining where the dependency must be cached. See
def find_dep_cache_dir(self, dep, cache_conf):in context.py. - Cloning the repository in the cache. See
def make_repo_ready(self, dep, cache_dir, should_clean=False):in context.py.
Example of a cloned repository in a cached dependency: json@com.github.nlohmann+65ee6845\repository
json@com.github.nlohmann: recipe ID65ee6845: commit hash
Configuring the dependency consists of:
- Determining the build location, which is a directory named after a condensed concatenation of characters designating the platform, architecture, compiler, runtime, linking and variant information. See
def build_path(self, dep=None):in context.py. - Running
golem configurewith all the needed options on the dependency. Seedef run_dep_command(self, dep, cache_dir, command):in context.py.
When configuring the dependency, the build directory is setup.
Preparation of the build directory by golem configure consists of:
- Determining if the project has a project file or a recipe (it not, error) See
def load_recipe(self):in context.py. - Creating an artifact directory with a specific slug to avoid conflicts between projects asking for different build options. See
def make_dependencies_slug(self, dependencies):in context.py. - Creating a configuration file to hold how a project can use the dependency.
- Creating a
dependencies.jsonfile to hold the list of dependencies the dependency relies on.
Example of an artifact directory in a cached dependency: json@com.github.nlohmann+65ee6845\w64mshshd\bin-da39a3ee
w64mshshd: Windows, x64, msvc, shared runtime, shared linking, debugda39a3ee: dependency slug to isolate the artifacts
Example of a configuration file: json@com.github.nlohmann+65ee6845\w64mshshd\conf\json@json@com.github.nlohmann.json
json@json@com.github.nlohmann.jsonthe name follows the format<target>@<recipe_id>
{
"configuration": {
"artifacts": [
{
"location": "${GOLEM_CACHE_DIR}\\json@com.github.nlohmann+65ee6845\\repository",
"path": "LICENSE.MIT",
"repository": "https://github.com/nlohmann/json.git",
"resolved_version": "\"v3.12.0\"",
"type": "license"
}
],
"header_only": true,
"isystem": [
"${GOLEM_CACHE_DIR}\\json@com.github.nlohmann+65ee6845\\include"
],
"licenses": [
"${GOLEM_CACHE_DIR}\\json@com.github.nlohmann+65ee6845\\repository\\LICENSE.MIT"
],
"targets": [
"json"
]
},
"dependencies": []
}This file contains all of what’s needed when linking against the target json of the dependency json@com.github.nlohmann.
Once resolved, the dependencies can be built with golem dependencies. An include directory will be added in the dependency’s cache directory to contain the headers meant to be used by the calling project. The artifacts will be built and stored in the artifact directory.