| 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. |
| 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. |
| 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>` |
| 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 |
| 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. |
| Package Layout | |||||
| ############## | |||||
| Packages and Layout | |||||
| ################### | |||||
| The units of distribution in ``dds`` are *packages*. A single package consists | 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 | of one or more *libraries*. In the simplest case, a package will contain a | ||||
| ``include/`` directory and will issue a warning on each file found. | ``include/`` directory and will issue a warning on each file found. | ||||
| .. _pkgs.libs: | |||||
| Libraries | Libraries | ||||
| ********* | ********* | ||||
| owning package. | owning package. | ||||
| .. _pkgs.pkgs: | |||||
| Packages | Packages | ||||
| ******** | ******** | ||||
| .. seealso:: | .. seealso:: | ||||
| The purpose of ``Namespace``, as well as additional options in this file, | 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. |
| return "invalid-pkg-filesystem.html"; | return "invalid-pkg-filesystem.html"; | ||||
| case errc::unknown_test_driver: | case errc::unknown_test_driver: | ||||
| return "unknown-test-driver.html"; | 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: | case errc::none: | ||||
| break; | break; | ||||
| } | } | ||||
| return R"( | return R"( | ||||
| `dds` has a pre-defined set of built-in test drivers, and the one specified is | `dds` has a pre-defined set of built-in test drivers, and the one specified is | ||||
| not recognized. Check the documentation for more information. | 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: | case errc::none: | ||||
| break; | break; | ||||
| case errc::invalid_pkg_filesystem: | case errc::invalid_pkg_filesystem: | ||||
| return "The filesystem structure of the package/library is invalid. <- (Seeing this text " | return "The filesystem structure of the package/library is invalid. <- (Seeing this text " | ||||
| "is a `dds` bug. Please report it.)"; | "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: | case errc::unknown_test_driver: | ||||
| return "The specified Test-Driver is not known to `dds`"; | 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: | case errc::none: | ||||
| break; | break; | ||||
| } | } |
| git_clone_failure, | git_clone_failure, | ||||
| sdist_ident_mismatch, | sdist_ident_mismatch, | ||||
| sdist_exists, | |||||
| corrupted_build_db, | corrupted_build_db, | ||||
| invalid_version_range_string, | invalid_version_range_string, | ||||
| invalid_version_string, | invalid_version_string, | ||||
| invalid_pkg_id, | |||||
| invalid_pkg_name, | |||||
| invalid_config_key, | invalid_config_key, | ||||
| unknown_test_driver, | unknown_test_driver, | ||||
| dependency_resolve_failure, | |||||
| dup_lib_name, | |||||
| unknown_usage_name, | |||||
| invalid_lib_filesystem, | invalid_lib_filesystem, | ||||
| invalid_pkg_filesystem, | invalid_pkg_filesystem, |
| #include <dds/package/id.hpp> | #include <dds/package/id.hpp> | ||||
| #include <dds/error/errors.hpp> | |||||
| #include <spdlog/fmt/fmt.h> | #include <spdlog/fmt/fmt.h> | ||||
| #include <tuple> | #include <tuple> | ||||
| package_id package_id::parse(std::string_view s) { | package_id package_id::parse(std::string_view s) { | ||||
| auto at_pos = s.find('@'); | auto at_pos = s.find('@'); | ||||
| if (at_pos == s.npos) { | 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); | auto name = s.substr(0, at_pos); | ||||
| : name(n) | : name(n) | ||||
| , version(std::move(v)) { | , version(std::move(v)) { | ||||
| if (name.find('@') != name.npos) { | 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)"); | |||||
| } | } | ||||
| } | } | ||||
| lm_reject_dym{{"Name", "Namespace", "Version", "Depends", "Test-Driver"}}); | lm_reject_dym{{"Name", "Namespace", "Version", "Depends", "Test-Driver"}}); | ||||
| if (ret.pkg_id.name.empty()) { | 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()) { | 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) { | if (opt_test_driver) { | ||||
| auto& test_driver_str = *opt_test_driver; | auto& test_driver_str = *opt_test_driver; |
| #include "./repo.hpp" | #include "./repo.hpp" | ||||
| #include <dds/catalog/catalog.hpp> | #include <dds/catalog/catalog.hpp> | ||||
| #include <dds/error/errors.hpp> | |||||
| #include <dds/solve/solve.hpp> | #include <dds/solve/solve.hpp> | ||||
| #include <dds/source/dist.hpp> | #include <dds/source/dist.hpp> | ||||
| #include <dds/util/paths.hpp> | #include <dds/util/paths.hpp> | ||||
| auto msg = fmt::format("Source distribution '{}' is already available in the local repo", | auto msg = fmt::format("Source distribution '{}' is already available in the local repo", | ||||
| sd.path.string()); | sd.path.string()); | ||||
| if (ife_action == if_exists::throw_exc) { | 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) { | } else if (ife_action == if_exists::ignore) { | ||||
| spdlog::warn(msg); | spdlog::warn(msg); | ||||
| return; | return; |
| #include "./solve.hpp" | #include "./solve.hpp" | ||||
| #include <dds/error/errors.hpp> | |||||
| #include <pubgrub/solve.hpp> | #include <pubgrub/solve.hpp> | ||||
| #include <range/v3/range/conversion.hpp> | #include <range/v3/range/conversion.hpp> | ||||
| } catch (const solve_fail_exc& failure) { | } catch (const solve_fail_exc& failure) { | ||||
| spdlog::error("Dependency resolution has failed! Explanation:"); | spdlog::error("Dependency resolution has failed! Explanation:"); | ||||
| pubgrub::generate_explaination(failure, explainer()); | pubgrub::generate_explaination(failure, explainer()); | ||||
| throw; | |||||
| throw_user_error<errc::dependency_resolve_failure>(); | |||||
| } | } | ||||
| } | } |
| auto dest = fs::absolute(params.dest_path); | auto dest = fs::absolute(params.dest_path); | ||||
| if (fs::exists(dest)) { | if (fs::exists(dest)) { | ||||
| if (!params.force) { | 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()); | |||||
| } | } | ||||
| } | } | ||||
| #include "./usage_reqs.hpp" | #include "./usage_reqs.hpp" | ||||
| #include <dds/build/plan/compile_file.hpp> | #include <dds/build/plan/compile_file.hpp> | ||||
| #include <dds/error/errors.hpp> | |||||
| #include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
| #include <spdlog/fmt/fmt.h> | #include <spdlog/fmt/fmt.h> | ||||
| auto pair = std::pair(library_key{ns, name}, lm::library{}); | auto pair = std::pair(library_key{ns, name}, lm::library{}); | ||||
| auto [inserted, did_insert] = _reqs.try_emplace(library_key{ns, name}, lm::library()); | auto [inserted, did_insert] = _reqs.try_emplace(library_key{ns, name}, lm::library()); | ||||
| if (!did_insert) { | 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; | return inserted->second; | ||||
| } | } | ||||
| std::vector<fs::path> usage_requirement_map::link_paths(const lm::usage& key) const { | std::vector<fs::path> usage_requirement_map::link_paths(const lm::usage& key) const { | ||||
| auto req = get(key); | auto req = get(key); | ||||
| if (!req) { | 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; | std::vector<fs::path> ret; | ||||
| if (req->linkable_path) { | if (req->linkable_path) { | ||||
| std::vector<fs::path> ret; | std::vector<fs::path> ret; | ||||
| auto lib = get(usage.namespace_, usage.name); | auto lib = get(usage.namespace_, usage.name); | ||||
| if (!lib) { | 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); | extend(ret, lib->include_paths); | ||||
| for (const auto& transitive : lib->uses) { | for (const auto& transitive : lib->uses) { |