.SILENT: | |||||
.PHONY: docs docs-server docs-watch docs-sync-server | |||||
_invalid: | |||||
echo "Specify a target name to execute" | |||||
docs: | |||||
sphinx-build -b html \ | |||||
docs \ | |||||
_build/docs \ | |||||
-Wqaj8 | |||||
echo "Docs generated to _build/docs" | |||||
docs-server: docs | |||||
echo "Docs are visible on http://localhost:9794/" | |||||
cd _build/docs && \ | |||||
python -m http.server 9794 | |||||
docs-watch: docs | |||||
+sh tools/docs-watch.sh | |||||
docs-sync-server: docs | |||||
cd _build/docs && \ | |||||
browser-sync start --server \ | |||||
--reload-delay 300 \ | |||||
--watch **/*.html |
# -*- coding: utf-8 -*- | |||||
# Refer: http://www.sphinx-doc.org/en/master/config | |||||
# -- Project information ----------------------------------------------------- | |||||
project = 'dds' | |||||
copyright = '2019, vector-of-bool' | |||||
author = 'vector-of-bool' | |||||
# The short X.Y version | |||||
version = '' | |||||
# The full version, including alpha/beta/rc tags | |||||
release = '0.1.0' | |||||
# -- General configuration --------------------------------------------------- | |||||
extensions = [] | |||||
templates_path = ['_templates'] | |||||
source_suffix = '.rst' | |||||
master_doc = 'index' | |||||
language = None | |||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] | |||||
pygments_style = None | |||||
# -- Options for HTML output ------------------------------------------------- | |||||
html_theme = 'pyramid' | |||||
html_theme_options = {} | |||||
html_static_path = ['_static'] | |||||
html_sidebars = {} | |||||
def setup(app): | |||||
app.add_stylesheet('tweaks.css') |
``dds`` Design and Rationale | |||||
############################ | |||||
``dds`` has been designed from the very beginning as an extremely opinionated | |||||
hybrid *build system* and *package manager*. Unlike most build systems however, | |||||
``dds`` has a hyper-specific focus on a particular aspect of software | |||||
development: C and C++ libraries. | |||||
This may sound pointless, right? Libraries are useless unless we can use them | |||||
to build applications! | |||||
Indeed, applications *are* essential, but that is "not our job" with ``dds``. | |||||
Another design decision is that ``dds`` is built to be driven by automated | |||||
tools as well as humans. ``dds`` is not designed to entirely replace existing | |||||
build systems and package management solutions. Rather, it is designed to be | |||||
easy to integrate *with* existing systems and tools. | |||||
Background | |||||
********** | |||||
I'm going to say something somewhat controversial: C and C++ don't need | |||||
"package management." At least, not *generalized* "package management." C++ | |||||
needs *library* "package management." | |||||
The C and C++ compilation model is inherently *more complex* than almost any | |||||
other language in use today. This isn't to say "bad," but rather than it is | |||||
built to meet extremely high and strange demands. It also comes with a large | |||||
burden of *legacy*. Meeting both of these requirements simultaneously presents | |||||
incredible implementation challenges. | |||||
Despite the vast amount of work put into build systems and tooling, virtually | |||||
all developers are using them *incorrectly* and/or *dangerously* without | |||||
realizing it. Despite this work, we seem to be a great distance from a unified | |||||
library package distribution and consumption mechanism. | |||||
Tabula Rasa | |||||
*********** | |||||
``dds`` attempts to break from the pattern of legacy demands and strange usage | |||||
demands in a few ways. The major differences between ``dds`` and other build | |||||
systems like CMake, Meson, build2, SCons, MSBuild, etc. is that of *tradeoffs*. | |||||
If you opt-in to have your library built by ``dds``, you forgoe | |||||
*customizability* in favor of *simplicity* and *ease*. | |||||
``dds`` takes a look at what is needed to build and develop *libraries* and | |||||
hyper-optimizes for that use case. It is also built with a very strong, very | |||||
opinionated idea of *how* libraries should be constructed and used. These | |||||
prescriptions are not at all arbitrary, though. They are built upon the | |||||
observations of the strengths and weaknesses of build systems in use throughout | |||||
industry and community. | |||||
There is some ambiguity on the term "build system." It can mean one of two | |||||
things: | |||||
1. A *proper noun* "Build System," such as CMake, Meson, Autotools, or even | |||||
Gulp, WebPack, and Mix. These are specific tools that have been developed | |||||
for the implementation of the second definition: | |||||
2. A general noun "build system" refers to the particular start-to-finish | |||||
process through which a specific piece of software is mapped from its raw | |||||
*inputs* (source code, resource libraries, toolchains) to the outputs | |||||
(applications, appliances, libraries, or web sites). | |||||
For example, LLVM and Blender both use the CMake "Build System," but their | |||||
"build system" is not the same. The "build system" for each is wildly | |||||
different, despite both using the same underlying "Build System." | |||||
``dds`` takes a massive divergence at this point. One project using ``dds`` as | |||||
their build system has a nearly identical build process to every other project | |||||
using ``dds``. Simply running :code:`dds build -t <toolchain>` should be enough | |||||
to build *any* ``dds`` project. | |||||
In order to reach this uniformity and simplicity, ``dds`` drops almost all | |||||
aspects of project-by-project customizability. Instead, ``dds`` affords the | |||||
developer a contract: | |||||
If you play by my rules, you get to play in my space. | |||||
.. _design.rules: | |||||
The Rules | |||||
********* | |||||
We've talked an awful lot about the "rules" and "restrictions" that ``dds`` | |||||
imposes, but what are they? | |||||
.. _design.rules.not-apps: | |||||
``dds`` Is not Made for Complex Applications | |||||
=============================================== | |||||
Alright, this one isn't a "rule" as much as a recommendation: If you are | |||||
building an application that *needs* some build process functionality that | |||||
``dds`` does not provide, ``dds`` is only open to changes that do not | |||||
violate any of the other existing rules. | |||||
.. note:: | |||||
**However:** If you are a *library* author and you find that ``dds`` | |||||
cannot correctly build your library without violating other rules, we may | |||||
have to take a look. This is certainly not to say it will allow arbitrary | |||||
customization features to permit the rules to be bent arbitrarily: Read | |||||
on. | |||||
``dds`` contains a minimal amount of functionality for building simple | |||||
applications, but it is certainly not its primary purpose. | |||||
.. _design.rules.change: | |||||
*Your* Code Should Be Changed Before ``dds`` Should Be Changed | |||||
================================================================= | |||||
The wording of this rule means that the onus is on the library developer to | |||||
meet the expectations that ``dds`` prescribes in order to make the build | |||||
work. | |||||
If your library meets all the requirements outlined in this document but you | |||||
still find trouble in making your build work, this is grounds for change in | |||||
``dds``, either in clarifying the rules or tweaking ``dds`` functionality. | |||||
.. _design.rules.layout: | |||||
Library Projects Must Meet the Layout Requirements | |||||
================================================== | |||||
This is a very concrete requirement. ``dds`` prescribes a particular project | |||||
structure layout with minimal differing options. ``dds`` prescribes the | |||||
`Pitchfork`_ layout requirements. | |||||
.. note:: | |||||
These prescriptions are not as draconian as they may sound upon first | |||||
reading. Refer to the :doc:`guide/packages` page for more information. | |||||
.. _Pitchfork: https://api.csswg.org/bikeshed/?force=1&url=https://raw.githubusercontent.com/vector-of-bool/pitchfork/develop/data/spec.bs | |||||
.. _design.rules.no-cond-compile: | |||||
A Library Build Must Successfully Compile All Source Files | |||||
========================================================== | |||||
Almost all Build Systems have a concept of *conditionally* adding a source file | |||||
to a build. ``dds`` elides this feature in place of relying on in-source | |||||
conditional compilation. | |||||
.. _design.rules.no-lazy-code-gen: | |||||
All Code Must Be in Place Before Building | |||||
========================================= | |||||
``dds`` does not provide code-generation functionality. Instead, any | |||||
generated code should be generated and committed to the repository to be only | |||||
ever modified through such generation scripts. | |||||
.. _design.rules.one-binary-per-src: | |||||
All Compilable Files in a ``src/`` Directory Must Link Together | |||||
=============================================================== | |||||
As part of the prescribed project layout, the ``src/`` project directory | |||||
contains source files. ``dds`` requires that *all* source files in a given | |||||
``src/`` directory should link together cleanly. Practically, this means that | |||||
every ``src/`` directory must correspond to *exactly* one library. | |||||
.. _design.rules.include: | |||||
No Arbitrary ``#include`` Directories | |||||
===================================== | |||||
Only ``src/`` and ``include/`` will ever be used as the basis for header | |||||
resolution while building a library, so all ``#include`` directives should be | |||||
relative to those directories. Refer to :ref:`pkg.source-root`. | |||||
.. _design.rules.uniform-compile: | |||||
All Files Compile with the Same Options | |||||
======================================= | |||||
When DDS compiles a library, every source file will be compiled with an | |||||
identical set of options. Additionally, when DDS compiles a dependency tree, | |||||
every library in that dependency tree will be compiled with an identical set of | |||||
options. Refer to the :doc:`guide/toolchains` page for more information. | |||||
Currently, the only exception to this rules is for flags that control compiler | |||||
warnings: Dependencies will be compiled without adding any warnings flags, | |||||
while the main project will be compiled with warnings enabled by default. |
The Package Catalog | |||||
################### | |||||
``dds`` stores a catalog of available packages, along with their dependency | |||||
statements and information about how a source distribution thereof may be | |||||
maintained. | |||||
Viewing Catalog Contents | |||||
************************ | |||||
The default catalog database is stored in a user-local location, and the | |||||
package IDs available can be listed with ``dds catalog list``. This will only | |||||
list the IDs of the packages, but none of the additional metadata about them. | |||||
Adding Packages to the Catalog | |||||
****************************** | |||||
There are two primary ways to add entries to the package catalog. | |||||
Adding Individual Packages | |||||
========================== | |||||
A single package can be added to the catalog with the ``dds catalog add`` | |||||
command: | |||||
.. code-block:: text | |||||
dds catalog add <package-id> | |||||
[--depends <requirement> [--depends <requirement> [...]]] | |||||
[--git-url <url>] | |||||
[--git-ref <ref>] | |||||
[--auto-lib <Namespace>/<Name>] | |||||
The ``<package-id>`` positional arguments is the ``name@version`` package ID | |||||
that will be added to the catalog. The following options are supported: | |||||
``--depends <requirement>`` | |||||
This argument, which can be specified multiple times to represent multiple | |||||
dependencies, sets the dependencies of the package within the catalog. If | |||||
the obtained package root contains a ``package.dds``, then the dependencies | |||||
listed here must be identical to those listed in ``package.dds``, or | |||||
dependency resolution may yield unexpected results. | |||||
``--git-url <url>`` | |||||
Specify a Git URL to clone from to obtain the package. The root of the | |||||
cloned repository must be a package root, but does not necessarily need to | |||||
have the ``package.dds`` and ``library.dds`` files if relying on the | |||||
``--auto-lib`` parameter. | |||||
``--git-ref`` **must** be passed with ``--git-url``. | |||||
``--git-ref <ref>`` | |||||
Specify a Git ref to clone. The remote must support cloning by the ref that | |||||
is specified here. Most usually this should be a Git tag. | |||||
``dds`` will perform a shallow clone of the package at the specified | |||||
Git reference. | |||||
``--auto-lib`` | |||||
This option must be provided if the upstream does not already contain the | |||||
``dds`` files that are necessary to export the library information. This | |||||
can only be specified for packages that contain a single library root at | |||||
the package root. | |||||
The form of the argument is that of ``<Namespapce>/<Name>``, where | |||||
``Namespace`` and ``Name`` are the usage requirement keys that should be | |||||
generated for the library. | |||||
Bulk Imports via JSON | |||||
===================== | |||||
The ``dds catalog import`` supports a ``--json`` flag that specifies a JSON | |||||
file from which catalog entries will be generated. | |||||
.. note:: | |||||
The ``--json`` flag can be passed more than once to import multiple JSON | |||||
files at once. | |||||
The JSON file has the following structure: | |||||
.. code-block:: javascript | |||||
{ | |||||
// Import version spec. | |||||
"version": 1, | |||||
// Packages section | |||||
"packages": { | |||||
// Subkeys are package names | |||||
"acme-gadgets": { | |||||
// Keys within the package names are the versions that are | |||||
// available for each package. | |||||
"0.4.2": { | |||||
// `depends` is an object of dependencies for this | |||||
// particular version of the package. | |||||
"depends": { | |||||
// A mapping of package names to version ranges | |||||
"acme-widgets": "^1.4.1" | |||||
}, | |||||
// Specify the Git remote information | |||||
"git": { | |||||
// `url` and `ref` are required. | |||||
"url": "http://example.com/git/repo/acme-gadgets.git", | |||||
"ref": "v0.4.2-stable", | |||||
// The `auto-lib` is optional, to specify an automatic | |||||
// library name/namespace pair to generate for the | |||||
// root library | |||||
"auto-lib": "Acme/Gadgets" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
.. _guide: | |||||
User Guide | |||||
########## | |||||
.. toctree:: | |||||
:maxdepth: 2 | |||||
packages | |||||
toolchains | |||||
source-dists | |||||
repo | |||||
catalog | |||||
interdeps |
.. highlight:: yaml | |||||
Library and Package Dependencies | |||||
################################ | |||||
``dds`` considers that all libraries belong to a single *package*, but a single | |||||
package may contain one or more *libraries*. For this reason, and to better | |||||
interoperate with other build and packaging tools, we consider the issues of | |||||
package dependencies and library dependencies separately. | |||||
.. _deps.pkg-deps: | |||||
Package Dependencies | |||||
******************** | |||||
Consider that we are creating a package ``acme-gadgets@4.3.6``. We declare the | |||||
name and version in the ``package.dds`` in the package root: | |||||
.. code-block:: | |||||
Name: acme-gadgets | |||||
Version: 4.3.6 | |||||
Namespace: acme | |||||
.. note:: | |||||
The ``Namespace`` field is required, but will be addressed in the | |||||
:ref:`deps.lib-deps` section. | |||||
Suppose that our package's libraries build upon the libraries in the | |||||
``acme-widgets`` package, and that we require version ``1.4.3`` or newer, but | |||||
not as new as ``2.0.0``. Such a dependency can be declared with the ``Depends`` | |||||
key: | |||||
.. code-block:: | |||||
:emphasize-lines: 5 | |||||
Name: acme-gadgets | |||||
Version: 4.3.6 | |||||
Namespace: acme | |||||
Depends: acme-widgets ^1.4.3 | |||||
If we wish to declare additional dependencies, we simply declare them with | |||||
additional ``Depends`` keys | |||||
.. code-block:: | |||||
:emphasize-lines: 5-7 | |||||
Name: acme-gadgets | |||||
Version: 4.3.6 | |||||
Namespace: acme | |||||
Depends: acme-widgets ^1.4.3 | |||||
Depends: acme-gizmos ~5.6.5 | |||||
Depends: acme-utils ^3.3.0 | |||||
When ``dds`` attempts to build a project, it will first build the dependency | |||||
solution by iteratively scanning the dependencies of the containing project and | |||||
all transitive dependencies. | |||||
.. note:: | |||||
Unlike other packaging tools, ``dds`` will find a solution with the | |||||
*lowest* possible version that satisfies the given requirements for each | |||||
package. | |||||
.. _deps.lib-deps: | |||||
Library Dependencies | |||||
******************** | |||||
In ``dds``, library interdependencies are tracked separately from the packages | |||||
that contain them. A library must declare its intent to use another library | |||||
in the ``library.dds`` at its library root. The minimal content of a | |||||
``library.dds`` is the ``Name`` key: | |||||
.. code-block:: | |||||
Name: gadgets | |||||
To announce that a library wishes to *use* another library, use the aptly-named | |||||
``Uses`` key: | |||||
.. code-block:: | |||||
:emphasize-lines: 3-5 | |||||
Name: gadgets | |||||
Uses: acme/widgets | |||||
Uses: acme/gizmos | |||||
Uses: acme/utils | |||||
Here is where the package's ``Namespace`` key comes into play: A library's | |||||
qualified name is specified by joining the ``Namespace`` of the containing | |||||
package with the ``Name`` of the library within that package with a ``/`` | |||||
between them. | |||||
It is the responsibility of package authors to document the ``Namespace`` and | |||||
``Name`` of the packages and libraries that they distribute. | |||||
.. note:: | |||||
The ``Namespace`` of a package is completely arbitrary, and need not relate | |||||
to a C++ ``namespace``. | |||||
.. note:: | |||||
The ``Namespace`` need not be unique to a single package. For example, a | |||||
single organization (Like Acme Inc.) can share a single ``Namespace`` for | |||||
many of their packages and libraries. | |||||
However, it is essential that the ``<Namespace>/<Name>`` pair be | |||||
universally unique, so choose wisely! | |||||
Once the ``Uses`` key appears in the ``library.dds`` file of a library, ``dds`` | |||||
will make available the headers for the library being used, and will | |||||
transitively propagate that usage requirement to users of the library. |
Package Layout | |||||
############## | |||||
The units of distribution in ``dds`` are *packages*. A single package consists | |||||
of one or more *libraries*. In the simplest case, a package will contain a | |||||
single library. | |||||
It may be easiest to work from the bottom-up when trying to understand how | |||||
``dds`` understands code. | |||||
The layout expected by ``dds`` is based on the `Pitchfork layout`_ (PFL). | |||||
``dds`` does not make use of every provision of the layout document, but the | |||||
features it does have are based on PFL. | |||||
.. _Pitchfork layout: https://api.csswg.org/bikeshed/?force=1&url=https://raw.githubusercontent.com/vector-of-bool/pitchfork/develop/data/spec.bs | |||||
In particular, the following directories are used: | |||||
- ``src/`` | |||||
- ``include/`` | |||||
- ``libs/`` | |||||
- ``_build/`` (the default build output directory used by ``dds``). | |||||
Note that the ``libs/*/`` directories can contain their own ``src/`` and | |||||
``include/`` directories, the purposes and behaviors of which match those of | |||||
their top-level counterparts. | |||||
Source Files | |||||
************ | |||||
The smallest subdivision of code that ``dds`` recognizes is the *source file*, | |||||
which is exactly as it sounds: A single file containing some amount of code. | |||||
Source files can be grouped on a few axes, the most fundamental of which is | |||||
"Is this compiled?" | |||||
``dds`` uses source file extensions to determine whether a source file should | |||||
be fed to the compiler. All of the common C and C++ file extensions are | |||||
supported: | |||||
.. list-table:: | |||||
- * Compiled as C | |||||
* ``.c`` and ``.C`` | |||||
- * Compiled as C++ | |||||
* ``.cpp``, ``.c++``, ``.cc``, and ``.cxx`` | |||||
- * Not compiled | |||||
* ``.H``, ``.H++``, ``.h``, ``.h++``, ``.hh``, ``.hpp``, ``.hxx``, and ``.inl`` | |||||
If a file's extension is not listed in the table above, ``dds`` will ignore it. | |||||
.. note:: | |||||
Although headers are not compiled, this does not mean they are ignored. | |||||
``dds`` still understands and respects headers, and they are collected | |||||
together as part of *source distribution*. | |||||
Applications and Tests | |||||
********************** | |||||
``dds`` will recognize certain compilable source files as belonging to | |||||
applications and tests. If a compilable source file stem ends with ``.main`` or | |||||
``.test``, that source file is assumed to correspond to an executable to | |||||
generate. The filename stem before the ``.main`` or ``.test`` will be used as | |||||
the name of the generated executable. For example: | |||||
- ``foo.main.cpp`` will generate an executable named ``foo``. | |||||
- ``bar.test.cpp`` will generate an executable named ``bar``. | |||||
- ``cat-meow.main.cpp`` will generate an executable named ``cat-meow``. | |||||
- ``cats.musical.test.cpp`` will generate an executable named ``cats.musical``. | |||||
.. note:: | |||||
``dds`` will automatically append the appropriate filename extension to the | |||||
generated executables based on the host and toolchain. | |||||
An *application* source file is a source file whose file stem ends with | |||||
``.main``. ``dds`` will assume this source file to contain a program entry | |||||
point function and not include it as part of the main library build. Instead, | |||||
when ``dds`` is generating applications, the source file will be compiled, and | |||||
the resulting object will be linked together with the enclosing library into an | |||||
executable. | |||||
A *test* source file is a source file whose file stem ends with ``.test``. Like | |||||
application sources, a *test* source file is omitted from the main library, and | |||||
it will be used to generate tests. The exact behavior of tests is determined by | |||||
the ``Test-Driver`` setting for the package, but the default is that each test | |||||
source file will generate a single test executable that is executed by ``dds`` | |||||
when running unit tests. | |||||
The building of tests and applications can be controlled when running | |||||
``dds build``. If tests are built, ``dds`` will automatically execute those | |||||
tests in parallel once the executables have been generated. | |||||
In any case, the executables are associated with a *library*, and, when those | |||||
executables are linked, the associated library (and its dependencies) will be | |||||
linked into the final executable. There is no need to manually specify this | |||||
linking behavior. | |||||
.. _pkg.source-root: | |||||
Source Roots | |||||
************ | |||||
Source files are collected as children of some *source root*. A *source | |||||
root* is a single directory that contains some *portable* bundle of source | |||||
files. The word "portable" is important: It is what distinguishes the | |||||
source root from its child directories. | |||||
Portability | |||||
=========== | |||||
By saying that a source root is "portable", It indicates that the directory | |||||
itself can be moved, renamed, or copied without breaking the ``#include`` | |||||
directives of its children or of the directory's referrers. | |||||
As a practical example, let's examine such a directory, which we'll call | |||||
``src/`` for the purposes of this example. Suppose we have a directory named | |||||
``src`` with the following structure: | |||||
.. code-block:: text | |||||
<path>/src/ | |||||
animals/ | |||||
mammal/ | |||||
mammal.hpp | |||||
cat/ | |||||
cat.hpp | |||||
sound.hpp | |||||
sound.cpp | |||||
dog/ | |||||
dog.hpp | |||||
sound.hpp | |||||
sound.cpp | |||||
In this example, ``src/`` is a *source root*, but ``src/animals/``, | |||||
``src/animals/cat/``, and ``src/animals/dog/`` are **not** source roots. | |||||
While they may be directories that contain source files, they are not "roots." | |||||
Suppose now that ``dog.hpp`` contains an ``#include`` directive: | |||||
.. code-block:: c++ | |||||
#include <animals/mammal/mammal.hpp> | |||||
or even a third-party user that wants to use our library: | |||||
.. code-block:: c++ | |||||
#include <animals/dog/dog.hpp> | |||||
#include <animals/dog/sound.hpp> | |||||
In order for any code to compile and resolve these ``#include`` directives, the | |||||
``src/`` directory must be added to their *include search path*. | |||||
Because the ``#include`` directives are based on the *portable* source root, | |||||
the exactly location of ``src/`` is not important to the content of the | |||||
consuming source code, and can thus be relocated and renamed as necessary. | |||||
Consumers only need to update the path of the *include search path* in a single | |||||
location rather than modifying their source code. | |||||
.. _pkgs.source-root: | |||||
Source Roots in ``dds`` | |||||
======================= | |||||
To avoid ambiguity and aide in portability, the following rules should be | |||||
strictly adhered to: | |||||
#. Source roots may not contain other source roots. | |||||
#. Only source roots will be added to the *include-search-path*. | |||||
#. All ``#include``-directives are relative to a source root. | |||||
By construction, ``dds`` cannot build a project that has nested source roots, | |||||
and it will only ever add source roots to the *include-search-path*. | |||||
``dds`` supports either one or two source roots in a library. | |||||
.. _pkgs.lib-roots: | |||||
Library Roots | |||||
************* | |||||
In ``dds``, a *library root* is a directory that contains a ``src/`` directory, | |||||
an ``include/`` directory, or both. ``dds`` will treat both directories as | |||||
source roots, but behaves differently between the two. The ``src/`` and | |||||
``include/`` directories are themselves *source roots*. | |||||
``dds`` distinguishes between a *public* include-directory, and a *private* | |||||
include-directory. When ``dds`` is compiling a library, both its *private* and | |||||
its *public* include-paths will be added to the compiler's | |||||
*include-search-path*. When a downstream user of a library is compiling against | |||||
a library managed by ``dds``, only the *public* include-directory will be | |||||
added to the compiler's *include-search-path*. This has the effect that only | |||||
the files that are children of the source root that is the *public* | |||||
include-directory will be available when compiling consumers. | |||||
.. warning:: | |||||
Because only the *public* include-directory is available when compiling | |||||
consumers, it is essential that no headers within the *public* | |||||
include-directory attempt to use headers from the *private* | |||||
include-directory, as they **will not** be visible. | |||||
If both ``src/`` and ``include/`` are present in a library root, then ``dds`` | |||||
will use ``include/`` as the *public* include-directory and ``src/`` as the | |||||
*private* include-directory. If only one of the two is present, then that | |||||
directory will be treated as the *public* include-directory, and there will be | |||||
no *private* include-directory. | |||||
When ``dds`` exports a library, the header files from the *public* | |||||
include-directory source root will be collected together and distributed as | |||||
that library's header tree. The path to the individual header files relative to | |||||
their source root will be retained as part of the library distribution. | |||||
``dds`` will compile every compilable source file that appears in the ``src/`` | |||||
directory. ``dds`` will not compile compilable source files that appear in the | |||||
``include/`` directory and will issue a warning on each file found. | |||||
Libraries | |||||
********* | |||||
The *library* is a fundamental unit of consumable code, and ``dds`` is | |||||
specifically built to work with them. When you are in ``dds``, the library is | |||||
the center of everything. | |||||
A single *library root* will always correspond to exactly one library. If the | |||||
library has any compilable sources then ``dds`` will use those sources to | |||||
generate a static library file that is linked into runtime binaries. If a | |||||
library contains only headers then ``dds`` will not generate an archive to be | |||||
included in downstream binaries, but it will still generate link rules for the | |||||
dependencies of a header-only library. | |||||
In order for ``dds`` to be able to distribute and interlink libraries, a | |||||
``library.dds`` file must be present at the corresponding library root. The | |||||
only required key in a ``library.dds`` file is ``Name``: | |||||
.. code-block:: yaml | |||||
Name: my-library | |||||
.. seealso:: More information is discussed on the :ref:`deps.lib-deps` page | |||||
.. _pkgs.pkg-root: | |||||
Package Roots | |||||
************* | |||||
A *package root* is a directory that contains some number of library roots. If | |||||
the package root contains a ``src/`` and/or ``include/`` directory then the | |||||
package root is itself a library root, and a library is defined at the root of | |||||
the package. This is intended to be the most common and simplest method of | |||||
creating libraries with ``dds``. | |||||
If the package root contains a ``libs/`` directory, then each subdirectory of | |||||
the ``libs/`` directory is checked to be a library root. Each direct child of | |||||
the ``libs/`` directory that is also a library root is added as a child of the | |||||
owning package. | |||||
Packages | |||||
******** | |||||
A package is defined by some *package root*, and contains some number of | |||||
*libraries*. | |||||
The primary distribution format of packages that is used by ``dds`` is the | |||||
*source distribution*. Refer to the page :doc:`source-dists`. | |||||
Packages are identified by a name/version pair, joined together by an ``@`` | |||||
symbol. The version of a package must be a semantic version string. Together, | |||||
the ``name@version`` string forms the *package ID*, and it must be unique | |||||
within a repository or package catalog. | |||||
In order for a package to be exported by ``dds`` it must have a | |||||
``package.dds`` file at its package root. Three keys are required to be | |||||
present in the ``package.dds`` file: ``Name``, ``Version``, and ``Namespace``: | |||||
.. code-block:: yaml | |||||
Name: acme-widgets | |||||
Version: 6.7.3 | |||||
Namespace: acme | |||||
``Version`` must be a valid semantic version string. | |||||
.. note:: | |||||
The ``Namespace`` key is arbitrary, and not necessarily associated with | |||||
and C++ ``namespace``. | |||||
.. seealso:: | |||||
The purpose of ``Namespace``, as well as additional options in this file, | |||||
are described in the :ref:`deps.pkg-deps` page |
The Local Package Repository | |||||
############################ | |||||
``dds`` maintains a local repository of packages that it has obtained at the | |||||
request of a user. The packages themselves are stored as | |||||
:doc:`source distributions <source-dists>` (``dds`` does not store the binaries | |||||
that it builds within the repository). | |||||
Reading Repository Contents | |||||
*************************** | |||||
Most times, ``dds`` will manage the repository content silently, but it may be | |||||
useful to see what ``dds`` is currently storing away. | |||||
The content of the repostiory can be seen with the ``repo`` subcommand:: | |||||
> dds repo ls | |||||
This will print the names of packages that ``dds`` has downloaded, as well as | |||||
the versions of each. | |||||
Obtaining Packages | |||||
****************** | |||||
.. seealso:: See also: :doc:`catalog` | |||||
When ``dds`` builds a package, it will also build the dependency libraries of | |||||
that package. In order for the dependency build to succeed, it must have a | |||||
local copy of the source distribution of that dependency. | |||||
When ``dds`` performs dependency resolution, it will consider both existing | |||||
packages in the local repository, as well as packages that are available from | |||||
the :doc:`package catalog <catalog>`. If the dependency solution requires any | |||||
packages that are not in the local repository, it will use the information in | |||||
the catalog to obtain a source distribution for each missing package. These | |||||
source distributions will automatically be added to the local repository, and | |||||
later dependency resolutions will not need to download that package again. | |||||
Manually Downloading a Dependency | |||||
================================= | |||||
It may be useful to obtain a copy of the source distribution of a package | |||||
contained in the catalog. The ``catalog get`` command can be used to do this:: | |||||
> dds catalog get <name>@<version> | |||||
This will obtain the source distribution of the package matching the named | |||||
identifier and place that distribution in current working directory, using the | |||||
package ID as the name of the source distribution directory:: | |||||
$ dds catalog get spdlog@1.4.2 | |||||
[ ... ] | |||||
$ ls . | |||||
. | |||||
.. | |||||
spdlog@1.4.2 | |||||
$ ls ./spdlog@1.4.2/ | |||||
include/ | |||||
src/ | |||||
library.dds | |||||
package.dds | |||||
.. _repo.export-local: | |||||
Exporting a Project into the Repository | |||||
*************************************** | |||||
``dds`` can only use packages that are available in the local repository. For | |||||
packages that have a listing in the catalog, this is not a problem. But if one | |||||
is developing a local package and wants to allow it to be used in another local | |||||
package, that can be done by exporting a source distribution from the package | |||||
root:: | |||||
> dds sdist export | |||||
This command will create a source distribution and place it in the local | |||||
repository. The package is now available to other projects on the local system. | |||||
.. note:: | |||||
This doesn't export in "editable" mode: A snapshot of the package root | |||||
will be taken and exported to the local repository. | |||||
If one tries to export a package root into a repository that already contains | |||||
a package with a matching identifier, ``dds`` will issue an error. If the | |||||
``--replace`` flag is specified with ``sdist export``, then ``dds`` will | |||||
forcibly replace the package in the local repository with a new copy. |
Source Distributions | |||||
#################### | |||||
A *source distribution* is ``dds``'s primary format for consuming and | |||||
distributing packages. A source distribution, in essence, is a | |||||
:ref:`package root <pkgs.pkg-root>` directory that contains only the files | |||||
necessary for ``dds`` to reproduce the full build of all libraries in the | |||||
package. The source distribution retains the directory structure of every | |||||
:ref:`source root <pkgs.source-root>` of the original package, and thus retains | |||||
the header structure thereof. In this way, the ``#include`` directives to use | |||||
a library in a source distribution are identical to if the libraries therein | |||||
were directly part of the consuming project. | |||||
Generating a Source Distribution | |||||
******************************** | |||||
Generating a source distribution from a project directory is done with the | |||||
``sdist`` subcommand:: | |||||
> dds sdist create | |||||
The above command can be executed within any package root, and the result will | |||||
be a new directory that reproduces the package's filesystem structure, but | |||||
only maintaining the files that are necessary for ``dds`` to reproduce the | |||||
build of that package. | |||||
The ``--project=<dir>`` flag can be provided to override the directory that | |||||
``dds`` will use as the package root. The default is the working directory of | |||||
the project. | |||||
The ``--out=<path>`` flag can be provided to override the destination path of | |||||
the resulting source distribution. The path should not name an existing file or | |||||
directory. By default, ``dds`` will generate a source distribution in the | |||||
working directory with the name ``project.dsd/`` (The output is itself a | |||||
directory, not an archive). If the ``--replace`` flag is provided, then ``dds`` | |||||
will overwrite the destination if it names an existing file or directory. | |||||
Exporting a Package to the Local Repository | |||||
******************************************* | |||||
.. seealso:: :ref:`repo.export-local` |
.. highlight:: yaml | |||||
Toolchains | |||||
########## | |||||
One of the core components of ``dds`` is that of the *toolchain*. A toolchain | |||||
encompasses the environment used to build and link source code, including, but | |||||
not limited to: | |||||
#. The executable binaries that constitute the language implementation: | |||||
Compilers, linkers, and archive managers. | |||||
#. The configuration of those tools, including most options given to those | |||||
tools when they are invoked. | |||||
#. The set of preprocessor macros and language features that are active during | |||||
compilation. | |||||
When a build is run, every file in the entire tree (including dependencies) | |||||
will be compiled, archived, and linked using the same toolchain. | |||||
This page provides an introduction on how one can make use of toolchains most | |||||
effectively in your project. | |||||
.. note:: | |||||
**IMPORTANT**: ``dds`` will *not* automatically load the Visual C++ | |||||
environment. To use Visual C++, ``dds`` must be executed from the | |||||
appropriate environment in order for the Visual C++ toolchain executables | |||||
and files to be available. | |||||
Passing a Toolchain | |||||
******************* | |||||
In ``dds``, the default format of a toolchain is that of a single file that | |||||
describes the entire toolchain, and uses the extension ``.tc.dds`` by | |||||
convention. When running a build for a project, the ``dds`` executable will | |||||
look for a file named ``toolchain.tc.dds`` by default, and will error out if | |||||
this file does not exist. A different toolchain can be provided by passing the | |||||
toolchain file for the ``--toolchain`` (or ``-t``) option on the command line:: | |||||
$ dds build -t my-toolchain.tc.dds | |||||
Alternatively, you can pass the name of a built-in toolchain. See below. | |||||
Built-in Toolchains | |||||
******************* | |||||
For convenience, ``dds`` includes several built-in toolchains that can be | |||||
accessed in the ``--toolchain`` command-line option using a colon ``:`` | |||||
prefix:: | |||||
$ dds build -T :gcc | |||||
``dds`` will treat the leading colon (``:``) as a name for a built-in | |||||
toolchain (this means that a toolchain's filepath may not begin with a colon). | |||||
There are several built-in toolchains that may be specified: | |||||
``:gcc`` | |||||
Uses the default ``gcc`` and ``g++`` executables, linkers, and options | |||||
thereof. | |||||
``:gcc-N`` (for some integer ``N``) | |||||
Equivalent to ``:gcc``, but uses the ``gcc-N`` and ``g++-N`` executables. | |||||
``:clang`` | |||||
Equivalent to ``:gcc``, but uses the ``clang`` and ``clang++`` executables. | |||||
``:clang-N`` (for some integer ``N``) | |||||
Equivalent to ``:clang``, but uses the ``clang-N`` and ``clang++-N`` | |||||
executables. | |||||
``:msvc`` | |||||
Compiles and links using the Visual C++ toolchain. | |||||
The following pseudo-toolchains are also available: | |||||
``:debug:XYZ`` | |||||
Uses built-in toolchain ``:XYZ``, but generates debugging information. | |||||
``:ccache:XYZ`` | |||||
Uses built-in toolchain ``:XYZ``, but prefixes all compile commands with | |||||
``ccache``. | |||||
``:c++UV:XYZ`` (for two integers ``UV``) | |||||
Sets the C++ version to ``C++UV`` and uses the ``:XYZ`` toolchain. | |||||
Toolchain Definitions | |||||
********************* | |||||
Besides using the built-in toolchains, it is likely that you'll soon want to | |||||
customize a toolchain further. Further customization must be done with a | |||||
file that contains the toolchain definition. The most basic toolchain file is | |||||
simply one line: | |||||
.. code-block:: | |||||
Compiler-ID: <compiler-id> | |||||
where ``<compiler-id>`` is one of the known ``Compiler-ID`` options (See the | |||||
toolchain option reference). ``dds`` will infer common suitable defaults for | |||||
the remaining options based on the value of ``Compiler-ID``. | |||||
For example, if you specify ``GNU``, then ``dds`` will assume ``gcc`` to be the | |||||
C compiler, ``g++`` to be the C++ compiler, and ``ar`` to be the library | |||||
archiving tool. | |||||
If you know that your compiler executable has a different name, you can | |||||
specify them with additional options: | |||||
.. code-block:: | |||||
Compiler-ID: GNU | |||||
C-Compiler: gcc-9 | |||||
C++-Compiler: g++-9 | |||||
``dds`` will continue to infer other options based on the ``Compiler-ID``, but | |||||
will use the provided executable names when compiling files for the respective | |||||
languages. | |||||
To specify compilation flags, the ``Flags`` option can be used: | |||||
.. code-block:: | |||||
Flags: -fsanitize=address -fno-inline | |||||
.. note:: | |||||
Use ``Warning-Flags`` to specify options regarding compiler warnings. | |||||
Flags for linking executables can be specified with ``Link-Flags``: | |||||
.. code-block:: | |||||
Link-Flags: -fsanitize=address -fPIE | |||||
Toolchain Option Reference | |||||
************************** | |||||
The following options are available to be specified within a toolchain file: | |||||
``Compiler-ID`` | |||||
--------------- | |||||
Specify the identity of the compiler. This option is used to infer many other | |||||
facts about the toolchain. If specifying the full toolchain with the command | |||||
templates, this option is not required. | |||||
Valid values are: | |||||
``GNU`` | |||||
For GCC | |||||
``Clang`` | |||||
For LLVM/Clang | |||||
``MSVC`` | |||||
For Microsoft Visual C++ | |||||
``C-Compiler`` and ``C++-Compiler`` | |||||
----------------------------------- | |||||
Names/paths of the C and C++ compilers, respectively. Defaults will be inferred | |||||
from ``Compiler-ID``. | |||||
``C-Version`` and ``C++-Version`` | |||||
--------------------------------- | |||||
Specify the language versions for C and C++, respectively. By default, ``dds`` | |||||
will not set any language version. Using this option requires that the | |||||
``Compiler-ID`` be specified | |||||
Valid ``C-Version`` values are: | |||||
- ``C89`` | |||||
- ``C99`` | |||||
- ``C11`` | |||||
- ``C18`` | |||||
Valid ``C++-Version`` values are: | |||||
- ``C++98`` | |||||
- ``C++03`` | |||||
- ``C++11`` | |||||
- ``C++14`` | |||||
- ``C++17`` | |||||
- ``C++20`` | |||||
.. warning:: | |||||
``dds`` will not do any "smarts" to infer the exact option to pass to have | |||||
the required effect. If you ask for ``C++20`` from ``gcc 5.3``, ``dds`` | |||||
will simply pass ``-std=c++20`` with no questions asked. If you need | |||||
finer-grained control, use the ``Flags`` option. | |||||
``Warning-Flags`` | |||||
----------------- | |||||
Override the compiler flags that should be used to enable warnings. This option | |||||
is stored separately from ``Flags``, as these options may be enabled/disabled | |||||
separately depending on how ``dds`` is invoked. | |||||
.. note:: | |||||
If ``Compiler-ID`` is provided, a default value will be used that enables | |||||
common warning levels. | |||||
If you need to tweak warnings further, use this option. | |||||
``Flags``, ``C-Flags``, and ``C++-Flags`` | |||||
----------------------------------------- | |||||
Specify *additional* compiler options, possibly per-language. | |||||
``Link-Flags`` | |||||
-------------- | |||||
Specify *additional* link options to use when linking executables. | |||||
``Optimize`` | |||||
------------ | |||||
Boolean option (``True`` or ``False``) to enable/disable optimizations. Default | |||||
is ``False``. | |||||
``Debug`` | |||||
--------- | |||||
Boolean option (``True`` or ``False``) to enable/disable the generation of | |||||
debugging information. Default is ``False``. | |||||
``Compiler-Launcher`` | |||||
--------------------- | |||||
Provide a command prefix that should be used on all compiler executions. | |||||
e.g. ``ccache``. | |||||
Advanced Options Reference | |||||
************************** | |||||
The options below are probably not good to tweak unless you *really* know what | |||||
you are doing. Their values will be inferred from ``Compiler-ID``. | |||||
``Deps-Mode`` | |||||
------------- | |||||
Specify the way in which ``dds`` should track compilation dependencies. One | |||||
of ``GNU``, ``MSVC``, or ``None``. | |||||
``C-Compile-File`` | |||||
------------------ | |||||
Override the *command template* that is used to compile C source files. | |||||
``C++-Compile-File`` | |||||
-------------------- | |||||
Override the *command template* that is used to compile C++ source files. | |||||
``Create-Archive`` | |||||
------------------ | |||||
Override the *command template* that is used to generate static library archive | |||||
files. | |||||
``Link-Executable`` | |||||
------------------- | |||||
Override the *command template* that is used to link executables. | |||||
``Include-Template`` | |||||
-------------------- | |||||
Override the *command template* for the flags to specify a header search path. | |||||
``External-Include-Template`` | |||||
----------------------------- | |||||
Override the *command template* for the flags to specify a header search path | |||||
of an external library. | |||||
``Define-Template`` | |||||
------------------- | |||||
Override the *command template* for the flags to set a preprocessor definition. |
DDS | |||||
###### | |||||
**dds** is the Drop-Dead-Simple build and library management tool. | |||||
dds is a hybrid build system and package manager with a few distinguishing | |||||
design decisions that set it apart from current offerings. There's a lot to | |||||
learn, but I'm glad you're here! I hope you find ``dds`` useful to you and your | |||||
projects. | |||||
If you're completely new and have no idea what the project is about, check out | |||||
the :doc:`tut/index` page. | |||||
.. toctree:: | |||||
:maxdepth: 2 | |||||
tut/index | |||||
guide/index | |||||
design | |||||
Indices and tables | |||||
================== | |||||
* :ref:`genindex` | |||||
* :ref:`modindex` | |||||
* :ref:`search` |
A *Hello, World* Library | |||||
######################## | |||||
Creating a single application is fun, but what if we want to create code that | |||||
we can distribute and reuse across other packages and projects? That's what | |||||
*libraries* are for. | |||||
You may notice something strange: This page is much shorter than the previous. | |||||
What gives? | |||||
It turns out that we've already covered all the material to make a library in | |||||
the page on creating a :doc:`Hello, world! executable <hello-world>`. As soon | |||||
as we created the ``strings.hpp`` file, our project had become a library. When | |||||
we added a ``strings.cpp`` file to accompany it, our library became a *compiled* | |||||
library. | |||||
The ``hello-world.main.cpp`` file is expressly *not* considered to be part of a | |||||
library, as it is specifically designated to be an application entry point, | |||||
and source files of such kind are not part of a library. | |||||
Before continuing on, note the following about creating a library that wasn't | |||||
specifically addressed in the prior example: | |||||
#. The *source roots* of a library are added to the compiler's ``#include`` | |||||
search-path. In our example, this is only the ``src/`` directory of the | |||||
project. | |||||
#. ``dds`` also supports a top-level directory named ``include/``. Both | |||||
``include/`` and ``src/`` may be present in a single library, but there are | |||||
some important differences. Refer to :ref:`pkgs.lib-roots` in the layout | |||||
guide. | |||||
#. A single *library root* may contain any number of applications defined in | |||||
``.main`` files, but a *library root* will correspond to exactly *one* | |||||
library. Defining additional libraries requires additional packages or | |||||
adding multiple library roots to a single package. | |||||
.. seealso:: | |||||
Like flossing, we all know we *should* be writing tests, but it can be such | |||||
a hassle. Fortunately, ``dds`` makes it simple. Read on to: | |||||
:doc:`hello-test`. |
A *Hello, World* Test | |||||
##################### | |||||
So far, we have a simple library with a single function: ``get_greeting()`` | |||||
and an application that makes use of it. How can we test it? | |||||
With ``dds``, similar to generating applications, creating a test requires | |||||
adding a suffix to a source filename stem. Instead of ``.main``, simply | |||||
add ``.test`` before the file extension. | |||||
A New Test Executable | |||||
********************* | |||||
We'll create a test for our ``strings`` component, in a file named | |||||
``strings.test.cpp``. We'll use an ``assert`` to check our ``get_greeting()`` | |||||
function: | |||||
.. code-block:: c++ | |||||
:caption: ``<root>/src/hello/strings.test.cpp`` | |||||
:linenos: | |||||
#include <hello/strings.hpp> | |||||
int main() { | |||||
if (hello::get_greeting() != "Hello world!") { | |||||
return 1; | |||||
} | |||||
} | |||||
If you run ``dds build`` once again, ``dds`` will generate a test executable | |||||
and run it immediately. If the test executable exits with a non-zero exit code, | |||||
then it will consider the test to have failed, and ``dds`` itself will exit | |||||
with a non-zero exit code. | |||||
.. important:: | |||||
``dds`` executes tests *in parallel* by default! If the tests need access | |||||
to a shared resource, locking must be implemented manually, or the shared | |||||
resource should be split. | |||||
.. note:: | |||||
``dds`` builds and executes tests for *every build* **by default**. The | |||||
``*.test.cpp`` tests are meant to be very fast *unit* tests, so consider | |||||
their execution time carefully. | |||||
If your code matches the examples so far, the above test will *fail*. Keen eyes | |||||
will already know the problem, but wouldn't it be better if we had better test | |||||
diagnostics? | |||||
A ``Test-Driver``: Using *Catch2* | |||||
********************************* | |||||
``dds`` ships with built-in support for the `Catch2`_ C and C++ testing | |||||
framework, a popular | |||||
.. _catch2: https://github.com/catchorg/Catch2 | |||||
To make use of Catch as our test driver, we simply declare this intent in the | |||||
``package.dds`` file at the package root: | |||||
.. code-block:: yaml | |||||
:caption: ``<root>/package.dds`` | |||||
:emphasize-lines: 5 | |||||
Name: hello-dds | |||||
Version: 0.1.0 | |||||
Namespace: tutorial | |||||
Test-Driver: Catch-Main | |||||
If you now run ``dds build``, we will get a linker error for a multiply-defined | |||||
``main`` function. When setting the ``Test-Driver`` to ``Catch-Main``, ``dds`` | |||||
will compile an entrypoint separately from any particular test, and the tests | |||||
will link against that entrypoint. This means we cannot provide our own | |||||
``main`` function, and should instead use Catch's ``TEST_CASE`` macro to | |||||
declare our test cases. | |||||
In addition to an entrypoint, ``dds`` provides a ``catch.hpp`` header that we | |||||
may use in our tests, simply by ``#include``-ing the appropriate path. We'll | |||||
modify our test to use the Catch test macros instead of our own logic. We'll | |||||
leave the condition the same, though: | |||||
.. code-block:: c++ | |||||
:caption: ``<root>/src/hello/strings.test.cpp`` | |||||
:linenos: | |||||
:emphasize-lines: 3, 5-7 | |||||
#include <hello/strings.hpp> | |||||
#include <catch2/catch.hpp> | |||||
TEST_CASE("Check the greeting") { | |||||
CHECK(hello::get_greeting() == "Hello world!"); | |||||
} | |||||
Now running ``dds build`` will print more output that Catch has generated as | |||||
part of test execution, and we can see the reason for the failing test:: | |||||
[16:41:45] [error] Test <root>/_build/test/hello/strings failed! Output: | |||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||||
strings is a Catch v2.10.2 host application. | |||||
Run with -? for options | |||||
------------------------------------------------------------------------------- | |||||
Check the greeting | |||||
------------------------------------------------------------------------------- | |||||
<root>/src/hello/strings.test.cpp:5 | |||||
............................................................................... | |||||
<root>/src/hello/strings.test.cpp:5: FAILED: | |||||
CHECK( hello::get_greeting() == "Hello world!" ) | |||||
with expansion: | |||||
"Hello, world!" == "Hello world!" | |||||
=============================================================================== | |||||
test cases: 1 | 1 failed | |||||
assertions: 1 | 1 failed | |||||
[dds - test output end] | |||||
Now that we have the direct results of the offending expression, we can | |||||
much more easily diagnose the nature of the test failure. In this case, the | |||||
function returns a string containing a comma ``,`` while our expectation lacks | |||||
one. If we fix either the ``get_greeting`` or the expected string, we will then | |||||
see our tests pass successfully and ``dds`` will exit cleanly. |
A *Hello, world!* Application | |||||
############################# | |||||
Creating a *hello world* application is very simple. | |||||
Creating a *Package Root* | |||||
************************* | |||||
To start, create a new directory for your project. This will be known as the | |||||
*package root*, and the entirety of our project will be placed in this | |||||
directory the name and location of this directory is not important, but the | |||||
contents therein will be significant. | |||||
.. note:: | |||||
The term *package root* is further described in the :doc:`/guide/packages` page. | |||||
From here on, this created directory will simply be noted as ``<root>``. In | |||||
the examples, this will refer to the directory package root directory we have | |||||
created. | |||||
Creating the First *Source Root* | |||||
******************************** | |||||
Within the package root, we create our first *source root*. Since we are | |||||
intending to compile files, we need to use the name that ``dds`` has designated | |||||
to be the source root that may contain compilable source files: ``src/``: | |||||
.. code-block:: bash | |||||
mkdir src | |||||
You should now have a single item in the package root, at ``<root>/src/``. This | |||||
is the directory from which ``dds`` will search for source files. | |||||
Creating an Application Entrypoint | |||||
********************************** | |||||
To add a source file to our project, we simply create a file within a source | |||||
root with the appropriate file extension. Our source root is ``<root>/src/``, | |||||
so we'll place a source file in there. In addition, because we want to create | |||||
an *application* we need to designate that the source file provides an | |||||
application *entry point*, i.e. a ``main()`` function. To do this, we simply | |||||
prepend ``.main`` to the file extension. Create a file:: | |||||
> <root>/src/hello-world.main.cpp | |||||
and open it in your editor of choice. We'll add the classic C++ *hello, world* | |||||
program: | |||||
.. code-block:: c++ | |||||
:linenos: | |||||
:caption: ``<root>/src/hello-world.main.cpp`` | |||||
#include <iostream> | |||||
int main() { | |||||
std::cout << "Hello, world!\n"; | |||||
} | |||||
Building *Hello, World* | |||||
*********************** | |||||
Now comes the fun part. It is time to actually compile the application! | |||||
.. important:: | |||||
If you intend to compile with Visual C++, the build must be executed | |||||
from within a Visual Studio or Visual C++ development command prompt. These | |||||
program shortcuts should be made available with any standard installation | |||||
of the Visual C++ toolchain. | |||||
``dds`` **will not** automatically load the Visual C++ environment. | |||||
To build the program, we must provide ``dds`` with information about our | |||||
program toolchain. ``dds`` comes with a few "built in" toolchain options that | |||||
can be used out-of-the-box, and they'll be suitable for our purposes. | |||||
- If you are compiling with GCC, the toolchain name is ``:gcc`` | |||||
- If you are compiling with Clang, the toolchain name is ``:clang`` | |||||
- If you are compiling with Visual C++, the toolchain name is ``:msvc`` | |||||
.. note:: | |||||
The leading colon ``:`` is important: This tells ``dds`` to use its | |||||
built-in toolchain information rather than looking for a toolchain file of | |||||
that name. | |||||
To execute the build, run the ``dds build`` command as in the following | |||||
example, providing the appropriate toolchain name in place of ``<toolchain>``:: | |||||
> dds build -t <toolchain> | |||||
For example, if you are using ``gcc``, you would run the command as:: | |||||
> dds build -t :gcc | |||||
If all successful, ``dds`` will emit information about the compile and link | |||||
process, and then exit without error. | |||||
By default, build results will be placed in a subdirectory of the package root | |||||
named ``_build``. Within this directory, you will find the generated executable | |||||
named ``hello-world`` (with a ``.exe`` suffix if on Windows). | |||||
We should not be able to run this executable and see our ``Hello, world!``:: | |||||
> ./_build/hello-world | |||||
Hello, world! | |||||
Obviously this isn't *all* there is to do with ``dds``. Read on to the next | |||||
pages to learn more. | |||||
.. note:: | |||||
You're reading a very early version of these docs. There will be a lot more | |||||
here in the future. Watch this space for changes! | |||||
More Sources | |||||
************ | |||||
Modularizing our program is good, right? Let's do that. | |||||
Add a Header | |||||
************ | |||||
Create a new subdirectory of ``src``, and we'll call it ``hello``:: | |||||
> mkdir src/hello | |||||
Within this directory, create a ``strings.hpp``. Edit the content in your | |||||
editor of choice: | |||||
.. code-block:: c++ | |||||
:caption: ``<root>/src/hello/strings.hpp`` | |||||
:linenos: | |||||
#ifndef HELLO_STRINGS_HPP_INCLUDED | |||||
#define HELLO_STRINGS_HPP_INCLUDED | |||||
#include <string> | |||||
namespace hello { | |||||
std::string get_greeting(); | |||||
} | |||||
#endif | |||||
Change our ``main()`` | |||||
********************* | |||||
Modify the content of ``<root>/src/hello-world.main.cpp`` to include our new | |||||
header and to use our ``get_greeting()`` function: | |||||
.. code-block:: c++ | |||||
:caption: ``<root>/src/hello-world.main.cpp`` | |||||
:linenos: | |||||
:emphasize-lines: 1, 6 | |||||
#include <hello/strings.hpp> | |||||
#include <iostream> | |||||
int main() { | |||||
std::cout << hello::get_greeting() << '\n'; | |||||
} | |||||
Compiling Again, and Linking...? | |||||
******************************** | |||||
If you run the ``dds build`` command again, you will now see an error: | |||||
.. code-block:: text | |||||
[12:55:25] [info ] [dds-hello] Link: hello-world | |||||
[12:55:25] [info ] [dds-hello] Link: hello-world - 57ms | |||||
[12:55:25] [error] Failed to link executable '<root>/_build/hello-world'. | |||||
... | |||||
<additional lines follow> | |||||
The problem, of course, is that we've declared ``get_greeting`` to *exist*, but | |||||
be haven't *defined it*. | |||||
Adding Another Compiled Source | |||||
****************************** | |||||
We'll add another compilable source file to our project. In the same | |||||
directory as ``strings.hpp``, add ``strings.cpp``: | |||||
.. code-block:: c++ | |||||
:caption: ``<root>/src/hello/strings.cpp`` | |||||
:linenos: | |||||
#include <hello/strings.hpp> | |||||
std::string hello::get_greeting() { | |||||
return "Hello, world!"; | |||||
} | |||||
Compiling and Linking! | |||||
********************** | |||||
Run the ``dds build`` command again, and you'll find that the application | |||||
successfully compiles and links! | |||||
If you've used other build systems, you may have noticed a missing step: We | |||||
never told ``dds`` about our new source file. Actually, we never told ``dds`` | |||||
about *any* of our source files. We never even told it the name of the | |||||
executable to generate. What gives? | |||||
It turns out, we *did* tell ``dds`` all of this information by simply placing | |||||
the files on the filesystem with the appropriate file paths. The name of the | |||||
executable, ``hello-world``, was inferred by stripping the trailing ``.main`` | |||||
from the stem of the filename which defined the entry point. | |||||
Cleaning Up | |||||
*********** | |||||
There's one final formality that should be taken care of before proceeding: | |||||
Creating a package manifest file. | |||||
``dds`` will work happily with packages that do not declare themselves, as long | |||||
as the filesystem structure is sufficient. However: To use features covered in | |||||
later tutorials, we'll need a simple ``package.dds`` file to declare | |||||
information about are package. This file should be placed directly in the | |||||
package root: | |||||
.. code-block:: yaml | |||||
:caption: ``<root>/package.dds`` | |||||
Name: hello-dds | |||||
Version: 0.1.0 | |||||
Namespace: tutorial | |||||
.. note:: | |||||
The ``Namespace`` option will be discussed later. | |||||
Rebuilding the project will show no difference at the moment. | |||||
.. seealso:: | |||||
Creating a single application executable is fine and all, but what if we | |||||
want to create libraries? See the next page: :doc:`hello-lib` |
Tutorials and Beginner's Guide | |||||
############################## | |||||
The child pages here contain introductory material for getting started with | |||||
using ``dds``. If you don't know where to start, this will be a good | |||||
place to begin! | |||||
.. note:: | |||||
The shell samples in these pages are written with Unix-flavored commands, | |||||
but the analogous process will work just as well on Windows systems. | |||||
Translate as appropriate. | |||||
.. toctree:: | |||||
:maxdepth: 2 | |||||
hello-world | |||||
hello-lib | |||||
hello-test |
set -eu | |||||
THIS_SCRIPT=$(readlink -m $0) | |||||
HERE=$(dirname ${THIS_SCRIPT}) | |||||
ROOT=$(dirname ${HERE}) | |||||
while true; do | |||||
echo "Watching for changes..." | |||||
inotifywait -r ${ROOT}/docs/ -q \ | |||||
-e modify \ | |||||
-e close_write \ | |||||
-e move \ | |||||
-e delete \ | |||||
-e create | |||||
make docs || : | |||||
done |