echo Executing Build and Tests | echo Executing Build and Tests | ||||
reg add HKLM\SYSTEM\CurrentControlSet\Control\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1 /f || exit 1 | 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 -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 | displayName: Full CI | ||||
- publish: _build/dds.exe | - publish: _build/dds.exe | ||||
artifact: DDS Executable - Windows VS2019 | artifact: DDS Executable - Windows VS2019 | ||||
sudo apt install -y python3-minimal g++-9 ccache | sudo apt install -y python3-minimal g++-9 ccache | ||||
python3 -m pip install pytest pytest-xdist | python3 -m pip install pytest pytest-xdist | ||||
displayName: Prepare System | 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 | displayName: Full CI | ||||
- publish: _build/dds | - publish: _build/dds | ||||
artifact: DDS Executable - Linux | artifact: DDS Executable - Linux | ||||
- script: | | - script: | | ||||
set -eu | set -eu | ||||
python3 -m pip install pytest pytest-xdist | 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 | displayName: Build and Run Unit Tests | ||||
- publish: _build/dds | - publish: _build/dds | ||||
artifact: DDS Executable - macOS | artifact: DDS Executable - macOS |
"url": "https://github.com/gabime/spdlog.git" | "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 | "version": 1 |
The "namespace" of a library in this case is arbitrary and not necessarily | The "namespace" of a library in this case is arbitrary and not necessarily | ||||
associated with any C++ ``namespace``. | 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 | 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. | 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 | 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 | 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. | with the maintainers of the packages in question. | ||||
.. seealso:: | .. seealso:: |
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. |
and is thoroughly explained on the :doc:`/guide/packages` page. | and is thoroughly explained on the :doc:`/guide/packages` page. | ||||
For exporting/generating a source distribution from a package, the *package | 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, | TODO: Create are more detailed reference page for package and library layout, |
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. |
This error message is printed when a project's tests encounter a failure | 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 | 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 | If you see this error, it is most likely that you have an issue in the tests of | ||||
your project. | your project. |
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 | that the driver you want to use is supported by ``dds``. Refer to the | ||||
:doc:`/guide/packages` page. | :doc:`/guide/packages` page. |
######################################### | ######################################### | ||||
A library can declare that it *uses* or *links* to another library by using the | 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 | 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 | known library, ``dds`` will not be able to resolve the usage requirements, and | ||||
will generate an error. | will generate an error. | ||||
dds catalog add <package-id> | dds catalog add <package-id> | ||||
[--depends <requirement> [--depends <requirement> [...]]] | [--depends <requirement> [--depends <requirement> [...]]] | ||||
[--git-url <url> --git-ref <ref>] | [--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 | The ``<package-id>`` positional arguments is the ``name@version`` package ID | ||||
that will be added to the catalog. The following options are supported: | that will be added to the catalog. The following options are supported: | ||||
``--depends <requirement>`` | ``--depends <requirement>`` | ||||
This argument, which can be specified multiple times to represent multiple | This argument, which can be specified multiple times to represent multiple | ||||
dependencies, sets the dependencies of the package within the catalog. If | 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>`` | ``--git-url <url>`` | ||||
Specify a Git URL to clone from to obtain the package. The root of the | 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 | 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. | ``--auto-lib`` parameter. | ||||
``--git-ref`` **must** be passed with ``--git-url``. | ``--git-ref`` **must** be passed with ``--git-url``. | ||||
can only be specified for packages that contain a single library root at | can only be specified for packages that contain a single library root at | ||||
the package root. | 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. | generated for the library. | ||||
******************** | ******************** | ||||
Consider that we are creating a package ``acme-gadgets@4.3.6``. We declare the | 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:: | .. 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. | :ref:`deps.lib-deps` section. | ||||
Suppose that our package's libraries build upon the libraries in the | 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`` | not as new as ``2.0.0``. Such a dependency can be declared with the ``Depends`` | ||||
key: | 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`. | .. seealso:: :ref:`deps.ranges`. | ||||
additional ``Depends`` keys | additional ``Depends`` keys | ||||
.. code-block:: | .. 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 | When ``dds`` attempts to build a project, it will first build the dependency | ||||
solution by iteratively scanning the dependencies of the containing project and | solution by iteratively scanning the dependencies of the containing project and | ||||
In ``dds``, library interdependencies are tracked separately from the packages | In ``dds``, library interdependencies are tracked separately from the packages | ||||
that contain them. A library must declare its intent to use another library | 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 | 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. | 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:: | .. 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``. | to a C++ ``namespace``. | ||||
.. note:: | .. 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. | 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! | 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 | will make available the headers for the library being used, and will | ||||
transitively propagate that usage requirement to users of the library. | transitively propagate that usage requirement to users of the library. |
A *test* source file is a source file whose file stem ends with ``.test``. Like | 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 | 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 | 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`` | source file will generate a single test executable that is executed by ``dds`` | ||||
when running unit tests. | when running unit tests. | ||||
dependencies of a header-only library. | dependencies of a header-only library. | ||||
In order for ``dds`` to be able to distribute and interlink libraries, a | 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 | .. seealso:: More information is discussed on the :ref:`deps.lib-deps` page | ||||
within a repository or package catalog. | within a repository or package catalog. | ||||
In order for a package to be exported by ``dds`` it must have a | 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:: | .. 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:: | .. seealso:: | ||||
The purpose of ``Namespace``, as well as additional options in this file, | |||||
The purpose of ``namespace``, as well as additional options in this file, | |||||
are described in the :ref:`deps.pkg-deps` page | are described in the :ref:`deps.pkg-deps` page | ||||
Package names aren't a complete free-for-all. Package names must follow a set | Package names aren't a complete free-for-all. Package names must follow a set | ||||
of specific rules: | 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:: | .. note:: | ||||
Different filesystems differ in their handling of filenames. Some platforms | Different filesystems differ in their handling of filenames. Some platforms |
$ ls ./spdlog@1.4.2/ | $ ls ./spdlog@1.4.2/ | ||||
include/ | include/ | ||||
src/ | src/ | ||||
library.dds | |||||
package.dds | |||||
library.json5 | |||||
package.json5 | |||||
.. _repo.export-local: | .. _repo.export-local: |
diagnostics? | diagnostics? | ||||
A ``Test-Driver``: Using *Catch2* | |||||
A ``test_driver``: Using *Catch2* | |||||
********************************* | ********************************* | ||||
``dds`` ships with built-in support for the `Catch2`_ C and C++ testing | ``dds`` ships with built-in support for the `Catch2`_ C and C++ testing | ||||
.. _catch2: https://github.com/catchorg/Catch2 | .. _catch2: https://github.com/catchorg/Catch2 | ||||
To make use of Catch as our test driver, we simply declare this intent in the | 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 | :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 | 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 compile an entrypoint separately from any particular test, and the tests | ||||
will link against that entrypoint. This means we cannot provide our own | will link against that entrypoint. This means we cannot provide our own | ||||
``main`` function, and should instead use Catch's ``TEST_CASE`` macro to | ``main`` function, and should instead use Catch's ``TEST_CASE`` macro to |
``dds`` will work happily with packages that do not declare themselves, as long | ``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 | 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 | information about are package. This file should be placed directly in the | ||||
package root: | 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:: | .. 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. | Rebuilding the project will show no difference at the moment. | ||||
Uses: neo/sqlite3 | Uses: neo/sqlite3 | ||||
Uses: neo/fun | Uses: neo/fun | ||||
Uses: semver/semver | Uses: semver/semver | ||||
Uses: vob/semester | |||||
Uses: pubgrub/pubgrub | Uses: pubgrub/pubgrub | ||||
Uses: vob/json5 |
{ | |||||
"$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", | |||||
] | |||||
} |
Depends: neo-fun 0.1.0 | Depends: neo-fun 0.1.0 | ||||
Depends: semver 0.2.1 | Depends: semver 0.2.1 | ||||
Depends: pubgrub 0.2.0 | 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 |
{ | |||||
"$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" | |||||
} |
{ | |||||
"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_]+)*$" | |||||
} | |||||
} | |||||
} | |||||
} |
{ | |||||
"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" | |||||
] | |||||
} | |||||
} | |||||
} |
{ | |||||
"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" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/repo/repo.hpp> | #include <dds/repo/repo.hpp> | ||||
#include <dds/source/dist.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/fs.hpp> | ||||
#include <dds/util/paths.hpp> | #include <dds/util/paths.hpp> | ||||
#include <dds/util/signal.hpp> | #include <dds/util/signal.hpp> | ||||
} | } | ||||
return std::move(*tc); | return std::move(*tc); | ||||
} else { | } 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)); | |||||
} | } | ||||
} | } | ||||
}; | }; | ||||
params.out_root = out.Get(); | params.out_root = out.Get(); | ||||
params.toolchain = tc_filepath.get_toolchain(); | params.toolchain = tc_filepath.get_toolchain(); | ||||
params.parallel_jobs = n_jobs.Get(); | 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::builder bd; | ||||
dds::sdist_build_params main_params; | dds::sdist_build_params main_params; |
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/proc.hpp> | #include <dds/proc.hpp> | ||||
#include <nlohmann/json.hpp> | |||||
#include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||
using namespace dds; | using namespace dds; | ||||
spdlog::info("Create sdist from clone ..."); | spdlog::info("Create sdist from clone ..."); | ||||
if (git.auto_lib.has_value()) { | if (git.auto_lib.has_value()) { | ||||
spdlog::info("Generating library data automatically"); | 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; | sdist_params params; |
return "sdist-ident-mismatch.html"; | return "sdist-ident-mismatch.html"; | ||||
case errc::corrupted_build_db: | case errc::corrupted_build_db: | ||||
return "corrupted-build-db.html"; | 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: | case errc::invalid_version_range_string: | ||||
return "invalid-version-string.html#range"; | return "invalid-version-string.html#range"; | ||||
case errc::invalid_version_string: | case errc::invalid_version_string: | ||||
The catalog database schema doesn't match what dds expects. This indicates that | 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 | the database file has been modified in a way that dds cannot automatically fix | ||||
and handle. | 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: | case errc::invalid_catalog_json: | ||||
return R"( | return R"( | ||||
)"; | )"; | ||||
case errc::unknown_usage_name: | case errc::unknown_usage_name: | ||||
return R"( | 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 | 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. | for the build. | ||||
)"; | )"; | ||||
case errc::none: | case errc::none: | ||||
"that was expected of it"; | "that was expected of it"; | ||||
case errc::corrupted_build_db: | case errc::corrupted_build_db: | ||||
return "The build database file is corrupted"; | 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: | case errc::invalid_version_range_string: | ||||
return "Attempted to parse an invalid version range string. <- (Seeing this text is a " | return "Attempted to parse an invalid version range string. <- (Seeing this text is a " | ||||
"`dds` bug. Please report it.)"; | "`dds` bug. Please report it.)"; | ||||
case errc::invalid_version_string: | 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: | 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_lib_filesystem: | ||||
case errc::invalid_pkg_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: | case errc::invalid_pkg_id: | ||||
return "A package identifier is invalid <- (Seeing this text is a `dds` bug. Please " | return "A package identifier is invalid <- (Seeing this text is a `dds` bug. Please " | ||||
"report it.)"; | "report it.)"; | ||||
case errc::invalid_pkg_name: | 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: | 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: | 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: | case errc::dependency_resolve_failure: | ||||
return "`dds` was unable to find a solution for the package dependencies given."; | return "`dds` was unable to find a solution for the package dependencies given."; | ||||
case errc::dup_lib_name: | case errc::dup_lib_name: | ||||
return "More than one library has claimed the same name."; | return "More than one library has claimed the same name."; | ||||
case errc::unknown_usage_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: | case errc::none: | ||||
break; | break; | ||||
} | } |
corrupted_build_db, | corrupted_build_db, | ||||
invalid_lib_manifest, | |||||
invalid_pkg_manifest, | |||||
invalid_version_range_string, | invalid_version_range_string, | ||||
invalid_version_string, | invalid_version_string, | ||||
invalid_pkg_id, | invalid_pkg_id, |
#include "./manifest.hpp" | #include "./manifest.hpp" | ||||
#include <dds/dym.hpp> | #include <dds/dym.hpp> | ||||
#include <dds/error/errors.hpp> | |||||
#include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
#include <range/v3/view/transform.hpp> | |||||
#include <libman/parse.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; | 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); | auto kvs = lm::parse_file(fpath); | ||||
library_manifest ret; | library_manifest ret; | ||||
ret.name = fpath.parent_path().filename().string(); | ret.name = fpath.parent_path().filename().string(); | ||||
extend(ret.links, ranges::views::transform(links_strings, lm::split_usage_string)); | extend(ret.links, ranges::views::transform(links_strings, lm::split_usage_string)); | ||||
return ret; | 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); | |||||
} | |||||
} |
namespace dds { | 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 | * 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 { | struct library_manifest { | ||||
/// The name of the library | /// The name of the library | ||||
/** | /** | ||||
* Load the library manifest from an existing file | * 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 | } // namespace dds |
auto sources = collect_pf_sources(lib_dir); | auto sources = collect_pf_sources(lib_dir); | ||||
library_manifest man; | 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)); | auto lib = library_root(lib_dir, std::move(sources), std::move(man)); |
#include <range/v3/view/split.hpp> | #include <range/v3/view/split.hpp> | ||||
#include <range/v3/view/split_when.hpp> | #include <range/v3/view/split_when.hpp> | ||||
#include <range/v3/view/transform.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; | 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); | auto kvs = lm::parse_file(fpath); | ||||
package_manifest ret; | package_manifest ret; | ||||
std::string version_str; | std::string version_str; | ||||
} else { | } else { | ||||
auto dym = *did_you_mean(test_driver_str, {"Catch-Main", "Catch"}); | auto dym = *did_you_mean(test_driver_str, {"Catch-Main", "Catch"}); | ||||
throw_user_error< | 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, | test_driver_str, | ||||
dym); | dym); | ||||
} | } | ||||
return ret; | 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); | |||||
} | |||||
} |
namespace dds { | namespace dds { | ||||
/** | /** | ||||
* Possible values for Test-Driver in a package.dds | |||||
* Possible values for test_driver in a package.json5 | |||||
*/ | */ | ||||
enum class test_lib { | enum class test_lib { | ||||
catch_, | catch_, | ||||
package_id pkg_id; | package_id pkg_id; | ||||
/// The declared `Namespace` of the package. This directly corresponds with the libman Namespace | /// The declared `Namespace` of the package. This directly corresponds with the libman Namespace | ||||
std::string 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; | std::optional<test_lib> test_driver; | ||||
/// The dependencies declared with the `Depends` fields, if any. | /// The dependencies declared with the `Depends` fields, if any. | ||||
std::vector<dependency> dependencies; | std::vector<dependency> dependencies; | ||||
* Load a package manifest from a file on disk. | * Load a package manifest from a file on disk. | ||||
*/ | */ | ||||
static package_manifest load_from_file(path_ref); | 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 | } // namespace dds |
ranges::sort(sources_to_keep, std::less<>(), [](auto&& s) { return s.path; }); | 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>( | 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()); | spdlog::info("sdist: Export library from {}", lib.path().string()); | ||||
fs::create_directories(out_root); | fs::create_directories(out_root); | ||||
sdist_copy_library(out, lib, 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>( | 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()); | spdlog::info("Generated export as {}", pkg_man.pkg_id.to_string()); | ||||
return sdist::from_directory(out); | return sdist::from_directory(out); | ||||
} | } | ||||
sdist sdist::from_directory(path_ref where) { | 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}; | |||||
} | } |
#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 |
#include "./from_dds.hpp" | |||||
#include "./from_json.hpp" | |||||
#include <dds/dym.hpp> | #include <dds/dym.hpp> | ||||
#include <dds/error/errors.hpp> | |||||
#include <dds/toolchain/prep.hpp> | #include <dds/toolchain/prep.hpp> | ||||
#include <dds/toolchain/toolchain.hpp> | |||||
#include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
#include <dds/util/shlex.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 <spdlog/fmt/fmt.h> | ||||
#include <map> | |||||
#include <optional> | |||||
#include <tuple> | |||||
#include <vector> | |||||
#include <string> | |||||
using namespace dds; | using namespace dds; | ||||
using fmt::format; | |||||
using std::optional; | using std::optional; | ||||
using std::string; | using std::string; | ||||
using std::vector; | 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 { | 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> | template <typename T, typename Func> | ||||
T read_opt(const std::optional<T>& what, Func&& fn) { | T read_opt(const std::optional<T>& what, Func&& fn) { | ||||
template <typename... Args> | template <typename... Args> | ||||
[[noreturn]] void fail(strv context, strv message, Args&&... 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 | } // 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 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 c_version; | ||||
opt_string cxx_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_prefix; | ||||
opt_string archive_suffix; | opt_string archive_suffix; | ||||
opt_string obj_prefix; | opt_string obj_prefix; | ||||
opt_string obj_suffix; | opt_string obj_suffix; | ||||
opt_string exe_prefix; | opt_string exe_prefix; | ||||
opt_string exe_suffix; | 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 { | enum compiler_id_e_t { | ||||
no_comp_id, | no_comp_id, | ||||
= [&] { | = [&] { | ||||
if (!compiler_id) { | if (!compiler_id) { | ||||
return no_comp_id; | return no_comp_id; | ||||
} else if (compiler_id == "MSVC") { | |||||
} else if (compiler_id == "msvc") { | |||||
return msvc; | return msvc; | ||||
} else if (compiler_id == "GNU") { | |||||
} else if (compiler_id == "gnu") { | |||||
return gnu; | return gnu; | ||||
} else if (compiler_id == "Clang") { | |||||
} else if (compiler_id == "clang") { | |||||
return clang; | return clang; | ||||
} else { | } else { | ||||
fail(context, "Unknown Compiler-ID '{}'", *compiler_id); | |||||
fail(context, "Invalid `compiler_id` value ‘{}’", *compiler_id); | |||||
} | } | ||||
}(); | }(); | ||||
} else { | } else { | ||||
return file_deps_mode::none; | return file_deps_mode::none; | ||||
} | } | ||||
} else if (deps_mode_str == "GNU") { | |||||
} else if (deps_mode_str == "gnu") { | |||||
return file_deps_mode::gnu; | return file_deps_mode::gnu; | ||||
} else if (deps_mode_str == "MSVC") { | |||||
} else if (deps_mode_str == "msvc") { | |||||
return file_deps_mode::msvc; | return file_deps_mode::msvc; | ||||
} else if (deps_mode_str == "None") { | |||||
} else if (deps_mode_str == "none") { | |||||
return file_deps_mode::none; | return file_deps_mode::none; | ||||
} else { | } 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 | // 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()) { | 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) { | if (is_gnu) { | ||||
return (lang == language::cxx) ? "g++" : "gcc"; | return (lang == language::cxx) ? "g++" : "gcc"; | ||||
std::terminate(); | std::terminate(); | ||||
}; | }; | ||||
// Determine the C language version | |||||
enum c_version_e_t { | enum c_version_e_t { | ||||
c_none, | c_none, | ||||
c89, | c89, | ||||
= [&] { | = [&] { | ||||
if (!c_version) { | if (!c_version) { | ||||
return c_none; | return c_none; | ||||
} else if (c_version == "C89") { | |||||
} else if (c_version == "c89") { | |||||
return c89; | return c89; | ||||
} else if (c_version == "C99") { | |||||
} else if (c_version == "c99") { | |||||
return c99; | return c99; | ||||
} else if (c_version == "C11") { | |||||
} else if (c_version == "c11") { | |||||
return c11; | return c11; | ||||
} else if (c_version == "C18") { | |||||
} else if (c_version == "c18") { | |||||
return c18; | return c18; | ||||
} else { | } else { | ||||
fail(context, "Unknown C-Version '{}'", *c_version); | |||||
fail(context, "Unknown `c_version` ‘{}’", *c_version); | |||||
} | } | ||||
}(); | }(); | ||||
= [&] { | = [&] { | ||||
if (!cxx_version) { | if (!cxx_version) { | ||||
return cxx_none; | return cxx_none; | ||||
} else if (cxx_version == "C++98") { | |||||
} else if (cxx_version == "c++98") { | |||||
return cxx98; | return cxx98; | ||||
} else if (cxx_version == "C++03") { | |||||
} else if (cxx_version == "c++03") { | |||||
return cxx03; | return cxx03; | ||||
} else if (cxx_version == "C++11") { | |||||
} else if (cxx_version == "c++11") { | |||||
return cxx11; | return cxx11; | ||||
} else if (cxx_version == "C++14") { | |||||
} else if (cxx_version == "c++14") { | |||||
return cxx14; | return cxx14; | ||||
} else if (cxx_version == "C++17") { | |||||
} else if (cxx_version == "c++17") { | |||||
return cxx17; | return cxx17; | ||||
} else if (cxx_version == "C++20") { | |||||
} else if (cxx_version == "c++20") { | |||||
return cxx20; | return cxx20; | ||||
} else { | } else { | ||||
fail(context, "Unknown C++-Version '{}'", *cxx_version); | |||||
fail(context, "Unknown `cxx_version` ‘{}’", *cxx_version); | |||||
} | } | ||||
}(); | }(); | ||||
auto get_c_version_flags = [&]() -> string_seq { | auto get_c_version_flags = [&]() -> string_seq { | ||||
if (!compiler_id.has_value()) { | 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}); | auto c_ver_iter = c_version_flag_table.find({compiler_id_e, c_version_e}); | ||||
assert(c_ver_iter != c_version_flag_table.end()); | assert(c_ver_iter != c_version_flag_table.end()); | ||||
auto get_cxx_version_flags = [&]() -> string_seq { | auto get_cxx_version_flags = [&]() -> string_seq { | ||||
if (!compiler_id.has_value()) { | 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}); | auto cxx_ver_iter = cxx_version_flag_table.find({compiler_id_e, cxx_version_e}); | ||||
assert(cxx_ver_iter != cxx_version_flag_table.end()); | assert(cxx_ver_iter != cxx_version_flag_table.end()); | ||||
auto get_flags = [&](language lang) -> string_seq { | auto get_flags = [&](language lang) -> string_seq { | ||||
string_seq ret; | 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) { | if (is_msvc) { | ||||
strv rt_lib = "/MT"; | strv rt_lib = "/MT"; | ||||
if (do_optimize.has_value() && *do_optimize) { | if (do_optimize.has_value() && *do_optimize) { | ||||
"<IN>", | "<IN>", | ||||
"-o<OUT>"}); | "-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; | return ret; | ||||
}; | }; | ||||
toolchain_prep tc; | |||||
tc.deps_mode = deps_mode; | tc.deps_mode = deps_mode; | ||||
tc.c_compile = read_opt(c_compile_file, [&] { | tc.c_compile = read_opt(c_compile_file, [&] { | ||||
string_seq c; | 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)); | extend(c, get_flags(language::c)); | ||||
return c; | return c; | ||||
}); | }); | ||||
tc.cxx_compile = read_opt(cxx_compile_file, [&] { | tc.cxx_compile = read_opt(cxx_compile_file, [&] { | ||||
string_seq cxx; | 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)); | extend(cxx, get_flags(language::cxx)); | ||||
return cxx; | return cxx; | ||||
}); | }); | ||||
tc.include_template = read_opt(include_template, [&]() -> string_seq { | tc.include_template = read_opt(include_template, [&]() -> string_seq { | ||||
if (!compiler_id) { | 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) { | if (is_gnu_like) { | ||||
return {"-I", "<PATH>"}; | return {"-I", "<PATH>"}; | ||||
} else if (is_msvc) { | } else if (is_msvc) { | ||||
return {"/I", "<PATH>"}; | return {"/I", "<PATH>"}; | ||||
} | } | ||||
assert(false && "Include-Template deduction failed"); | |||||
assert(false && "'include_template' deduction failed"); | |||||
std::terminate(); | std::terminate(); | ||||
}); | }); | ||||
// MSVC has external-header support inbound, but it is not fully ready yet | // MSVC has external-header support inbound, but it is not fully ready yet | ||||
return {"/I", "<PATH>"}; | return {"/I", "<PATH>"}; | ||||
} | } | ||||
assert(false && "External-Include-Template deduction failed"); | |||||
assert(false && "external_include_template deduction failed"); | |||||
std::terminate(); | std::terminate(); | ||||
}); | }); | ||||
tc.define_template = read_opt(define_template, [&]() -> string_seq { | tc.define_template = read_opt(define_template, [&]() -> string_seq { | ||||
if (!compiler_id) { | 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) { | if (is_gnu_like) { | ||||
return {"-D", "<DEF>"}; | return {"-D", "<DEF>"}; | ||||
} else if (is_msvc) { | } else if (is_msvc) { | ||||
return {"/D", "<DEF>"}; | return {"/D", "<DEF>"}; | ||||
} | } | ||||
assert(false && "Define-Template deduction failed"); | |||||
assert(false && "define_template deduction failed"); | |||||
std::terminate(); | std::terminate(); | ||||
}); | }); | ||||
#endif | #endif | ||||
}); | }); | ||||
/// TODO: Handle base_warning_flags: | |||||
tc.warning_flags = read_opt(warning_flags, [&]() -> string_seq { | tc.warning_flags = read_opt(warning_flags, [&]() -> string_seq { | ||||
if (!compiler_id) { | if (!compiler_id) { | ||||
// No error. Just no warning flags | // No error. Just no warning flags | ||||
tc.link_archive = read_opt(create_archive, [&]() -> string_seq { | tc.link_archive = read_opt(create_archive, [&]() -> string_seq { | ||||
if (!compiler_id) { | 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) { | if (is_msvc) { | ||||
return {"lib", "/nologo", "/OUT:<OUT>", "<IN>"}; | return {"lib", "/nologo", "/OUT:<OUT>", "<IN>"}; | ||||
tc.link_exe = read_opt(link_executable, [&]() -> string_seq { | tc.link_exe = read_opt(link_executable, [&]() -> string_seq { | ||||
if (!compiler_id) { | 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; | string_seq ret; | ||||
if (is_msvc) { | 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) { | } else if (is_gnu_like) { | ||||
ret = {get_compiler(language::cxx), | |||||
ret = {get_compiler_executable_path(language::cxx), | |||||
"-fPIC", | "-fPIC", | ||||
"-fdiagnostics-color", | "-fdiagnostics-color", | ||||
"<IN>", | "<IN>", | ||||
}); | }); | ||||
return tc.realize(); | return tc.realize(); | ||||
} | |||||
} |
#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 |
#include <dds/toolchain/from_dds.hpp> | |||||
#include <dds/toolchain/from_json.hpp> | |||||
#include <dds/proc.hpp> | #include <dds/proc.hpp> | ||||
// #include <dds/util.test.hpp> | |||||
#include <catch2/catch.hpp> | #include <catch2/catch.hpp> | ||||
namespace { | namespace { | ||||
void check_tc_compile(std::string_view tc_content, | void check_tc_compile(std::string_view tc_content, | ||||
std::string_view expected_compile, | std::string_view expected_compile, | ||||
std::string_view expected_compile_warnings, | std::string_view expected_compile_warnings, | ||||
std::string_view expected_ar, | std::string_view expected_ar, | ||||
std::string_view expected_exe) { | 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; | dds::compile_file_spec cf; | ||||
cf.source_path = "foo.cpp"; | cf.source_path = "foo.cpp"; | ||||
CHECK(exe_cmd_str == expected_exe); | CHECK(exe_cmd_str == expected_exe); | ||||
} | } | ||||
} // namespace | |||||
TEST_CASE("Generating toolchain commands") { | TEST_CASE("Generating toolchain commands") { | ||||
check_tc_compile( | 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 -MD -MF foo.o.d -MT foo.o -c foo.cpp -ofoo.o", | ||||
"g++ -fPIC -fdiagnostics-color -pthread -Wall -Wextra -Wpedantic -Wconversion " | "g++ -fPIC -fdiagnostics-color -pthread -Wall -Wextra -Wpedantic -Wconversion " | ||||
"-MD -MF foo.o.d -MT foo.o -c foo.cpp -ofoo.o", | "-MD -MF foo.o.d -MT foo.o -c foo.cpp -ofoo.o", | ||||
"g++ -fPIC -fdiagnostics-color foo.o bar.a -pthread -omeow.exe"); | "g++ -fPIC -fdiagnostics-color foo.o bar.a -pthread -omeow.exe"); | ||||
check_tc_compile( | 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 -MD -MF foo.o.d -MT foo.o -c foo.cpp -ofoo.o", | ||||
"g++ -g -fPIC -fdiagnostics-color -pthread -Wall -Wextra -Wpedantic -Wconversion " | "g++ -g -fPIC -fdiagnostics-color -pthread -Wall -Wextra -Wpedantic -Wconversion " | ||||
"-MD -MF foo.o.d -MT foo.o -c foo.cpp -ofoo.o", | "-MD -MF foo.o.d -MT foo.o -c foo.cpp -ofoo.o", | ||||
"g++ -fPIC -fdiagnostics-color foo.o bar.a -pthread -omeow.exe -g"); | "g++ -fPIC -fdiagnostics-color foo.o bar.a -pthread -omeow.exe -g"); | ||||
check_tc_compile( | 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 " | "g++ -O2 -g -fPIC -fdiagnostics-color -pthread -MD -MF foo.o.d -MT foo.o -c foo.cpp " | ||||
"-ofoo.o", | "-ofoo.o", | ||||
"g++ -O2 -g -fPIC -fdiagnostics-color -pthread -Wall -Wextra -Wpedantic -Wconversion " | "g++ -O2 -g -fPIC -fdiagnostics-color -pthread -Wall -Wextra -Wpedantic -Wconversion " | ||||
"ar rcs stuff.a foo.o bar.o", | "ar rcs stuff.a foo.o bar.o", | ||||
"g++ -fPIC -fdiagnostics-color foo.o bar.a -pthread -omeow.exe -O2 -g"); | "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- /showIncludes /c foo.cpp /Fofoo.o", | ||||
"cl.exe /MT /EHsc /nologo /permissive- /W4 /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", | "lib /nologo /OUT:stuff.a foo.o bar.o", | ||||
"cl.exe /nologo /EHsc foo.o bar.a /Femeow.exe /MT"); | "cl.exe /nologo /EHsc foo.o bar.a /Femeow.exe /MT"); | ||||
check_tc_compile( | 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- /showIncludes /c foo.cpp /Fofoo.o", | ||||
"cl.exe /Z7 /DEBUG /MTd /EHsc /nologo /permissive- /W4 /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", | "lib /nologo /OUT:stuff.a foo.o bar.o", | ||||
"cl.exe /nologo /EHsc foo.o bar.a /Femeow.exe /Z7 /DEBUG /MTd"); | "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; | dds::compile_file_spec cfs; | ||||
cfs.source_path = "foo.cpp"; | cfs.source_path = "foo.cpp"; | ||||
"-c", | "-c", | ||||
"foo.cpp", | "foo.cpp", | ||||
"-ofoo.o"}); | "-ofoo.o"}); | ||||
} | |||||
} // namespace | |||||
} |
#include "./toolchain.hpp" | #include "./toolchain.hpp" | ||||
#include <dds/toolchain/from_dds.hpp> | |||||
#include <dds/toolchain/from_json.hpp> | |||||
#include <dds/toolchain/prep.hpp> | #include <dds/toolchain/prep.hpp> | ||||
#include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
#include <dds/util/paths.hpp> | #include <dds/util/paths.hpp> | ||||
std::optional<toolchain> toolchain::get_builtin(std::string_view tc_id) noexcept { | std::optional<toolchain> toolchain::get_builtin(std::string_view tc_id) noexcept { | ||||
using namespace std::literals; | 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:")) { | if (starts_with(tc_id, "debug:")) { | ||||
tc_id = tc_id.substr("debug:"sv.length()); | tc_id = tc_id.substr("debug:"sv.length()); | ||||
tc_content += "Debug: True\n"; | |||||
root_map.emplace("debug", true); | |||||
} | } | ||||
if (starts_with(tc_id, "ccache:")) { | if (starts_with(tc_id, "ccache:")) { | ||||
tc_id = tc_id.substr("ccache:"sv.length()); | 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) \ | #define CXX_VER_TAG(str, version) \ | ||||
if (starts_with(tc_id, str)) { \ | if (starts_with(tc_id, str)) { \ | ||||
tc_id = tc_id.substr(std::string_view(str).length()); \ | 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) | 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 { | struct compiler_info { | ||||
string c; | string c; | ||||
const auto [c_compiler_base, cxx_compiler_base, compiler_id] = [&]() -> compiler_info { | const auto [c_compiler_base, cxx_compiler_base, compiler_id] = [&]() -> compiler_info { | ||||
if (is_gcc) { | if (is_gcc) { | ||||
return {"gcc", "g++", "GNU"}; | |||||
return {"gcc", "g++", "gnu"}; | |||||
} else if (is_clang) { | } else if (is_clang) { | ||||
return {"clang", "clang++", "Clang"}; | |||||
return {"clang", "clang++", "clang"}; | |||||
} | } | ||||
assert(false && "Unreachable"); | assert(false && "Unreachable"); | ||||
std::terminate(); | std::terminate(); | ||||
auto cxx_compiler_name = cxx_compiler_base + compiler_suffix; | auto cxx_compiler_name = cxx_compiler_base + compiler_suffix; | ||||
return compiler_info{c_compiler_name, cxx_compiler_name, compiler_id}; | return compiler_info{c_compiler_name, cxx_compiler_name, compiler_id}; | ||||
} else if (tc_id == "msvc") { | } else if (tc_id == "msvc") { | ||||
return compiler_info{"cl.exe", "cl.exe", "MSVC"}; | |||||
return compiler_info{"cl.exe", "cl.exe", "msvc"}; | |||||
} else { | } else { | ||||
return std::nullopt; | return std::nullopt; | ||||
} | } | ||||
return std::nullopt; | 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() { | std::optional<dds::toolchain> dds::toolchain::get_default() { | ||||
auto candidates = { | 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) { | for (auto&& cand : candidates) { | ||||
if (fs::exists(cand)) { | if (fs::exists(cand)) { | ||||
return parse_toolchain_dds(slurp_file(cand)); | |||||
return parse_toolchain_json5(slurp_file(cand)); | |||||
} | } | ||||
} | } | ||||
return std::nullopt; | return std::nullopt; | ||||
} | |||||
} |
def basic_pkg_dds(dds: DDS): | def basic_pkg_dds(dds: DDS): | ||||
return set_contents( | 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', | |||||
} | |||||
''') | ''') | ||||
from tests import dds, DDS | from tests import dds, DDS | ||||
from tests.fileutil import ensure_dir | from tests.fileutil import ensure_dir | ||||
import pytest | |||||
def test_get(dds: DDS): | def test_get(dds: DDS): | ||||
dds.scope.enter_context(ensure_dir(dds.build_dir)) | dds.scope.enter_context(ensure_dir(dds.build_dir)) | ||||
'0.2.2': { | '0.2.2': { | ||||
'depends': {}, | 'depends': {}, | ||||
'git': { | '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', | |||||
}, | }, | ||||
}, | }, | ||||
}, | }, |
"0.1.0": { | "0.1.0": { | ||||
"git": { | "git": { | ||||
"url": "https://github.com/vector-of-bool/neo-buffer.git", | "url": "https://github.com/vector-of-bool/neo-buffer.git", | ||||
"ref": "develop" | |||||
"ref": "0.1.0" | |||||
}, | }, | ||||
"depends": {} | "depends": {} | ||||
} | } |
Name: deps-test | |||||
Version: 0.0.0 | |||||
Depends: neo-buffer 0.1.0 | |||||
Depends: range-v3 0.9.1 |
{ | |||||
name: 'deps-test', | |||||
"namespace": "test", | |||||
version: '0.0.0', | |||||
depends: { | |||||
'neo-buffer': '0.1.0', | |||||
'range-v3': '0.9.1', | |||||
} | |||||
} |
Name: deps-test | |||||
Version: 0.0.0 |
{ | |||||
name: 'deps-test', | |||||
version: '0.0.0', | |||||
"namespace": "test", | |||||
} |
Name: dummy | |||||
Uses: nlohmann/json |
{ | |||||
name: "dummy", | |||||
uses: [ | |||||
'nlohmann/json', | |||||
] | |||||
} |
Name: json-test | |||||
Version: 0.0.0 | |||||
Depends: nlohmann-json 3.7.1 |
{ | |||||
"name": "json-test", | |||||
"version": "0.0.0", | |||||
"namespace": "test", | |||||
"depends": { | |||||
"nlohmann-json": "3.7.1" | |||||
} | |||||
} |
{ | |||||
"compiler_id": 'gnu', | |||||
"cxx_version": 'c++17', | |||||
"cxx_compiler": 'g++-9', | |||||
"flags": '-DSPDLOG_COMPILED_LIB', | |||||
} |
{ | |||||
"compiler_id": 'msvc', | |||||
"flags": '-DSPDLOG_COMPILED_LIB', | |||||
} |
Name: spdlog-user | |||||
Uses: spdlog/spdlog |
{ | |||||
name: 'spdlog-user', | |||||
uses: [ | |||||
'spdlog/spdlog', | |||||
] | |||||
} |
Name: test | |||||
Version: 0.0.0 | |||||
Depends: spdlog 1.4.2 |
{ | |||||
name: 'test', | |||||
version: '0.0.0', | |||||
"namespace": "test", | |||||
depends: { | |||||
'spdlog': '1.4.2', | |||||
}, | |||||
} |
def test_get_build_use_spdlog(dds: DDS): | def test_get_build_use_spdlog(dds: DDS): | ||||
dds.catalog_import(dds.source_root / 'catalog.json') | 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) | tc = str(dds.test_dir / tc_fname) | ||||
dds.build(toolchain=tc, apps=True) | dds.build(toolchain=tc, apps=True) | ||||
proc.check_run((dds.build_dir / 'use-spdlog').with_suffix(dds.exe_suffix)) | proc.check_run((dds.build_dir / 'use-spdlog').with_suffix(dds.exe_suffix)) |
Name: foo |
{ | |||||
"name": "foo" | |||||
} |
Name: foo | |||||
Version: 1.2.3 |
{ | |||||
name: 'foo', | |||||
version: '1.2.3', | |||||
"namespace": "test", | |||||
} |
Name: Test | |||||
Version: 0.0.0 | |||||
Test-Driver: Catch |
{ | |||||
"name": "test", | |||||
"version": "0.0.0", | |||||
"namespace": "test", | |||||
"test_driver": "Catch", | |||||
} |
Name: Test | |||||
Version: 0.0.0 | |||||
Test-Driver: Catch-Main |
{ | |||||
"name": "test", | |||||
"version": "0.0.0", | |||||
"namespace": "test", | |||||
"test_driver": "Catch-Main", | |||||
} |
class CIOptions(NamedTuple): | class CIOptions(NamedTuple): | ||||
toolchain: str | toolchain: str | ||||
toolchain_json5: str | |||||
def _do_bootstrap_build(opts: CIOptions) -> None: | def _do_bootstrap_build(opts: CIOptions) -> None: | ||||
'-T', | '-T', | ||||
help='The toolchain to use for the CI process', | help='The toolchain to use for the CI process', | ||||
required=True) | 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) | 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': | if args.bootstrap_with == 'build': | ||||
_do_bootstrap_build(opts) | _do_bootstrap_build(opts) | ||||
]) | ]) | ||||
self_build( | self_build( | ||||
paths.CUR_BUILT_DDS, | paths.CUR_BUILT_DDS, | ||||
toolchain=opts.toolchain, | |||||
toolchain=opts.toolchain_json5, | |||||
dds_flags=[f'--repo-dir={ci_repo_dir}', f'--catalog={cat_path}']) | dds_flags=[f'--repo-dir={ci_repo_dir}', f'--catalog={cat_path}']) | ||||
print('Bootstrap test PASSED!') | print('Bootstrap test PASSED!') | ||||
# Range-v3 0.10.0 contains an accidental conversion warning | # Range-v3 0.10.0 contains an accidental conversion warning | ||||
Flags: -D SPDLOG_COMPILED_LIB -Werror=return-type -Wno-conversion | Flags: -D SPDLOG_COMPILED_LIB -Werror=return-type -Wno-conversion | ||||
C++-Flags: -fconcepts | 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 | Compiler-Launcher: ccache |
{ | |||||
"$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" | |||||
} |
ret: dict = { | ret: dict = { | ||||
'description': self.description, | 'description': self.description, | ||||
} | } | ||||
ret['depends'] = {} | |||||
ret['depends'] = self.depends | |||||
if isinstance(self.remote, Git): | if isinstance(self.remote, Git): | ||||
ret['git'] = self.remote.to_dict() | ret['git'] = self.remote.to_dict() | ||||
return ret | return ret | ||||
'A C++ implementation of the Pubgrub version solving algorithm', | 'A C++ implementation of the Pubgrub version solving algorithm', | ||||
git_url='https://github.com/vector-of-bool/pubgrub.git', | 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( | many_versions( | ||||
'spdlog', | 'spdlog', | ||||
( | ( |
{ | |||||
"$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 | |||||
} |