-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathDEVELOPMENT.rst.template
568 lines (416 loc) · 19.4 KB
/
DEVELOPMENT.rst.template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
###########
Development
###########
.. contents::
:local:
***************
Adding Features
***************
In order to add a feature to ``bezier``:
#. **Discuss**: `File an issue`_ to notify maintainer(s) of the
proposed changes (i.e. just sending a large PR with a finished
feature may catch maintainer(s) off guard).
#. **Add tests**: The feature must work fully on CPython versions 3.10, 3.11
and 3.12 and on PyPy 3; on Linux, macOS and Windows. In addition, the
feature should have 100% line coverage.
#. **Documentation**: The feature must (should) be documented with
helpful `doctest`_ examples wherever relevant.
.. _File an issue: https://github.com/dhermes/bezier/issues/new
.. _doctest: http://www.sphinx-doc.org/en/stable/ext/doctest.html
*************
``libbezier``
*************
To build the Fortran shared library directly, use `CMake`_ version
``3.5`` or later:
.. code-block:: console
$ SRC_DIR="src/fortran/"
$ BUILD_DIR=".../libbezier-debug/build"
$ INSTALL_PREFIX=".../libbezier-debug/usr"
$ mkdir -p "${{BUILD_DIR}}"
$ cmake \
> -DCMAKE_BUILD_TYPE=Debug \
> -DCMAKE_INSTALL_PREFIX:PATH="${{INSTALL_PREFIX}}" \
> -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON \
> -S "${{SRC_DIR}}" \
> -B "${{BUILD_DIR}}"
$ cmake \
> --build "${{BUILD_DIR}}" \
> --config Debug \
> --target install
.. _CMake: https://cmake.org/
****************
Binary Extension
****************
Many low-level computations have alternate implementations in Fortran.
See the `Python Binary Extension`_ page for a more detailed description.
.. _Python Binary Extension: https://bezier.readthedocs.io/en/{rtd_version}/python/binary-extension.html
Building / Compiling
====================
To compile the binary extension (with ``libbezier`` built and installed
already) run:
.. code-block:: console
$ # One of
$ BEZIER_INSTALL_PREFIX=.../usr/ python -m pip wheel .
$ BEZIER_INSTALL_PREFIX=.../usr/ python -m pip install .
$ BEZIER_INSTALL_PREFIX=.../usr/ python setup.py build_ext
$ BEZIER_INSTALL_PREFIX=.../usr/ python setup.py build_ext --inplace
Using a Release build of ``libbezier`` may make debugging more difficult.
Instead, a Debug build of ``libbezier`` will include debug symbols, without
optimizations that move code around, etc.
To explicitly disable the building of the extension, the
``BEZIER_NO_EXTENSION`` environment variable can be used:
.. code-block:: console
$ BEZIER_NO_EXTENSION=True .../bin/python -m pip wheel .
This environment variable is actually used for the ``nox --session docs``
session to emulate the `RTD`_ build environment (where no Fortran compiler is
present).
Dependencies
============
Currently, the ``src/fortran/quadpack.f90`` `file`_ has a subset of Fortran 77
subroutines from `QUADPACK`_ converted into Fortran 90 by `John Burkardt`_.
This code is Public Domain, so does not conflict with the Apache 2.0 license
(as far as we know).
QUADPACK is used to perform numerical quadrature to compute the length
of a curve segment.
.. _file: https://github.com/dhermes/bezier/tree/{revision}/src/fortran/quadpack.f90
.. _QUADPACK: https://en.wikipedia.org/wiki/QUADPACK
.. _John Burkardt: https://people.math.sc.edu/Burkardt/f_src/quadpack_double/quadpack_double.html
Windows
=======
Building the binary extension requires a Fortran compiler. Unfortunately, there
is no Fortran compiler provided by MSVC. The `MinGW-w64`_ suite of tools is a
port of the GNU Compiler Collection (``gcc``) for Windows. In particular, MinGW
includes ``gfortran``.
To `install MinGW-w64`_, download a recent `MSYS2 Installer`_ (e.g.
``msys2-x86_64-20230526.exe``). When installing, take note of the install
location. It should default to ``C:\msys64``. After installing, use
``pacman`` (provided by MSYS2) to install the MinGW-w64 toolchain:
.. code-block:: powershell
> pacman -S --needed base-devel mingw-w64-x86_64-toolchain
After doing this, you may want to permanently add the MinGW bin directory
(e.g. ``C:\msys64\mingw64\bin``) to your ``PATH``. If you don't want to make
a permanent change, you'll need to temporarily modify your ``PATH`` in shell
sessions where ``bezier`` is being developed:
.. code-block:: powershell
> $env:Path = "C:\msys64\mingw64\bin;" + $env:Path
Additionally, the ``BEZIER_EXTRA_DLL`` environment variable may need to be
set for ``nox`` sessions if the MinGW-w64 DLLs cannot be found (to be added the
DLL search path):
.. code-block:: powershell
> $env:BEZIER_EXTRA_DLL = "C:\msys64\mingw64\bin"
In addition to a Fortran toolchain, you will also need the MSVC toolchain that
was used to build each version of Python. The `Microsoft C++ Build Tools`_ can
be installed to enable this toolchain.
.. _MinGW-w64: http://mingw-w64.org
.. _install MinGW-w64: https://code.visualstudio.com/docs/cpp/config-mingw
.. _MSYS2 Installer: https://github.com/msys2/msys2-installer/releases
.. _Microsoft C++ Build Tools: https://visualstudio.microsoft.com/visual-cpp-build-tools/
******************
Running Unit Tests
******************
We recommend using `Nox`_ to run unit tests:
.. code-block:: console
$ nox --session "unit-3.10"
$ nox --session "unit-3.11"
$ nox --session "unit-3.12"
$ nox --session "unit-pypy3"
$ nox --session unit # Run all versions
However, `pytest`_ can be used directly (though it won't
manage dependencies or build the binary extension):
.. code-block:: console
$ PYTHONPATH=src/python/ python3.10 -m pytest tests/unit/
$ PYTHONPATH=src/python/ python3.11 -m pytest tests/unit/
$ PYTHONPATH=src/python/ python3.12 -m pytest tests/unit/
$ PYTHONPATH=src/python/ pypy3 -m pytest tests/unit/
.. _Nox: https://nox.readthedocs.io
.. _pytest: https://docs.pytest.org
Testing the Binary Extension
============================
When using ``nox``, ``libbezier`` will be built and installed into a well-known
``BEZIER_INSTALL_PREFIX`` within the ``nox`` envdir (typically ``.nox/``), the
``bezier`` package will automatically be installed into a virtual environment
and the binary extension will be built during install.
However, if the tests are run directly from the source tree via
.. code-block:: console
$ PYTHONPATH=src/python/ python -m pytest tests/unit/
some unit tests may be skipped. The unit tests that explicitly exercise the
binary extension will skip (rather than fail) if the extension isn't
compiled (with ``build_ext --inplace``) and present in the source tree.
Test Coverage
=============
``bezier`` has 100% `line coverage`_. The coverage is checked
on every build and uploaded to `coveralls.io`_ via the
``COVERALLS_REPO_TOKEN`` environment variable set in
the `GitHub Actions secrets`_.
.. _line coverage: https://coveralls.io/github/dhermes/bezier
.. _coveralls.io: https://coveralls.io/
.. _GitHub Actions secrets: https://github.com/dhermes/bezier/settings/secrets/actions
To run the coverage report locally:
.. code-block:: console
$ nox --session cover
$ # OR
$ PYTHONPATH=src/python/ python -m pytest \
> --cov=bezier \
> --cov=tests.unit \
> tests/unit/
Slow Tests
==========
To run unit tests without test cases that have been (explicitly)
marked slow, use the ``--ignore-slow`` flag:
.. code-block:: console
$ nox --session "unit-3.10" -- --ignore-slow
$ nox --session "unit-3.11" -- --ignore-slow
$ nox --session "unit-3.12" -- --ignore-slow
$ nox --session unit -- --ignore-slow
These slow tests have been identified via:
.. code-block:: console
$ ...
$ nox --session "unit-3.11" -- --durations=10
and then marked with ``pytest.mark.skipif``.
Slow Install
============
Installing NumPy with `PyPy`_ can take upwards of two minutes (however
the NumPy project has started publishing built wheels for PyPy) and installing
SciPy can take as much as seven minutes. This makes it prohibitive to create a
new environment for testing.
.. _PyPy: https://pypy.org/
In order to avoid this penalty, the ``WHEELHOUSE`` environment variable can be
used to instruct ``nox`` to install NumPy and SciPy from locally built wheels
when installing the ``pypy3`` sessions.
To pre-build NumPy and SciPy wheels:
.. code-block:: console
$ pypy3 -m virtualenv pypy3-venv
$ pypy3-venv/bin/python -m pip wheel --wheel-dir="${{WHEELHOUSE}}" numpy
$ pypy3-venv/bin/python -m pip install "${{WHEELHOUSE}}/numpy*.whl"
$ pypy3-venv/bin/python -m pip wheel --wheel-dir="${{WHEELHOUSE}}" scipy
$ rm -fr pypy3-venv/
In addition to the ``WHEELHOUSE`` environment variable, the paths
``${{HOME}}/wheelhouse`` and ``/wheelhouse`` will also be searched for
pre-built wheels.
****************
Functional Tests
****************
Line coverage and unit tests are not entirely sufficient to
test **numerical software**. As a result, there is a fairly
large collection of `functional tests`_ for ``bezier``.
These give a broad sampling of curve-curve intersection,
triangle-triangle intersection and segment-box intersection problems to
check both the accuracy (i.e. detecting all intersections) and the
precision of the detected intersections.
To run the functional tests:
.. code-block:: console
$ nox --session "functional-3.10"
$ nox --session "functional-3.11"
$ nox --session "functional-3.12"
$ nox --session "functional-pypy3"
$ nox --session functional # Run all versions
$ # OR
$ PYTHONPATH=src/python/ python3.10 -m pytest tests/functional/
$ PYTHONPATH=src/python/ python3.11 -m pytest tests/functional/
$ PYTHONPATH=src/python/ python3.12 -m pytest tests/functional/
$ PYTHONPATH=src/python/ pypy3 -m pytest tests/functional/
.. _functional tests: https://github.com/dhermes/bezier/tree/{revision}/tests/functional
For example, the following curve-curve intersection is a
functional test case:
.. image:: https://raw.githubusercontent.com/dhermes/bezier/{revision}/docs/images/curves11_and_26.png
:align: center
and there is a `Curve-Curve Intersection`_ document which captures many of
the cases in the functional tests.
.. _Curve-Curve Intersection: https://bezier.readthedocs.io/en/{rtd_version}/algorithms/curve-curve-intersection.html
A triangle-triangle intersection functional test case:
.. image:: https://raw.githubusercontent.com/dhermes/bezier/{revision}/docs/images/triangles1Q_and_2Q.png
:align: center
a segment-box functional test case:
.. image:: https://raw.githubusercontent.com/dhermes/bezier/{revision}/docs/images/test_goes_through_box08.png
:align: center
and a "locate point on triangle" functional test case:
.. image:: https://raw.githubusercontent.com/dhermes/bezier/{revision}/docs/images/test_triangle3_and_point1.png
:align: center
Functional Test Data
====================
The curve-curve and triangle-triangle intersection test cases are stored in
JSON files:
* `curves.json`_
* `curve_intersections.json`_
* `triangles.json`_
* `triangle_intersections.json`_
This way, the test cases are programming language agnostic and can be
repurposed. The `JSON schema`_ for these files are stored in the
``tests/functional/schema`` directory.
.. _curves.json: https://github.com/dhermes/bezier/blob/{revision}/tests/functional/curves.json
.. _curve_intersections.json: https://github.com/dhermes/bezier/blob/{revision}/tests/functional/curve_intersections.json
.. _triangles.json: https://github.com/dhermes/bezier/blob/{revision}/tests/functional/triangles.json
.. _triangle_intersections.json: https://github.com/dhermes/bezier/blob/{revision}/tests/functional/triangle_intersections.json
.. _JSON schema: http://json-schema.org/
************
Coding Style
************
Code is `PEP8`_ compliant and this is enforced with `flake8`_
and `Pylint`_.
.. _PEP8: https://www.python.org/dev/peps/pep-0008/
.. _flake8: http://flake8.pycqa.org
.. _Pylint: https://www.pylint.org
To check compliance:
.. code-block:: console
$ nox --session lint
A few extensions and overrides have been specified in the `pylintrc`_
configuration for ``bezier``.
.. _pylintrc: https://github.com/dhermes/bezier/blob/{revision}/pylintrc
Docstring Style
===============
We require docstrings on all public objects and enforce this with
our ``lint`` checks. The docstrings mostly follow `PEP257`_
and are written in the `Google style`_, e.g.
.. code-block:: rest
Args:
path (str): The path of the file to wrap
field_storage (FileStorage): The :class:`FileStorage` instance to wrap
temporary (bool): Whether or not to delete the file when the File
instance is destructed
Returns:
BufferedFileStorage: A buffered writable file descriptor
In order to support these in Sphinx, we use the `Napoleon`_ extension.
In addition, the `sphinx-docstring-typing`_ Sphinx extension is used to
allow for `type annotation`_ for arguments and result (introduced in
Python 3.5).
.. _PEP257: https://www.python.org/dev/peps/pep-0257/
.. _Google style: https://google.github.io/styleguide/pyguide.html#Comments__body
.. _Napoleon: https://sphinxcontrib-napoleon.readthedocs.io
.. _sphinx-docstring-typing: https://pypi.org/project/sphinx-docstring-typing/
.. _type annotation: https://docs.python.org/3/library/typing.html
*************
Documentation
*************
The documentation is built with `Sphinx`_ and automatically
updated on `RTD`_ every time a commit is pushed to ``main``.
.. _Sphinx: http://www.sphinx-doc.org
.. _RTD: https://readthedocs.org/
To build the documentation locally:
.. code-block:: console
$ nox --session docs
$ # OR (from a Python 3.10 or later environment)
$ PYTHONPATH=src/python/ ./scripts/build-docs.sh
Documentation Snippets
======================
A large effort is made to provide useful snippets in documentation.
To make sure these snippets are valid (and remain valid over
time), `doctest`_ is used to check that the interpreter output
in the snippets are valid.
To run the documentation tests:
.. code-block:: console
$ nox --session doctest
$ # OR (from a Python 3.10 or later environment)
$ PYTHONPATH=src/python/:. sphinx-build -W \
> -b doctest \
> -d docs/build/doctrees \
> docs \
> docs/build/doctest
Documentation Images
====================
Many images are included to illustrate the curves / triangles / etc.
under consideration and to display the result of the operation
being described. To keep these images up-to-date with the doctest
snippets, the images are created as doctest cleanup.
In addition, the images in the `Curve-Curve Intersection`_ document and
this document are generated as part of the functional tests.
To regenerate all the images:
.. code-block:: console
$ nox --session docs_images
$ # OR (from a Python 3.10 or later environment)
$ export MATPLOTLIBRC=docs/ GENERATE_IMAGES=True PYTHONPATH=src/python/
$ sphinx-build -W \
> -b doctest \
> -d docs/build/doctrees \
> docs \
> docs/build/doctest
$ python tests/functional/make_segment_box_images.py
$ python tests/functional/make_triangle_locate_images.py
$ python tests/functional/make_curve_curve_images.py
$ python tests/functional/make_triangle_triangle_images.py
$ unset MATPLOTLIBRC GENERATE_IMAGES PYTHONPATH
**********************
Continuous Integration
**********************
Tests are run on `GitHub Actions`_ (Linux, macOS and Windows)
after every commit. To see which tests are run, see
the `Linux config`_, the `macOS config`_ and the `Windows config`_.
.. _GitHub Actions: https://github.com/dhermes/bezier/actions
.. _Linux config: https://github.com/dhermes/bezier/blob/{revision}/.github/workflows/linux.yaml
.. _macOS config: https://github.com/dhermes/bezier/blob/{revision}/.github/workflows/macos.yaml
.. _Windows config: https://github.com/dhermes/bezier/blob/{revision}/.github/workflows/windows.yaml
****************************************
Release Process / Deploying New Versions
****************************************
New versions are pushed to `PyPI`_ manually after a ``git`` tag is
created. The process is manual (rather than automated) for several
reasons:
* The documentation and README (which acts as the landing page text on
PyPI) will be updated with links scoped to the versioned tag (rather
than ``main``). This update occurs via the ``doc_template_release.py``
script.
* Several badges on the documentation landing page (``index.rst``) are
irrelevant to a fixed version (such as the "latest" version of the
package).
* The build badges in the README and the documentation will be
changed to point to a fixed (and passing) build that has already
completed (will be the build that occurred when the tag was pushed). If
the builds pushed to PyPI automatically, a build would need to
link to itself **while** being run.
* Wheels need to be built for Linux, macOS and Windows. Building wheels occurs
via the `Building Wheels workflow`_. After being built, each wheel will be
pushed directly to PyPI via `twine`_.
* The release will be manually pushed to `TestPyPI`_ so the landing
page can be visually inspected and the package can be installed
from TestPyPI rather than from a local file.
.. _PyPI: https://pypi.org/project/bezier/
.. _twine: https://packaging.python.org/distributing/
.. _TestPyPI: https://packaging.python.org/guides/using-testpypi/
.. _Building Wheels workflow: https://github.com/dhermes/bezier/blob/{revision}/.github/workflows/wheels.yaml
Supported Python Versions
=========================
``bezier`` explicitly supports:
- `Python 3.10`_
- `Python 3.11`_
- `Python 3.12`_
- `PyPy 3`_
.. _Python 3.10: https://docs.python.org/3.10/
.. _Python 3.11: https://docs.python.org/3.11/
.. _Python 3.12: https://docs.python.org/3.12/
.. _PyPy 3: https://pypy.org/
Supported versions can be found in the ``noxfile.py`` `config`_.
.. _config: https://github.com/dhermes/bezier/blob/{revision}/noxfile.py
Versioning
==========
``bezier`` follows `calendar versioning`_.
.. _calendar versioning: https://calver.org/
*********************
Environment Variables
*********************
This project uses environment variables for building the
``bezier._speedup`` binary extension:
- ``BEZIER_INSTALL_PREFIX``: A directory where ``libbezier`` is installed,
including the shared library (``lib/``) and headers (``include/``). This
environment variable is required to build the binary extension.
- ``BEZIER_NO_EXTENSION``: If set, this will indicate that only the pure
Python package should be built and installed (i.e. without the binary
extension).
- ``BEZIER_IGNORE_VERSION_CHECK``: Will instruct ``pip`` and ``setup.py`` to
ignore a check on the current version of Python. By default, Python installs
of ``bezier`` will explicitly check for supported versions and this opts
out of that check (e.g. if a new version of Python was just released).
This will only be relevant when installing from source, but a new version of
Python will also mean the existing wheels on PyPI won't support that new
version.
- ``BEZIER_EXTRA_DLL``: Used to add (optional) extra directory to DLL search
path on Windows. This is intended to be used in tests primarily, but may also
be required when building from source. Multiple directories can be provided,
separated by the Windows path separator (``;``).
and for running tests and interacting with Continuous Integration
services:
- ``WHEELHOUSE``: If set, this gives a path to prebuilt NumPy and SciPy wheels
for PyPy 3.
- ``GENERATE_IMAGES``: Indicates to ``nox --session doctest`` that images
should be generated during cleanup of each test case.
- ``READTHEDOCS``: Indicates currently running on Read The Docs (RTD). This is
used to tell Sphinx to use the RTD theme when **not** running on RTD.
- ``COVERALLS_REPO_TOKEN``: To upload the coverage report.