| .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 |