Error: Creating a static library archive failed | |||||
############################################### | |||||
``dds`` primarily works with *static libraries*, and the file format of a | |||||
static library is known as an *archive*. This error indicates that ``dds`` | |||||
failed in its attempt to collect one or more compiled *object files* together | |||||
into an archive. | |||||
It is unlikely that regular user action can cause this error, and is more | |||||
likely an error related to the environment and/or filesystem. | |||||
If you are using a custom toolchain and have specified the archiving command | |||||
explicitly, it may also be possible that the argument list you have given to | |||||
the archiving tool is invalid. | |||||
When archiving fails, ``dds`` will print the output and command that tried to | |||||
generate the archive. It should provide more details into the nature of the | |||||
failure. |
Error: The package catalog database is for a later ``dds`` version | |||||
################################################################## | |||||
If you receive this error, it indicates that the schema version of a catalog | |||||
database is newer than the schema expected by the ``dds`` process that is | |||||
trying to actually make use of the catalog. ``dds`` catalog databases are not | |||||
forwards-compatible and cannot be downgraded. | |||||
If you generated/modified the catalog using a ``dds`` executable that is newer | |||||
than the one you attempted to open it with, it is possible that the newer | |||||
``dds`` executable performed a schema upgrade that is unsupported by the older | |||||
version of ``dds``. | |||||
If you have not specified a path to a catalog, ``dds`` will use the user-local | |||||
default catalog database. If you receive this error while using the user-local | |||||
database, the solution is to either upgrade ``dds`` to match the newer catalog | |||||
or to delete the user-local catalog database file. | |||||
.. note:: | |||||
Deleting the catalog database will lose any customizations that were | |||||
contained in that catalog, and they will need to be reconstructed. |
Error: Compilation failed | |||||
######################### | |||||
Receiving this error indicates that the source code compiler exited with an | |||||
error. This usually indicates that there is a syntactic or semantic error in | |||||
the code that was given to the compiler. Refer to the compiler process's output | |||||
for more information. |
Error: The build database is corrupted | |||||
###################################### | |||||
``dds`` stores various data about the local project's build in a database file | |||||
kept in the build output directory (default is ``_build``). The file is named | |||||
``.dds.db``, and can be safely deleted with no ill effects on the project. | |||||
This error is not caused by regular user action, and is probably a bug in | |||||
``dds``. If you see this error message frequently, please file a bug report. |
Error: The catalog database appears corrupted/invalid | |||||
##################################################### | |||||
This issue will occur if the schema contained in the catalog database does not | |||||
match the schema that ``dds`` expects. This should never occur during normal | |||||
use of ``dds``. | |||||
.. note:: | |||||
If you suspect that ``dds`` has accidentally corrupted its own catalog | |||||
database, please file a bug report. | |||||
``dds`` cannot reliably repair a corrupted database, and the only solution will | |||||
be to either manually fix it or to delete the database file and start fresh. | |||||
.. note:: | |||||
Deleting the catalog database will lose any customizations that were | |||||
contained in that catalog, and they will need to be reconstructed. |
Error: A Git ``clone`` operation failed | |||||
####################################### | |||||
This error indicates that ``dds`` failed to clone a Git repository. | |||||
``dds`` will invoke ``git clone`` as a subprocess to retrieve a copy of a | |||||
remote repository. There are several reasons this might fail, but it is best | |||||
to refer to the output of the ``git`` subprocess to diagnose the issue. | |||||
A non-exhaustive list of things to check: | |||||
#. Is the ``git`` executable available and on the ``PATH`` environment variable? | |||||
#. Is the URL to the repository correct? | |||||
#. Is the remote server accessible? | |||||
#. Do you have read-access to the repository in question? | |||||
#. Does the named tag/branch exist in the remote? | |||||
#. If cloning a specific Git revision, does the remote server support cloning | |||||
a repository by a specific commit? (Very often Git servers are not | |||||
configured to support this capability). | |||||
Be aware if you are using SSH-style ``git-clone`` that it will require the | |||||
correct SSH keys to be available on the system where ``dds`` is running. |
Error: Git requires both a URL and a ref to clone | |||||
################################################# | |||||
This error occurs when attempting to add an entry to the package catalog that | |||||
uses the ``Git`` acquisition method. | |||||
When ``dds`` obtains a package from the catalog using the ``Git`` method, it | |||||
needs a URL to clone from, and a Git ref to clone. ``dds`` uses a technique | |||||
known as "shallow cloning," which requires a known Git reference to clone from. | |||||
The reference may be a tag, branch, or an individual commit (Using a Git commit | |||||
as the ``ref`` requires support from the remote Git server, and it is often | |||||
unavailable in most setups). Using a Git tag is strongly recommended. | |||||
.. seealso:: | |||||
Refer to the documentation on :doc:`/guide/catalog`. |
Runtime Error References | |||||
######################## | |||||
This page enumerates the possible errors that you may encounter while using | |||||
DDS, either from user error or environmental issues. | |||||
.. note:: | |||||
DDS will automatically emit links to the relevant error document when it | |||||
occurs. | |||||
Errors | |||||
****** | |||||
.. toctree:: | |||||
:maxdepth: 2 | |||||
:glob: | |||||
* |
Error: Invalid built-in toolchain | |||||
################################# | |||||
``dds`` requires a toolchain in order to build any packages or projects. It | |||||
ships with several built-in toolchains that do not require the user to write | |||||
any configuration files. | |||||
If you start your toolchain name (The `-t` or `--toolchain` argument) | |||||
with a leading colon, dds will interpret it as a reference to a built-in | |||||
toolchain. (Toolchain file paths cannot begin with a leading colon). | |||||
These toolchain names are encoded into the dds executable and cannot be | |||||
modified. | |||||
.. seealso:: | |||||
Refer to the documentation on :doc:`toolchains </guide/toolchains>` and | |||||
:ref:`toolchains.builtin`. |
Error: Invalid catalog JSON | |||||
########################### | |||||
This error occurs when the JSON data given to import into the package catalog | |||||
is in some way invalid. Refer to the catalog documentation for a description of | |||||
the proper JSON format. | |||||
.. seealso:: | |||||
:ref:`catalog.adding` |
Error: Invalid version string | |||||
############################# | |||||
``dds`` stores version numbers in a type-safe manner, and all version numbers | |||||
are requried to match `Semantic Versioning <https://semver.org>`_. | |||||
If you see this error, it means that a ``dds`` found a version number that does | |||||
not correctly conform to Semantic Versioning's requirements for version numbers | |||||
refer to the ``dds`` output for a description of *where* the bad version string | |||||
was found, and refer to the Semantic Versioning website for information about | |||||
how to properly format a version number. | |||||
.. _range: | |||||
Version Ranges | |||||
************** | |||||
In addition to requirements on individual versions, providing a version range | |||||
requires a slightly different syntax. | |||||
.. TODO: Write docs on version range strings. |
Error: Linking a runtime binary failed | |||||
###################################### | |||||
.. contents:: | |||||
.. highlight:: c++ | |||||
This error indicates that the final phases of the build pipeline, the link | |||||
phases, failed. | |||||
The end result of the software development process is to produce applications | |||||
and programs that can be executed by users. In the traditional compilation and | |||||
linking model, which we still use to this day, multiple *translation units* | |||||
(which can be thought of as "source files") are combined together in a process | |||||
known as *linking*. The result of linking is an actual executable. | |||||
.. note:: | |||||
Linking is also used to generate executables that are used as tests. | |||||
Refer: :ref:`pkgs.apps-tests`. | |||||
What is "Linking"? | |||||
****************** | |||||
The *phases of translation* define the steps taken by a compiler (and linker) | |||||
to map from the input human-readable source code to the code that can be | |||||
executed by a machine. The first few phases are collected bundled together in | |||||
a phase known as "compilation," while the later phases are known as "linking." | |||||
The output of the compilation phases is often generated by a *compiler* and | |||||
then written to the filesystem. The resulting files, known as *object files* | |||||
are then fed into another tool known as a *linker*. | |||||
.. note:: | |||||
"Object files" are just one possible intermediate product. Compilers and | |||||
linkers may deal with different intermediate products depending on compiler | |||||
and linker options. | |||||
Symbol Reference Resolution | |||||
*************************** | |||||
When code within a translation unit uses a function, variable, or member of a | |||||
class that is declared **but not defined** in that translation unit, that | |||||
usage is stored as an "unresolved" reference to an external symbol within the | |||||
resulting object file. | |||||
Each translation unit may also contain the *definitions* of any number of | |||||
symbols. It is possible that other translation units may contain unresolved | |||||
references to the symbol that the translation unit defines. | |||||
It is the job of the linker to fill in these unresolved references when it | |||||
combines translation units together. | |||||
Failure Modes | |||||
************* | |||||
There are two very common types of linker errors that may be seen: | |||||
Multiple Definitions Found | |||||
========================== | |||||
When translation units are combined together, the symbols within them are | |||||
combined together into a final binary. If two or more translation units contain | |||||
the *definition* of a single symbol, then the linker must make a decision: | |||||
#. If the symbol is marked properly, then the linker can discard all except one | |||||
of the definitions and choose one to keep in the final binary. For example: | |||||
This is allowed if the associated symbol has been declared with the | |||||
``inline`` keyword, or is a symbol in any context that is "implicitly | |||||
``inline``," which includes member functions and static variables which are | |||||
defined within their class's body, and any function template. | |||||
#. **Fail** | |||||
If the linker is not allowed to discard all-but-one of the multiple | |||||
definitions, this is a hard-error. This can happen if multiple translation | |||||
units defined the same variable or function at the same namespace. | |||||
Issue: A non-``inline`` function is defined in a header file | |||||
------------------------------------------------------------ | |||||
A likely case is that of defining a function in a header file without | |||||
marking it as ``inline``: | |||||
.. code-block:: | |||||
:caption: ``hello.hpp`` | |||||
#ifndef MY_HEADER_INC | |||||
#define MY_HEADER_INC | |||||
#include <cstdio> | |||||
void say_hello() { | |||||
std::puts("Hello!\n"); | |||||
} | |||||
#endif | |||||
and then that header is ``#include``-ed in multiple source files: | |||||
.. code-block:: | |||||
:caption: ``a.cpp`` | |||||
#include "hello.hpp" | |||||
// ... stuff ... | |||||
.. code-block:: | |||||
:caption: ``b.cpp`` | |||||
#include "hello.hpp" | |||||
// .. different stuff ... | |||||
.. note:: | |||||
``template`` functions and member functions *defined within the class body* | |||||
are implicitly ``inline``, and using the ``inline`` keyword is then | |||||
redundant. | |||||
In the above configuration, the linker will generate an error about multiple | |||||
definitions of the ``say_hello`` function. Possibly confusingly, it will point | |||||
to ``a.cpp`` and ``b.cpp`` as the "definers" of ``say_hello``, even though it | |||||
is actually defined in the header. The issue is that no tools are currently | |||||
able to understand this structure in a way that they can clearly issue | |||||
appropriate instruction on how to fix this. There are two ways to fix this: | |||||
#. Add the ``inline`` keyword to the definition of ``say_hello``:: | |||||
#ifndef MY_HEADER_INC | |||||
#define MY_HEADER_INC | |||||
#include <cstdio> | |||||
inline void say_hello() { | |||||
std::puts("Hello!\n"); | |||||
} | |||||
#endif | |||||
This activates the rule that permits the linker to disregard the multiple | |||||
definitions and choose one to keep arbitrarily. | |||||
.. note:: | |||||
Only use ``inline`` in headers! | |||||
#. Change the definition of ``say_hello`` to be a *declaration*, and move the | |||||
*definition* to a separate source file: | |||||
.. code-block:: | |||||
:caption: ``hello.hpp`` | |||||
#ifndef MY_HEADER_INC | |||||
#define MY_HEADER_INC | |||||
#include <cstdio> | |||||
void say_hello() { | |||||
std::puts("Hello!\n"); | |||||
} | |||||
#endif | |||||
.. code-block:: | |||||
:caption: ``hello.cpp`` | |||||
#include "hello.hpp" | |||||
void say_hello() { | |||||
std::puts("Hello!\n"); | |||||
} | |||||
This will place the sole location of the ``say_hello`` definition within | |||||
``hello.cpp``. | |||||
Issue: There are two colliding and distinct definitions | |||||
------------------------------------------------------- | |||||
Suppose you have two different source files: | |||||
.. code-block:: | |||||
:caption: ``a.cpp`` | |||||
#include "a.hpp" | |||||
void error(string message) { | |||||
cerr << "An error occured: " << msg << '\n'; | |||||
} | |||||
void a_func() { | |||||
bool had_error = first_a(); | |||||
if (err) { | |||||
error(*err); | |||||
} | |||||
err = second_a(); | |||||
if (err) { | |||||
error(*err); | |||||
} | |||||
} | |||||
.. code-block:: | |||||
:caption: ``b.cpp`` | |||||
void error(string message) { | |||||
throw runtime_error(msg); | |||||
} | |||||
void b_func() { | |||||
bool had_error = first_b(); | |||||
if (had_error) { | |||||
error("The first step failed!"); | |||||
} | |||||
had_error = second_b(); | |||||
if (had_error) { | |||||
error("The second step failed!"); | |||||
} | |||||
} | |||||
The two functions, ``a_func`` and ``b_func``, despite having a similar | |||||
structure, are *completely different* because of the behavior of ``error``: | |||||
- In ``a.cpp``: | |||||
- ``error()`` will simply log a message but let execution continue. | |||||
- If ``first_a()`` fails, execution will continue into ``second_a()``. | |||||
- In ``b.cpp``: | |||||
- ``error()`` will throw an exception. | |||||
- If ``first_b()`` fails, execution will never reach ``second_b()`` | |||||
Nevertheless, the linker will produce an error that there are multiple visible | |||||
definitions of ``error()``, even though the translation units individually have | |||||
no ambiguity. | |||||
The issue is that both of the definitions have *external linkage* and must be | |||||
visible to all other translation units. | |||||
It may be tempting to fix this issue in the same way that we did in the prior | |||||
example: to declare them ``inline``, and it will *seem* to have worked, but | |||||
**this will not work correctly!!** | |||||
Remember what the linker does in the presence of ``inline`` on multiple | |||||
definitions between different translation units: It will *pick one* and | |||||
*discard the others*. This means that either ``error`` function may replace the | |||||
other across translation units, and the resulting code will have wildly | |||||
different behavior. | |||||
The *correct* solution is to give the ``error`` function *internal linkage*, | |||||
which means that its definition is not visible across translation units. This | |||||
will allow both definitions of ``error`` to live together in the linked binary | |||||
without ambiguity. The classic way of doing this is through the usage of the | |||||
global-scope ``static`` keyword which is present in C:: | |||||
static void error(string s) { | |||||
// ... | |||||
} | |||||
C++ presents another way it can be done: via an *unnamed namespace*:: | |||||
namespace { | |||||
void error(string s) { | |||||
// ... | |||||
} | |||||
} // close namespace | |||||
The benefit of the unnamed namespace is it can be used to mark an entire | |||||
section of declarations to be *internal*, and it can also be used to mark a | |||||
class definition to have *internal linkage* (There is no way to declare a | |||||
"``static class``"). | |||||
Unresolved External Symbol / Undefined Reference | |||||
================================================ | |||||
Another common error seen while linking is that of the *unresolved external | |||||
symbol* (Visual C++) or *undefined reference* (GCC and Clang). Both have the | |||||
same underlying cause, and both have the same solutions. | |||||
When a translation unit makes use of a symbol which has been declared *but not | |||||
defined within that translation unit*, it is up to the linker to resolve that | |||||
reference to another translation unit that contains the definition. | |||||
If the linker is unable to find the definition of the referenced entity, it | |||||
will emit this error. | |||||
Issue: An external library is not being included in the link | |||||
------------------------------------------------------------ | |||||
If the unresolved reference is to an entity belonging to an external library, | |||||
you may be missing the linker inputs to actually use that library. | |||||
If your project makes use of a declared entity from a third party (even if that | |||||
usage is transitive through a dependency), it is required that the definitions | |||||
from that third party library are included in the link step. This usually comes | |||||
in the form of a static library, shared library/DLL, or even plain object | |||||
files. | |||||
If the external library containing the definition in question is managed by | |||||
``dds``, this issue should never occur. If the library exists outside of | |||||
``dds`` (e.g. a system library), then that library will need to be manually | |||||
added as a linker input using a toolchain file using the ``Link-Flags`` option. | |||||
See: :ref:`toolchains.opt-ref`. | |||||
If the name of the unresolved symbol appears unfamiliar or you do not believe | |||||
that you are making use of it, it is possible that one of your dependencies is | |||||
making use of a system library symbol that needs to be part of the link. The | |||||
link error will refer to the object/source file that is actually making the | |||||
unresolvable reference. Seeing this filepath will be a reliable way to discover | |||||
who would be making the reference, and therefore a good way to track down the | |||||
dependency that needs an additional linker input. Refer to the documentation | |||||
of the dependency in question to see if it requires additional linker inputs | |||||
in order to be used. | |||||
If the library that should contain the unresolved reference is a dependency | |||||
managed by ``dds``, it is possible that the library author has mistakenly | |||||
declared a symbol without providing a definition. If the definition *is* | |||||
present in the ``dds``-provided dependency library, then the failure to resolve | |||||
the reference would be a ``dds`` bug. | |||||
Issue: The definition is simply missing | |||||
--------------------------------------- | |||||
C and C++ allow for an entity to be *declared* and *defined* separately. If you | |||||
*declare* and entity but do not *define* that entity, your code will work as | |||||
long as no one attempts to refer to that entity. | |||||
Ensure that the entity that is "missing" exists. | |||||
Issue: Missing ``virtual`` method implementations | |||||
------------------------------------------------- | |||||
If the error refers to a missing ``vtable for class``, or if the error refers | |||||
to a missing definition of a ``virtual`` function, it means that one or more | |||||
``virtual`` functions are not defined. | |||||
Note that ``virtual`` functions are slightly different in this regard: It is | |||||
not required that someone actually make a call to the ``virtual`` function for | |||||
the definition to be required. The metadata that the compiler generates for | |||||
the class containing the ``virtual`` functions will implicitly form a reference | |||||
to every ``virtual`` function, so they must all be defined if someone attempts | |||||
to instantiate the class, as instantiating the class will form a reference to | |||||
that metadata. | |||||
Issue: Mismatched declarations and definitions | |||||
---------------------------------------------- | |||||
Suppose you have a header file and a corresponding source file: | |||||
.. code-block:: | |||||
:caption: ``a.hpp`` | |||||
namespace foo { | |||||
size_t string_length(const string& str); | |||||
} | |||||
.. code-block:: | |||||
:caption: ``a.cpp`` | |||||
#include "a.hpp" | |||||
using namespace foo; | |||||
size_t string_length(const string& str) { | |||||
// ... implementation goes here ... | |||||
} | |||||
The above code will link correctly, as the definition of ``foo::string_length``, | |||||
is available from ``a.cpp``, while the declaration exists in ``a.hpp``. | |||||
However, if we modify *only the declaration* to use ``string_view`` instead of | |||||
``const string&``, something different occurs:: | |||||
namespace foo { | |||||
size_t string_length(string_view str); | |||||
} | |||||
It may be tempting to say that "our declaration and definition do not match," | |||||
but that is semantically incorrect: We have declared a function | |||||
``size_t foo::string_length(string_view)``, but we have defined *and declared* | |||||
a **completely different function** ``size_t string_length(const string&)``! | |||||
The compiler will not warn about this: There is nothing semantically incorrect | |||||
about this code. | |||||
The linker, however, will not find any definition of ``foo::string_length``. | |||||
The function ``::string_length(const string&)`` isn't even in the ``foo`` | |||||
``namespace``: It was declared and defined at the global scope within | |||||
``a.cpp``. | |||||
If you are seeing an error about an unresolved reference to a function that is | |||||
declared and defined separately, and you are *sure* is being compiled, check | |||||
that the signature (and name) of the definition and declaration match | |||||
*exactly*. | |||||
.. tip:: | |||||
In essence, the error originates from relying on the | |||||
``using namespace foo`` directive to cause the definition of | |||||
``string_length`` to incidentally hit the name lookup of the prior | |||||
declaration. | |||||
In C++, using a *qualified name* at the definition site can prevent this | |||||
error from slipping through:: | |||||
#include "a.hpp" | |||||
using namespace foo; | |||||
size_t foo::string_length(const string& str) { | |||||
// ... implementation goes here ... | |||||
} | |||||
By using the qualified name ``foo::string_length`` at the definition site, | |||||
the compiler will validate that the function being defined has a prior | |||||
declaration that matches *exactly* to the signature of the definition. | |||||
Note that this *is not* the same as defining the function within a | |||||
``namespace`` block:: | |||||
#include "a.hpp" | |||||
// NOT HELPFUL! | |||||
namespace foo { | |||||
size_t string_length(const string& str) { | |||||
// ... implementation goes here ... | |||||
} | |||||
} | |||||
This will suffer the same potential mistake as defining it with an | |||||
unqualified name. | |||||
Note that within the scope of a function that has been declared within the | |||||
namespace, that namespace is currently within scope even if the definition | |||||
itself is not wrapped in a ``namespace`` block. It may be a good option to | |||||
simply remove the ``using namespace`` directive altogether. | |||||
.. note:: | |||||
This trick cannot be applied to names that are declared at the global | |||||
scope, since you cannot use the global-namespace qualifier at a | |||||
function definition (it is not valid syntax):: | |||||
// Declaration at global scope | |||||
void some_function(); | |||||
// Definition? No: Invalid syntax! | |||||
void ::some_function() { | |||||
// ... stuff ... | |||||
} | |||||
Issue: The source file containing definition is not being included in the link | |||||
------------------------------------------------------------------------------ | |||||
If the translation unit that contains the definition of an entity is not being | |||||
passed to the linker, the linker will not be able to find it! | |||||
If you are using ``dds`` correctly, and the compiled source file containing the | |||||
definition is placed as a (direct or indirect) descendent of the ``src/`` | |||||
directory, then ``dds`` will always include that source file as part of the | |||||
link for the enclosing library. | |||||
Build systems that require you to enumerate your source files explicitly will | |||||
not automatically see a source file unless it has been added to the source | |||||
list. Even build systems that allow directory-globbing (like CMake) will need | |||||
to have the globbing pattern match the path to the source file. |
Error: Package is missing remote acquisition information | |||||
######################################################## | |||||
When a package is being added/imported into the package catalog, ``dds`` | |||||
requires some information regarding how to actually *acquire* that package | |||||
when it is requested. | |||||
If such information is not provided, ``dds`` will issue an error. | |||||
.. seealso:: :ref:`catalog.adding`. |
Error: No such package in the catalog | |||||
##################################### | |||||
This error will occur when one attempts to obtain a package from the package | |||||
catalog by its specific ``name@version`` pair, but no such entry exists | |||||
in the catalog. | |||||
It is possible that the intended package *does exist* but that the spelling of | |||||
the package name or version number is incorrect. Firstly, check your spelling | |||||
and that the version number you have requested is correct. | |||||
In another case, it is possible that the package *exists somewhere*, but has | |||||
not been loaded into the local catalog. As of this writing, ``dds`` does not | |||||
automatically maintain the catalog against a central package repository, so | |||||
package entries must be loaded and imported manually. If you believe this to be | |||||
the case, refer to the section on the :doc:`/guide/catalog`, especially the | |||||
section :ref:`catalog.adding`. |
Error: The generated source distribution's identity is not correct | |||||
################################################################## | |||||
When ``dds`` attempts to automatically generate a source distribution, | |||||
especially when generating from a remote that was acquired using a catalog | |||||
listing, ``dds`` expects the generated source distribution to have a matching | |||||
package identity to match what was intended. | |||||
This can happen if a catalog listing's package version does not match the | |||||
source of the remote acquisition method. For example, if using ``git`` to clone | |||||
a repository, the ``git-ref`` used to clone must match the package version of | |||||
the listing. If the ``git-ref`` is a branch, it is possible that additional | |||||
changes were pushed into the branch that changed the package version, thus | |||||
invalidating the package. [#f1]_ | |||||
.. [#f1] | |||||
For this reason, it is **highly recommended** to use Git *tags* to | |||||
refer to remote packages *instead of branches*. |
Error: One or more tests failed | |||||
############################### | |||||
This error message is printed when a project's tests encounter a failure | |||||
condition. The exact behavior of tests is determined by a project's | |||||
``Test-Driver``. | |||||
If you see this error, it is most likely that you have an issue in the tests of | |||||
your project. | |||||
When a project is built with the ``build`` command, it is the default behavior | |||||
for ``dds`` to compile, link, and execute all tests defined for the project. | |||||
Test execution can be suppressed using the ``--no-tests`` command line option | |||||
with the ``build`` subcommand. | |||||
.. seealso:: | |||||
Refer to the page on :ref:`pkgs.apps-tests`. |
list the IDs of the packages, but none of the additional metadata about them. | list the IDs of the packages, but none of the additional metadata about them. | ||||
.. _catalog.adding: | |||||
Adding Packages to the Catalog | Adding Packages to the Catalog | ||||
****************************** | ****************************** | ||||
dds catalog add <package-id> | dds catalog add <package-id> | ||||
[--depends <requirement> [--depends <requirement> [...]]] | [--depends <requirement> [--depends <requirement> [...]]] | ||||
[--git-url <url>] | |||||
[--git-ref <ref>] | |||||
[--git-url <url> --git-ref <ref>] | |||||
[--auto-lib <Namespace>/<Name>] | [--auto-lib <Namespace>/<Name>] | ||||
The ``<package-id>`` positional arguments is the ``name@version`` package ID | The ``<package-id>`` positional arguments is the ``name@version`` package ID |
together as part of *source distribution*. | together as part of *source distribution*. | ||||
.. _pkgs.apps-tests: | |||||
Applications and Tests | Applications and Tests | ||||
********************** | ********************** | ||||
Alternatively, you can pass the name of a built-in toolchain. See below. | Alternatively, you can pass the name of a built-in toolchain. See below. | ||||
.. _toolchains.builtin: | |||||
Built-in Toolchains | Built-in Toolchains | ||||
******************* | ******************* | ||||
Link-Flags: -fsanitize=address -fPIE | Link-Flags: -fsanitize=address -fPIE | ||||
.. _toolchains.opt-ref: | |||||
Toolchain Option Reference | Toolchain Option Reference | ||||
************************** | ************************** | ||||
tut/index | tut/index | ||||
guide/index | guide/index | ||||
design | design | ||||
err/index | |||||
.. Hide the link to the error reference since we don't want it cluttering our | |||||
main toc, but we want Sphinx to not consider it "unreferenced." We'll generate | |||||
our own link | |||||
.. seealso:: | |||||
For in-depth error and troubleshooting information see: :doc:`err/index`. | |||||
Indices and tables | Indices and tables |
********************************* | ********************************* | ||||
``dds`` ships with built-in support for the `Catch2`_ C and C++ testing | ``dds`` ships with built-in support for the `Catch2`_ C and C++ testing | ||||
framework, a popular | |||||
framework. | |||||
.. _catch2: https://github.com/catchorg/Catch2 | .. _catch2: https://github.com/catchorg/Catch2 | ||||
To start, create a new directory for your project. This will be known as the | 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 | *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 | |||||
directory. The name and location of this directory is not important, but the | |||||
contents therein will be significant. | contents therein will be significant. | ||||
.. note:: | .. note:: | ||||
named ``_build``. Within this directory, you will find the generated executable | named ``_build``. Within this directory, you will find the generated executable | ||||
named ``hello-world`` (with a ``.exe`` suffix if on Windows). | named ``hello-world`` (with a ``.exe`` suffix if on Windows). | ||||
We should not be able to run this executable and see our ``Hello, world!``:: | |||||
We should now be able to run this executable and see our ``Hello, world!``:: | |||||
> ./_build/hello-world | > ./_build/hello-world | ||||
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 | More Sources | ||||
************ | ************ | ||||
#include <dds/build/builder.hpp> | #include <dds/build/builder.hpp> | ||||
#include <dds/catalog/catalog.hpp> | #include <dds/catalog/catalog.hpp> | ||||
#include <dds/catalog/get.hpp> | #include <dds/catalog/get.hpp> | ||||
#include <dds/error/errors.hpp> | |||||
#include <dds/repo/repo.hpp> | #include <dds/repo/repo.hpp> | ||||
#include <dds/source/dist.hpp> | #include <dds/source/dist.hpp> | ||||
#include <dds/toolchain/from_dds.hpp> | #include <dds/toolchain/from_dds.hpp> | ||||
auto default_tc = tc_path.substr(1); | auto default_tc = tc_path.substr(1); | ||||
auto tc = dds::toolchain::get_builtin(default_tc); | auto tc = dds::toolchain::get_builtin(default_tc); | ||||
if (!tc.has_value()) { | if (!tc.has_value()) { | ||||
throw std::runtime_error( | |||||
fmt::format("Invalid default toolchain name '{}'", default_tc)); | |||||
dds::throw_user_error< | |||||
dds::errc::invalid_builtin_toolchain>("Invalid built-in toolchain name '{}'", | |||||
default_tc); | |||||
} | } | ||||
return std::move(*tc); | return std::move(*tc); | ||||
} else { | } else { | ||||
auto id = dds::package_id::parse(req); | auto id = dds::package_id::parse(req); | ||||
auto info = cat.get(id); | auto info = cat.get(id); | ||||
if (!info) { | if (!info) { | ||||
throw std::runtime_error( | |||||
fmt::format("No package in the catalog matched the ID '{}'", req)); | |||||
dds::throw_user_error<dds::errc::no_such_catalog_package>( | |||||
"No package in the catalog matched the ID '{}'", req); | |||||
} | } | ||||
auto tsd = dds::get_package_sdist(*info); | auto tsd = dds::get_package_sdist(*info); | ||||
auto out_path = out.Get(); | auto out_path = out.Get(); | ||||
if (git_url) { | if (git_url) { | ||||
if (!git_ref) { | if (!git_ref) { | ||||
throw std::runtime_error( | |||||
"`--git-ref` must be specified when using `--git-url`"); | |||||
dds::throw_user_error<dds::errc::git_url_ref_mutual_req>(); | |||||
} | } | ||||
auto git = dds::git_remote_listing{git_url.Get(), git_ref.Get(), std::nullopt}; | auto git = dds::git_remote_listing{git_url.Get(), git_ref.Get(), std::nullopt}; | ||||
if (auto_lib) { | if (auto_lib) { | ||||
git.auto_lib = lm::split_usage_string(auto_lib.Get()); | git.auto_lib = lm::split_usage_string(auto_lib.Get()); | ||||
} | } | ||||
info.remote = std::move(git); | info.remote = std::move(git); | ||||
} else if (git_ref) { | |||||
dds::throw_user_error<dds::errc::git_url_ref_mutual_req>(); | |||||
} | } | ||||
cat_path.open().store(info); | cat_path.open().store(info); | ||||
} catch (const dds::user_cancelled&) { | } catch (const dds::user_cancelled&) { | ||||
spdlog::critical("Operation cancelled by user"); | spdlog::critical("Operation cancelled by user"); | ||||
return 2; | return 2; | ||||
} catch (const dds::error_base& e) { | |||||
spdlog::error("{}", e.what()); | |||||
spdlog::error("{}", e.explanation()); | |||||
spdlog::error("Refer: {}", e.error_reference()); | |||||
return 1; | |||||
} catch (const std::exception& e) { | } catch (const std::exception& e) { | ||||
spdlog::critical(e.what()); | spdlog::critical(e.what()); | ||||
return 2; | return 2; |
#include <dds/build/plan/full.hpp> | #include <dds/build/plan/full.hpp> | ||||
#include <dds/catch2_embedded.hpp> | #include <dds/catch2_embedded.hpp> | ||||
#include <dds/compdb.hpp> | #include <dds/compdb.hpp> | ||||
#include <dds/error/errors.hpp> | |||||
#include <dds/usage_reqs.hpp> | #include <dds/usage_reqs.hpp> | ||||
#include <dds/util/time.hpp> | #include <dds/util/time.hpp> | ||||
failures.output); | failures.output); | ||||
} | } | ||||
if (!test_failures.empty()) { | if (!test_failures.empty()) { | ||||
throw compile_failure("Test failures during the build!"); | |||||
throw_user_error<errc::test_failure>(); | |||||
} | } | ||||
if (params.emit_lmi) { | if (params.emit_lmi) { |
#include "./archive.hpp" | #include "./archive.hpp" | ||||
#include <dds/error/errors.hpp> | |||||
#include <dds/proc.hpp> | #include <dds/proc.hpp> | ||||
#include <dds/util/time.hpp> | #include <dds/util/time.hpp> | ||||
// Check, log, and throw | // Check, log, and throw | ||||
if (!ar_res.okay()) { | if (!ar_res.okay()) { | ||||
spdlog::error("Creating static library archive failed: {}", out_relpath); | |||||
spdlog::error("Creating static library archive [{}] failed for '{}'", out_relpath, _name); | |||||
spdlog::error("Subcommand FAILED: {}\n{}", quote_command(ar_cmd), ar_res.output); | spdlog::error("Subcommand FAILED: {}\n{}", quote_command(ar_cmd), ar_res.output); | ||||
throw std::runtime_error( | |||||
fmt::format("Creating archive [{}] failed for '{}'", out_relpath, _name)); | |||||
throw_external_error< | |||||
errc::archive_failure>("Creating static library archive [{}] failed for '{}'", | |||||
out_relpath, | |||||
_name); | |||||
} | } | ||||
} | } |
#include "./compile_exec.hpp" | #include "./compile_exec.hpp" | ||||
#include <dds/build/file_deps.hpp> | #include <dds/build/file_deps.hpp> | ||||
#include <dds/error/errors.hpp> | |||||
#include <dds/proc.hpp> | #include <dds/proc.hpp> | ||||
#include <dds/util/string.hpp> | #include <dds/util/string.hpp> | ||||
#include <dds/util/time.hpp> | #include <dds/util/time.hpp> | ||||
if (compile_signal) { | if (compile_signal) { | ||||
spdlog::error("Process exited via signal {}", compile_signal); | spdlog::error("Process exited via signal {}", compile_signal); | ||||
} | } | ||||
throw compile_failure(fmt::format("Compilation failed for {}", source_path.string())); | |||||
throw_user_error<errc::compile_failure>("Compilation failed [{}]", source_path.string()); | |||||
} | } | ||||
// Print any compiler output, sans whitespace | // Print any compiler output, sans whitespace |
#include "./exe.hpp" | #include "./exe.hpp" | ||||
#include <dds/build/plan/library.hpp> | #include <dds/build/plan/library.hpp> | ||||
#include <dds/error/errors.hpp> | |||||
#include <dds/proc.hpp> | #include <dds/proc.hpp> | ||||
#include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
#include <dds/util/time.hpp> | #include <dds/util/time.hpp> | ||||
// Check and throw if errant | // Check and throw if errant | ||||
if (!proc_res.okay()) { | if (!proc_res.okay()) { | ||||
throw compile_failure( | |||||
fmt::format("Failed to link test executable '{}'. Link command [{}] returned {}:\n{}", | |||||
spec.output.string(), | |||||
quote_command(link_command), | |||||
proc_res.retc, | |||||
proc_res.output)); | |||||
throw_external_error< | |||||
errc::link_failure>("Failed to link executable [{}]. Link command was [{}]", | |||||
spec.output.string(), | |||||
quote_command(link_command), | |||||
proc_res.retc, | |||||
proc_res.output); | |||||
} | } | ||||
} | } | ||||
#include <dds/build/iter_compilations.hpp> | #include <dds/build/iter_compilations.hpp> | ||||
#include <dds/build/plan/compile_exec.hpp> | #include <dds/build/plan/compile_exec.hpp> | ||||
#include <dds/error/errors.hpp> | |||||
#include <range/v3/view/concat.hpp> | #include <range/v3/view/concat.hpp> | ||||
#include <range/v3/view/filter.hpp> | #include <range/v3/view/filter.hpp> | ||||
void build_plan::compile_all(const build_env& env, int njobs) const { | void build_plan::compile_all(const build_env& env, int njobs) const { | ||||
auto okay = dds::compile_all(iter_compilations(*this), env, njobs); | auto okay = dds::compile_all(iter_compilations(*this), env, njobs); | ||||
if (!okay) { | if (!okay) { | ||||
throw std::runtime_error("Compilation failed."); | |||||
throw_user_error<errc::compile_failure>(); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
}); | }); | ||||
if (!okay) { | if (!okay) { | ||||
throw std::runtime_error("Error creating static library archives"); | |||||
throw_external_error<errc::archive_failure>(); | |||||
} | } | ||||
} | } | ||||
exe.get().link(env, lib); | exe.get().link(env, lib); | ||||
}); | }); | ||||
if (!okay) { | if (!okay) { | ||||
throw std::runtime_error("Failure to link executables"); | |||||
throw_user_error<errc::link_failure>(); | |||||
} | } | ||||
} | } | ||||
#include "./catalog.hpp" | #include "./catalog.hpp" | ||||
#include <dds/error/errors.hpp> | |||||
#include <dds/solve/solve.hpp> | #include <dds/solve/solve.hpp> | ||||
#include <neo/sqlite3/exec.hpp> | #include <neo/sqlite3/exec.hpp> | ||||
auto meta = nlohmann::json::parse(meta_json); | auto meta = nlohmann::json::parse(meta_json); | ||||
if (!meta.is_object()) { | if (!meta.is_object()) { | ||||
throw std::runtime_error("Corrupted repository database file."); | |||||
throw_external_error<errc::corrupted_catalog_db>(); | |||||
} | } | ||||
auto version_ = meta["version"]; | auto version_ = meta["version"]; | ||||
if (!version_.is_number_integer()) { | if (!version_.is_number_integer()) { | ||||
throw std::runtime_error("Corrupted repository database file [bad dds_meta.version]"); | |||||
throw_external_error<errc::corrupted_catalog_db>( | |||||
"The catalog database metadata is invalid [bad dds_meta.version]"); | |||||
} | } | ||||
constexpr int current_database_version = 1; | |||||
int version = version_; | int version = version_; | ||||
if (version > current_database_version) { | |||||
throw_external_error<errc::catalog_too_new>(); | |||||
} | |||||
if (version < 1) { | if (version < 1) { | ||||
migrate_repodb_1(db); | migrate_repodb_1(db); | ||||
} | } | ||||
ensure_migrated(db); | ensure_migrated(db); | ||||
} catch (const sqlite3::sqlite3_error& e) { | } catch (const sqlite3::sqlite3_error& e) { | ||||
spdlog::critical( | spdlog::critical( | ||||
"Failed to load the repository databsae. It appears to be invalid/corrupted. The " | |||||
"Failed to load the repository database. It appears to be invalid/corrupted. The " | |||||
"exception message is: {}", | "exception message is: {}", | ||||
e.what()); | e.what()); | ||||
throw; | |||||
throw_external_error<errc::corrupted_catalog_db>(); | |||||
} | } | ||||
return catalog(std::move(db)); | return catalog(std::move(db)); | ||||
} | } | ||||
)"_sql); | )"_sql); | ||||
for (const auto& dep : pkg.deps) { | for (const auto& dep : pkg.deps) { | ||||
new_dep_st.reset(); | new_dep_st.reset(); | ||||
if (dep.versions.num_intervals() != 1) { | |||||
throw std::runtime_error( | |||||
"Package dependency may only contain a single version interval"); | |||||
} | |||||
assert(dep.versions.num_intervals() == 1); | |||||
auto iv_1 = *dep.versions.iter_intervals().begin(); | auto iv_1 = *dep.versions.iter_intervals().begin(); | ||||
sqlite3::exec(new_dep_st, | sqlite3::exec(new_dep_st, | ||||
std::forward_as_tuple(db_pkg_id, | std::forward_as_tuple(db_pkg_id, | ||||
void check_json(bool b, std::string_view what) { | void check_json(bool b, std::string_view what) { | ||||
if (!b) { | if (!b) { | ||||
throw std::runtime_error("Unable to read repository JSON: " + std::string(what)); | |||||
throw_user_error<errc::invalid_catalog_json>("Catalog JSON is invalid: {}", what); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
info.remote = git_remote_listing{url, ref, autolib}; | info.remote = git_remote_listing{url, ref, autolib}; | ||||
} else { | } else { | ||||
throw std::runtime_error( | |||||
fmt::format("No remote info for /packages/{}/{}", pkg_name, version_)); | |||||
throw_user_error<errc::no_catalog_remote_info>("No remote info for /packages/{}/{}", | |||||
pkg_name, | |||||
version_); | |||||
} | } | ||||
store(info); | store(info); |
#include "./get.hpp" | #include "./get.hpp" | ||||
#include <dds/catalog/catalog.hpp> | #include <dds/catalog/catalog.hpp> | ||||
#include <dds/error/errors.hpp> | |||||
#include <dds/proc.hpp> | #include <dds/proc.hpp> | ||||
#include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||
tmpdir.path().generic_string()}; | tmpdir.path().generic_string()}; | ||||
auto git_res = run_proc(command); | auto git_res = run_proc(command); | ||||
if (!git_res.okay()) { | if (!git_res.okay()) { | ||||
throw std::runtime_error( | |||||
fmt::format("Git clone operation failed [Git command: {}] [Exitted {}]:\n{}", | |||||
quote_command(command), | |||||
git_res.retc, | |||||
git_res.output)); | |||||
throw_external_error<errc::git_clone_failure>( | |||||
"Git clone operation failed [Git command: {}] [Exitted {}]:\n{}", | |||||
quote_command(command), | |||||
git_res.retc, | |||||
git_res.output); | |||||
} | } | ||||
spdlog::info("Create sdist from clone ..."); | spdlog::info("Create sdist from clone ..."); | ||||
if (git.auto_lib.has_value()) { | if (git.auto_lib.has_value()) { | ||||
temporary_sdist dds::get_package_sdist(const package_info& pkg) { | temporary_sdist dds::get_package_sdist(const package_info& pkg) { | ||||
auto tsd = std::visit([&](auto&& remote) { return do_pull_sdist(pkg, remote); }, pkg.remote); | auto tsd = std::visit([&](auto&& remote) { return do_pull_sdist(pkg, remote); }, pkg.remote); | ||||
if (!(tsd.sdist.manifest.pkg_id == pkg.ident)) { | if (!(tsd.sdist.manifest.pkg_id == pkg.ident)) { | ||||
throw std::runtime_error(fmt::format( | |||||
"The package name@version in the generated sdist does not match the name listed in " | |||||
"the remote listing file (expected '{}', but got '{}')", | |||||
throw_external_error<errc::sdist_ident_mismatch>( | |||||
"The package name@version in the generated source distribution does not match the name " | |||||
"listed in the remote listing file (expected '{}', but got '{}')", | |||||
pkg.ident.to_string(), | pkg.ident.to_string(), | ||||
tsd.sdist.manifest.pkg_id.to_string())); | |||||
tsd.sdist.manifest.pkg_id.to_string()); | |||||
} | } | ||||
return tsd; | return tsd; | ||||
} | } |
#include "./database.hpp" | #include "./database.hpp" | ||||
#include <dds/error/errors.hpp> | |||||
#include <neo/sqlite3/exec.hpp> | #include <neo/sqlite3/exec.hpp> | ||||
#include <neo/sqlite3/iter_tuples.hpp> | #include <neo/sqlite3/iter_tuples.hpp> | ||||
#include <neo/sqlite3/single.hpp> | #include <neo/sqlite3/single.hpp> | ||||
auto meta = nlohmann::json::parse(meta_json); | auto meta = nlohmann::json::parse(meta_json); | ||||
if (!meta.is_object()) { | if (!meta.is_object()) { | ||||
throw std::runtime_error("Correupted database file."); | |||||
throw_external_error<errc::corrupted_build_db>(); | |||||
} | } | ||||
auto version_ = meta["version"]; | auto version_ = meta["version"]; | ||||
if (!version_.is_number_integer()) { | if (!version_.is_number_integer()) { | ||||
throw std::runtime_error("Corrupted database file [bad dds_meta.version]"); | |||||
throw_external_error<errc::corrupted_build_db>( | |||||
"The build database file is corrupted [bad dds_meta.version]"); | |||||
} | } | ||||
int version = version_; | int version = version_; | ||||
if (version < 1) { | if (version < 1) { |
#include "./deps.hpp" | #include "./deps.hpp" | ||||
#include <dds/error/errors.hpp> | |||||
#include <dds/repo/repo.hpp> | #include <dds/repo/repo.hpp> | ||||
#include <dds/source/dist.hpp> | #include <dds/source/dist.hpp> | ||||
#include <dds/usage_reqs.hpp> | #include <dds/usage_reqs.hpp> | ||||
auto rng = semver::range::parse_restricted(version_str); | auto rng = semver::range::parse_restricted(version_str); | ||||
return dependency{std::string(name), {rng.low(), rng.high()}}; | return dependency{std::string(name), {rng.low(), rng.high()}}; | ||||
} catch (const semver::invalid_range&) { | } catch (const semver::invalid_range&) { | ||||
throw std::runtime_error(fmt::format( | |||||
"Invalid version range string '{}' in dependency declaration '{}' (Should be a " | |||||
"semver range string. See https://semver.org/ for info)", | |||||
version_str, | |||||
str)); | |||||
throw_user_error<errc::invalid_version_range_string>( | |||||
"Invalid version range string '{}' in dependency declaration '{}'", version_str, str); | |||||
} | } | ||||
} | } | ||||
#include "./errors.hpp" | |||||
#include <cassert> | |||||
#include <stdexcept> | |||||
using namespace dds; | |||||
namespace { | |||||
std::string error_url_prefix = "http://localhost:3000/err/"; | |||||
std::string error_url_suffix(dds::errc ec) noexcept { | |||||
switch (ec) { | |||||
case errc::invalid_builtin_toolchain: | |||||
return "invalid-builtin-toolchain.html"; | |||||
case errc::no_such_catalog_package: | |||||
return "no-such-catalog-package.html"; | |||||
case errc::git_url_ref_mutual_req: | |||||
return "git-url-ref-mutual-req.html"; | |||||
case errc::test_failure: | |||||
return "test-failure.html"; | |||||
case errc::compile_failure: | |||||
return "compile-failure.html"; | |||||
case errc::archive_failure: | |||||
return "archive-failure.html"; | |||||
case errc::link_failure: | |||||
return "link-failure.html"; | |||||
case errc::catalog_too_new: | |||||
return "catalog-too-new.html"; | |||||
case errc::corrupted_catalog_db: | |||||
return "corrupted-catalog-db.html"; | |||||
case errc::invalid_catalog_json: | |||||
return "invalid-catalog-json.html"; | |||||
case errc::no_catalog_remote_info: | |||||
return "no-catalog-remote-info.html"; | |||||
case errc::git_clone_failure: | |||||
return "git-clone-failure.html"; | |||||
case errc::sdist_ident_mismatch: | |||||
return "sdist-ident-mismatch.html"; | |||||
case errc::corrupted_build_db: | |||||
return "corrupted-build-db.html"; | |||||
case errc::invalid_version_range_string: | |||||
return "invalid-version-string.html#range"; | |||||
case errc::invalid_version_string: | |||||
return "invalid-version-string.html"; | |||||
case errc::none: | |||||
break; | |||||
} | |||||
assert(false && "Unreachable code path generating error explanation URL"); | |||||
std::terminate(); | |||||
} | |||||
} // namespace | |||||
std::string dds::error_reference_of(dds::errc ec) noexcept { | |||||
return error_url_prefix + error_url_suffix(ec); | |||||
} | |||||
std::string_view dds::explanation_of(dds::errc ec) noexcept { | |||||
switch (ec) { | |||||
case errc::invalid_builtin_toolchain: | |||||
return R"( | |||||
If you start your toolchain name (The `-t` or `--toolchain` argument) | |||||
with a leading colon, dds will interpret it as a reference to a built-in | |||||
toolchain. (Toolchain file paths cannot begin with a leading colon). | |||||
These toolchain names are encoded into the dds executable and cannot be | |||||
modified. | |||||
)"; | |||||
case errc::no_such_catalog_package: | |||||
return R"( | |||||
The installation of a package was requested, but the given package ID was not | |||||
able to be found in the package catalog. Check the spelling and version number. | |||||
)"; | |||||
case errc::git_url_ref_mutual_req: | |||||
return R"( | |||||
Creating a Git-based catalog entry requires both a URL to clone from and a Git | |||||
reference (tag, branch, commit) to clone. | |||||
)"; | |||||
case errc::test_failure: | |||||
return R"( | |||||
One or more of the project's tests failed. The failing tests are listed above, | |||||
along with their exit code and output. | |||||
)"; | |||||
case errc::compile_failure: | |||||
return R"( | |||||
Source compilation failed. Refer to the compiler output. | |||||
)"; | |||||
case errc::archive_failure: | |||||
return R"( | |||||
Creating a static library archive failed, which prevents the associated library | |||||
from being used as this archive is the input to the linker for downstream | |||||
build targets. | |||||
It is unlikely that regular user action can cause static library archiving to | |||||
fail. Refer to the output of the archiving tool. | |||||
)"; | |||||
case errc::link_failure: | |||||
return R"( | |||||
Linking a runtime binary file failed. There are a variety of possible causes | |||||
for this error. Refer to the documentation for more information. | |||||
)"; | |||||
case errc::catalog_too_new: | |||||
return R"( | |||||
The catalog database file contains a schema that will automatically be upgraded | |||||
by dds when it is opened/modified. It appears that the given catalog database | |||||
has had a migration beyond a version that we support. Has the catalog been | |||||
modified by a newer version of dds? | |||||
)"; | |||||
case errc::corrupted_catalog_db: | |||||
return R"( | |||||
The catalog database schema doesn't match what dds expects. This indicates that | |||||
the database file has been modified in a way that dds cannot automatically fix | |||||
and handle. | |||||
)"; | |||||
case errc::invalid_catalog_json: | |||||
return R"( | |||||
The catalog JSON that was provided does not match the format that was expected. | |||||
Check the JSON schema and try your submission again. | |||||
)"; | |||||
case errc::no_catalog_remote_info: | |||||
return R"( | |||||
The catalog entry requires information regarding the remote acquisition method. | |||||
Refer to the documentation for details. | |||||
)"; | |||||
case errc::git_clone_failure: | |||||
return R"( | |||||
dds tried to clone a repository using Git, but the clone operation failed. | |||||
There are a variety of possible causes. It is best to check the output from | |||||
Git in diagnosing this failure. | |||||
)"; | |||||
case errc::sdist_ident_mismatch: | |||||
return R"( | |||||
We tried to automatically generate a source distribution from a package, but | |||||
the name and/or version of the package that was generated does not match what | |||||
we expected of it. | |||||
)"; | |||||
case errc::corrupted_build_db: | |||||
return R"( | |||||
The local build database file is corrupted. The file is stored in the build | |||||
directory as `.dds.db', and is safe to delete to clear the bad data. This is | |||||
not a likely error, and if you receive this message frequently, please file a | |||||
bug report. | |||||
)"; | |||||
case errc::invalid_version_range_string: | |||||
return R"( | |||||
Parsing of a version range string failed. Refer to the documentation for more | |||||
information. | |||||
)"; | |||||
case errc::invalid_version_string: | |||||
return R"( | |||||
`dds` expects all version numbers to conform to the Semantic Versioning | |||||
specification. Refer to the documentation and https://semver.org/ for more | |||||
information. | |||||
)"; | |||||
case errc::none: | |||||
break; | |||||
} | |||||
assert(false && "Unexpected execution path during error explanation. This is a DDS bug"); | |||||
std::terminate(); | |||||
} | |||||
std::string_view dds::default_error_string(dds::errc ec) noexcept { | |||||
switch (ec) { | |||||
case errc::invalid_builtin_toolchain: | |||||
return "The built-in toolchain name is invalid"; | |||||
case errc::no_such_catalog_package: | |||||
return "The catalog has no entry for the given package ID"; | |||||
case errc::git_url_ref_mutual_req: | |||||
return "Git requires both a URL and a ref to clone"; | |||||
case errc::test_failure: | |||||
return "One or more tests failed"; | |||||
case errc::compile_failure: | |||||
return "Source compilation failed."; | |||||
case errc::archive_failure: | |||||
return "Creating a static library archive failed"; | |||||
case errc::link_failure: | |||||
return "Linking a runtime binary (executable/shared library/DLL) failed"; | |||||
case errc::catalog_too_new: | |||||
return "The catalog appears to be from a newer version of dds."; | |||||
case errc::corrupted_catalog_db: | |||||
return "The catalog database appears to be corrupted or invalid"; | |||||
case errc::invalid_catalog_json: | |||||
return "The given catalog JSON data is not valid"; | |||||
case errc::no_catalog_remote_info: | |||||
return "The catalog JSON is missing remote acquisition information for one or more\n" | |||||
"packages"; | |||||
case errc::git_clone_failure: | |||||
return "A git-clone operation failed."; | |||||
case errc::sdist_ident_mismatch: | |||||
return "The package version of a generated source distribution did not match the version\n" | |||||
"that was expected of it"; | |||||
case errc::corrupted_build_db: | |||||
return "The build database file is corrupted"; | |||||
case errc::invalid_version_range_string: | |||||
return "Attempted to parse an invalid version range string. <- (Seeing this text is a " | |||||
"`dds` bug. Please report it.)"; | |||||
case errc::invalid_version_string: | |||||
return "Attempted to parse an invalid version string. <- (Seeing this text is a `dds` bug. " | |||||
"Please report it.)"; | |||||
case errc::none: | |||||
break; | |||||
} | |||||
assert(false && "Unexpected execution path during error message creation. This is a DDS bug"); | |||||
std::terminate(); | |||||
} |
#pragma once | |||||
#include <spdlog/fmt/fmt.h> | |||||
#include <stdexcept> | |||||
#include <string_view> | |||||
namespace dds { | |||||
enum class errc { | |||||
none = 0, | |||||
invalid_builtin_toolchain, | |||||
no_such_catalog_package, | |||||
git_url_ref_mutual_req, | |||||
test_failure, | |||||
compile_failure, | |||||
archive_failure, | |||||
link_failure, | |||||
catalog_too_new, | |||||
corrupted_catalog_db, | |||||
invalid_catalog_json, | |||||
no_catalog_remote_info, | |||||
git_clone_failure, | |||||
sdist_ident_mismatch, | |||||
corrupted_build_db, | |||||
invalid_version_range_string, | |||||
invalid_version_string, | |||||
}; | |||||
std::string error_reference_of(errc) noexcept; | |||||
std::string_view explanation_of(errc) noexcept; | |||||
std::string_view default_error_string(errc) noexcept; | |||||
struct exception_base : std::runtime_error { | |||||
using runtime_error::runtime_error; | |||||
}; | |||||
struct error_base : exception_base { | |||||
using exception_base::exception_base; | |||||
virtual errc get_errc() const noexcept = 0; | |||||
std::string error_reference() const noexcept { return error_reference_of(get_errc()); } | |||||
std::string_view explanation() const noexcept { return explanation_of(get_errc()); } | |||||
}; | |||||
struct external_error_base : error_base { | |||||
using error_base::error_base; | |||||
}; | |||||
struct user_error_base : error_base { | |||||
using error_base::error_base; | |||||
}; | |||||
template <errc ErrorCode> | |||||
struct user_error : user_error_base { | |||||
using user_error_base::user_error_base; | |||||
errc get_errc() const noexcept override { return ErrorCode; } | |||||
}; | |||||
template <errc ErrorCode> | |||||
struct external_error : external_error_base { | |||||
using external_error_base::external_error_base; | |||||
errc get_errc() const noexcept override { return ErrorCode; } | |||||
}; | |||||
using error_invalid_default_toolchain = user_error<errc::invalid_builtin_toolchain>; | |||||
template <errc ErrorCode, typename... Args> | |||||
[[noreturn]] void throw_user_error(std::string_view fmt_str, Args&&... args) { | |||||
throw user_error<ErrorCode>(fmt::format(fmt_str, std::forward<Args>(args)...)); | |||||
} | |||||
template <errc ErrorCode> | |||||
[[noreturn]] void throw_user_error() { | |||||
throw user_error<ErrorCode>(std::string(default_error_string(ErrorCode))); | |||||
} | |||||
template <errc ErrorCode, typename... Args> | |||||
[[noreturn]] void throw_external_error(std::string_view fmt_str, Args&&... args) { | |||||
throw external_error<ErrorCode>(fmt::format(fmt_str, std::forward<Args>(args)...)); | |||||
} | |||||
template <errc ErrorCode> | |||||
[[noreturn]] void throw_external_error() { | |||||
throw external_error<ErrorCode>(std::string(default_error_string(ErrorCode))); | |||||
} | |||||
} // namespace dds |