DDS
Packages and Layout

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

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:

Compiled as C

.c and .C

Compiled as C++

.cpp, .c++, .cc, and .cxx

Not compiled

.H, .H++, .h, .h++, .hh, .hpp, .hxx, .ipp, .inc, 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 a source distribution.

Applications and Tests

dds will recognize certain compilable source files as belonging to applications and tests, depending on the filenames “stem,” which is the part of the filename not including the outer-most file extension. If a compilable source filename stem ends with .main or .test, that source file is assumed to correspond to an executable to generate. The filename second-inner stem before the .main or .test will be used as the name of the generated executable. For example:

  • Given foo.main.cpp

    • The stem is foo.main, whose extension is .main, so we will generate an application.

    • The stem of foo.main is foo, so the executable will be named foo.

  • Given bar.test.cpp

    • The stem is bar.test, whose extension is .test, so we will generate a test.

    • The stem of bar.test is bar, so will generate an executable named bar.

  • Given cat-meow.main.cpp

    • The stem is cat-meow.main, which has extension .main, so it is an application.

    • The stem of cat-meow.main is cat-meow, so will generate an executable named cat-meow.

  • Given cats.musical.test.cpp

    • The stem is cats.musical.test, which has extension .test, so this is a text executable.

    • The stem of cats.musical.test is cats.musical, so we will generate an executable named cats.musical.

    • Note that the dot in cats.musical is not significant, as dds does strip any further extensions.

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.

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:

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

#include <animals/mammal/mammal.hpp>

or even a third-party user that wants to use our library:

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

Source Roots in dds

To avoid ambiguity and aide in portability, the following rules should be strictly adhered to:

  1. Source roots may not contain other source roots.

  2. Only source roots will be added to the include-search-path.

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

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.json5 file must be present at the corresponding library root. The only required key in a library.json5 file is name:

{
  name: 'my-library'
}

See also

More information is discussed on the Library Dependencies page

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

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 local package cache.

In order for a package to be exported by dds it must have a package.json5 file at its package root. Three keys are required to be present in the package.json5 file: name, version, and namespace:

{
  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 any C++ namespace.

See also

The purpose of namespace, as well as additional options in this file, are described in the Package Dependencies page

Naming Requirements

Package names aren’t a complete free-for-all. Package names must follow a set of specific rules:

  • Package names may consist of a subset of ASCII including lowercase characters, digits, underscores (_), hyphens (-), and periods (.).

    Note

    Different filesystems differ in their handling of filenames. Some platforms perform unicode and case normalization, which can significantly confuse tools that don’t use the same normalization rules. Different platforms have different filename limitations and allowable characters. This set of characters is valid on most currently popular filesystems.

  • Package names must begin with an alphabetic character

  • Package names must end with an alphanumeric character (letter or digit).

  • Package names may not contain adjacent punctuation characters.