Explorar el Código

Merge branch 'feature/better-errors' into develop

default_compile_flags
vector-of-bool hace 5 años
padre
commit
a84c382ed9
Se han modificado 34 ficheros con 1105 adiciones y 60 borrados
  1. +18
    -0
      docs/err/archive-failure.rst
  2. +21
    -0
      docs/err/catalog-too-new.rst
  3. +7
    -0
      docs/err/compile-failure.rst
  4. +9
    -0
      docs/err/corrupted-build-db.rst
  5. +17
    -0
      docs/err/corrupted-catalog-db.rst
  6. +22
    -0
      docs/err/git-clone-failure.rst
  7. +15
    -0
      docs/err/git-url-ref-mutual-req.rst
  8. +18
    -0
      docs/err/index.rst
  9. +17
    -0
      docs/err/invalid-builtin-toolchain.rst
  10. +9
    -0
      docs/err/invalid-catalog-json.rst
  11. +22
    -0
      docs/err/invalid-version-string.rst
  12. +480
    -0
      docs/err/link-failure.rst
  13. +10
    -0
      docs/err/no-catalog-remote-info.rst
  14. +17
    -0
      docs/err/no-such-catalog-package.rst
  15. +18
    -0
      docs/err/sdist-ident-mismatch.rst
  16. +17
    -0
      docs/err/test-failure.rst
  17. +3
    -2
      docs/guide/catalog.rst
  18. +2
    -0
      docs/guide/packages.rst
  19. +4
    -0
      docs/guide/toolchains.rst
  20. +8
    -0
      docs/index.rst
  21. +1
    -1
      docs/tut/hello-test.rst
  22. +2
    -10
      docs/tut/hello-world.rst
  23. +14
    -6
      src/dds.main.cpp
  24. +2
    -1
      src/dds/build/builder.cpp
  25. +6
    -3
      src/dds/build/plan/archive.cpp
  26. +2
    -1
      src/dds/build/plan/compile_exec.cpp
  27. +7
    -6
      src/dds/build/plan/exe.cpp
  28. +4
    -3
      src/dds/build/plan/full.cpp
  29. +18
    -11
      src/dds/catalog/catalog.cpp
  30. +10
    -9
      src/dds/catalog/get.cpp
  31. +5
    -2
      src/dds/db/database.cpp
  32. +3
    -5
      src/dds/deps.cpp
  33. +206
    -0
      src/dds/error/errors.cpp
  34. +91
    -0
      src/dds/error/errors.hpp

+ 18
- 0
docs/err/archive-failure.rst Ver fichero

@@ -0,0 +1,18 @@
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.

+ 21
- 0
docs/err/catalog-too-new.rst Ver fichero

@@ -0,0 +1,21 @@
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.

+ 7
- 0
docs/err/compile-failure.rst Ver fichero

@@ -0,0 +1,7 @@
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.

+ 9
- 0
docs/err/corrupted-build-db.rst Ver fichero

@@ -0,0 +1,9 @@
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.

+ 17
- 0
docs/err/corrupted-catalog-db.rst Ver fichero

@@ -0,0 +1,17 @@
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.

+ 22
- 0
docs/err/git-clone-failure.rst Ver fichero

@@ -0,0 +1,22 @@
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.

+ 15
- 0
docs/err/git-url-ref-mutual-req.rst Ver fichero

@@ -0,0 +1,15 @@
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`.

+ 18
- 0
docs/err/index.rst Ver fichero

@@ -0,0 +1,18 @@
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:

*

+ 17
- 0
docs/err/invalid-builtin-toolchain.rst Ver fichero

@@ -0,0 +1,17 @@
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`.

+ 9
- 0
docs/err/invalid-catalog-json.rst Ver fichero

@@ -0,0 +1,9 @@
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`

+ 22
- 0
docs/err/invalid-version-string.rst Ver fichero

@@ -0,0 +1,22 @@
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.

+ 480
- 0
docs/err/link-failure.rst Ver fichero

@@ -0,0 +1,480 @@
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.

+ 10
- 0
docs/err/no-catalog-remote-info.rst Ver fichero

@@ -0,0 +1,10 @@
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`.

+ 17
- 0
docs/err/no-such-catalog-package.rst Ver fichero

@@ -0,0 +1,17 @@
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`.

+ 18
- 0
docs/err/sdist-ident-mismatch.rst Ver fichero

@@ -0,0 +1,18 @@
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*.

+ 17
- 0
docs/err/test-failure.rst Ver fichero

@@ -0,0 +1,17 @@
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`.

+ 3
- 2
docs/guide/catalog.rst Ver fichero

@@ -14,6 +14,8 @@ 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.


.. _catalog.adding:

Adding Packages to the Catalog
******************************

@@ -30,8 +32,7 @@ command:

dds catalog add <package-id>
[--depends <requirement> [--depends <requirement> [...]]]
[--git-url <url>]
[--git-ref <ref>]
[--git-url <url> --git-ref <ref>]
[--auto-lib <Namespace>/<Name>]

The ``<package-id>`` positional arguments is the ``name@version`` package ID

+ 2
- 0
docs/guide/packages.rst Ver fichero

@@ -58,6 +58,8 @@ If a file's extension is not listed in the table above, ``dds`` will ignore it.
together as part of *source distribution*.


.. _pkgs.apps-tests:

Applications and Tests
**********************


+ 4
- 0
docs/guide/toolchains.rst Ver fichero

@@ -42,6 +42,8 @@ toolchain file for the ``--toolchain`` (or ``-t``) option on the command line::
Alternatively, you can pass the name of a built-in toolchain. See below.


.. _toolchains.builtin:

Built-in Toolchains
*******************

@@ -135,6 +137,8 @@ Flags for linking executables can be specified with ``Link-Flags``:
Link-Flags: -fsanitize=address -fPIE


.. _toolchains.opt-ref:

Toolchain Option Reference
**************************


+ 8
- 0
docs/index.rst Ver fichero

@@ -17,6 +17,14 @@ the :doc:`tut/index` page.
tut/index
guide/index
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

+ 1
- 1
docs/tut/hello-test.rst Ver fichero

@@ -52,7 +52,7 @@ A ``Test-Driver``: Using *Catch2*
*********************************

``dds`` ships with built-in support for the `Catch2`_ C and C++ testing
framework, a popular
framework.

.. _catch2: https://github.com/catchorg/Catch2


+ 2
- 10
docs/tut/hello-world.rst Ver fichero

@@ -9,7 +9,7 @@ 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
directory. The name and location of this directory is not important, but the
contents therein will be significant.

.. note::
@@ -103,19 +103,11 @@ 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!``::
We should now 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
************


+ 14
- 6
src/dds.main.cpp Ver fichero

@@ -1,6 +1,7 @@
#include <dds/build/builder.hpp>
#include <dds/catalog/catalog.hpp>
#include <dds/catalog/get.hpp>
#include <dds/error/errors.hpp>
#include <dds/repo/repo.hpp>
#include <dds/source/dist.hpp>
#include <dds/toolchain/from_dds.hpp>
@@ -38,8 +39,9 @@ struct toolchain_flag : string_flag {
auto default_tc = tc_path.substr(1);
auto tc = dds::toolchain::get_builtin(default_tc);
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);
} else {
@@ -190,8 +192,8 @@ struct cli_catalog {
auto id = dds::package_id::parse(req);
auto info = cat.get(id);
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 out_path = out.Get();
@@ -246,14 +248,15 @@ struct cli_catalog {

if (git_url) {
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};
if (auto_lib) {
git.auto_lib = lm::split_usage_string(auto_lib.Get());
}
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);
@@ -719,6 +722,11 @@ int main(int argc, char** argv) {
} catch (const dds::user_cancelled&) {
spdlog::critical("Operation cancelled by user");
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) {
spdlog::critical(e.what());
return 2;

+ 2
- 1
src/dds/build/builder.cpp Ver fichero

@@ -4,6 +4,7 @@
#include <dds/build/plan/full.hpp>
#include <dds/catch2_embedded.hpp>
#include <dds/compdb.hpp>
#include <dds/error/errors.hpp>
#include <dds/usage_reqs.hpp>
#include <dds/util/time.hpp>

@@ -234,7 +235,7 @@ void builder::build(const build_params& params) const {
failures.output);
}
if (!test_failures.empty()) {
throw compile_failure("Test failures during the build!");
throw_user_error<errc::test_failure>();
}

if (params.emit_lmi) {

+ 6
- 3
src/dds/build/plan/archive.cpp Ver fichero

@@ -1,5 +1,6 @@
#include "./archive.hpp"

#include <dds/error/errors.hpp>
#include <dds/proc.hpp>
#include <dds/util/time.hpp>

@@ -45,9 +46,11 @@ void create_archive_plan::archive(const build_env& env) const {

// Check, log, and throw
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);
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);
}
}

+ 2
- 1
src/dds/build/plan/compile_exec.cpp Ver fichero

@@ -1,6 +1,7 @@
#include "./compile_exec.hpp"

#include <dds/build/file_deps.hpp>
#include <dds/error/errors.hpp>
#include <dds/proc.hpp>
#include <dds/util/string.hpp>
#include <dds/util/time.hpp>
@@ -180,7 +181,7 @@ do_compile(const compile_file_full& cf, build_env_ref env, compile_counter& coun
if (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

+ 7
- 6
src/dds/build/plan/exe.cpp Ver fichero

@@ -1,6 +1,7 @@
#include "./exe.hpp"

#include <dds/build/plan/library.hpp>
#include <dds/error/errors.hpp>
#include <dds/proc.hpp>
#include <dds/util/algo.hpp>
#include <dds/util/time.hpp>
@@ -54,12 +55,12 @@ void link_executable_plan::link(build_env_ref env, const library_plan& lib) cons

// Check and throw if errant
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);
}
}


+ 4
- 3
src/dds/build/plan/full.cpp Ver fichero

@@ -2,6 +2,7 @@

#include <dds/build/iter_compilations.hpp>
#include <dds/build/plan/compile_exec.hpp>
#include <dds/error/errors.hpp>

#include <range/v3/view/concat.hpp>
#include <range/v3/view/filter.hpp>
@@ -76,7 +77,7 @@ bool parallel_run(Range&& rng, int n_jobs, Fn&& fn) {
void build_plan::compile_all(const build_env& env, int njobs) const {
auto okay = dds::compile_all(iter_compilations(*this), env, njobs);
if (!okay) {
throw std::runtime_error("Compilation failed.");
throw_user_error<errc::compile_failure>();
}
}

@@ -87,7 +88,7 @@ void build_plan::archive_all(const build_env& env, int njobs) const {
}
});
if (!okay) {
throw std::runtime_error("Error creating static library archives");
throw_external_error<errc::archive_failure>();
}
}

@@ -107,7 +108,7 @@ void build_plan::link_all(const build_env& env, int njobs) const {
exe.get().link(env, lib);
});
if (!okay) {
throw std::runtime_error("Failure to link executables");
throw_user_error<errc::link_failure>();
}
}


+ 18
- 11
src/dds/catalog/catalog.cpp Ver fichero

@@ -1,5 +1,6 @@
#include "./catalog.hpp"

#include <dds/error/errors.hpp>
#include <dds/solve/solve.hpp>

#include <neo/sqlite3/exec.hpp>
@@ -75,14 +76,22 @@ void ensure_migrated(sqlite3::database& db) {

auto meta = nlohmann::json::parse(meta_json);
if (!meta.is_object()) {
throw std::runtime_error("Corrupted repository database file.");
throw_external_error<errc::corrupted_catalog_db>();
}

auto version_ = meta["version"];
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_;
if (version > current_database_version) {
throw_external_error<errc::catalog_too_new>();
}

if (version < 1) {
migrate_repodb_1(db);
}
@@ -101,10 +110,10 @@ catalog catalog::open(const std::string& db_path) {
ensure_migrated(db);
} catch (const sqlite3::sqlite3_error& e) {
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: {}",
e.what());
throw;
throw_external_error<errc::corrupted_catalog_db>();
}
return catalog(std::move(db));
}
@@ -163,10 +172,7 @@ void catalog::store(const package_info& pkg) {
)"_sql);
for (const auto& dep : pkg.deps) {
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();
sqlite3::exec(new_dep_st,
std::forward_as_tuple(db_pkg_id,
@@ -274,7 +280,7 @@ namespace {

void check_json(bool b, std::string_view what) {
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);
}
}

@@ -337,8 +343,9 @@ void catalog::import_json_str(std::string_view content) {
}
info.remote = git_remote_listing{url, ref, autolib};
} 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);

+ 10
- 9
src/dds/catalog/get.cpp Ver fichero

@@ -1,6 +1,7 @@
#include "./get.hpp"

#include <dds/catalog/catalog.hpp>
#include <dds/error/errors.hpp>
#include <dds/proc.hpp>

#include <spdlog/spdlog.h>
@@ -22,11 +23,11 @@ temporary_sdist do_pull_sdist(const package_info& listing, const git_remote_list
tmpdir.path().generic_string()};
auto git_res = run_proc(command);
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 ...");
if (git.auto_lib.has_value()) {
@@ -53,11 +54,11 @@ temporary_sdist do_pull_sdist(const package_info& listing, const git_remote_list
temporary_sdist dds::get_package_sdist(const package_info& pkg) {
auto tsd = std::visit([&](auto&& remote) { return do_pull_sdist(pkg, remote); }, pkg.remote);
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(),
tsd.sdist.manifest.pkg_id.to_string()));
tsd.sdist.manifest.pkg_id.to_string());
}
return tsd;
}

+ 5
- 2
src/dds/db/database.cpp Ver fichero

@@ -1,5 +1,7 @@
#include "./database.hpp"

#include <dds/error/errors.hpp>

#include <neo/sqlite3/exec.hpp>
#include <neo/sqlite3/iter_tuples.hpp>
#include <neo/sqlite3/single.hpp>
@@ -62,12 +64,13 @@ void ensure_migrated(sqlite3::database& db) {

auto meta = nlohmann::json::parse(meta_json);
if (!meta.is_object()) {
throw std::runtime_error("Correupted database file.");
throw_external_error<errc::corrupted_build_db>();
}

auto version_ = meta["version"];
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_;
if (version < 1) {

+ 3
- 5
src/dds/deps.cpp Ver fichero

@@ -1,5 +1,6 @@
#include "./deps.hpp"

#include <dds/error/errors.hpp>
#include <dds/repo/repo.hpp>
#include <dds/source/dist.hpp>
#include <dds/usage_reqs.hpp>
@@ -34,11 +35,8 @@ dependency dependency::parse_depends_string(std::string_view str) {
auto rng = semver::range::parse_restricted(version_str);
return dependency{std::string(name), {rng.low(), rng.high()}};
} 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);
}
}


+ 206
- 0
src/dds/error/errors.cpp Ver fichero

@@ -0,0 +1,206 @@
#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();
}

+ 91
- 0
src/dds/error/errors.hpp Ver fichero

@@ -0,0 +1,91 @@
#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

Cargando…
Cancelar
Guardar