Browse Source

Merge branch 'release/0.1.0-alpha.2' into develop

default_compile_flags
vector-of-bool 4 years ago
parent
commit
cec40a326c
14 changed files with 326 additions and 22 deletions
  1. +116
    -0
      docs/err/dep-res-failure.rst
  2. +28
    -0
      docs/err/dup-lib-name.rst
  3. +9
    -0
      docs/err/invalid-pkg-ident.rst
  4. +38
    -0
      docs/err/sdist-exists.rst
  5. +15
    -0
      docs/err/unknown-usage.rst
  6. +30
    -3
      docs/guide/packages.rst
  7. +56
    -0
      src/dds/error/errors.cpp
  8. +6
    -0
      src/dds/error/errors.hpp
  9. +5
    -3
      src/dds/package/id.cpp
  10. +5
    -4
      src/dds/package/manifest.cpp
  11. +2
    -1
      src/dds/repo/repo.cpp
  12. +3
    -1
      src/dds/solve/solve.cpp
  13. +2
    -2
      src/dds/source/dist.cpp
  14. +11
    -8
      src/dds/usage_reqs.cpp

+ 116
- 0
docs/err/dep-res-failure.rst View File

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

+ 28
- 0
docs/err/dup-lib-name.rst View File

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

+ 9
- 0
docs/err/invalid-pkg-ident.rst View File

@@ -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>`

+ 38
- 0
docs/err/sdist-exists.rst View File

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

+ 15
- 0
docs/err/unknown-usage.rst View File

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

+ 30
- 3
docs/guide/packages.rst View File

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

+ 56
- 0
src/dds/error/errors.cpp View File

@@ -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;
}

+ 6
- 0
src/dds/error/errors.hpp View File

@@ -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,

+ 5
- 3
src/dds/package/id.cpp View File

@@ -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)");
}
}


+ 5
- 4
src/dds/package/manifest.cpp View File

@@ -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;

+ 2
- 1
src/dds/repo/repo.cpp View File

@@ -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;

+ 3
- 1
src/dds/solve/solve.cpp View File

@@ -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>();
}
}

+ 2
- 2
src/dds/source/dist.cpp View File

@@ -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());
}
}


+ 11
- 8
src/dds/usage_reqs.cpp View File

@@ -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) {

Loading…
Cancel
Save