We recommend the following project tree:

├── CMakeLists.txt
├── CMakePresets.json
├── CMakeUserPresets.json
├── _all_cmake.dune           # /auto-generated/
├── dependencies/
│   └── CMakeLists.txt
├── dune
├── dune-project              # /auto-generated/
├── opam/
│   ├── <project>.opam            # /auto-generated/
│   └── <project>_<package>.opam  # /auto-generated/
├── src/
│   ├── CMakeLists.txt
│   └── <package>/
│       ├── CMakeLists.txt
│       └── dune              # /auto-generated/
└── tests/
    └── CMakeLists.txt



The DkHelloWorldActor project is a good example project. It also has a ci/ folder and a .gitlab-ci.yml for running in CI, but be aware in an upcoming release of DkSDK the ci/ folder will be auto-generated as well.

Project Tree



A "project" is a directory tree containing source code and an (auto-generated) dune-project file.

Projects are a form of source code isolation. That means that one project cannot directly access the source code of another project; instead projects declare dependencies on each other through the use of public libraries or public (installable) executables.

You place the DkSDKProject_AddProject() command in this CMake script.


The purpose of this CMake script is to state the other projects, both C and OCaml, that the current project relies on.

You place the DkSDKProject_DeclareAvailable() DkSDKProject_MakeAvailable(), and DkSDKRequire_Add() commands in this CMake script.



You organize your source code into one or more "packages".

Typically you will have at least one package that contains your add_executable() executable targets, and optionally one package for each add_library(name STATIC static library targets.

The purpose of the src/CMakeLists.txt CMake script is to enumerate and enable your packages.

It is can be as simple as:

# ...


The purpose of this script is to either:

  1. Add a single STATIC library that aggregates .ml OCaml source files into a single STATIC OCaml package library.

  2. Add executables that embed OCaml using a STATIC OCaml package library.

This script is also an appropriate place to define your CMake C libraries and executables.

You place the DkSDKProject_AddPackage() command in this CMake script.

If you are embedding OCaml into an executable, you can also use the DkSDKProject_CreateMainC() command to write a main() C file that will invoke OCaml.




Adds the current source directory (typically your project's top-level directory) and all of its sub-directories as an OCaml project:


A typical usage is:

  # ...

include(DkSDKProject) DkSDKProject_AddProject( [PROJECT_NAME "<name>"] [PROJECT_VERSION "<version>"]

[LICENSE "Apache-2.0"] [SOURCE_URI "git+"]

[SUMMARY_ONELINER "A sentence describing your project that ends with a period."] [SUMMARY_PARAGRAPHS "A paragraph describing your project." "Another paragraph, and so on."]


# Advanced: Dune escape hatch [DUNE_VERSION <version>] [DISABLE_DUNE_SKIP_BINARY_TREE] )


You are responsible for writing your own dune file in the project directory that at minimum contains:

(include _all_cmake.dune)

When CMake generates a buildsystem a file _all_cmake.dune will now be generated in the project top-level directory.

For transparency, put _all_cmake.dune in source control

Every time you re-generate with CMake the _all_cmake.dune file may be updated. When you check the file into source control, you will be able to track what changes over time. If the need arises you can see what changes caused your continuous integration tests to fail.



By default the OCaml project has the same name as PROJECT_NAME. However it must follow certain conventions:

  • it must start with a capital letter since it will become part of OCaml module names

  • it cannot be all capital letters since capital letters are often used to indicate OCaml module types rather than OCaml modules. We recommend that you use the first few letters to name your organization (or use your initials if it is personal). We use Dk for Diskuv!


By default the OCaml project has the same version as PROJECT_VERSION.

We suggest that you version your project using semantic versioning. Many system package managers, including winget on Windows, use a semantic version (or something fairly similar) hardcoded into your installer to know when an update is available.

Installers can created with CPack. We recommend Professional CMake - A Practical Guide for detailed steps on how to create installers.


  • 1.0.0

  • 1.0.0-alpha.1

LICENSE <license>

A SPDX open-source license identifier or a SPDX license expression.

The list of SPDX open-source licenses is at SPDX licenses.

An example of a user-defined license is DocumentRef-yourcompany-commercial-1.0:LicenseRef-AllRightsReserved-1

Defaults to Apache-2.0.


The URI (not just a URL) to the source code. For example, git repositories should include a git+ in the scheme, like git+ GitHub and GitLab git repositories always end in a .git suffix.


The email address of the current maintainer of the source code.

Defaults to DkSDKProject_AddProject-MAINTAINER-<PROJECT_NAME>

AUTHORS <author1> ... <authorN>

The email list of authors who have contributed to the source code.

Defaults to the maintainer.


The URL to the homepage of your product.

Defaults to<PROJECT_NAME>


The URL to where issues/tickets/bugs can be filed.

Defaults to <HOMEPAGE_URL>/issues.


The URL to the technical documentation (APIs, etc.) of your product.

Targets created

The following targets are created:

Directory properties created

The following directory properties are created. All are inherited which means they are available in any subdirectory (however deep) of the project as well.

Advanced: Dune escape hatch

We could have made DkSDK CMake create the top-level dune file for you. But the tiny one-time step where you create the one-liner (include _all_cmake.dune) lets you add more Dune instructions to your project. Said another way, you now have an escape hatch to Dune, a lower-level yet feature-packed OCaml build tool.

Use Dune if you find that DkSDK CMake does not do what you want it do. However, you do not need to know Dune to develop and build with DkSDK CMake. Consult the Dune manual if you need to use the Dune escape hatch.

The following arguments are available if you decide to use the Dune escape hatch.

DUNE_VERSION <version>

The Dune API version present in any .dune file generated by DkSDK. In particular it is present as the first line: (lang dune <version>).

Defaults to 3.5 if not specified.

Dune 3.5+ is the minimum so that the Dune (aliases) expression is available (3.5), and the (opam_file_location inside_opam_directory) expression is also available (3.8). As of Dune 3.8.2 [dune build] stalls in [windows-1809] tagged GitLab CI runners, so avoid Dune 3.8+ until it is stable.


When DUNE_VERSION 3.8 or higher, the default is to place generated .opam files in your project's opam/ folder. The default avoids polluting the top-level project folder.

However, if you are transitioning from an older version of Dune, like DUNE_VERSION 3.5 (which is the default Dune version), you may want to keep the .opam files in the top-level project folder by using the DISABLE_OPAM_SUBFOLDER option.


It is quite possible that the binary directory is inside the source tree. By default a dune file is placed in the CMAKE_CURRENT_BINARY_DIR that was current during the DkSDKProject_AddProject() command. That dune file will tell Dune to skip scanning the binary directory and its subdirectories. Usually this is great because the binary directory may be huge (wasting time with "scanning directory" messages), and often there may be duplicated opam files that result in 'Too many opam files for package "xxx"'. This is the one time where placing a file directly in the binary directory (not in a DkSDKFiles/ subdirectory) is almost always correct.

However, if you want different behavior, you can set the option DISABLE_DUNE_SKIP_BINARY_TREE. A dune file will then be placed inside the DkSDKFiles/ of the CMAKE_CURRENT_BINARY_DIR that corresponds to the DkSDKProject_AddProject(). That will stop scanning the large tree in the one DkSDKFiles/ directory, but misses all the other directories.



Creates a C entry point containing main() or wmain() that will immediately start running one node:

DkSDKProject_CreateMainC(<output filename>)

A typical usage is:

DkSDKProject_CreateMainC(OUTPUT _main.c)
add_executable(example _main.c)
target_link_libraries(example PRIVATE

A C file will be generated that will have an int main (int argc, char **argv) function that runs OCaml. As long as you add the generated C file to add_executable() any top-level OCaml statements (ex. let () = print_endline "Hi!") that are linked into the executable will be executed inside main().

The generated C file will be placed relative to CMAKE_CURRENT_BINARY_DIR unless you have given an absolute path.



DkSDKProject_DeclareAvailable( <name> [URL Download options] [Patch Step options] [FINDLIBS lib1 lib2 ...] [OPAM_PACKAGE "..."] [VENDOR_OPAM_PACKAGES pkg1 pkg2 ...] [PPXLIB name] [PPX name] [CONSTRAINT "..."] [EXECUTABLES program1.exe program2.exe ...] )

This function records the options that describe how to populate the specified package. If such details have already been recorded earlier in this project (regardless of where in the project hierarchy), this and all later calls for the same package <name> are ignored. This "first to record, wins" approach is what allows hierarchical projects to have parent projects override package details of child projects.

The <name> must be only letters, numbers and underscores. The name will be treated case-insensitively and it should be obvious for the package it represents, often being the official name of the Opam project, with dashes (-) replaced with underscores. Choosing an unusual name makes it unlikely that other projects needing that same package will use the same name, leading to the package being populated multiple times.

Main Options


The names of the executables, if any, that would be installed. The executables must include the extensions (ex. .exe).

FINDLIBS lib1 lib2 ...

A list of findlib compliant libraries that can be added as library dependencies.

An ALIAS is constructed. FINDLIBS lib1 lib2 ... produces the ALIASes Findlib::lib1, Findlib::lib2 and so on. You can add the ALIAS as a target dependency for your executable or library. The Dune clause (depends <name>) will be generated for your executable or library.

There is a complicated specification for findlib compliance. The simpler way is to:

  1. Go to OCaml Packages

  2. Search for the package (example: capnp)

  3. Click on "Docs"

  4. Under "Libraries" you will see the findlib compliant library names. For the capnp package there was both capnp and capnp.unix. It is common for a single package to have multiple libraries.

PPXLIBS lib1 lib2 ...

A list of findlib compliant libraries that can be added as ppxlib-based "fast" preprocessors.

An ALIAS is constructed. PPXLIB name produces the ALIAS Findlib::name. You can add the ALIAS as a target dependency for your executable or library. If both FINDLIBS <name> and PPXLIBS <name are specified, then both Dune clauses (preprocess (pps <name>)) and (depends <name>) will be generated when used as a target dependency. Otherwise just a (preprocess (pps <name>)) will be generated.

PPXES lib1 lib2 ...

A list of findlib compliant libraries that can be added as list of ppxlib-based "classic" preprocessors.

An ALIAS is constructed. PPX name produces the ALIAS Findlib::name. You can add the ALIAS as a target dependency for your executable or library. If both FINDLIBS <name> and PPXES <name are specified, then both Dune clauses (preprocess (staged_pps <name>)) and (depends <name>) will be generated when used as a target dependency. Otherwise just a (preprocess (staged_pps <name>)) will be generated.


A list of CMake libraries that will be added to interface requirements to all the FINDLIBS/PPXLIBS/PPXES with target_link_libraries(Findlib::name INTERFACE ...)


Given DkSDKProject_DeclareAvailable(<name> INTERFACE_PACKAGES pkg1 pkg2 ...), the list of named dependencies pkg1, pkg2 and ... will be made available when DkSDKProject_MakeAvailable(<name>) is executed.

Use this option to declare the immediate transitive dependencies of <name>.

Ignored when DISABLE_MAKE_AVAILABLE is specified.


When set, no FetchContent_MakeAvailable will be performed. Use only when the package is already present on the local file system.

REQUIRED_TARGETS tgt1 tgt2 ...

A list of targets that will be added to all the FINDLIBS/PPXLIBS/PPXES with add_dependency(name tgt1 tgt2 ...)

Opam Options


The name of the opam package without any .opam suffix that will be used to download the opam package. Used only when no Download Options are used.

If not specified, defaults to the name first argument of the DkSDKProject_DeclareAvailable(name) command.


The opam constraint

Non-Opam Options


The names of the opam packages without any .opam suffix that will be vendored if and only if the Download Options are used.

If not specified and the Download Options are used, defaults to the name first argument of the DkSDKProject_DeclareAvailable(name) command.

The vendoring specification is important for two reasons.

  1. So that there are not duplicate packages. Without knowing which opam packages to vendor, one package downloaded by (for example) a URL option and another same-named package installed by opam (perhaps transitively through some other package).

  2. Even though the dependency is vendored, we still need opam to calculate and install the transitive dependencies. Some of those transitive dependencies may not be vendored.

Often many opam packages belong to the same project. All of the opam packages within the project that owns the DkSDKProject_DeclareAvailable(dependency) should be specified with VENDOR_OPAM_PACKAGES.

Download Options

These options are the same as ExternalProject.


A local directory containing the package. It is not recommended to use this with PATCH_COMMAND because the SOURCE_DIR will be overwritten. Instead use URL <dir> which makes a copy of the package first.


If a download method is specified, any existing contents of the source directory may be deleted. Only the URL download method checks whether this directory is either missing or empty before initiating the download, stopping with an error if it is not empty. All other download methods silently discard any previous contents of the source directory.

URL Download
URL <url1> [<url2>...]

List of paths and/or URL(s) of the external project's source. When more than one URL is given, they are tried in turn until one succeeds. A URL may be an ordinary path in the local file system (in which case it must be the only URL provided) or any downloadable URL supported by the file(DOWNLOAD) command. A local filesystem path may refer to either an existing directory or to an archive file, whereas a URL is expected to point to a file which can be treated as an archive. When an archive is used, it will be unpacked automatically unless the DOWNLOAD_NO_EXTRACT option is set to prevent it. The archive type is determined by inspecting the actual content rather than using logic based on the file extension.

URL_HASH <algo>=<hashValue>

Hash of the archive file to be downloaded. The argument should be of the form <algo>=<hashValue> where algo can be any of the hashing algorithms supported by the file(DOWNLOAD) command. Specifying this option is strongly recommended for URL downloads, as it ensures the integrity of the downloaded content. It is also used as a check for a previously downloaded file, allowing connection to the remote location to be avoided altogether if the local directory already has a file from an earlier download that matches the specified hash.


URL of the git repository. Any URL understood by the git command may be used.

Changed in version 3.27: A relative URL will be resolved based on the parent project's remote, subject to CMP0150. See the policy documentation for how the remote is selected, including conditions where the remote selection can fail. Local filesystem remotes should always use absolute paths.

GIT_TAG <tag>

Git branch name, tag or commit hash. Note that branch names and tags should generally be specified as remote names (i.e. origin/myBranch rather than simply myBranch). This ensures that if the remote end has its tag moved or branch rebased or history rewritten, the local clone will still be updated correctly. In general, however, specifying a commit hash should be preferred for a number of reasons:

  • If the local clone already has the commit corresponding to the hash, no git fetch needs to be performed to check for changes each time CMake is re-run. This can result in a significant speed up if many external projects are being used.

  • Using a specific git hash ensures that the main project's own history is fully traceable to a specific point in the external project's evolution. If a branch or tag name is used instead, then checking out a specific commit of the main project doesn't necessarily pin the whole build to a specific point in the life of the external project. The lack of such deterministic behavior makes the main project lose traceability and repeatability.

If GIT_SHALLOW is enabled then GIT_TAG works only with branch names and tags. A commit hash is not allowed.

Note that if not provided, GIT_TAG defaults to master, not the default Git branch name.


The optional name of the remote. If this option is not specified, it defaults to origin.

GIT_SUBMODULES <module>...

Specific git submodules that should also be updated. If this option is not provided, all git submodules will be updated.

Changed in version 3.16: When CMP0097 is set to NEW, if this value is set to an empty string then no submodules are initialized or updated.


New in version 3.17.

Specify whether git submodules (if any) should update recursively by passing the --recursive flag to git submodule update. If not specified, the default is on.


New in version 3.6.

When this option is enabled, the git clone operation will be given the --depth 1 option. This performs a shallow clone, which avoids downloading the whole history and instead retrieves just the commit denoted by the GIT_TAG option.


New in version 3.8.

When enabled, this option instructs the git clone operation to report its progress by passing it the --progress option. Without this option, the clone step for large projects may appear to make the build stall, since nothing will be logged until the clone operation finishes. While this option can be used to provide progress to prevent the appearance of the build having stalled, it may also make the build overly noisy if lots of external projects are used.

GIT_CONFIG <option1> [<option2>...]

New in version 3.8.

Specify a list of config options to pass to git clone. Each option listed will be transformed into its own --config <option> on the git clone command line, with each option required to be in the form key=value.


New in version 3.18.

When GIT_TAG refers to a remote branch, this option can be used to specify how the update step behaves. The <strategy> must be one of the following:


Ignore the local branch and always checkout the branch specified by GIT_TAG.


Try to rebase the current branch to the one specified by GIT_TAG. If there are local uncommitted changes, they will be stashed first and popped again after rebasing. If rebasing or popping stashed changes fail, abort the rebase and halt with an error. When GIT_REMOTE_UPDATE_STRATEGY is not present, this is the default strategy unless the default has been overridden with CMAKE_EP_GIT_REMOTE_UPDATE_STRATEGY (see below). Note that if the branch specified in GIT_TAG is different to the upstream branch currently being tracked, it is not safe to perform a rebase. In that situation, REBASE will silently be treated as CHECKOUT instead.


Same as REBASE except if the rebase fails, an annotated tag will be created at the original HEAD position from before the rebase and then checkout GIT_TAG just like the CHECKOUT strategy. The message stored on the annotated tag will give information about what was attempted and the tag name will include a timestamp so that each failed run will add a new tag. This strategy ensures no changes will be lost, but updates should always succeed if GIT_TAG refers to a valid ref unless there are uncommitted changes that cannot be popped successfully.

The variable CMAKE_EP_GIT_REMOTE_UPDATE_STRATEGY can be set to override the default strategy. This variable should not be set by a project, it is intended for the user to set. It is primarily intended for use in continuous integration scripts to ensure that when history is rewritten on a remote branch, the build doesn't end up with unintended changes or failed builds resulting from conflicts during rebase operations.

Patch Step Options


Specifies a custom command to patch the sources after an update. By default, no patch command is defined. Note that it can be quite difficult to define an appropriate patch command that performs robustly, especially for download methods such as git where changing the GIT_TAG will not discard changes from a previous patch, but the patch command will be called again after updating to the new tag.



DkSDKProject_MakeAvailable( [TEST] <name1> ... <nameN> )

This command ensures that each of the named dependencies are made available to the project by the time it returns.

Each named dependency must have a corresponding call to DkSDKProject_DeclareAvailable(). This call can happen in two ways:

  1. Explicitly by you calling DkSDKProject_DeclareAvailable() before DkSDKProject_MakeAvailable().

  2. Implicitly if the dependency is one of the "supported dependencies" listed below. You don't need to call DkSDKProject_DeclareAvailable() for any supported dependency because DkSDK does that automatically for you. However, you are free to override details for any supported dependency by explicitly calling DkSDKProject_DeclareAvailable() before DkSDKProject_MakeAvailable().

The list of supported dependencies for this version of DkSDK are:

If there are multiple calls to DkSDKProject_DeclareAvailable() for a single dependency, then the first call will control how that dependency will be made available.

Main Options


Make the named dependencies available for tests only.

Targets created


An INTERFACE library created for:

Target properties available

The findlib-<name> INTERFACE library will have the following target properties:


The opam package.


The opam constraint.


The name of the optional library as given with FINDLIBS in the DkSDKProject_DeclareAvailable(FINDLIBS name1 ...) command.


The name of the preprocessor as given with PPXLIB in the DkSDKProject_DeclareAvailable(PPXLIB name) command.


The name of the preprocessor as given with PPX in the DkSDKProject_DeclareAvailable(PPX name) command.



Enables the current CMake script to declare second and third-party dependencies. Sets a series of CMake policies and CMake non-cache variables. Typically this is used at the top of dependencies/CMakeLists.txt.


Once DkSDKProject_AddDependencies() has been used, it is safe to use DkSDKProject_DeclareAvailable(), DkSDKProject_MakeAvailable(), and DkSDKRequire_Add() commands, as in:



Always use DkSDKProject_AddDependencies() before using any of the dependency commands listed above. Future versions of DkSDK may enforce the rule.



Enables the current directory and subdirectories to be an OCaml package. A single DkSDK OCaml package is synonymous with a single opam package, and a single findlib library. There is a single module interface .mli all consumers must use to access the OCaml package.

  [DUNE_SUBDIRS <mode>|unspecified]

Core Arguments


All .ml code compiled in the current directory and subdirectories will belong to the computed opam package. The opam package is computed to be the concatenation of:

  1. The OCaml project name taken from DkSDKProject_AddProject(DKSDK_PROJECT_NAME)

  2. The _ underscore character

  3. The basename of the current source directory.


All .ml code compiled in the current directory and subdirectories will belong to the named opam package. No .opam suffix should be used. Specifically, -DDKSDK_PACKAGE=<package> will be added to the COMPILE_DEFINITIONS directory property. ocamldunec uses -DDKSDK_PACKAGE to write (package PACKAGE) clauses in generated dune files.


Designate the package as dedicated to tests.

The designation will:


A sentence describing your package that ends with a period.

Defaults to:

The package created within the PROJECT_NAME "<PROJECT_NAME>" project
by a DkSDKProject_AddPackage() command in <FILE> that is missing
SUMMARY_PARAGRAPHS <paragraph1> ... <paragraphN>

One or more paragraphs describing your package; each paragraph is one string. Use the $<SEMICOLON> generator expression instead of plain semicolons since CMake interprets a semicolon as a new item (a new paragraph).

Advanced Arguments


Create native code .cmxa OCaml libraries. You can set both NATIVE and BYTECODE at the same time.


Create bytecode .cma OCaml libraries. STATIC libraries may not contain bytecode OCaml libraries. Other than that restriction, you can set both NATIVE and BYTECODE at the same time.


Create a Javascript library. BYTECODE will automatically be turned on if you create a Javascript library.

DUNE_SUBDIRS <mode>|unspecified

For all values except unspecified, the generated _all_cmake.dune will include a Dune stanza (include_subdirs <mode>).

With <mode> as unqualified (the default for DkSDK) you can place .ml files not only as siblings to a dune file, but can also place .ml files in the dune subdirectories.

With <mode> as no you must place .ml files as siblings to a dune file.

With unspecified the generated _all_cmake.dune file will not contain the Dune stanza (include_subdirs <mode>). By default Dune when (include_subdirs ...) is not specified, Dune treats it as a no, but you can override it in your own top-level dune file.



Validates that the specified name is a valid DkSDK project name:

  PROJECT_NAME "<proposed project name>"
  [RECOMMENDATION "<recommendation>"]

If the proposed project name is invalid, a FATAL message will be communicated with the reasons for the rejection. The one or more complete sentences in RECOMMENDATION, if specified, will also be part of the FATAL message.

The project naming rules are as follows:

  • The project name must be at least 3 characters long

  • must only have alphanumeric characters

  • must have its first character be an uppercase letter

  • must have its second character be a lowercase letter

  • must contain, after the second character, another sequence of an upper case letter followed by a lowercase letter