Переглянути джерело

Merge branch 'feature/use-json5' into develop

default_compile_flags
vector-of-bool 4 роки тому
джерело
коміт
b3aac5c06f
68 змінених файлів з 1299 додано та 469 видалено
  1. +3
    -3
      azure-pipelines.yml
  2. +25
    -0
      catalog.json
  3. +5
    -5
      docs/err/dup-lib-name.rst
  4. +11
    -0
      docs/err/invalid-lib-manifest.rst
  5. +2
    -2
      docs/err/invalid-pkg-filesystem.rst
  6. +9
    -0
      docs/err/invalid-pkg-manifest.rst
  7. +1
    -1
      docs/err/test-failure.rst
  8. +4
    -4
      docs/err/unknown-test-driver.rst
  9. +3
    -3
      docs/err/unknown-usage.rst
  10. +7
    -7
      docs/guide/catalog.rst
  11. +60
    -47
      docs/guide/interdeps.rst
  12. +22
    -18
      docs/guide/packages.rst
  13. +2
    -2
      docs/guide/repo.rst
  14. +11
    -10
      docs/tut/hello-test.rst
  15. +9
    -8
      docs/tut/hello-world.rst
  16. +2
    -0
      library.dds
  17. +16
    -0
      library.jsonc
  18. +3
    -1
      package.dds
  19. +19
    -0
      package.jsonc
  20. +34
    -0
      res/library-schema.json
  21. +53
    -0
      res/package-schema.json
  22. +183
    -0
      res/toolchain-schema.json
  23. +6
    -7
      src/dds.main.cpp
  24. +15
    -6
      src/dds/catalog/get.cpp
  25. +32
    -13
      src/dds/error/errors.cpp
  26. +2
    -0
      src/dds/error/errors.hpp
  27. +113
    -4
      src/dds/library/manifest.cpp
  28. +12
    -3
      src/dds/library/manifest.hpp
  29. +8
    -4
      src/dds/library/root.cpp
  30. +137
    -3
      src/dds/package/manifest.cpp
  31. +11
    -2
      src/dds/package/manifest.hpp
  32. +18
    -16
      src/dds/source/dist.cpp
  33. +0
    -18
      src/dds/toolchain/from_dds.hpp
  34. +245
    -199
      src/dds/toolchain/from_json.cpp
  35. +17
    -0
      src/dds/toolchain/from_json.hpp
  36. +21
    -15
      src/dds/toolchain/from_json.test.cpp
  37. +30
    -23
      src/dds/toolchain/toolchain.cpp
  38. +6
    -3
      tests/basics/test_basics.py
  39. +6
    -2
      tests/catalog/get_test.py
  40. +1
    -1
      tests/deps/git-remote/catalog.json
  41. +0
    -5
      tests/deps/git-remote/package.dds
  42. +9
    -0
      tests/deps/git-remote/package.json5
  43. +0
    -2
      tests/deps/no-deps/package.dds
  44. +5
    -0
      tests/deps/no-deps/package.json5
  45. +0
    -3
      tests/deps/use-remote/library.dds
  46. +6
    -0
      tests/deps/use-remote/library.json5
  47. +0
    -4
      tests/deps/use-remote/package.dds
  48. +8
    -0
      tests/deps/use-remote/package.json5
  49. +6
    -0
      tests/deps/use-spdlog/gcc.tc.jsonc
  50. +4
    -0
      tests/deps/use-spdlog/msvc.tc.jsonc
  51. +0
    -3
      tests/deps/use-spdlog/project/library.dds
  52. +6
    -0
      tests/deps/use-spdlog/project/library.json5
  53. +0
    -4
      tests/deps/use-spdlog/project/package.dds
  54. +8
    -0
      tests/deps/use-spdlog/project/package.json5
  55. +1
    -1
      tests/deps/use-spdlog/use_spdlog_test.py
  56. +0
    -1
      tests/sdist/create/library.dds
  57. +3
    -0
      tests/sdist/create/library.jsonc
  58. +0
    -2
      tests/sdist/create/package.dds
  59. +5
    -0
      tests/sdist/create/package.json5
  60. +0
    -4
      tests/test_drivers/catch/custom-runner/package.dds
  61. +6
    -0
      tests/test_drivers/catch/custom-runner/package.json5
  62. +0
    -4
      tests/test_drivers/catch/main/package.dds
  63. +6
    -0
      tests/test_drivers/catch/main/package.json5
  64. +10
    -2
      tools/ci.py
  65. +3
    -3
      tools/gcc-9.dds
  66. +17
    -0
      tools/gcc-9.jsonc
  67. +18
    -1
      tools/gen-catalog-json.py
  68. +14
    -0
      tools/msvc.jsonc

+ 3
- 3
azure-pipelines.yml Переглянути файл

@@ -12,7 +12,7 @@ jobs:
echo Executing Build and Tests
reg add HKLM\SYSTEM\CurrentControlSet\Control\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1 /f || exit 1
python -m pip install pytest pytest-xdist || exit 1
python -u tools/ci.py -B download -T tools\msvc.dds || exit 1
python -u tools/ci.py -B download -T tools\msvc.dds -T2 tools\msvc.jsonc || exit 1
displayName: Full CI
- publish: _build/dds.exe
artifact: DDS Executable - Windows VS2019
@@ -27,7 +27,7 @@ jobs:
sudo apt install -y python3-minimal g++-9 ccache
python3 -m pip install pytest pytest-xdist
displayName: Prepare System
- script: python3 -u tools/ci.py -B download -T tools/gcc-9.dds
- script: python3 -u tools/ci.py -B download -T tools/gcc-9.dds -T2 tools/gcc-9.jsonc
displayName: Full CI
- publish: _build/dds
artifact: DDS Executable - Linux
@@ -41,7 +41,7 @@ jobs:
- script: |
set -eu
python3 -m pip install pytest pytest-xdist
python3 -u tools/ci.py -B download -T tools/gcc-9.dds
python3 -u tools/ci.py -B download -T tools/gcc-9.dds -T2 tools/gcc-9.jsonc
displayName: Build and Run Unit Tests
- publish: _build/dds
artifact: DDS Executable - macOS

+ 25
- 0
catalog.json Переглянути файл

@@ -685,6 +685,31 @@
"url": "https://github.com/gabime/spdlog.git"
}
}
},
"vob-json5": {
"0.1.5": {
"depends": {},
"description": "A C++ implementation of a JSON5 parser",
"git": {
"auto-lib": null,
"ref": "0.1.5",
"url": "https://github.com/vector-of-bool/json5.git"
}
}
},
"vob-semester": {
"0.1.0": {
"depends": {
"neo-concepts": "^0.2.1",
"neo-fun": "^0.1.0"
},
"description": "A C++ library to process recursive dynamic data",
"git": {
"auto-lib": null,
"ref": "0.1.0",
"url": "https://github.com/vector-of-bool/semester.git"
}
}
}
},
"version": 1

+ 5
- 5
docs/err/dup-lib-name.rst Переглянути файл

@@ -10,17 +10,17 @@ identifier would be ``ACME/Gadgets``.
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.
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
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
of the same ``name`` in the same ``namespace``. This issue should be raised
with the maintainers of the packages in question.

.. seealso::

+ 11
- 0
docs/err/invalid-lib-manifest.rst Переглянути файл

@@ -0,0 +1,11 @@
Error: Invalid library manifest
###############################

Every ``dds`` package must contain a valid library manifest for each library
that it exports. These manifests are stored in the library root to which they
correspond, and are stored in JSON5 format in ``library.json5`` (or similarly
named file).

The contents of this file must follow a prescribed content, or ``dds`` will
reject the manifest. Refer to the :ref:`pkgs.libs` documentation page for more
information about how to declare libraries.

+ 2
- 2
docs/err/invalid-pkg-filesystem.rst Переглянути файл

@@ -6,8 +6,8 @@ build and process packages and libraries. This structure isn't overly strict,
and is thoroughly explained on the :doc:`/guide/packages` page.

For exporting/generating a source distribution from a package, the *package
root* requires a ``package.dds`` file and each *library root* requires a
``library.dds`` file.
root* requires a ``package.json5`` file and each *library root* requires a
``library.json5`` file.

.. .
TODO: Create are more detailed reference page for package and library layout,

+ 9
- 0
docs/err/invalid-pkg-manifest.rst Переглянути файл

@@ -0,0 +1,9 @@
Error: Invalid package manifest
###############################

Every ``dds`` package must contain a valid package manifest, which is stored in
JSON5 format in ``package.json5`` (or similarly named file).

The contents of this file must follow a prescribed content, or ``dds`` will
reject the manifest. Refer to the :ref:`pkgs.pkgs` documentation page for more
information about how to declare packages.

+ 1
- 1
docs/err/test-failure.rst Переглянути файл

@@ -3,7 +3,7 @@ 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``.
``test_driver``.

If you see this error, it is most likely that you have an issue in the tests of
your project.

+ 4
- 4
docs/err/unknown-test-driver.rst Переглянути файл

@@ -1,8 +1,8 @@
Error: Unknown ``Test-Driver``
Error: Unknown ``test_driver``
##############################

``dds`` has a set of known ``Test-Driver``s built-in, and they may be specified
with the ``Test-Driver`` key. Receiving this error indicates that the specified
``Test-Driver`` was not recognized by ``dds``. Check your spelling, and check
``dds`` has a set of known ``test_driver``s built-in, and they may be specified
with the ``test_driver`` key. Receiving this error indicates that the specified
``test_driver`` was not recognized by ``dds``. Check your spelling, and check
that the driver you want to use is supported by ``dds``. Refer to the
:doc:`/guide/packages` page.

+ 3
- 3
docs/err/unknown-usage.rst Переглянути файл

@@ -2,11 +2,11 @@ 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.
``uses`` and ``links`` keys in ``library.json5``, respectively.

These requirements are specified by using the ``Namespace/Name`` pair that
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
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.


+ 7
- 7
docs/guide/catalog.rst Переглянути файл

@@ -33,7 +33,7 @@ command:
dds catalog add <package-id>
[--depends <requirement> [--depends <requirement> [...]]]
[--git-url <url> --git-ref <ref>]
[--auto-lib <Namespace>/<Name>]
[--auto-lib <namespace>/<name>]

The ``<package-id>`` positional arguments is the ``name@version`` package ID
that will be added to the catalog. The following options are supported:
@@ -41,14 +41,14 @@ that will be added to the catalog. The following options are supported:
``--depends <requirement>``
This argument, which can be specified multiple times to represent multiple
dependencies, sets the dependencies of the package within the catalog. If
the obtained package root contains a ``package.dds``, then the dependencies
listed here must be identical to those listed in ``package.dds``, or
dependency resolution may yield unexpected results.
the obtained package root contains a ``package.json5``, then the
dependencies listed here must be identical to those listed in
``package.json5``, or dependency resolution may yield unexpected results.

``--git-url <url>``
Specify a Git URL to clone from to obtain the package. The root of the
cloned repository must be a package root, but does not necessarily need to
have the ``package.dds`` and ``library.dds`` files if relying on the
have the ``package.json5`` and ``library.json5`` files if relying on the
``--auto-lib`` parameter.

``--git-ref`` **must** be passed with ``--git-url``.
@@ -66,8 +66,8 @@ that will be added to the catalog. The following options are supported:
can only be specified for packages that contain a single library root at
the package root.

The form of the argument is that of ``<Namespapce>/<Name>``, where
``Namespace`` and ``Name`` are the usage requirement keys that should be
The form of the argument is that of ``<namespace>/<name>``, where
``namespace`` and ``name`` are the usage requirement keys that should be
generated for the library.



+ 60
- 47
docs/guide/interdeps.rst Переглянути файл

@@ -15,16 +15,18 @@ Package Dependencies
********************

Consider that we are creating a package ``acme-gadgets@4.3.6``. We declare the
name and version in the ``package.dds`` in the package root:
name and version in the ``package.json5`` in the package root:

.. code-block::
.. code-block:: js

Name: acme-gadgets
Version: 4.3.6
Namespace: acme
{
name: 'acme-widgets',
version: '4.3.6',
namespace: 'acme',
}

.. note::
The ``Namespace`` field is required, but will be addressed in the
The ``namespace`` field is required, but will be addressed in the
:ref:`deps.lib-deps` section.

Suppose that our package's libraries build upon the libraries in the
@@ -32,14 +34,17 @@ Suppose that our package's libraries build upon the libraries in the
not as new as ``2.0.0``. Such a dependency can be declared with the ``Depends``
key:

.. code-block::
:emphasize-lines: 5

Name: acme-gadgets
Version: 4.3.6
Namespace: acme
.. code-block:: js
:emphasize-lines: 5-7

Depends: acme-widgets ^1.4.3
{
name: 'acme-gadgets',
version: '4.3.6',
namespace: 'acme',
depends: {
'acme-widgets': '^1.4.3',
},
}

.. seealso:: :ref:`deps.ranges`.

@@ -47,15 +52,18 @@ If we wish to declare additional dependencies, we simply declare them with
additional ``Depends`` keys

.. code-block::
:emphasize-lines: 5-7

Name: acme-gadgets
Version: 4.3.6
Namespace: acme

Depends: acme-widgets ^1.4.3
Depends: acme-gizmos ~5.6.5
Depends: acme-utils ^3.3.0
:emphasize-lines: 7-8

{
name: 'acme-gadgets',
version: '4.3.6',
namespace: 'acme',
depends: {
'acme-widgets': '^1.4.3',
'acme-gizmos': '~5.6.5',
'acme-utils': '^3.3.0',
},
}

When ``dds`` attempts to build a project, it will first build the dependency
solution by iteratively scanning the dependencies of the containing project and
@@ -190,45 +198,50 @@ Library Dependencies

In ``dds``, library interdependencies are tracked separately from the packages
that contain them. A library must declare its intent to use another library
in the ``library.dds`` at its library root. The minimal content of a
``library.dds`` is the ``Name`` key:
in the ``library.json5`` at its library root. The minimal content of a
``library.json5`` is the ``name`` key:

.. code-block::
.. code-block:: js

Name: gadgets
{
name: 'gadgets'
}

To announce that a library wishes to *use* another library, use the aptly-named
``Uses`` key:

.. code-block::
:emphasize-lines: 3-5

Name: gadgets

Uses: acme/widgets
Uses: acme/gizmos
Uses: acme/utils

Here is where the package's ``Namespace`` key comes into play: A library's
qualified name is specified by joining the ``Namespace`` of the containing
package with the ``Name`` of the library within that package with a ``/``
``uses`` key:

.. code-block:: js
:emphasize-lines: 3-7

{
name: 'gadgets',
uses: [
'acme/widgets',
'acme/gizmos',
'acme/utils',
],
}

Here is where the package's ``namespace`` key comes into play: A library's
qualified name is specified by joining the ``namespace`` of the containing
package with the ``name`` of the library within that package with a ``/``
between them.

It is the responsibility of package authors to document the ``Namespace`` and
``Name`` of the packages and libraries that they distribute.
It is the responsibility of package authors to document the ``namespace`` and
``name`` of the packages and libraries that they distribute.

.. note::
The ``Namespace`` of a package is completely arbitrary, and need not relate
The ``namespace`` of a package is completely arbitrary, and need not relate
to a C++ ``namespace``.

.. note::
The ``Namespace`` need not be unique to a single package. For example, a
single organization (Like Acme Inc.) can share a single ``Namespace`` for
The ``namespace`` need not be unique to a single package. For example, a
single organization (Like Acme Inc.) can share a single ``namespace`` for
many of their packages and libraries.

However, it is essential that the ``<Namespace>/<Name>`` pair be
However, it is essential that the ``<namespace>/<name>`` pair be
universally unique, so choose wisely!

Once the ``Uses`` key appears in the ``library.dds`` file of a library, ``dds``
Once the ``uses`` key appears in the ``library.dds`` file of a library, ``dds``
will make available the headers for the library being used, and will
transitively propagate that usage requirement to users of the library.

+ 22
- 18
docs/guide/packages.rst Переглянути файл

@@ -88,7 +88,7 @@ executable.
A *test* source file is a source file whose file stem ends with ``.test``. Like
application sources, a *test* source file is omitted from the main library, and
it will be used to generate tests. The exact behavior of tests is determined by
the ``Test-Driver`` setting for the package, but the default is that each test
the ``test_driver`` setting for the package, but the default is that each test
source file will generate a single test executable that is executed by ``dds``
when running unit tests.

@@ -242,13 +242,14 @@ included in downstream binaries, but it will still generate link rules for the
dependencies of a header-only library.

In order for ``dds`` to be able to distribute and interlink libraries, a
``library.dds`` file must be present at the corresponding library root. The
only required key in a ``library.dds`` file is ``Name``:
``library.json5`` file must be present at the corresponding library root. The
only required key in a ``library.json5`` file is ``name``:

.. code-block:: yaml

Name: my-library
.. code-block:: js

{
name: 'my-library'
}

.. seealso:: More information is discussed on the :ref:`deps.lib-deps` page

@@ -287,23 +288,25 @@ the ``name@version`` string forms the *package ID*, and it must be unique
within a repository or package catalog.

In order for a package to be exported by ``dds`` it must have a
``package.dds`` file at its package root. Three keys are required to be
present in the ``package.dds`` file: ``Name``, ``Version``, and ``Namespace``:
``package.json5`` file at its package root. Three keys are required to be
present in the ``package.json5`` file: ``name``, ``version``, and ``namespace``:

.. code-block:: yaml
.. code-block:: js

Name: acme-widgets
Version: 6.7.3
Namespace: acme
{
name: 'acme-widgets',
version: '6.7.3',
namespace: 'acme',
}

``Version`` must be a valid semantic version string.
``version`` must be a valid semantic version string.

.. note::
The ``Namespace`` key is arbitrary, and not necessarily associated with
and C++ ``namespace``.
The ``namespace`` key is arbitrary, and not necessarily associated with
any C++ ``namespace``.

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


@@ -315,8 +318,9 @@ 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 (``.``).
- Package names may consist of a subset of ASCII including lowercase
characters, digits, underscores (``_``), hyphens (``-``), and periods
(``.``).

.. note::
Different filesystems differ in their handling of filenames. Some platforms

+ 2
- 2
docs/guide/repo.rst Переглянути файл

@@ -62,8 +62,8 @@ package ID as the name of the source distribution directory::
$ ls ./spdlog@1.4.2/
include/
src/
library.dds
package.dds
library.json5
package.json5


.. _repo.export-local:

+ 11
- 10
docs/tut/hello-test.rst Переглянути файл

@@ -48,7 +48,7 @@ will already know the problem, but wouldn't it be better if we had better test
diagnostics?


A ``Test-Driver``: Using *Catch2*
A ``test_driver``: Using *Catch2*
*********************************

``dds`` ships with built-in support for the `Catch2`_ C and C++ testing
@@ -57,20 +57,21 @@ framework.
.. _catch2: https://github.com/catchorg/Catch2

To make use of Catch as our test driver, we simply declare this intent in the
``package.dds`` file at the package root:
``package.json5`` file at the package root:

.. code-block:: yaml
:caption: ``<root>/package.dds``
.. code-block:: js
:caption: ``<root>/package.json5``
:emphasize-lines: 5

Name: hello-dds
Version: 0.1.0
Namespace: tutorial

Test-Driver: Catch-Main
{
name: 'hello-dds',
version: '0.1.0',
namespace: 'tutorial',
test_driver: 'Catch-Main',
}

If you now run ``dds build``, we will get a linker error for a multiply-defined
``main`` function. When setting the ``Test-Driver`` to ``Catch-Main``, ``dds``
``main`` function. When setting the ``test_driver`` to ``Catch-Main``, ``dds``
will compile an entrypoint separately from any particular test, and the tests
will link against that entrypoint. This means we cannot provide our own
``main`` function, and should instead use Catch's ``TEST_CASE`` macro to

+ 9
- 8
docs/tut/hello-world.rst Переглянути файл

@@ -221,20 +221,21 @@ Creating a package manifest file.

``dds`` will work happily with packages that do not declare themselves, as long
as the filesystem structure is sufficient. However: To use features covered in
later tutorials, we'll need a simple ``package.dds`` file to declare
later tutorials, we'll need a simple ``package.json5`` file to declare
information about are package. This file should be placed directly in the
package root:

.. code-block:: yaml
:caption: ``<root>/package.dds``

Name: hello-dds
Version: 0.1.0
Namespace: tutorial
.. code-block:: js
:caption: ``<root>/package.json5``

{
name: 'hello-dds',
version: '0.1.0',
namespace: 'tutorial',
}

.. note::
The ``Namespace`` option will be discussed later.
The ``namespace`` option will be discussed later.

Rebuilding the project will show no difference at the moment.


+ 2
- 0
library.dds Переглянути файл

@@ -7,4 +7,6 @@ Uses: nlohmann/json
Uses: neo/sqlite3
Uses: neo/fun
Uses: semver/semver
Uses: vob/semester
Uses: pubgrub/pubgrub
Uses: vob/json5

+ 16
- 0
library.jsonc Переглянути файл

@@ -0,0 +1,16 @@
{
"$schema": "./res/library-schema.json",
"name": "dds",
"uses": [
"spdlog/spdlog",
"Microsoft/wil",
"range-v3/range-v3",
"nlohmann/json",
"neo/sqlite3",
"neo/fun",
"semver/semver",
"pubgrub/pubgrub",
"vob/json5",
"vob/semester",
]
}

+ 3
- 1
package.dds Переглянути файл

@@ -10,5 +10,7 @@ Depends: neo-sqlite3 0.2.2
Depends: neo-fun 0.1.0
Depends: semver 0.2.1
Depends: pubgrub 0.2.0
Depends: vob-json5 0.1.5
Depends: vob-semester 0.1.0

Test-Driver: Catch-Main
Test-Driver: Catch-Main

+ 19
- 0
package.jsonc Переглянути файл

@@ -0,0 +1,19 @@
{
"$schema": "./res/package-schema.json",
"name": "dds",
"version": "0.1.0",
"namespace": "dds",
"depends": {
"spdlog": "1.4.2",
"ms-wil": "2019.11.10",
"range-v3": "0.10.0",
"nlohmann-json": "3.7.1",
"neo-sqlite3": "0.2.2",
"neo-fun": "0.1.0",
"semver": "0.2.1",
"pubgrub": "0.2.0",
"vob-json5": "0.1.5",
"vob-semester": "0.1.0"
},
"test_driver": "Catch-Main"
}

+ 34
- 0
res/library-schema.json Переглянути файл

@@ -0,0 +1,34 @@
{
"type": "object",
"description": "DDS Library Manifest",
"additionalProperties": false,
"patternProperties": {
"^\\$": {}
},
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the library within the package.",
"pattern": "^[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*$"
},
"uses": {
"type": "array",
"items": {
"type": "string",
"description": "A library that is used by this library. Should be of the form `namespace/name`.",
"pattern": "^[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*/[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*$"
}
},
"links": {
"type": "array",
"items": {
"type": "string",
"description": "A library that is linked to this library. Should be of the form `namespace/name`.",
"pattern": "^[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*/[A-z][A-z0-9_]*((\\.|-)[A-z0-9_]+)*$"
}
}
}
}

+ 53
- 0
res/package-schema.json Переглянути файл

@@ -0,0 +1,53 @@
{
"type": "object",
"description": "DDS Package Manifest",
"additionalProperties": false,
"patternProperties": {
"^\\$": {}
},
"required": [
"name",
"version",
"namespace"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the package",
"pattern": "^[a-z][a-z0-9_]*((\\.|-)[a-z0-9_]+)*$"
},
"version": {
"type": "string",
"pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$",
"description": "The version of the package. Must be a valid Semantic Version string.",
"default": "0.1.0"
},
"namespace": {
"type": "string",
"description": "The package's namespace. Must be a valid string.",
"pattern": "^[a-z][a-z0-9_]*((\\.|-)[a-z0-9_]+)*$"
},
"$schema": {
"type": "string",
"description": "JSON schema tag. Ignored by dds."
},
"depends": {
"type": "object",
"patternProperties": {
"^[a-z][a-z0-9_]*((\\.|-)[a-z0-9_]+)*$": {
"type": "string",
"description": "The version of the dependency. Must be a valid Semantic Version string",
"pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"
}
}
},
"test_driver": {
"type": "string",
"default": "Catch-Main",
"enum": [
"Catch-Main",
"Catch"
]
}
}
}

+ 183
- 0
res/toolchain-schema.json Переглянути файл

@@ -0,0 +1,183 @@
{
"type": "object",
"description": "DDS Toolchain Description File",
"additionalProperties": false,
"patternProperties": {
"^\\$": {}
},
"definitions": {
"command_line_flags": {
"anyOf": [
{
"type": "string",
"description": "Shell-style string of command-line arguments"
},
{
"type": "array",
"description": "An array of command-line arguments. Will be passed verbatim.",
"items": {
"type": "string",
"description": "A single command-line argument. Will be passed verbatim."
}
}
]
}
},
"properties": {
"compiler_id": {
"type": "string",
"description": "The general compiler identification. This is one of a fixed set of values that DDS will use to infer most toolchain attributes.",
"enum": [
"msvc",
"gnu",
"clang"
]
},
"c_compiler": {
"type": "string",
"description": "Executable name or filepath for a C compiler",
"examples": [
"gcc",
"clang-9",
"cl.exe"
]
},
"cxx_compiler": {
"type": "string",
"description": "Executable name or filepath for a C++ compiler",
"examples": [
"g++",
"clang++-9",
"cl.exe"
]
},
"flags": {
"description": "Pass additional compile flags, regardless of the source language",
"$ref": "#/definitions/command_line_flags"
},
"c_flags": {
"description": "Pass additional flags to the C compiler.",
"$ref": "#/definitions/command_line_flags"
},
"cxx_flags": {
"description": "Pass additional flags to the C++ compiler.",
"$ref": "#/definitions/command_line_flags"
},
"c_version": {
"description": "The C language version",
"type": "string",
"enum": [
"c89",
"c99",
"c11",
"c18"
]
},
"cxx_version": {
"description": "The C++ language version",
"type": "string",
"enum": [
"c++98",
"c++03",
"c++11",
"c++14",
"c++17",
"c++20"
]
},
"warning_flags": {
"description": "Set the flags that will be passed to the compiler to enable/disable warnings",
"$ref": "#/definitions/command_line_flags"
},
"link_flags": {
"description": "Pass additional flags to the compiler when it is linking runtime binaries (executables)",
"$ref": "#/definitions/command_line_flags"
},
"compiler_launcher": {
"description": "Set a command-line prefix that will be prepended to all compiler invocations",
"$ref": "#/definitions/command_line_flags"
},
"debug": {
"description": "Enable the generation of debug information",
"type": "boolean",
"default": true
},
"optimize": {
"description": "Optimize generated code",
"type": "boolean",
"default": true
},
"advanced": {
"type": "object",
"additionalProperties": false,
"description": "Advanced toolchain options. All of these options will be inferred from `compiler_id` by default. Handle with care.",
"properties": {
"deps_mode": {
"type": "string",
"description": "Dependency tracking mode.",
"enum": [
"msvc",
"gnu",
"none"
]
},
"include_template": {
"description": "Set the include-directory flags template",
"$ref": "#/definitions/command_line_flags"
},
"external_include_template": {
"description": "Set the external include-directory flags template",
"$ref": "#/definitions/command_line_flags"
},
"define_template": {
"description": "Set the preprocessor-definition flags template",
"$ref": "#/definitions/command_line_flags"
},
"base_warning_flags": {
"description": "Set the base warning flags for the toolchain. These are always prepended to `warning_flags`.",
"$ref": "#/definitions/command_line_flags"
},
"c_compile_file": {
"description": "Set the command template for compiling C source files",
"$ref": "#/definitions/command_line_flags"
},
"cxx_compile_file": {
"description": "Set the command template for compiling C++ source files",
"$ref": "#/definitions/command_line_flags"
},
"create_archive": {
"description": "Set the command template for generating static library archives",
"$ref": "#/definitions/command_line_flags"
},
"link_executable": {
"description": "Set the command template for linking executable binaries",
"$ref": "#/definitions/command_line_flags"
},
"obj_prefix": {
"description": "Set the filename prefix for object files",
"type": "string"
},
"obj_suffix": {
"description": "Set the filename suffix for object files",
"type": "string"
},
"archive_prefix": {
"description": "Set the filename prefix for library archive files",
"type": "string"
},
"archive_suffix": {
"description": "Set the filename suffix for library archive files",
"type": "string"
},
"exe_prefix": {
"description": "Set the filename prefix for executable files",
"type": "string"
},
"exe_suffix": {
"description": "Set the filename suffix for executable files",
"type": "string"
}
}
}
}
}

+ 6
- 7
src/dds.main.cpp Переглянути файл

@@ -5,7 +5,7 @@
#include <dds/error/errors.hpp>
#include <dds/repo/repo.hpp>
#include <dds/source/dist.hpp>
#include <dds/toolchain/from_dds.hpp>
#include <dds/toolchain/from_json.hpp>
#include <dds/util/fs.hpp>
#include <dds/util/paths.hpp>
#include <dds/util/signal.hpp>
@@ -58,7 +58,8 @@ struct toolchain_flag : string_flag {
}
return std::move(*tc);
} else {
return dds::parse_toolchain_dds(dds::slurp_file(tc_path));
return dds::parse_toolchain_json5(dds::slurp_file(tc_path));
// return dds::parse_toolchain_dds(dds::slurp_file(tc_path));
}
}
};
@@ -591,11 +592,9 @@ struct cli_build {
params.out_root = out.Get();
params.toolchain = tc_filepath.get_toolchain();
params.parallel_jobs = n_jobs.Get();
dds::package_manifest man;
const auto man_filepath = project.root.Get() / "package.dds";
if (exists(man_filepath)) {
man = dds::package_manifest::load_from_file(man_filepath);
}

auto man = dds::package_manifest::load_from_directory(project.root.Get())
.value_or(dds::package_manifest{});

dds::builder bd;
dds::sdist_build_params main_params;

+ 15
- 6
src/dds/catalog/get.cpp Переглянути файл

@@ -4,6 +4,7 @@
#include <dds/error/errors.hpp>
#include <dds/proc.hpp>

#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>

using namespace dds;
@@ -32,12 +33,20 @@ temporary_sdist do_pull_sdist(const package_info& listing, const git_remote_list
spdlog::info("Create sdist from clone ...");
if (git.auto_lib.has_value()) {
spdlog::info("Generating library data automatically");
auto pkg_strm = dds::open(tmpdir.path() / "package.dds", std::ios::binary | std::ios::out);
pkg_strm << "Name: " << listing.ident.name << '\n' //
<< "Version: " << listing.ident.version.to_string() << '\n' //
<< "Namespace: " << git.auto_lib->namespace_;
auto lib_strm = dds::open(tmpdir.path() / "library.dds", std::ios::binary | std::ios::out);
lib_strm << "Name: " << git.auto_lib->name;

auto pkg_strm
= dds::open(tmpdir.path() / "package.json5", std::ios::binary | std::ios::out);
auto man_json = nlohmann::json::object();
man_json["name"] = listing.ident.name;
man_json["version"] = listing.ident.version.to_string();
man_json["namespace"] = git.auto_lib->namespace_;
pkg_strm << nlohmann::to_string(man_json);

auto lib_strm
= dds::open(tmpdir.path() / "library.json5", std::ios::binary | std::ios::out);
auto lib_json = nlohmann::json::object();
lib_json["name"] = git.auto_lib->name;
lib_strm << nlohmann::to_string(lib_json);
}

sdist_params params;

+ 32
- 13
src/dds/error/errors.cpp Переглянути файл

@@ -41,6 +41,10 @@ std::string error_url_suffix(dds::errc ec) noexcept {
return "sdist-ident-mismatch.html";
case errc::corrupted_build_db:
return "corrupted-build-db.html";
case errc::invalid_lib_manifest:
return "invalid-lib-manifest.html";
case errc::invalid_pkg_manifest:
return "invalid-pkg-manifest.html";
case errc::invalid_version_range_string:
return "invalid-version-string.html#range";
case errc::invalid_version_string:
@@ -139,6 +143,16 @@ modified by a newer version of dds?
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_lib_manifest:
return R"(
A library manifest is malformed Refer to the documentation and above error
message for more details.
)";
case errc::invalid_pkg_manifest:
return R"(
The package manifest is malformed. Refer to the documentation and above error
message for more details.
)";
case errc::invalid_catalog_json:
return R"(
@@ -220,9 +234,9 @@ 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.
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
available, either from the `package.json5` or from the `INDEX.lmi` that was used
for the build.
)";
case errc::none:
@@ -266,35 +280,40 @@ std::string_view dds::default_error_string(dds::errc ec) noexcept {
"that was expected of it";
case errc::corrupted_build_db:
return "The build database file is corrupted";
case errc::invalid_lib_manifest:
return "The library manifest is invalid";
case errc::invalid_pkg_manifest:
return "The package manifest is invalid";
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.)";
return "Attempted to parse an invalid version string. <- (Seeing this text is a `dds` "
"bug. Please report it.)";
case errc::invalid_config_key:
return "Found an invalid configuration key. <- (Seeing this text is a `dds` bug. Please "
"report it.)";
return "Found an invalid configuration key. <- (Seeing this text is a `dds` bug. "
"Please report it.)";
case errc::invalid_lib_filesystem:
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.)";
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.)";
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.)";
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`";
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.";
return "A `uses` or `links` field names a library that isn't recognized.";
case errc::none:
break;
}

+ 2
- 0
src/dds/error/errors.hpp Переглянути файл

@@ -29,6 +29,8 @@ enum class errc {

corrupted_build_db,

invalid_lib_manifest,
invalid_pkg_manifest,
invalid_version_range_string,
invalid_version_string,
invalid_pkg_id,

+ 113
- 4
src/dds/library/manifest.cpp Переглянути файл

@@ -1,16 +1,21 @@
#include "./manifest.hpp"

#include <dds/dym.hpp>
#include <dds/error/errors.hpp>
#include <dds/util/algo.hpp>
#include <range/v3/view/transform.hpp>

#include <libman/parse.hpp>

#include <spdlog/fmt/fmt.h>
#include <json5/parse_data.hpp>
#include <range/v3/view/transform.hpp>
#include <semester/decomp.hpp>
#include <spdlog/spdlog.h>

using namespace dds;

library_manifest library_manifest::load_from_file(const fs::path& fpath) {
library_manifest library_manifest::load_from_dds_file(path_ref fpath) {
spdlog::warn(
"Using deprecated library.dds parsing (on file {}). This will be removed soon. Migrate!",
fpath.string());
auto kvs = lm::parse_file(fpath);
library_manifest ret;
ret.name = fpath.parent_path().filename().string();
@@ -27,3 +32,107 @@ library_manifest library_manifest::load_from_file(const fs::path& fpath) {
extend(ret.links, ranges::views::transform(links_strings, lm::split_usage_string));
return ret;
}

library_manifest library_manifest::load_from_file(path_ref fpath) {
auto content = slurp_file(fpath);
auto data = json5::parse_data(content);

if (!data.is_object()) {
throw_user_error<errc::invalid_lib_manifest>("Root value must be an object");
}

library_manifest lib;
using namespace semester::decompose_ops;
auto res = semester::decompose( //
data,
try_seq{require_type<json5::data::mapping_type>{
"The root of the library manifest must be an object (mapping)"},
mapping{
if_key{"name",
require_type<std::string>{"`name` must be a string"},
put_into{lib.name}},
if_key{"uses",
require_type<json5::data::array_type>{
"`uses` must be an array of usage requirements"},
for_each{
require_type<std::string>{"`uses` elements must be strings"},
[&](auto&& uses) {
lib.uses.push_back(lm::split_usage_string(uses.as_string()));
return semester::dc_accept;
},
}},
if_key{"links",
require_type<json5::data::array_type>{
"`links` must be an array of usage requirements"},
for_each{
require_type<std::string>{"`links` elements must be strings"},
[&](auto&& links) {
lib.links.push_back(lm::split_usage_string(links.as_string()));
return semester::dc_accept;
},
}},
}});
auto rej = std::get_if<semester::dc_reject_t>(&res);
if (rej) {
throw_user_error<errc::invalid_lib_manifest>(rej->message);
}
// using namespace json_read::ops;
// json_read::decompose( //
// data.as_object(),
// object(key("name", require_string(put_into{lib.name}, "`name` must be a string")),
// key("uses",
// array_each{require_string(
// [&](auto&& uses) {
// lib.uses.push_back(lm::split_usage_string(uses.as_string()));
// return json_read::accept_t{};
// },
// "All `uses` items must be strings")}),
// key("links",
// array_each{require_string(
// [&](auto&& links) {
// lib.links.push_back(lm::split_usage_string(links.as_string()));
// return json_read::accept_t{};
// },
// "All `links` items must be strings")})));

if (lib.name.empty()) {
throw_user_error<errc::invalid_lib_manifest>(
"The 'name' field is required (Reading library manifest [{}])", fpath.string());
}

return lib;
}

std::optional<fs::path> library_manifest::find_in_directory(path_ref dirpath) {
auto fnames = {
"library.json5",
"library.jsonc",
"library.json",
};
for (auto c : fnames) {
auto cand = dirpath / c;
if (fs::is_regular_file(cand)) {
return cand;
}
}

auto dds_file = dirpath / "library.dds";
if (fs::is_regular_file(dds_file)) {
return dds_file;
}

return std::nullopt;
}

std::optional<library_manifest> library_manifest::load_from_directory(path_ref dirpath) {
auto found = find_in_directory(dirpath);
if (!found.has_value()) {
return std::nullopt;
}

if (found->extension() == ".dds") {
return load_from_dds_file(*found);
} else {
return load_from_file(*found);
}
}

+ 12
- 3
src/dds/library/manifest.hpp Переглянути файл

@@ -9,9 +9,9 @@
namespace dds {

/**
* Represents the contents of a `library.dds`. This is somewhat a stripped-down
* Represents the contents of a `library.json5`. This is somewhat a stripped-down
* version of lm::library, to only represent exactly the parts that we want to
* offer via `library.dds`.
* offer via `library.json5`.
*/
struct library_manifest {
/// The name of the library
@@ -24,7 +24,16 @@ struct library_manifest {
/**
* Load the library manifest from an existing file
*/
static library_manifest load_from_file(const fs::path&);
static library_manifest load_from_file(path_ref);
static library_manifest load_from_dds_file(path_ref);

/**
* Find a library manifest within a directory. This will search for a few
* file candidates and return the result from the first matching. If none
* match, it will return nullopt.
*/
static std::optional<fs::path> find_in_directory(path_ref);
static std::optional<library_manifest> load_from_directory(path_ref);
};

} // namespace dds

+ 8
- 4
src/dds/library/root.cpp Переглянути файл

@@ -55,10 +55,14 @@ library_root library_root::from_directory(path_ref lib_dir) {
auto sources = collect_pf_sources(lib_dir);

library_manifest man;
man.name = lib_dir.filename().string();
auto man_path = lib_dir / "library.dds";
if (fs::is_regular_file(man_path)) {
man = library_manifest::load_from_file(man_path);
man.name = lib_dir.filename().string();
auto found = library_manifest::find_in_directory(lib_dir);
if (found) {
if (found->extension() == ".dds") {
man = library_manifest::load_from_dds_file(*found);
} else {
man = library_manifest::load_from_file(*found);
}
}

auto lib = library_root(lib_dir, std::move(sources), std::move(man));

+ 137
- 3
src/dds/package/manifest.cpp Переглянути файл

@@ -8,11 +8,17 @@
#include <range/v3/view/split.hpp>
#include <range/v3/view/split_when.hpp>
#include <range/v3/view/transform.hpp>
#include <spdlog/fmt/fmt.h>
#include <semester/decomp.hpp>
#include <spdlog/spdlog.h>

#include <json5/parse_data.hpp>

using namespace dds;

package_manifest package_manifest::load_from_file(const fs::path& fpath) {
package_manifest package_manifest::load_from_dds_file(const fs::path& fpath) {
spdlog::warn(
"Using deprecated package.dds parsing (on file {}). This will be removed soon. Migrate!",
fpath.string());
auto kvs = lm::parse_file(fpath);
package_manifest ret;
std::string version_str;
@@ -45,7 +51,7 @@ package_manifest package_manifest::load_from_file(const fs::path& fpath) {
} else {
auto dym = *did_you_mean(test_driver_str, {"Catch-Main", "Catch"});
throw_user_error<
errc::unknown_test_driver>("Unknown 'Test-Driver' '{}' (Did you mean '{}'?)",
errc::unknown_test_driver>("Unknown 'test_driver' '{}' (Did you mean '{}'?)",
test_driver_str,
dym);
}
@@ -63,3 +69,131 @@ package_manifest package_manifest::load_from_file(const fs::path& fpath) {

return ret;
}

package_manifest package_manifest::load_from_file(const fs::path& fpath) {
auto content = slurp_file(fpath);
auto data = json5::parse_data(content);

if (!data.is_object()) {
throw_user_error<errc::invalid_pkg_manifest>("Root value must be an object");
}

package_manifest ret;
using namespace semester::decompose_ops;
auto res = semester::decompose( //
data,
try_seq{
require_type<json5::data::mapping_type>{
"The root of a package manifest must be an object (mapping)"},
mapping{
if_key{"$schema", just_accept},
if_key{
"name",
require_type<std::string>{"`name` must be a string"},
put_into{ret.pkg_id.name},
},
if_key{
"namespace",
require_type<std::string>{"`namespace` must be a string"},
put_into{ret.namespace_},
},
if_key{
"version",
require_type<std::string>{"`version` must be a string"},
[&](auto&& version_str_) {
auto& version = version_str_.as_string();
ret.pkg_id.version = semver::version::parse(version);
return semester::dc_accept;
},
},
if_key{
"depends",
require_type<json5::data::mapping_type>{
"`depends` must be a mapping between package names and version ranges"},
mapping{[&](auto pkg_name, auto&& range_str_) {
if (!range_str_.is_string()) {
throw_user_error<errc::invalid_pkg_manifest>(
"Dependency for '{}' must be a range string", pkg_name);
}
try {
auto rng = semver::range::parse_restricted(range_str_.as_string());
dependency dep{std::string(pkg_name), {rng.low(), rng.high()}};
ret.dependencies.push_back(std::move(dep));
} catch (const semver::invalid_range&) {
throw_user_error<errc::invalid_version_range_string>(
"Invalid version range string '{}' in dependency declaration for "
"'{}'",
range_str_.as_string(),
pkg_name);
}
return semester::dc_accept;
}},
},
if_key{"test_driver",
require_type<std::string>{"`test_driver` must be a string"},
[&](auto&& test_driver_str_) {
auto& test_driver = test_driver_str_.as_string();
if (test_driver == "Catch-Main") {
ret.test_driver = test_lib::catch_main;
} else if (test_driver == "Catch") {
ret.test_driver = test_lib::catch_;
} else {
auto dym = *did_you_mean(test_driver, {"Catch-Main", "Catch"});
throw_user_error<errc::unknown_test_driver>(
"Unknown 'test_driver' '{}' (Did you mean '{}'?)",
test_driver,
dym);
}
return semester::dc_accept;
}},
[&](auto key, auto&&) {
return semester::dc_reject_t{
fmt::format("Unknown key `{}` in package manifest", key)};
}}});
auto rej = std::get_if<semester::dc_reject_t>(&res);
if (rej) {
throw_user_error<errc::invalid_pkg_manifest>(rej->message);
}

if (ret.pkg_id.name.empty()) {
throw_user_error<errc::invalid_pkg_manifest>("The 'name' field is required.");
}

if (ret.namespace_.empty()) {
throw_user_error<errc::invalid_pkg_manifest>("The 'namespace'` field is required.");
}

return ret;
}

std::optional<fs::path> package_manifest::find_in_directory(path_ref dirpath) {
auto cands = {
"package.json5",
"package.jsonc",
"package.json",
};
for (auto c : cands) {
auto cand = dirpath / c;
if (fs::is_regular_file(cand)) {
return cand;
}
}

auto dds_fname = dirpath / "package.dds";
if (fs::is_regular_file(dds_fname)) {
return dds_fname;
}
return std::nullopt;
}

std::optional<package_manifest> package_manifest::load_from_directory(path_ref dirpath) {
auto found = find_in_directory(dirpath);
if (!found.has_value()) {
return std::nullopt;
}
if (found->extension() == ".dds") {
return load_from_dds_file(*found);
} else {
return load_from_file(*found);
}
}

+ 11
- 2
src/dds/package/manifest.hpp Переглянути файл

@@ -11,7 +11,7 @@
namespace dds {

/**
* Possible values for Test-Driver in a package.dds
* Possible values for test_driver in a package.json5
*/
enum class test_lib {
catch_,
@@ -26,7 +26,7 @@ struct package_manifest {
package_id pkg_id;
/// The declared `Namespace` of the package. This directly corresponds with the libman Namespace
std::string namespace_;
/// The `Test-Driver` that this package declares, or `nullopt` if absent.
/// The `test_driver` that this package declares, or `nullopt` if absent.
std::optional<test_lib> test_driver;
/// The dependencies declared with the `Depends` fields, if any.
std::vector<dependency> dependencies;
@@ -35,6 +35,15 @@ struct package_manifest {
* Load a package manifest from a file on disk.
*/
static package_manifest load_from_file(path_ref);
static package_manifest load_from_dds_file(path_ref);

/**
* Find a package manifest contained within a directory. This will search
* for a few file candidates and return the result from the first matching.
* If none match, it will return nullopt.
*/
static std::optional<fs::path> find_in_directory(path_ref);
static std::optional<package_manifest> load_from_directory(path_ref);
};

} // namespace dds

+ 18
- 16
src/dds/source/dist.cpp Переглянути файл

@@ -43,14 +43,14 @@ void sdist_copy_library(path_ref out_root, const library_root& lib, const sdist_

ranges::sort(sources_to_keep, std::less<>(), [](auto&& s) { return s.path; });

auto lib_dds_path = lib.path() / "library.dds";
if (!fs::is_regular_file(lib_dds_path)) {
auto lib_man_path = library_manifest::find_in_directory(lib.path());
if (!lib_man_path) {
throw_user_error<errc::invalid_lib_filesystem>(
"Each library root in a source distribution requires a library manifest (Expected "
"[{}])",
lib_dds_path.string());
"Each library root in a source distribution requires a library manifest (Expected a "
"library manifest in [{}])",
lib.path().string());
}
sdist_export_file(out_root, params.project_dir, lib_dds_path);
sdist_export_file(out_root, params.project_dir, *lib_man_path);

spdlog::info("sdist: Export library from {}", lib.path().string());
fs::create_directories(out_root);
@@ -88,22 +88,24 @@ sdist dds::create_sdist_in_dir(path_ref out, const sdist_params& params) {
sdist_copy_library(out, lib, params);
}

auto man_path = params.project_dir / "package.dds";
if (!fs::is_regular_file(man_path)) {
auto man_path = package_manifest::find_in_directory(params.project_dir);
if (!man_path) {
throw_user_error<errc::invalid_pkg_filesystem>(
"Creating a source distribution requires a package.dds file for the project (Expected "
"[{}])",
man_path.string());
"Creating a source distribution requires a package.json5 file for the project "
"(Expected manifest in [{}])",
params.project_dir.string());
}
sdist_export_file(out, params.project_dir, man_path);
auto pkg_man = package_manifest::load_from_file(man_path);

auto pkg_man = man_path->extension() == ".dds" ? package_manifest::load_from_dds_file(*man_path)
: package_manifest::load_from_file(*man_path);
sdist_export_file(out, params.project_dir, *man_path);
spdlog::info("Generated export as {}", pkg_man.pkg_id.to_string());

return sdist::from_directory(out);
}

sdist sdist::from_directory(path_ref where) {
auto pkg_man = package_manifest::load_from_file(where / "package.dds");
return sdist{std::move(pkg_man), where};
auto pkg_man = package_manifest::load_from_directory(where);
// Code paths should only call here if they *know* that the sdist is valid
assert(pkg_man.has_value());
return sdist{pkg_man.value(), where};
}

+ 0
- 18
src/dds/toolchain/from_dds.hpp Переглянути файл

@@ -1,18 +0,0 @@
#pragma once

#include <dds/toolchain/toolchain.hpp>

#include <libman/parse_fwd.hpp>

#include <string_view>

namespace dds {

class toolchain;

toolchain parse_toolchain_dds(std::string_view str,
std::string_view context = "Loading toolchain file");
toolchain parse_toolchain_dds(const lm::pair_list&,
std::string_view context = "Loading toolchain file");

} // namespace dds

src/dds/toolchain/from_dds.cpp → src/dds/toolchain/from_json.cpp Переглянути файл

@@ -1,69 +1,28 @@
#include "./from_dds.hpp"
#include "./from_json.hpp"

#include <dds/dym.hpp>
#include <dds/error/errors.hpp>
#include <dds/toolchain/prep.hpp>
#include <dds/toolchain/toolchain.hpp>
#include <dds/util/algo.hpp>
#include <dds/util/shlex.hpp>
#include <libman/parse.hpp>

#include <json5/parse_data.hpp>
#include <semester/decomp.hpp>
#include <spdlog/fmt/fmt.h>

#include <map>
#include <optional>
#include <tuple>
#include <vector>
#include <string>

using namespace dds;

using fmt::format;
using std::optional;
using std::string;
using std::vector;
using string_seq = vector<string>;
using opt_string = optional<string>;
using opt_str_seq = optional<string_seq>;
using strv = std::string_view;

toolchain dds::parse_toolchain_dds(strv str, strv context) {
auto kvs = lm::parse_string(str);
return parse_toolchain_dds(kvs, context);
}
using string_seq = vector<string>;
using opt_string = optional<string>;
using opt_string_seq = optional<string_seq>;
using strv = std::string_view;

namespace {
struct read_argv_acc {
strv my_key;
opt_str_seq& out;

bool operator()(strv, strv key, strv value) const {
if (key != my_key) {
return false;
}
if (!out) {
out.emplace();
}
auto cmd = split_shell_string(value);
extend(*out, cmd);
return true;
}
};

struct read_argv {
strv my_key;
opt_str_seq& out;

bool operator()(strv ctx, strv key, strv value) const {
if (key != my_key) {
return false;
}
if (out.has_value()) {
throw std::runtime_error(
format("{}: More than one value provided for key '{}'", ctx, key));
}
out.emplace(split_shell_string(value));
return true;
}
};

template <typename T, typename Func>
T read_opt(const std::optional<T>& what, Func&& fn) {
@@ -75,109 +34,185 @@ T read_opt(const std::optional<T>& what, Func&& fn) {

template <typename... Args>
[[noreturn]] void fail(strv context, strv message, Args&&... args) {
auto fmtd = format(message, args...);
throw std::runtime_error(format("{} - Failed to read toolchain file: {}", context, fmtd));
auto fmtd = fmt::format(message, args...);
throw std::runtime_error(fmt::format("{} - Failed to read toolchain file: {}", context, fmtd));
}

} // namespace

toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {
toolchain dds::parse_toolchain_json5(std::string_view j5_str, std::string_view context) {
auto dat = json5::parse_data(j5_str);
return parse_toolchain_json_data(dat, context);
}

toolchain dds::parse_toolchain_json_data(const json5::data& dat, std::string_view context) {
using namespace semester;

opt_string compiler_id;
opt_string c_compiler_fpath;
opt_string cxx_compiler_fpath;
opt_string c_compiler;
opt_string cxx_compiler;
opt_string c_version;
opt_string cxx_version;
opt_string_seq compiler_launcher;

opt_string_seq common_flags;
opt_string_seq c_flags;
opt_string_seq cxx_flags;
opt_string_seq link_flags;
opt_string_seq warning_flags;

optional<bool> do_debug;
optional<bool> do_optimize;

// Advanced-mode:
opt_string deps_mode_str;
opt_string archive_prefix;
opt_string archive_suffix;
opt_string obj_prefix;
opt_string obj_suffix;
opt_string exe_prefix;
opt_string exe_suffix;
opt_string deps_mode_str;
optional<bool> do_debug;
optional<bool> do_optimize;
opt_str_seq include_template;
opt_str_seq external_include_template;
opt_str_seq define_template;
opt_str_seq warning_flags;
opt_str_seq flags;
opt_str_seq c_flags;
opt_str_seq cxx_flags;
opt_str_seq link_flags;
opt_str_seq c_compile_file;
opt_str_seq cxx_compile_file;
opt_str_seq create_archive;
opt_str_seq link_executable;
opt_str_seq compile_launcher;

lm::read(context,
pairs,
// Base compile info:
lm::read_opt("Compiler-ID", compiler_id),
lm::read_opt("C-Compiler", c_compiler_fpath),
lm::read_opt("C++-Compiler", cxx_compiler_fpath),
// Language options
lm::read_opt("C-Version", c_version),
lm::read_opt("C++-Version", cxx_version),
// Flag templates
read_argv{"Include-Template", include_template},
read_argv{"External-Include-Template", include_template},
read_argv{"Define-Template", define_template},
// Flags
read_argv_acc{"Warning-Flags", warning_flags},
read_argv_acc{"Flags", flags},
read_argv_acc{"C-Flags", c_flags},
read_argv_acc{"C++-Flags", cxx_flags},
read_argv_acc{"Link-Flags", link_flags},
// Options for flags
lm::read_bool("Optimize", do_optimize),
lm::read_bool("Debug", do_debug),
// Miscellaneous
read_argv{"Compiler-Launcher", compile_launcher},
lm::read_opt("Deps-Mode", deps_mode_str),
// Command templates
read_argv{"C-Compile-File", c_compile_file},
read_argv{"C++-Compile-File", cxx_compile_file},
read_argv{"Create-Archive", create_archive},
read_argv{"Link-Executable", link_executable},
// Filename affixes
lm::read_opt("Archive-Prefix", archive_prefix),
lm::read_opt("Archive-Suffix", archive_suffix),
lm::read_opt("Object-Prefix", obj_prefix),
lm::read_opt("Object-Suffix", obj_suffix),
lm::read_opt("Executable-Prefix", exe_prefix),
lm::read_opt("Executable-Suffix", exe_suffix),
// Die:
lm_reject_dym{{
"Compiler-ID",
"C-Compiler",
"C++-Compiler",
"C-Version",
"C++-Version",
"Include-Template",
"External-Include-Template",
"Define-Template",
"Warning-Flags",
"Flags",
"C-Flags",
"C++-Flags",
"Link-Flags",
"Optimize",
"Debug",
"Compiler-Launcher",
"Deps-Mode",
"C-Compile-File",
"C++-Compile-File",
"Create-Archive",
"Link-Executable",
"Archive-Prefix",
"Archive-Suffix",
"Object-Prefix",
"Object-Suffix",
"Executable-Prefix",
"Executable-Suffix",
}});
opt_string_seq base_warning_flags;
opt_string_seq include_template;
opt_string_seq external_include_template;
opt_string_seq define_template;
opt_string_seq c_compile_file;
opt_string_seq cxx_compile_file;
opt_string_seq create_archive;
opt_string_seq link_executable;

// For copy-pasting convenience: ‘{}’

auto extend_flags = [&](string key, auto& opt_flags) {
return [&opt_flags, key](const json5::data& dat) {
if (!opt_flags) {
opt_flags.emplace();
}
return decompose( //
dat,
try_seq{
if_type<string>([&](auto& str_) {
auto more_flags = split_shell_string(str_.as_string());
extend(*opt_flags, more_flags);
return dc_accept;
}),
if_array{for_each{
require_type<string>{
fmt::format("Elements of `{}` array must be strings", key)},
write_to{std::back_inserter(*opt_flags)},
}},
reject_with{fmt::format("`{}` must be an array or a shell-like string", key)},
});
};
};

toolchain_prep tc;
#define KEY_EXTEND_FLAGS(Name) \
if_key { #Name, extend_flags(#Name, Name) }

#define KEY_STRING(Name) \
if_key { #Name, require_type < string>("`" #Name "` must be a string"), put_into{Name }, }

auto result = semester::decompose( //
dat,
try_seq{
require_type<json5::data::mapping_type>("Root of toolchain data must be a mapping"),
mapping{
if_key{"$schema", just_accept},
KEY_STRING(compiler_id),
KEY_STRING(c_compiler),
KEY_STRING(cxx_compiler),
KEY_STRING(c_version),
KEY_STRING(cxx_version),
KEY_EXTEND_FLAGS(c_flags),
KEY_EXTEND_FLAGS(cxx_flags),
KEY_EXTEND_FLAGS(warning_flags),
KEY_EXTEND_FLAGS(link_flags),
KEY_EXTEND_FLAGS(compiler_launcher),
if_key{"debug",
require_type<bool>("`debug` must be a boolean value"),
put_into{do_debug}},
if_key{"optimize",
require_type<bool>("`optimize` must be a boolean value"),
put_into{do_optimize}},
if_key{"flags", extend_flags("flags", common_flags)},
if_key{
"advanced",
require_type<json5::data::mapping_type>("`advanced` must be a mapping"),
mapping{
if_key{"deps_mode",
require_type<string>("`deps_mode` must be a string"),
put_into{deps_mode_str}},
KEY_EXTEND_FLAGS(include_template),
KEY_EXTEND_FLAGS(external_include_template),
KEY_EXTEND_FLAGS(define_template),
KEY_EXTEND_FLAGS(base_warning_flags),
KEY_EXTEND_FLAGS(c_compile_file),
KEY_EXTEND_FLAGS(cxx_compile_file),
KEY_EXTEND_FLAGS(create_archive),
KEY_EXTEND_FLAGS(link_executable),
KEY_STRING(obj_prefix),
KEY_STRING(obj_suffix),
KEY_STRING(archive_prefix),
KEY_STRING(archive_suffix),
KEY_STRING(exe_prefix),
KEY_STRING(exe_suffix),
[&](auto key, auto) -> dc_reject_t {
auto dym = did_you_mean(key,
{
"deps_mode",
"include_template",
"external_include_template",
"define_template",
"base_warning_flags",
"c_compile_file",
"cxx_compile_file",
"create_archive",
"link_executable",
"obj_prefix",
"obj_suffix",
"archive_prefix",
"archive_suffix",
"exe_prefix",
"exe_suffix",
});
fail(context,
"Unknown toolchain advanced-config key ‘{}’ (Did you mean ‘{}’?)",
key,
*dym);
std::terminate();
},
},
},
[&](auto key, auto &&) -> dc_reject_t {
// They've given an unknown key. Ouch.
auto dym = did_you_mean(key,
{
"compiler_id",
"c_compiler",
"cxx_compiler",
"c_version",
"cxx_version",
"c_flags",
"cxx_flags",
"warning_flags",
"link_flags",
"flags",
"debug",
"optimize",
});
fail(context,
"Unknown toolchain config key ‘{}’ (Did you mean ‘{}’?)",
key,
*dym);
std::terminate();
},
},
});

auto rej_opt = std::get_if<dc_reject_t>(&result);
if (rej_opt) {
fail(context, rej_opt->message);
}

enum compiler_id_e_t {
no_comp_id,
@@ -188,14 +223,14 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {
= [&] {
if (!compiler_id) {
return no_comp_id;
} else if (compiler_id == "MSVC") {
} else if (compiler_id == "msvc") {
return msvc;
} else if (compiler_id == "GNU") {
} else if (compiler_id == "gnu") {
return gnu;
} else if (compiler_id == "Clang") {
} else if (compiler_id == "clang") {
return clang;
} else {
fail(context, "Unknown Compiler-ID '{}'", *compiler_id);
fail(context, "Invalid `compiler_id` value ‘{}’", *compiler_id);
}
}();

@@ -213,27 +248,32 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {
} else {
return file_deps_mode::none;
}
} else if (deps_mode_str == "GNU") {
} else if (deps_mode_str == "gnu") {
return file_deps_mode::gnu;
} else if (deps_mode_str == "MSVC") {
} else if (deps_mode_str == "msvc") {
return file_deps_mode::msvc;
} else if (deps_mode_str == "None") {
} else if (deps_mode_str == "none") {
return file_deps_mode::none;
} else {
fail(context, "Unknown Deps-Mode '{}'", *deps_mode_str);
fail(context, "Invalid `deps_mode` value ‘{}’", *deps_mode_str);
}
}();

// Now convert the flags we've been given into a real toolchain
auto get_compiler = [&](language lang) -> string {
if (lang == language::cxx && cxx_compiler_fpath) {
return *cxx_compiler_fpath;
auto get_compiler_executable_path = [&](language lang) -> string {
if (lang == language::cxx && cxx_compiler) {
return *cxx_compiler;
}
if (lang == language::c && c_compiler_fpath) {
return *c_compiler_fpath;
if (lang == language::c && c_compiler) {
return *c_compiler;
}
if (!compiler_id.has_value()) {
fail(context, "Unable to determine what compiler to use.");
if (lang == language::c) {
fail(context, "Unable to determine the executable for a C compiler");
}
if (lang == language::cxx) {
fail(context, "Unable to determine the executable for a C++ compiler");
}
}
if (is_gnu) {
return (lang == language::cxx) ? "g++" : "gcc";
@@ -248,6 +288,7 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {
std::terminate();
};

// Determine the C language version
enum c_version_e_t {
c_none,
c89,
@@ -258,16 +299,16 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {
= [&] {
if (!c_version) {
return c_none;
} else if (c_version == "C89") {
} else if (c_version == "c89") {
return c89;
} else if (c_version == "C99") {
} else if (c_version == "c99") {
return c99;
} else if (c_version == "C11") {
} else if (c_version == "c11") {
return c11;
} else if (c_version == "C18") {
} else if (c_version == "c18") {
return c18;
} else {
fail(context, "Unknown C-Version '{}'", *c_version);
fail(context, "Unknown `c_version` ‘{}’", *c_version);
}
}();

@@ -283,20 +324,20 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {
= [&] {
if (!cxx_version) {
return cxx_none;
} else if (cxx_version == "C++98") {
} else if (cxx_version == "c++98") {
return cxx98;
} else if (cxx_version == "C++03") {
} else if (cxx_version == "c++03") {
return cxx03;
} else if (cxx_version == "C++11") {
} else if (cxx_version == "c++11") {
return cxx11;
} else if (cxx_version == "C++14") {
} else if (cxx_version == "c++14") {
return cxx14;
} else if (cxx_version == "C++17") {
} else if (cxx_version == "c++17") {
return cxx17;
} else if (cxx_version == "C++20") {
} else if (cxx_version == "c++20") {
return cxx20;
} else {
fail(context, "Unknown C++-Version '{}'", *cxx_version);
fail(context, "Unknown `cxx_version` ‘{}’", *cxx_version);
}
}();

@@ -320,7 +361,7 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {

auto get_c_version_flags = [&]() -> string_seq {
if (!compiler_id.has_value()) {
fail(context, "Unable to deduce flags for 'C-Version' without setting 'Compiler-ID'");
fail(context, "Unable to deduce flags for 'c_version' without setting 'compiler_id'");
}
auto c_ver_iter = c_version_flag_table.find({compiler_id_e, c_version_e});
assert(c_ver_iter != c_version_flag_table.end());
@@ -353,7 +394,7 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {

auto get_cxx_version_flags = [&]() -> string_seq {
if (!compiler_id.has_value()) {
fail(context, "Unable to deduce flags for 'C++-Version' without setting 'Compiler-ID'");
fail(context, "Unable to deduce flags for 'cxx_version' without setting 'compiler_id'");
}
auto cxx_ver_iter = cxx_version_flag_table.find({compiler_id_e, cxx_version_e});
assert(cxx_ver_iter != cxx_version_flag_table.end());
@@ -388,18 +429,6 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {

auto get_flags = [&](language lang) -> string_seq {
string_seq ret;
if (lang == language::cxx && cxx_flags) {
extend(ret, *cxx_flags);
}
if (lang == language::cxx && cxx_version) {
extend(ret, get_cxx_version_flags());
}
if (lang == language::c && c_flags) {
extend(ret, *c_flags);
}
if (lang == language::c && c_version) {
extend(ret, get_c_version_flags());
}
if (is_msvc) {
strv rt_lib = "/MT";
if (do_optimize.has_value() && *do_optimize) {
@@ -430,44 +459,56 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {
"<IN>",
"-o<OUT>"});
}
if (flags) {
extend(ret, *flags);
if (common_flags) {
extend(ret, *common_flags);
}
if (lang == language::cxx && cxx_flags) {
extend(ret, *cxx_flags);
}
if (lang == language::cxx && cxx_version) {
extend(ret, get_cxx_version_flags());
}
if (lang == language::c && c_flags) {
extend(ret, *c_flags);
}
if (lang == language::c && c_version) {
extend(ret, get_c_version_flags());
}
return ret;
};

toolchain_prep tc;
tc.deps_mode = deps_mode;

tc.c_compile = read_opt(c_compile_file, [&] {
string_seq c;
if (compile_launcher) {
extend(c, *compile_launcher);
if (compiler_launcher) {
extend(c, *compiler_launcher);
}
c.push_back(get_compiler(language::c));
c.push_back(get_compiler_executable_path(language::c));
extend(c, get_flags(language::c));
return c;
});

tc.cxx_compile = read_opt(cxx_compile_file, [&] {
string_seq cxx;
if (compile_launcher) {
extend(cxx, *compile_launcher);
if (compiler_launcher) {
extend(cxx, *compiler_launcher);
}
cxx.push_back(get_compiler(language::cxx));
cxx.push_back(get_compiler_executable_path(language::cxx));
extend(cxx, get_flags(language::cxx));
return cxx;
});

tc.include_template = read_opt(include_template, [&]() -> string_seq {
if (!compiler_id) {
fail(context, "Cannot deduce 'Include-Template' without 'Compiler-ID'");
fail(context, "Cannot deduce 'include_template' without 'compiler_id'");
}
if (is_gnu_like) {
return {"-I", "<PATH>"};
} else if (is_msvc) {
return {"/I", "<PATH>"};
}
assert(false && "Include-Template deduction failed");
assert(false && "'include_template' deduction failed");
std::terminate();
});

@@ -482,20 +523,20 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {
// MSVC has external-header support inbound, but it is not fully ready yet
return {"/I", "<PATH>"};
}
assert(false && "External-Include-Template deduction failed");
assert(false && "external_include_template deduction failed");
std::terminate();
});

tc.define_template = read_opt(define_template, [&]() -> string_seq {
if (!compiler_id) {
fail(context, "Cannot deduce 'Define-Template' without 'Compiler-ID'");
fail(context, "Cannot deduce 'define_template' without 'compiler_id'");
}
if (is_gnu_like) {
return {"-D", "<DEF>"};
} else if (is_msvc) {
return {"/D", "<DEF>"};
}
assert(false && "Define-Template deduction failed");
assert(false && "define_template deduction failed");
std::terminate();
});

@@ -536,6 +577,7 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {
#endif
});

/// TODO: Handle base_warning_flags:
tc.warning_flags = read_opt(warning_flags, [&]() -> string_seq {
if (!compiler_id) {
// No error. Just no warning flags
@@ -552,7 +594,7 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {

tc.link_archive = read_opt(create_archive, [&]() -> string_seq {
if (!compiler_id) {
fail(context, "Unable to deduce archive creation rules without a Compiler-ID");
fail(context, "Unable to deduce archive creation rules without a 'compiler_id'");
}
if (is_msvc) {
return {"lib", "/nologo", "/OUT:<OUT>", "<IN>"};
@@ -565,13 +607,17 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {

tc.link_exe = read_opt(link_executable, [&]() -> string_seq {
if (!compiler_id) {
fail(context, "Unable to deduce how to link executables without a Compiler-ID");
fail(context, "Unable to deduce how to link executables without a 'compiler_id'");
}
string_seq ret;
if (is_msvc) {
ret = {get_compiler(language::cxx), "/nologo", "/EHsc", "<IN>", "/Fe<OUT>"};
ret = {get_compiler_executable_path(language::cxx),
"/nologo",
"/EHsc",
"<IN>",
"/Fe<OUT>"};
} else if (is_gnu_like) {
ret = {get_compiler(language::cxx),
ret = {get_compiler_executable_path(language::cxx),
"-fPIC",
"-fdiagnostics-color",
"<IN>",
@@ -586,4 +632,4 @@ toolchain dds::parse_toolchain_dds(const lm::pair_list& pairs, strv context) {
});

return tc.realize();
}
}

+ 17
- 0
src/dds/toolchain/from_json.hpp Переглянути файл

@@ -0,0 +1,17 @@
#pragma once

#include <dds/toolchain/toolchain.hpp>

#include <json5/data.hpp>

#include <string_view>

namespace dds {

toolchain parse_toolchain_json5(std::string_view json5,
std::string_view context = "Loading toolchain JSON");

toolchain parse_toolchain_json_data(const json5::data& data,
std::string_view context = "Loading toolchain JSON");

} // namespace dds

src/dds/toolchain/from_dds.test.cpp → src/dds/toolchain/from_json.test.cpp Переглянути файл

@@ -1,18 +1,16 @@
#include <dds/toolchain/from_dds.hpp>
#include <dds/toolchain/from_json.hpp>

#include <dds/proc.hpp>

// #include <dds/util.test.hpp>
#include <catch2/catch.hpp>

namespace {

void check_tc_compile(std::string_view tc_content,
std::string_view expected_compile,
std::string_view expected_compile_warnings,
std::string_view expected_ar,
std::string_view expected_exe) {
auto tc = dds::parse_toolchain_dds(tc_content);
auto tc = dds::parse_toolchain_json5(tc_content);

dds::compile_file_spec cf;
cf.source_path = "foo.cpp";
@@ -43,9 +41,11 @@ void check_tc_compile(std::string_view tc_content,
CHECK(exe_cmd_str == expected_exe);
}

} // namespace

TEST_CASE("Generating toolchain commands") {
check_tc_compile(
"Compiler-ID: GNU",
"{compiler_id: 'gnu'}",
"g++ -fPIC -fdiagnostics-color -pthread -MD -MF foo.o.d -MT foo.o -c foo.cpp -ofoo.o",
"g++ -fPIC -fdiagnostics-color -pthread -Wall -Wextra -Wpedantic -Wconversion "
"-MD -MF foo.o.d -MT foo.o -c foo.cpp -ofoo.o",
@@ -53,7 +53,7 @@ TEST_CASE("Generating toolchain commands") {
"g++ -fPIC -fdiagnostics-color foo.o bar.a -pthread -omeow.exe");

check_tc_compile(
"Compiler-ID: GNU\nDebug: True",
"{compiler_id: 'gnu', debug: true}",
"g++ -g -fPIC -fdiagnostics-color -pthread -MD -MF foo.o.d -MT foo.o -c foo.cpp -ofoo.o",
"g++ -g -fPIC -fdiagnostics-color -pthread -Wall -Wextra -Wpedantic -Wconversion "
"-MD -MF foo.o.d -MT foo.o -c foo.cpp -ofoo.o",
@@ -61,7 +61,7 @@ TEST_CASE("Generating toolchain commands") {
"g++ -fPIC -fdiagnostics-color foo.o bar.a -pthread -omeow.exe -g");

check_tc_compile(
"Compiler-ID: GNU\nDebug: True\nOptimize: True",
"{compiler_id: 'gnu', debug: true, optimize: true}",
"g++ -O2 -g -fPIC -fdiagnostics-color -pthread -MD -MF foo.o.d -MT foo.o -c foo.cpp "
"-ofoo.o",
"g++ -O2 -g -fPIC -fdiagnostics-color -pthread -Wall -Wextra -Wpedantic -Wconversion "
@@ -69,22 +69,30 @@ TEST_CASE("Generating toolchain commands") {
"ar rcs stuff.a foo.o bar.o",
"g++ -fPIC -fdiagnostics-color foo.o bar.a -pthread -omeow.exe -O2 -g");

check_tc_compile("Compiler-ID: MSVC",
check_tc_compile("{compiler_id: 'msvc'}",
"cl.exe /MT /EHsc /nologo /permissive- /showIncludes /c foo.cpp /Fofoo.o",
"cl.exe /MT /EHsc /nologo /permissive- /W4 /showIncludes /c foo.cpp /Fofoo.o",
"lib /nologo /OUT:stuff.a foo.o bar.o",
"cl.exe /nologo /EHsc foo.o bar.a /Femeow.exe /MT");

check_tc_compile(
"Compiler-ID: MSVC\nDebug: True",
"{compiler_id: 'msvc', debug: true}",
"cl.exe /Z7 /DEBUG /MTd /EHsc /nologo /permissive- /showIncludes /c foo.cpp /Fofoo.o",
"cl.exe /Z7 /DEBUG /MTd /EHsc /nologo /permissive- /W4 /showIncludes /c foo.cpp /Fofoo.o",
"lib /nologo /OUT:stuff.a foo.o bar.o",
"cl.exe /nologo /EHsc foo.o bar.a /Femeow.exe /Z7 /DEBUG /MTd");

auto tc = dds::parse_toolchain_dds(R"(
Compiler-ID: GNU
)");
check_tc_compile(
"{compiler_id: 'msvc', flags: '-DFOO'}",
"cl.exe /MT /EHsc /nologo /permissive- /showIncludes /c foo.cpp /Fofoo.o -DFOO",
"cl.exe /MT /EHsc /nologo /permissive- /W4 /showIncludes /c foo.cpp /Fofoo.o -DFOO",
"lib /nologo /OUT:stuff.a foo.o bar.o",
"cl.exe /nologo /EHsc foo.o bar.a /Femeow.exe /MT");
}

TEST_CASE("Manipulate a toolchain and file compilation") {

auto tc = dds::parse_toolchain_json5("{compiler_id: 'gnu'}");

dds::compile_file_spec cfs;
cfs.source_path = "foo.cpp";
@@ -141,6 +149,4 @@ TEST_CASE("Generating toolchain commands") {
"-c",
"foo.cpp",
"-ofoo.o"});
}

} // namespace
}

+ 30
- 23
src/dds/toolchain/toolchain.cpp Переглянути файл

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

#include <dds/toolchain/from_dds.hpp>
#include <dds/toolchain/from_json.hpp>
#include <dds/toolchain/prep.hpp>
#include <dds/util/algo.hpp>
#include <dds/util/paths.hpp>
@@ -148,31 +148,32 @@ vector<string> toolchain::create_link_executable_command(const link_exe_spec& sp
std::optional<toolchain> toolchain::get_builtin(std::string_view tc_id) noexcept {
using namespace std::literals;

std::string tc_content;
json5::data tc_data = json5::data::mapping_type();
auto& root_map = tc_data.as_object();

if (starts_with(tc_id, "debug:")) {
tc_id = tc_id.substr("debug:"sv.length());
tc_content += "Debug: True\n";
root_map.emplace("debug", true);
}

if (starts_with(tc_id, "ccache:")) {
tc_id = tc_id.substr("ccache:"sv.length());
tc_content += "Compiler-Launcher: ccache\n";
root_map.emplace("compiler_launcher", "ccache");
}

#define CXX_VER_TAG(str, version) \
if (starts_with(tc_id, str)) { \
tc_id = tc_id.substr(std::string_view(str).length()); \
tc_content += "C++-Version: "s + version + "\n"; \
root_map.emplace("cxx_version", version); \
} \
static_assert(true)

CXX_VER_TAG("c++98:", "C++98");
CXX_VER_TAG("c++03:", "C++03");
CXX_VER_TAG("c++11:", "C++11");
CXX_VER_TAG("c++14:", "C++14");
CXX_VER_TAG("c++17:", "C++17");
CXX_VER_TAG("c++20:", "C++20");
CXX_VER_TAG("c++98:", "c++98");
CXX_VER_TAG("c++03:", "c++03");
CXX_VER_TAG("c++11:", "c++11");
CXX_VER_TAG("c++14:", "c++14");
CXX_VER_TAG("c++17:", "c++17");
CXX_VER_TAG("c++20:", "c++20");

struct compiler_info {
string c;
@@ -187,9 +188,9 @@ std::optional<toolchain> toolchain::get_builtin(std::string_view tc_id) noexcept

const auto [c_compiler_base, cxx_compiler_base, compiler_id] = [&]() -> compiler_info {
if (is_gcc) {
return {"gcc", "g++", "GNU"};
return {"gcc", "g++", "gnu"};
} else if (is_clang) {
return {"clang", "clang++", "Clang"};
return {"clang", "clang++", "clang"};
}
assert(false && "Unreachable");
std::terminate();
@@ -221,7 +222,7 @@ std::optional<toolchain> toolchain::get_builtin(std::string_view tc_id) noexcept
auto cxx_compiler_name = cxx_compiler_base + compiler_suffix;
return compiler_info{c_compiler_name, cxx_compiler_name, compiler_id};
} else if (tc_id == "msvc") {
return compiler_info{"cl.exe", "cl.exe", "MSVC"};
return compiler_info{"cl.exe", "cl.exe", "msvc"};
} else {
return std::nullopt;
}
@@ -231,22 +232,28 @@ std::optional<toolchain> toolchain::get_builtin(std::string_view tc_id) noexcept
return std::nullopt;
}

tc_content += "C-Compiler: "s + opt_triple->c + "\n";
tc_content += "C++-Compiler: "s + opt_triple->cxx + "\n";
tc_content += "Compiler-ID: " + opt_triple->id + "\n";
return parse_toolchain_dds(tc_content);
root_map.emplace("c_compiler", opt_triple->c);
root_map.emplace("cxx_compiler", opt_triple->cxx);
root_map.emplace("compiler_id", opt_triple->id);
return parse_toolchain_json_data(tc_data);
}

std::optional<dds::toolchain> dds::toolchain::get_default() {
auto candidates = {
fs::current_path() / "toolchain.dds",
dds_config_dir() / "toolchain.dds",
user_home_dir() / "toolchain.dds",
fs::current_path() / "toolchain.json5",
fs::current_path() / "toolchain.jsonc",
fs::current_path() / "toolchain.json",
dds_config_dir() / "toolchain.json5",
dds_config_dir() / "toolchain.jsonc",
dds_config_dir() / "toolchain.json",
user_home_dir() / "toolchain.json5",
user_home_dir() / "toolchain.jsonc",
user_home_dir() / "toolchain.json",
};
for (auto&& cand : candidates) {
if (fs::exists(cand)) {
return parse_toolchain_dds(slurp_file(cand));
return parse_toolchain_json5(slurp_file(cand));
}
}
return std::nullopt;
}
}

+ 6
- 3
tests/basics/test_basics.py Переглянути файл

@@ -17,9 +17,12 @@ def test_build_simple(dds: DDS):

def basic_pkg_dds(dds: DDS):
return set_contents(
dds.source_root / 'package.dds', b'''
Name: test-pkg
Version: 0.2.2
dds.source_root / 'package.json5', b'''
{
name: 'test-pkg',
version: '0.2.2',
namespace: 'test',
}
''')



+ 6
- 2
tests/catalog/get_test.py Переглянути файл

@@ -3,6 +3,8 @@ import json
from tests import dds, DDS
from tests.fileutil import ensure_dir

import pytest


def test_get(dds: DDS):
dds.scope.enter_context(ensure_dir(dds.build_dir))
@@ -16,8 +18,10 @@ def test_get(dds: DDS):
'0.2.2': {
'depends': {},
'git': {
'url': 'https://github.com/vector-of-bool/neo-sqlite3.git',
'ref': '0.2.2',
'url':
'https://github.com/vector-of-bool/neo-sqlite3.git',
'ref':
'0.2.2',
},
},
},

+ 1
- 1
tests/deps/git-remote/catalog.json Переглянути файл

@@ -5,7 +5,7 @@
"0.1.0": {
"git": {
"url": "https://github.com/vector-of-bool/neo-buffer.git",
"ref": "develop"
"ref": "0.1.0"
},
"depends": {}
}

+ 0
- 5
tests/deps/git-remote/package.dds Переглянути файл

@@ -1,5 +0,0 @@
Name: deps-test
Version: 0.0.0

Depends: neo-buffer 0.1.0
Depends: range-v3 0.9.1

+ 9
- 0
tests/deps/git-remote/package.json5 Переглянути файл

@@ -0,0 +1,9 @@
{
name: 'deps-test',
"namespace": "test",
version: '0.0.0',
depends: {
'neo-buffer': '0.1.0',
'range-v3': '0.9.1',
}
}

+ 0
- 2
tests/deps/no-deps/package.dds Переглянути файл

@@ -1,2 +0,0 @@
Name: deps-test
Version: 0.0.0

+ 5
- 0
tests/deps/no-deps/package.json5 Переглянути файл

@@ -0,0 +1,5 @@
{
name: 'deps-test',
version: '0.0.0',
"namespace": "test",
}

+ 0
- 3
tests/deps/use-remote/library.dds Переглянути файл

@@ -1,3 +0,0 @@
Name: dummy

Uses: nlohmann/json

+ 6
- 0
tests/deps/use-remote/library.json5 Переглянути файл

@@ -0,0 +1,6 @@
{
name: "dummy",
uses: [
'nlohmann/json',
]
}

+ 0
- 4
tests/deps/use-remote/package.dds Переглянути файл

@@ -1,4 +0,0 @@
Name: json-test
Version: 0.0.0

Depends: nlohmann-json 3.7.1

+ 8
- 0
tests/deps/use-remote/package.json5 Переглянути файл

@@ -0,0 +1,8 @@
{
"name": "json-test",
"version": "0.0.0",
"namespace": "test",
"depends": {
"nlohmann-json": "3.7.1"
}
}

+ 6
- 0
tests/deps/use-spdlog/gcc.tc.jsonc Переглянути файл

@@ -0,0 +1,6 @@
{
"compiler_id": 'gnu',
"cxx_version": 'c++17',
"cxx_compiler": 'g++-9',
"flags": '-DSPDLOG_COMPILED_LIB',
}

+ 4
- 0
tests/deps/use-spdlog/msvc.tc.jsonc Переглянути файл

@@ -0,0 +1,4 @@
{
"compiler_id": 'msvc',
"flags": '-DSPDLOG_COMPILED_LIB',
}

+ 0
- 3
tests/deps/use-spdlog/project/library.dds Переглянути файл

@@ -1,3 +0,0 @@
Name: spdlog-user

Uses: spdlog/spdlog

+ 6
- 0
tests/deps/use-spdlog/project/library.json5 Переглянути файл

@@ -0,0 +1,6 @@
{
name: 'spdlog-user',
uses: [
'spdlog/spdlog',
]
}

+ 0
- 4
tests/deps/use-spdlog/project/package.dds Переглянути файл

@@ -1,4 +0,0 @@
Name: test
Version: 0.0.0

Depends: spdlog 1.4.2

+ 8
- 0
tests/deps/use-spdlog/project/package.json5 Переглянути файл

@@ -0,0 +1,8 @@
{
name: 'test',
version: '0.0.0',
"namespace": "test",
depends: {
'spdlog': '1.4.2',
},
}

+ 1
- 1
tests/deps/use-spdlog/use_spdlog_test.py Переглянути файл

@@ -5,7 +5,7 @@ from dds_ci import proc

def test_get_build_use_spdlog(dds: DDS):
dds.catalog_import(dds.source_root / 'catalog.json')
tc_fname = 'gcc.tc.dds' if 'gcc' in dds.default_builtin_toolchain else 'msvc.tc.dds'
tc_fname = 'gcc.tc.jsonc' if 'gcc' in dds.default_builtin_toolchain else 'msvc.tc.jsonc'
tc = str(dds.test_dir / tc_fname)
dds.build(toolchain=tc, apps=True)
proc.check_run((dds.build_dir / 'use-spdlog').with_suffix(dds.exe_suffix))

+ 0
- 1
tests/sdist/create/library.dds Переглянути файл

@@ -1 +0,0 @@
Name: foo

+ 3
- 0
tests/sdist/create/library.jsonc Переглянути файл

@@ -0,0 +1,3 @@
{
"name": "foo"
}

+ 0
- 2
tests/sdist/create/package.dds Переглянути файл

@@ -1,2 +0,0 @@
Name: foo
Version: 1.2.3

+ 5
- 0
tests/sdist/create/package.json5 Переглянути файл

@@ -0,0 +1,5 @@
{
name: 'foo',
version: '1.2.3',
"namespace": "test",
}

+ 0
- 4
tests/test_drivers/catch/custom-runner/package.dds Переглянути файл

@@ -1,4 +0,0 @@
Name: Test
Version: 0.0.0

Test-Driver: Catch

+ 6
- 0
tests/test_drivers/catch/custom-runner/package.json5 Переглянути файл

@@ -0,0 +1,6 @@
{
"name": "test",
"version": "0.0.0",
"namespace": "test",
"test_driver": "Catch",
}

+ 0
- 4
tests/test_drivers/catch/main/package.dds Переглянути файл

@@ -1,4 +0,0 @@
Name: Test
Version: 0.0.0

Test-Driver: Catch-Main

+ 6
- 0
tests/test_drivers/catch/main/package.json5 Переглянути файл

@@ -0,0 +1,6 @@
{
"name": "test",
"version": "0.0.0",
"namespace": "test",
"test_driver": "Catch-Main",
}

+ 10
- 2
tools/ci.py Переглянути файл

@@ -14,6 +14,7 @@ from dds_ci import paths, proc

class CIOptions(NamedTuple):
toolchain: str
toolchain_json5: str


def _do_bootstrap_build(opts: CIOptions) -> None:
@@ -67,9 +68,16 @@ def main(argv: Sequence[str]) -> int:
'-T',
help='The toolchain to use for the CI process',
required=True)
parser.add_argument(
'--toolchain-json5',
'-T2',
help='The toolchain JSON to use with the bootstrapped executable',
required=True,
)
args = parser.parse_args(argv)

opts = CIOptions(toolchain=args.toolchain)
opts = CIOptions(
toolchain=args.toolchain, toolchain_json5=args.toolchain_json5)

if args.bootstrap_with == 'build':
_do_bootstrap_build(opts)
@@ -117,7 +125,7 @@ def main(argv: Sequence[str]) -> int:
])
self_build(
paths.CUR_BUILT_DDS,
toolchain=opts.toolchain,
toolchain=opts.toolchain_json5,
dds_flags=[f'--repo-dir={ci_repo_dir}', f'--catalog={cat_path}'])
print('Bootstrap test PASSED!')


+ 3
- 3
tools/gcc-9.dds Переглянути файл

@@ -5,7 +5,7 @@ C++-Compiler: g++-9
# Range-v3 0.10.0 contains an accidental conversion warning
Flags: -D SPDLOG_COMPILED_LIB -Werror=return-type -Wno-conversion
C++-Flags: -fconcepts
Link-Flags: -static-libgcc -static-libstdc++
# Debug: True
Optimize: True
# Link-Flags: -static-libgcc -static-libstdc++
Debug: True
#Optimize: True
Compiler-Launcher: ccache

+ 17
- 0
tools/gcc-9.jsonc Переглянути файл

@@ -0,0 +1,17 @@
{
"$schema": "../res/toolchain-schema.json",
"compiler_id": "gnu",
"c_compiler": "gcc-9",
"cxx_compiler": "g++-9",
"cxx_version": "c++17",
"flags": [
"-DSPDLOG_COMPILED_LIB", // Required to use a compiled spdlog
"-Werror=return-type"
],
"cxx_flags": [
"-fconcepts"
],
// "debug": true,
"optimize": true,
"compiler_launcher": "ccache"
}

+ 18
- 1
tools/gen-catalog-json.py Переглянути файл

@@ -30,7 +30,7 @@ class Version(NamedTuple):
ret: dict = {
'description': self.description,
}
ret['depends'] = {}
ret['depends'] = self.depends
if isinstance(self.remote, Git):
ret['git'] = self.remote.to_dict()
return ret
@@ -150,6 +150,23 @@ packages = [
'A C++ implementation of the Pubgrub version solving algorithm',
git_url='https://github.com/vector-of-bool/pubgrub.git',
),
many_versions(
'vob-json5',
('0.1.5', ),
description='A C++ implementation of a JSON5 parser',
git_url='https://github.com/vector-of-bool/json5.git',
),
Package('vob-semester', [
Version(
'0.1.0',
description='A C++ library to process recursive dynamic data',
remote=Git('https://github.com/vector-of-bool/semester.git',
'0.1.0'),
depends={
'neo-fun': '^0.1.0',
'neo-concepts': '^0.2.1',
}),
]),
many_versions(
'spdlog',
(

+ 14
- 0
tools/msvc.jsonc Переглянути файл

@@ -0,0 +1,14 @@
{
"$schema": "../res/toolchain-schema.json",
"compiler_id": "msvc",
"flags": [
"/experimental:preprocessor", // Required for range-v3
"/DSPDLOG_COMPILED_LIB", // Required to use spdlog as a compiled lib
"/std:c++latest",
],
"link_flags": [
"rpcrt4.lib",
],
// "debug": true,
"optimize": true
}

Завантаження…
Відмінити
Зберегти