@@ -0,0 +1,116 @@ | |||
Error: Dependency Resolution Failure | |||
#################################### | |||
.. note:: | |||
``dds`` implements the `Pubgrub`_ dependency resolution algorithm. | |||
.. _Pubgrub: https://github.com/dart-lang/pub/blob/master/doc/solver.md | |||
If you receive this error, it indicates that the requested dependencies create | |||
one or more conflicts. ``dds`` will do its best to emit a useful explanation of | |||
how this conflict was formed that can hopefully be used to find the original | |||
basis for the conflict. | |||
Every package can have some number of dependencies, which are other packages | |||
that are required for the dependent package to be used. Beyond just a package | |||
name, a dependency will also have a *compatible version range*. | |||
Resolution Example | |||
****************** | |||
For example, let us suppose a package ``Widgets@4.2.8`` may have a *dependency* | |||
on ``Gadgets^2.4.4`` and ``Gizmos^3.2.0``. | |||
.. note:: | |||
The ``@4.2.8`` suffix on ``Widgets`` means that ``4.2.8`` is the *exact* | |||
version of ``Widgets``, while the ``^2.4.4`` is a *version range* on | |||
``Gadgets`` which starts at ``2.4.4`` and includes every version until (but | |||
not including) ``3.0.0``. ``^3.2.0`` is a version range on ``Gizmos`` that | |||
starts at ``3.2.0`` and includes every version until (but not including) | |||
``4.0.0``. | |||
Now let us suppose the following versions of ``Gadgets`` and ``Gizmos`` are | |||
available: | |||
``Gadgets``: | |||
- ``2.4.0`` | |||
- ``2.4.3`` | |||
- ``2.4.4`` | |||
- ``2.5.0`` | |||
- ``2.6.0`` | |||
- ``3.1.0`` | |||
``Gizmos``: | |||
- ``2.1.0`` | |||
- ``3.2.0`` | |||
- ``3.5.6`` | |||
- ``4.5.0`` | |||
We can immediately rule out some candidates of ``Gadgets``: for the dependency | |||
``Gadgets^2.4.4``, ``2.4.0`` and ``2.4.3`` are *too old*, while ``3.1.0`` is | |||
*too new*. This leaves us with ``2.4.4``, ``2.5.0``, and ``2.6.0``. | |||
We'll first look at ``Gadgets@2.4.4``. We need to recursively solve its | |||
dependencies. Suppose that it declares a dependency of ``Gizmos^2.1.0``. We | |||
have already established that we *require* ``Gizmos^3.2.0``, and because | |||
``^2.1.0`` and ``^3.2.0`` are *disjoint* (they share no common versions) we can | |||
say that ``Gizmos^3.2.0`` is *incompatible* with our existing partial solution, | |||
and that its dependent, ``Gadgets@2.4.4`` is *transitively incompatible* with | |||
the partial solution. Thus, ``Gadgets@2.4.4`` is out of the running. | |||
This doesn't mean we're immediately broken, though. We still have two more | |||
versions of ``Gadgets`` to inspect. We'll start with the next version in line: | |||
``Gadgets@2.5.0``. Suppose that it has a dependency on ``Gizmos^3.4.0``. We | |||
have already established a requirement of ``Gizmos^3.2.0``, so we must find | |||
a candidate for ``Gizmos`` that satisfies both dependencies. Fortunately, we | |||
have exactly one: ``Gizmos@3.5.6`` satisfies *both* ``Gizmos^3.2.0`` *and* | |||
``Gizmos^3.4.0``. | |||
Suppose that ``Gizmos@3.5.6`` has no further dependencies. At this point, we | |||
have inspected all dependencies and have resolutions for every named package: | |||
Thus, we have a valid solution of ``Widgets@4.2.8``, ``Gadgets@2.5.0``, and | |||
``Gizmos@2.6.0``! We didn't even need to inspect ``Gadgets@2.6.0``. | |||
In this case, ``dds`` will not produce an error, and the given package solution | |||
will be used. | |||
Breaking the Solution | |||
===================== | |||
Now suppose the same case, except that ``Gadgets@2.5.0`` is not available. | |||
We'll instead move to check ``Gadgets@2.6.0``. | |||
Suppose that ``Gadgets@2.6.0`` has a dependency on ``Gizmos^4.0.6``. While we | |||
*do* have a candidate thereof, we've already declared a requriement on | |||
``Gizmos^3.2.0``. Because ``^4.0.6`` and ``^3.2.0`` are disjoint, then there is | |||
no possible satisfier for both ranges. This means that ``Gizmos^4.0.6`` is | |||
incompatible in the partial solution, and that ``Gadgets@2.6.0`` is | |||
transitively incompatible as well. It is no longer a candidate. | |||
We've exhausted the available candidates for ``Gadgets^2.4.4``, so we must now | |||
conclude that ``Gadgets^2.4.4`` is *also incompatible*. Transitively, this also | |||
means that ``Widgets@4.2.8`` is incompatible as well. | |||
We've reached a problem, though: ``Widgets@4.2.8`` is our original requirement! | |||
There is nothing left to invalidate in our partial solution, so we rule that | |||
our original requirements are *unsatisfiable*. | |||
At this point, ``dds`` will raise the error that *dependency resolution has | |||
failed*. It will attempt its best to reconstruct the logic that we have used | |||
above in order to explain what has gone wrong. | |||
Fixing the Problem | |||
****************** | |||
There is no strict process for fixing these conflicts. | |||
Fixing a dependency conflict is a manual process. It will require reviewing the | |||
available versions and underlying reasons that the dependency maintainers have | |||
chosen their compatibility ranges statements. | |||
Your own dependency statements will often need to be changed, and sometimes | |||
even code will have to be revised to reach compatibility with newer or older | |||
dependency versions. |
@@ -0,0 +1,28 @@ | |||
Error: Duplicate Library Identifier | |||
################################### | |||
Libraries in ``dds`` are represented by a *namespace* and a *name*, joined | |||
together with a forward-slash "``/``". Suppose that we have a library named | |||
``Gadgets`` that lives in the ``ACME`` library-namespace. The combined library | |||
identifier would be ``ACME/Gadgets``. | |||
.. note:: | |||
The "namespace" of a library in this case is arbitrary and not necessarily | |||
associated with any C++ ``namespace``. | |||
If more than one library declares itself to have the same ``Name`` and lives in | |||
the same ``Namespace``, ``dds`` will issue an error. | |||
To avoid this error in your own project and to avoid causing this error in your | |||
downstream consumers, the ``Namespace`` of your package should be considered | |||
carefully and be unique. Do not use a ``Namespace`` that is likely to be used | |||
by another developer or organization, especially generic names. | |||
If you are seeing this issue and it names a library that you do not own, it | |||
means that two or more of your dependencies are attempting to declare a library | |||
of the same ``Name`` in the same ``Namespace``. This issue should be raised | |||
with the maintainers of the packages in question. | |||
.. seealso:: | |||
For more information, refer to the :ref:`pkgs.pkgs` section and the | |||
:ref:`pkgs.libs` section. |
@@ -0,0 +1,9 @@ | |||
Error: Invalid package name *or* Invalid package identifier | |||
########################################################### | |||
Package identifiers in ``dds`` must follow a well-defined pattern of | |||
``<name>@<version>``, where ``<version>`` is a valid Semantic Version and | |||
``<name>`` follows a few simple rules. | |||
.. seealso:: | |||
:ref:`pkgs.pkgs` and :ref:`Package Naming Requirements <pkgs.naming-reqs>` |
@@ -0,0 +1,38 @@ | |||
Error: Source Distribution Already Exists | |||
######################################### | |||
This error is presented when an attempt is made to export/create a source | |||
distribution of a package in a way that would overwrite an existing source | |||
distribution. | |||
**If exporting to a repository**, this means that a source distribution with | |||
the same name and version is already present in the repository. The | |||
``--replace`` option can be used to make ``dds`` forcibly overwrite the source | |||
distribution in the repository. This will be a common workflow when developing | |||
a package and one desires to see those changes reflected in another project | |||
that is try to use it. | |||
**If creating a source distribution manually**, this means that the destination | |||
path of the source distribution directory is already an existing directory | |||
(which may not be a source distribution itself). If ``dds`` were to try and | |||
write a source distribution to the named path, it would be required to delete | |||
whatever exists there before creating the source distribution. | |||
.. warning:: | |||
When using ``dds sdist create`` with the ``--out <path>`` parameter, the | |||
``<path>`` given **is not the directory in which to place the source | |||
distribution, but the filepath to the source distribution itself**! | |||
If I have a directory named ``foo/``, and I want to create a source | |||
distribution in that directory, **the following command is incorrect**:: | |||
# Do not do this: | |||
dds sdist create --out foo/ | |||
If you pass ``--replace`` to the above command, ``dds`` will **destroy the | |||
existing directory** and replace it with the source distribution! | |||
You **must** provide the full path to the source distribution:: | |||
# Do this: | |||
dds sdist create --out foo/my-project.dsd |
@@ -0,0 +1,15 @@ | |||
Error: Unknown Usage/Linking Requirements | |||
######################################### | |||
A library can declare that it *uses* or *links* to another library by using the | |||
``Uses`` and ``Links`` keys in ``library.dds``, respectively. | |||
These requirements are specified by using the ``Namespace/Name`` pair that | |||
identifies a library. These are defined by both the project's dependencies and | |||
the project itself. If a ``Uses`` or ``Links`` key does not correspond to a | |||
known library, ``dds`` will not be able to resolve the usage requirements, and | |||
will generate an error. | |||
To fix this issue, ensure that you have correctly spelled the library | |||
identifier and that the package that contains the respective library has been | |||
declared as a dependency of the library that is trying to use it. |
@@ -1,5 +1,5 @@ | |||
Package Layout | |||
############## | |||
Packages and Layout | |||
################### | |||
The units of distribution in ``dds`` are *packages*. A single package consists | |||
of one or more *libraries*. In the simplest case, a package will contain a | |||
@@ -225,6 +225,8 @@ directory. ``dds`` will not compile compilable source files that appear in the | |||
``include/`` directory and will issue a warning on each file found. | |||
.. _pkgs.libs: | |||
Libraries | |||
********* | |||
@@ -268,6 +270,8 @@ the ``libs/`` directory that is also a library root is added as a child of the | |||
owning package. | |||
.. _pkgs.pkgs: | |||
Packages | |||
******** | |||
@@ -300,4 +304,27 @@ present in the ``package.dds`` file: ``Name``, ``Version``, and ``Namespace``: | |||
.. seealso:: | |||
The purpose of ``Namespace``, as well as additional options in this file, | |||
are described in the :ref:`deps.pkg-deps` page | |||
are described in the :ref:`deps.pkg-deps` page | |||
.. _pkgs.naming-reqs: | |||
Naming Requirements | |||
=================== | |||
Package names aren't a complete free-for-all. Package names must follow a set | |||
of specific rules: | |||
- Package names may consist of ASCII, lowercase characters, digits, | |||
underscores (``_``), hyphens (``-``), and periods (``.``). | |||
.. note:: | |||
Different filesystems differ in their handling of filenames. Some platforms | |||
perform unicode and case normalization, which can significantly confuse tools | |||
that don't use the same normalization rules. Different platforms have | |||
different filename limitations and allowable characters. This set of | |||
characters is valid on most currently popular filesystems. | |||
- Package names must begin with an alphabetic character | |||
- Package names must end with an alphanumeric character (letter or digit). | |||
- Package names may not contain adjacent punctuation characters. |
@@ -50,6 +50,17 @@ std::string error_url_suffix(dds::errc ec) noexcept { | |||
return "invalid-pkg-filesystem.html"; | |||
case errc::unknown_test_driver: | |||
return "unknown-test-driver.html"; | |||
case errc::invalid_pkg_name: | |||
case errc::invalid_pkg_id: | |||
return "invalid-pkg-ident.html"; | |||
case errc::sdist_exists: | |||
return "sdist-exists.html"; | |||
case errc::dependency_resolve_failure: | |||
return "dep-res-failure.html"; | |||
case errc::dup_lib_name: | |||
return "dup-lib-name.html"; | |||
case errc::unknown_usage_name: | |||
return "unknown-usage.html"; | |||
case errc::none: | |||
break; | |||
} | |||
@@ -173,6 +184,37 @@ reference on these prescriptions. | |||
return R"( | |||
`dds` has a pre-defined set of built-in test drivers, and the one specified is | |||
not recognized. Check the documentation for more information. | |||
)"; | |||
case errc::invalid_pkg_id: | |||
return R"(Package IDs must follow a strict format of <name>@<version>.)"; | |||
case errc::invalid_pkg_name: | |||
return R"(Package names allow a limited set of characters and must not be empty.)"; | |||
case errc::sdist_exists: | |||
return R"( | |||
By default, `dds` will not overwrite source distributions that already exist | |||
(either in the repository or a filesystem path). Such an action could | |||
potentially destroy important data. | |||
)"; | |||
case errc::dependency_resolve_failure: | |||
return R"( | |||
The dependency resolution algorithm failed to resolve the requirements of the | |||
project. The algorithm's explanation should give enough information to infer | |||
why there is no possible solution. You may need to reconsider your dependency | |||
versions to avoid conflicts. | |||
)"; | |||
case errc::dup_lib_name: | |||
return R"( | |||
`dds` cannot build code correctly when there is more than one library that has | |||
claimed the same name. It is possible that the duplicate name appears in a | |||
dependency and is not an issue in your own project. Consult the output to see | |||
which packages are claiming the library name. | |||
)"; | |||
case errc::unknown_usage_name: | |||
return R"( | |||
A `Uses` or `Links` field for a library specifies a library of an unknown name. | |||
Check your spelling, and check that the package containing the library is | |||
available, either from the `package.dds` or from the `INDEX.lmi` that was used | |||
for the build. | |||
)"; | |||
case errc::none: | |||
break; | |||
@@ -226,8 +268,22 @@ std::string_view dds::default_error_string(dds::errc ec) noexcept { | |||
case errc::invalid_pkg_filesystem: | |||
return "The filesystem structure of the package/library is invalid. <- (Seeing this text " | |||
"is a `dds` bug. Please report it.)"; | |||
case errc::invalid_pkg_id: | |||
return "A package identifier is invalid <- (Seeing this text is a `dds` bug. Please " | |||
"report it.)"; | |||
case errc::invalid_pkg_name: | |||
return "A package name is invalid <- (Seeing this text is a `dds` bug. Please report it.)"; | |||
case errc::sdist_exists: | |||
return "The source ditsribution already exists at the destination <- (Seeing this text is " | |||
"a `dds` bug. Please report it.)"; | |||
case errc::unknown_test_driver: | |||
return "The specified Test-Driver is not known to `dds`"; | |||
case errc::dependency_resolve_failure: | |||
return "`dds` was unable to find a solution for the package dependencies given."; | |||
case errc::dup_lib_name: | |||
return "More than one library has claimed the same name."; | |||
case errc::unknown_usage_name: | |||
return "A `Uses` or `Links` field names a library that isn't recognized."; | |||
case errc::none: | |||
break; | |||
} |
@@ -24,13 +24,19 @@ enum class errc { | |||
git_clone_failure, | |||
sdist_ident_mismatch, | |||
sdist_exists, | |||
corrupted_build_db, | |||
invalid_version_range_string, | |||
invalid_version_string, | |||
invalid_pkg_id, | |||
invalid_pkg_name, | |||
invalid_config_key, | |||
unknown_test_driver, | |||
dependency_resolve_failure, | |||
dup_lib_name, | |||
unknown_usage_name, | |||
invalid_lib_filesystem, | |||
invalid_pkg_filesystem, |
@@ -1,5 +1,7 @@ | |||
#include <dds/package/id.hpp> | |||
#include <dds/error/errors.hpp> | |||
#include <spdlog/fmt/fmt.h> | |||
#include <tuple> | |||
@@ -9,7 +11,7 @@ using namespace dds; | |||
package_id package_id::parse(std::string_view s) { | |||
auto at_pos = s.find('@'); | |||
if (at_pos == s.npos) { | |||
throw std::runtime_error(fmt::format("Invalid package ID string '{}'", s)); | |||
throw_user_error<errc::invalid_pkg_id>("Invalid package ID '{}'", s); | |||
} | |||
auto name = s.substr(0, at_pos); | |||
@@ -22,8 +24,8 @@ package_id::package_id(std::string_view n, semver::version v) | |||
: name(n) | |||
, version(std::move(v)) { | |||
if (name.find('@') != name.npos) { | |||
throw std::runtime_error( | |||
fmt::format("Invalid package name '{}' (The '@' character is not allowed)")); | |||
throw_user_error<errc::invalid_pkg_name>( | |||
"Invalid package name '{}' (The '@' character is not allowed)"); | |||
} | |||
} | |||
@@ -28,12 +28,13 @@ package_manifest package_manifest::load_from_file(const fs::path& fpath) { | |||
lm_reject_dym{{"Name", "Namespace", "Version", "Depends", "Test-Driver"}}); | |||
if (ret.pkg_id.name.empty()) { | |||
throw std::runtime_error( | |||
fmt::format("'Name' field in [{}] may not be an empty string", fpath.string())); | |||
throw_user_error<errc::invalid_pkg_name>("'Name' field in [{}] may not be an empty string", | |||
fpath.string()); | |||
} | |||
if (version_str.empty()) { | |||
throw std::runtime_error( | |||
fmt::format("'Version' field in [{}] may not be an empty string", fpath.string())); | |||
throw_user_error< | |||
errc::invalid_version_string>("'Version' field in [{}] may not be an empty string", | |||
fpath.string()); | |||
} | |||
if (opt_test_driver) { | |||
auto& test_driver_str = *opt_test_driver; |
@@ -1,6 +1,7 @@ | |||
#include "./repo.hpp" | |||
#include <dds/catalog/catalog.hpp> | |||
#include <dds/error/errors.hpp> | |||
#include <dds/solve/solve.hpp> | |||
#include <dds/source/dist.hpp> | |||
#include <dds/util/paths.hpp> | |||
@@ -79,7 +80,7 @@ void repository::add_sdist(const sdist& sd, if_exists ife_action) { | |||
auto msg = fmt::format("Source distribution '{}' is already available in the local repo", | |||
sd.path.string()); | |||
if (ife_action == if_exists::throw_exc) { | |||
throw std::runtime_error(msg); | |||
throw_user_error<errc::sdist_exists>(msg); | |||
} else if (ife_action == if_exists::ignore) { | |||
spdlog::warn(msg); | |||
return; |
@@ -1,5 +1,7 @@ | |||
#include "./solve.hpp" | |||
#include <dds/error/errors.hpp> | |||
#include <pubgrub/solve.hpp> | |||
#include <range/v3/range/conversion.hpp> | |||
@@ -156,6 +158,6 @@ std::vector<package_id> dds::solve(const std::vector<dependency>& deps, | |||
} catch (const solve_fail_exc& failure) { | |||
spdlog::error("Dependency resolution has failed! Explanation:"); | |||
pubgrub::generate_explaination(failure, explainer()); | |||
throw; | |||
throw_user_error<errc::dependency_resolve_failure>(); | |||
} | |||
} |
@@ -65,8 +65,8 @@ sdist dds::create_sdist(const sdist_params& params) { | |||
auto dest = fs::absolute(params.dest_path); | |||
if (fs::exists(dest)) { | |||
if (!params.force) { | |||
throw std::runtime_error( | |||
fmt::format("Destination path '{}' already exists", dest.string())); | |||
throw_user_error<errc::sdist_exists>("Destination path '{}' already exists", | |||
dest.string()); | |||
} | |||
} | |||
@@ -1,6 +1,7 @@ | |||
#include "./usage_reqs.hpp" | |||
#include <dds/build/plan/compile_file.hpp> | |||
#include <dds/error/errors.hpp> | |||
#include <dds/util/algo.hpp> | |||
#include <spdlog/fmt/fmt.h> | |||
@@ -21,8 +22,9 @@ lm::library& usage_requirement_map::add(std::string ns, std::string name) { | |||
auto pair = std::pair(library_key{ns, name}, lm::library{}); | |||
auto [inserted, did_insert] = _reqs.try_emplace(library_key{ns, name}, lm::library()); | |||
if (!did_insert) { | |||
throw std::runtime_error( | |||
fmt::format("More than one library is registered as {}/{}", ns, name)); | |||
throw_user_error<errc::dup_lib_name>("More than one library is registered as `{}/{}'", | |||
ns, | |||
name); | |||
} | |||
return inserted->second; | |||
} | |||
@@ -40,8 +42,9 @@ usage_requirement_map usage_requirement_map::from_lm_index(const lm::index& idx) | |||
std::vector<fs::path> usage_requirement_map::link_paths(const lm::usage& key) const { | |||
auto req = get(key); | |||
if (!req) { | |||
throw std::runtime_error( | |||
fmt::format("Unable to find linking requirement '{}/{}'", key.namespace_, key.name)); | |||
throw_user_error<errc::unknown_usage_name>("Unable to find linking requirement '{}/{}'", | |||
key.namespace_, | |||
key.name); | |||
} | |||
std::vector<fs::path> ret; | |||
if (req->linkable_path) { | |||
@@ -60,10 +63,10 @@ std::vector<fs::path> usage_requirement_map::include_paths(const lm::usage& usag | |||
std::vector<fs::path> ret; | |||
auto lib = get(usage.namespace_, usage.name); | |||
if (!lib) { | |||
throw std::runtime_error( | |||
fmt::format("Cannot find non-existent usage requirements for '{}/{}'", | |||
usage.namespace_, | |||
usage.name)); | |||
throw_user_error< | |||
errc::unknown_usage_name>("Cannot find non-existent usage requirements for '{}/{}'", | |||
usage.namespace_, | |||
usage.name); | |||
} | |||
extend(ret, lib->include_paths); | |||
for (const auto& transitive : lib->uses) { |