summaryrefslogtreecommitdiff
path: root/lib/spack/docs/build_systems/pythonpackage.rst
diff options
context:
space:
mode:
Diffstat (limited to 'lib/spack/docs/build_systems/pythonpackage.rst')
-rw-r--r--lib/spack/docs/build_systems/pythonpackage.rst777
1 files changed, 275 insertions, 502 deletions
diff --git a/lib/spack/docs/build_systems/pythonpackage.rst b/lib/spack/docs/build_systems/pythonpackage.rst
index 365c5d7bce..6fdd3ee9b0 100644
--- a/lib/spack/docs/build_systems/pythonpackage.rst
+++ b/lib/spack/docs/build_systems/pythonpackage.rst
@@ -9,216 +9,80 @@
PythonPackage
-------------
-Python packages and modules have their own special build system.
+Python packages and modules have their own special build system. This
+documentation covers everything you'll need to know in order to write
+a Spack build recipe for a Python library.
-^^^^^^
-Phases
-^^^^^^
-
-The ``PythonPackage`` base class provides the following phases that
-can be overridden:
-
-* ``build``
-* ``build_py``
-* ``build_ext``
-* ``build_clib``
-* ``build_scripts``
-* ``install``
-* ``install_lib``
-* ``install_headers``
-* ``install_scripts``
-* ``install_data``
-
-These are all standard ``setup.py`` commands and can be found by running:
-
-.. code-block:: console
-
- $ python setup.py --help-commands
-
-
-By default, only the ``build`` and ``install`` phases are run:
-
-#. ``build`` - build everything needed to install
-#. ``install`` - install everything from build directory
-
-If for whatever reason you need to run more phases, simply modify your
-``phases`` list like so:
-
-.. code-block:: python
-
- phases = ['build_ext', 'install']
-
-
-Each phase provides a function ``<phase>`` that runs:
-
-.. code-block:: console
-
- $ python -s setup.py --no-user-cfg <phase>
-
-
-Each phase also has a ``<phase_args>`` function that can pass arguments to
-this call. All of these functions are empty except for the ``install_args``
-function, which passes ``--prefix=/path/to/installation/prefix``. There is
-also some additional logic specific to setuptools and eggs.
-
-If you need to run a phase that is not a standard ``setup.py`` command,
-you'll need to define a function for it like so:
-
-.. code-block:: python
-
- phases = ['configure', 'build', 'install']
-
- def configure(self, spec, prefix):
- self.setup_py('configure')
-
-
-^^^^^^
-Wheels
-^^^^^^
-
-Some Python packages are closed-source and distributed as wheels.
-Instead of using the ``PythonPackage`` base class, you should extend
-the ``Package`` base class and implement the following custom installation
-procedure:
-
-.. code-block:: python
-
- def install(self, spec, prefix):
- pip = which('pip')
- pip('install', self.stage.archive_file, '--prefix={0}'.format(prefix))
-
-
-This will require a dependency on pip, as mentioned below.
-
-^^^^^^^^^^^^^^^
-Important files
-^^^^^^^^^^^^^^^
-
-Python packages can be identified by the presence of a ``setup.py`` file.
-This file is used by package managers like ``pip`` to determine a
-package's dependencies and the version of dependencies required, so if
-the ``setup.py`` file is not accurate, the package will not build properly.
-For this reason, the ``setup.py`` file should be fairly reliable. If the
-documentation and ``setup.py`` disagree on something, the ``setup.py``
-file should be considered to be the truth. As dependencies are added or
-removed, the documentation is much more likely to become outdated than
-the ``setup.py``.
-
-The Python ecosystem has evolved significantly over the years. Before
-setuptools became popular, most packages listed their dependencies in a
-``requirements.txt`` file. Once setuptools took over, these dependencies
-were listed directly in the ``setup.py``. Newer PEPs introduced additional
-files, like ``setup.cfg`` and ``pyproject.toml``. You should look out for
-all of these files, as they may all contain important information about
-package dependencies.
-
-Some Python packages are closed-source and are distributed as Python
-wheels. For example, ``py-azureml-sdk`` downloads a ``.whl`` file. This
-file is simply a zip file, and can be extracted using:
-
-.. code-block:: console
-
- $ unzip *.whl
-
-
-The zip file will not contain a ``setup.py``, but it will contain a
-``METADATA`` file which contains all the information you need to
-write a ``package.py`` build recipe.
-
-.. _pypi:
-
-^^^^
-PyPI
-^^^^
+^^^^^^^^^^^
+Terminology
+^^^^^^^^^^^
-The vast majority of Python packages are hosted on PyPI (The Python
-Package Index), which is :ref:`preferred over GitHub <pypi-vs-github>`
-for downloading packages. ``pip`` only supports packages hosted on PyPI, making
-it the only option for developers who want a simple installation.
-Search for "PyPI <package-name>" to find the download page. Note that
-some pages are versioned, and the first result may not be the newest
-version. Click on the "Latest Version" button to the top right to see
-if a newer version is available. The download page is usually at::
+In the Python ecosystem, there are a number of terms that are
+important to understand.
- https://pypi.org/project/<package-name>
+**PyPI**
+ The `Python Package Index <https://pypi.org/>`_, where most Python
+ libraries are hosted.
+**sdist**
+ Source distributions, distributed as tarballs (.tar.gz) and zip
+ files (.zip). Contain the source code of the package.
-Since PyPI is so common, the ``PythonPackage`` base class has a
-``pypi`` attribute that can be set. Once set, ``pypi`` will be used
-to define the ``homepage``, ``url``, and ``list_url``. For example,
-the following:
+**bdist**
+ Built distributions, distributed as wheels (.whl). Contain the
+ pre-built library.
-.. code-block:: python
-
- homepage = 'https://pypi.org/project/setuptools/'
- url = 'https://pypi.org/packages/source/s/setuptools/setuptools-49.2.0.zip'
- list_url = 'https://pypi.org/simple/setuptools/'
-
-
-is equivalent to:
-
-.. code-block:: python
+**wheel**
+ A binary distribution format common in the Python ecosystem. This
+ file is actually just a zip file containing specific metadata and
+ code. See the
+ `documentation <https://packaging.python.org/en/latest/specifications/binary-distribution-format/>`_
+ for more details.
- pypi = 'setuptools/setuptools-49.2.0.zip'
+**build frontend**
+ Command-line tools used to build and install wheels. Examples
+ include `pip <https://pip.pypa.io/>`_,
+ `build <https://pypa-build.readthedocs.io/>`_, and
+ `installer <https://installer.readthedocs.io/>`_.
+**build backend**
+ Libraries used to define how to build a wheel. Examples
+ include `setuptools <https://setuptools.pypa.io/>`__,
+ `flit <https://flit.readthedocs.io/>`_, and
+ `poetry <https://python-poetry.org/>`_.
^^^^^^^^^^^
-Description
+Downloading
^^^^^^^^^^^
-The top of the PyPI downloads page contains a description of the
-package. The first line is usually a short description, while there
-may be a several line "Project Description" that follows. Choose whichever
-is more useful. You can also get these descriptions on the command-line
-using:
-
-.. code-block:: console
+The first step in packaging a Python library is to figure out where
+to download it from. The vast majority of Python packages are hosted
+on `PyPI <https://pypi.org/>`_, which is
+:ref:`preferred over GitHub <pypi-vs-github>` for downloading
+packages. Search for the package name on PyPI to find the project
+page. The project page is usually located at::
- $ python setup.py --description
- $ python setup.py --long-description
+ https://pypi.org/project/<package-name>
+On the project page, there is a "Download files" tab containing
+download URLs. Whenever possible, we prefer to build Spack packages
+from source. If PyPI only has wheels, check to see if the project is
+hosted on GitHub and see if GitHub has source distributions. The
+project page usually has a "Homepage" and/or "Source code" link for
+this. If the project is closed-source, it may only have wheels
+available. For example, ``py-azureml-sdk`` is closed-source and can
+be downloaded from::
-^^^^^^^^
-Homepage
-^^^^^^^^
+ https://pypi.io/packages/py3/a/azureml_sdk/azureml_sdk-1.11.0-py3-none-any.whl
-Package developers use ``setup.py`` to upload new versions to PyPI.
-The ``setup`` method often passes metadata like ``homepage`` to PyPI.
-This metadata is displayed on the left side of the download page.
-Search for the text "Homepage" under "Project links" to find it. You
-should use this page instead of the PyPI page if they differ. You can
-also get the homepage on the command-line by running:
+Once you've found a URL to download the package from, run:
.. code-block:: console
- $ python setup.py --url
-
-
-^^^
-URL
-^^^
-
-If ``pypi`` is set as mentioned above, ``url`` and ``list_url`` will
-be automatically set for you. If both ``.tar.gz`` and ``.zip`` versions
-are available, ``.tar.gz`` is preferred. If some releases offer both
-``.tar.gz`` and ``.zip`` versions, but some only offer ``.zip`` versions,
-use ``.zip``.
-
-Some Python packages are closed-source and do not ship ``.tar.gz`` or ``.zip``
-files on either PyPI or GitHub. If this is the case, you can still download
-and install a Python wheel. For example, ``py-azureml-sdk`` is closed source
-and can be downloaded from::
-
- https://pypi.io/packages/py3/a/azureml_sdk/azureml_sdk-1.11.0-py3-none-any.whl
-
+ $ spack create <url>
-You may see Python-specific or OS-specific URLs. Note that when you add a
-``.whl`` URL, you should add ``expand=False`` to ensure that Spack doesn't
-try to extract the wheel:
-.. code-block:: python
-
- version('1.11.0', sha256='d8c9d24ea90457214d798b0d922489863dad518adde3638e08ef62de28fb183a', expand=False)
+to create a new package template.
.. _pypi-vs-github:
@@ -226,11 +90,13 @@ try to extract the wheel:
PyPI vs. GitHub
"""""""""""""""
-Many packages are hosted on PyPI, but are developed on GitHub or another
-version control systems. The tarball can be downloaded from either
-location, but PyPI is preferred for the following reasons:
+Many packages are hosted on PyPI, but are developed on GitHub or
+another version control system hosting service. The source code can
+be downloaded from either location, but PyPI is preferred for the
+following reasons:
-#. PyPI contains the bare minimum number of files needed to install the package.
+#. PyPI contains the bare minimum number of files needed to install
+ the package.
You may notice that the tarball you download from PyPI does not
have the same checksum as the tarball you download from GitHub.
@@ -267,300 +133,217 @@ location, but PyPI is preferred for the following reasons:
PyPI is nice because it makes it physically impossible to
re-release the same version of a package with a different checksum.
-Use the :ref:`pypi attribute <pypi>` to facilitate construction of PyPI package
-references.
-
-^^^^^^^^^^^^^^^^^^^^^^^^^
-Build system dependencies
-^^^^^^^^^^^^^^^^^^^^^^^^^
-
-There are a few dependencies common to the ``PythonPackage`` build system.
+The only reason to use GitHub instead of PyPI is if PyPI only has
+wheels or if the PyPI sdist is missing a file needed to build the
+package. If this is the case, please add a comment above the ``url``
+explaining this.
-""""""
-Python
-""""""
+^^^^
+PyPI
+^^^^
-Obviously, every ``PythonPackage`` needs Python at build-time to run
-``python setup.py build && python setup.py install``. Python is also
-needed at run-time if you want to import the module. Due to backwards
-incompatible changes between Python 2 and 3, it is very important to
-specify which versions of Python are supported. If the documentation
-mentions that Python 3 is required, this can be specified as:
+Since PyPI is so commonly used to host Python libraries, the
+``PythonPackage`` base class has a ``pypi`` attribute that can be
+set. Once set, ``pypi`` will be used to define the ``homepage``,
+``url``, and ``list_url``. For example, the following:
.. code-block:: python
- depends_on('python@3:', type=('build', 'run'))
+ homepage = 'https://pypi.org/project/setuptools/'
+ url = 'https://pypi.org/packages/source/s/setuptools/setuptools-49.2.0.zip'
+ list_url = 'https://pypi.org/simple/setuptools/'
-If Python 2 is required, this would look like:
+is equivalent to:
.. code-block:: python
- depends_on('python@:2', type=('build', 'run'))
-
-
-If Python 2.7 is the only version that works, you can use:
-
-.. code-block:: python
+ pypi = 'setuptools/setuptools-49.2.0.zip'
- depends_on('python@2.7:2.8', type=('build', 'run'))
+If a package has a different homepage listed on PyPI, you can
+override it by setting your own ``homepage``.
-The documentation may not always specify supported Python versions.
-Another place to check is in the ``setup.py`` or ``setup.cfg`` file.
-Look for a line containing ``python_requires``. An example from
-`py-numpy <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/py-numpy/package.py>`_
-looks like:
+^^^^^^^^^^^
+Description
+^^^^^^^^^^^
-.. code-block:: python
+The top of the PyPI project page contains a short description of the
+package. The "Project description" tab may also contain a longer
+description of the package. Either of these can be used to populate
+the package docstring.
- python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*'
+^^^^^^^^^^^^^
+Build backend
+^^^^^^^^^^^^^
+Once you've determined the basic metadata for a package, the next
+step is to determine the build backend. ``PythonPackage`` uses
+`pip <https://pip.pypa.io/>`_ to install the package, but pip
+requires a backend to actually build the package.
-You may also find a version check at the top of the ``setup.py``:
+To determine the build backend, look for a ``pyproject.toml`` file.
+If there is no ``pyproject.toml`` file and only a ``setup.py`` or
+``setup.cfg`` file, you can assume that the project uses
+:ref:`setuptools`. If there is a ``pyproject.toml`` file, see if it
+contains a ``[build-system]`` section. For example:
-.. code-block:: python
+.. code-block:: toml
- if sys.version_info[:2] < (2, 7) or (3, 0) <= sys.version_info[:2] < (3, 4):
- raise RuntimeError("Python version 2.7 or >= 3.4 required.")
+ [build-system]
+ requires = [
+ "setuptools>=42",
+ "wheel",
+ ]
+ build-backend = "setuptools.build_meta"
-This can be converted to Spack's spec notation like so:
+This section does two things: the ``requires`` key lists build
+dependencies of the project, and the ``build-backend`` key defines
+the build backend. All of these build dependencies should be added as
+dependencies to your package:
.. code-block:: python
- depends_on('python@2.7:2.8,3.4:', type=('build', 'run'))
+ depends_on('py-setuptools@42:', type='build')
-If you are writing a recipe for a package that only distributes
-wheels, look for a section in the ``METADATA`` file that looks like::
-
- Requires-Python: >=3.5,<4
+Note that ``py-wheel`` is already listed as a build dependency in the
+``PythonPackage`` base class, so you don't need to add it unless you
+need to specify a specific version requirement or change the
+dependency type.
+See `PEP 517 <https://www.python.org/dev/peps/pep-0517/>`_ and
+`PEP 518 <https://www.python.org/dev/peps/pep-0518/>`_ for more
+information on the design of ``pyproject.toml``.
-This would be translated to:
+Depending on which build backend a project uses, there are various
+places that run-time dependencies can be listed.
-.. code-block:: python
+"""""""""
+distutils
+"""""""""
- extends('python')
- depends_on('python@3.5:3', type=('build', 'run'))
+Before the introduction of setuptools and other build backends,
+Python packages had to rely on the built-in distutils library.
+Distutils is missing many of the features that setuptools and other
+build backends offer, and users are encouraged to use setuptools
+instead. In fact, distutils was deprecated in Python 3.10 and will be
+removed in Python 3.12. Because of this, pip actually replaces all
+imports of distutils with setuptools. If a package uses distutils,
+you should instead add a build dependency on setuptools. Check for a
+``requirements.txt`` file that may list dependencies of the project.
-
-Many ``setup.py`` or ``setup.cfg`` files also contain information like::
-
- Programming Language :: Python :: 2
- Programming Language :: Python :: 2.6
- Programming Language :: Python :: 2.7
- Programming Language :: Python :: 3
- Programming Language :: Python :: 3.3
- Programming Language :: Python :: 3.4
- Programming Language :: Python :: 3.5
- Programming Language :: Python :: 3.6
-
-
-This is a list of versions of Python that the developer likely tests.
-However, you should not use this to restrict the versions of Python
-the package uses unless one of the two former methods (``python_requires``
-or ``sys.version_info``) is used. There is no logic in setuptools
-that prevents the package from building for Python versions not in
-this list, and often new releases like Python 3.7 or 3.8 work just fine.
+.. _setuptools:
""""""""""
setuptools
""""""""""
-Originally, the Python language had a single build system called
-distutils, which is built into Python. Distutils provided a common
-framework for package authors to describe their project and how it
-should be built. However, distutils was not without limitations.
-Most notably, there was no way to list a project's dependencies
-with distutils. Along came setuptools, a non-builtin build system
-designed to overcome the limitations of distutils. Both projects
-use a similar API, making the transition easy while adding much
-needed functionality. Today, setuptools is used in around 90% of
-the Python packages in Spack.
-
-Since setuptools isn't built-in to Python, you need to add it as a
-dependency. To determine whether or not a package uses setuptools,
-search the file for an import statement like:
+If the ``pyproject.toml`` lists ``setuptools.build_meta`` as a
+``build-backend``, or if the package has a ``setup.py`` that imports
+``setuptools``, or if the package has a ``setup.cfg`` file, then it
+uses setuptools to build. Setuptools is a replacement for the
+distutils library, and has almost the exact same API. Dependencies
+can be listed in the ``setup.py`` or ``setup.cfg`` file. Look for the
+following arguments:
-.. code-block:: python
-
- import setuptools
+* ``python_requires``
+ This specifies the version of Python that is required.
-or:
+* ``setup_requires``
-.. code-block:: python
+ These packages are usually only needed at build-time, so you can
+ add them with ``type='build'``.
- from setuptools import setup
+* ``install_requires``
+ These packages are required for building and installation. You can
+ add them with ``type=('build', 'run')``.
-Some packages are designed to work with both setuptools and distutils,
-so you may find something like:
+* ``extras_require``
-.. code-block:: python
+ These packages are optional dependencies that enable additional
+ functionality. You should add a variant that optionally adds these
+ dependencies. This variant should be False by default.
- try:
- from setuptools import setup
- except ImportError:
- from distutils.core import setup
+* ``tests_require``
+ These are packages that are required to run the unit tests for the
+ package. These dependencies can be specified using the
+ ``type='test'`` dependency type. However, the PyPI tarballs rarely
+ contain unit tests, so there is usually no reason to add these.
-This uses setuptools if available, and falls back to distutils if not.
-In this case, you would still want to add a setuptools dependency, as
-it offers us more control over the installation.
+See https://setuptools.pypa.io/en/latest/userguide/dependency_management.html
+for more information on how setuptools handles dependency management.
+See `PEP 440 <https://www.python.org/dev/peps/pep-0440/#version-specifiers>`_
+for documentation on version specifiers in setuptools.
-Unless specified otherwise, setuptools is usually a build-only dependency.
-That is, it is needed to install the software, but is not needed at
-run-time. This can be specified as:
+""""
+flit
+""""
-.. code-block:: python
+There are actually two possible ``build-backend`` for flit, ``flit``
+and ``flit_core``. If you see these in the ``pyproject.toml``, add a
+build dependency to your package. With flit, all dependencies are
+listed directly in the ``pyproject.toml`` file. Older versions of
+flit used to store this info in a ``flit.ini`` file, so check for
+this too.
- depends_on('py-setuptools', type='build')
+Either of these files may contain keys like:
+* ``requires-python``
-"""
-pip
-"""
+ This specifies the version of Python that is required
-Packages distributed as Python wheels will require an extra dependency
-on pip:
+* ``dependencies`` or ``requires``
-.. code-block:: python
+ These packages are required for building and installation. You can
+ add them with ``type=('build', 'run')``.
- depends_on('py-pip', type='build')
+* ``project.optional-dependencies`` or ``requires-extra``
+ This section includes keys with lists of optional dependencies
+ needed to enable those features. You should add a variant that
+ optionally adds these dependencies. This variant should be False
+ by default.
-We will use pip to install the actual wheel.
+See https://flit.readthedocs.io/en/latest/pyproject_toml.html for
+more information.
""""""
-cython
+poetry
""""""
-Compared to compiled languages, interpreted languages like Python can
-be quite a bit slower. To work around this, some Python developers
-rewrite computationally demanding sections of code in C, a process
-referred to as "cythonizing". In order to build these package, you
-need to add a build dependency on cython:
-
-.. code-block:: python
-
- depends_on('py-cython', type='build')
-
-
-Look for references to "cython" in the ``setup.py`` to determine
-whether or not this is necessary. Cython may be optional, but
-even then you should list it as a required dependency. Spack is
-designed to compile software, and is meant for HPC facilities
-where speed is crucial. There is no reason why someone would not
-want an optimized version of a library instead of the pure-Python
-version.
-
-Note that some release tarballs come pre-cythonized, and cython is
-not needed as a dependency. However, this is becoming less common
-as Python continues to evolve and developers discover that cythonized
-sources are no longer compatible with newer versions of Python and
-need to be re-cythonized.
-
-^^^^^^^^^^^^^^^^^^^
-Python dependencies
-^^^^^^^^^^^^^^^^^^^
-
-When you install a package with ``pip``, it reads the ``setup.py``
-file in order to determine the dependencies of the package.
-If the dependencies are not yet installed, ``pip`` downloads them
-and installs them for you. This may sound convenient, but Spack
-cannot rely on this behavior for two reasons:
-
-#. Spack needs to be able to install packages on air-gapped networks.
-
- If there is no internet connection, ``pip`` can't download the
- package dependencies. By explicitly listing every dependency in
- the ``package.py``, Spack knows what to download ahead of time.
-
-#. Duplicate installations of the same dependency may occur.
-
- Spack supports *activation* of Python extensions, which involves
- symlinking the package installation prefix to the Python installation
- prefix. If your package is missing a dependency, that dependency
- will be installed to the installation directory of the same package.
- If you try to activate the package + dependency, it may cause a
- problem if that package has already been activated.
-
-For these reasons, you must always explicitly list all dependencies.
-Although the documentation may list the package's dependencies,
-often the developers assume people will use ``pip`` and won't have to
-worry about it. Always check the ``setup.py`` to find the true
-dependencies.
-
-If the package relies on ``distutils``, it may not explicitly list its
-dependencies. Check for statements like:
-
-.. code-block:: python
-
- try:
- import numpy
- except ImportError:
- raise ImportError("numpy must be installed prior to installation")
-
-
-Obviously, this means that ``py-numpy`` is a dependency.
-
-If the package uses ``setuptools``, check for the following clues:
-
-* ``python_requires``
-
- As mentioned above, this specifies which versions of Python are
- required.
-
-* ``setup_requires``
-
- These packages are usually only needed at build-time, so you can
- add them with ``type='build'``.
-
-* ``install_requires``
-
- These packages are required for building and installation. You can
- add them with ``type=('build', 'run')``.
-
-* ``extra_requires``
-
- These packages are optional dependencies that enable additional
- functionality. You should add a variant that optionally adds these
- dependencies. This variant should be False by default.
-
-* ``test_requires``
+Like flit, poetry also has two possible ``build-backend``, ``poetry``
+and ``poetry_core``. If you see these in the ``pyproject.toml``, add
+a build dependency to your package. With poetry, all dependencies are
+listed directly in the ``pyproject.toml`` file. Dependencies are
+listed in a ``[tool.poetry.dependencies]`` section, and use a
+`custom syntax <https://python-poetry.org/docs/dependency-specification/#version-constraints>`_
+for specifying the version requirements. Note that ``~=`` works
+differently in poetry than in setuptools and flit for versions that
+start with a zero.
- These are packages that are required to run the unit tests for the
- package. These dependencies can be specified using the
- ``type='test'`` dependency type. However, the PyPI tarballs rarely
- contain unit tests, so there is usually no reason to add these.
+""""""
+wheels
+""""""
-In the root directory of the package, you may notice a
-``requirements.txt`` file. It may look like this file contains a list
-of all of the package's dependencies. Don't be fooled. This file is
-used by tools like Travis to install the pre-requisites for the
-package... and a whole bunch of other things. It often contains
-dependencies only needed for unit tests, like:
+Some Python packages are closed-source and are distributed as Python
+wheels. For example, ``py-azureml-sdk`` downloads a ``.whl`` file. This
+file is simply a zip file, and can be extracted using:
-* mock
-* nose
-* pytest
+.. code-block:: console
-It can also contain dependencies for building the documentation, like
-sphinx. If you can't find any information about the package's
-dependencies, you can take a look in ``requirements.txt``, but be sure
-not to add test or documentation dependencies.
+ $ unzip *.whl
-Newer PEPs have added alternative ways to specify a package's dependencies.
-If you don't see any dependencies listed in the ``setup.py``, look for a
-``setup.cfg`` or ``pyproject.toml``. These files can be used to store the
-same ``install_requires`` information that ``setup.py`` used to use.
-If you are write a recipe for a package that only distributes wheels,
-check the ``METADATA`` file for lines like::
+The zip file will not contain a ``setup.py``, but it will contain a
+``METADATA`` file which contains all the information you need to
+write a ``package.py`` build recipe. Check for lines like::
+ Requires-Python: >=3.5,<4
Requires-Dist: azureml-core (~=1.11.0)
Requires-Dist: azureml-dataset-runtime[fuse] (~=1.11.0)
Requires-Dist: azureml-train (~=1.11.0)
@@ -572,62 +355,58 @@ check the ``METADATA`` file for lines like::
Requires-Dist: azureml-train-automl (~=1.11.0); extra == 'automl'
-Lines that use ``Requires-Dist`` are similar to ``install_requires``.
-Lines that use ``Provides-Extra`` are similar to ``extra_requires``,
-and you can add a variant for those dependencies. The ``~=1.11.0``
-syntax is equivalent to ``1.11.0:1.11``.
-
-""""""""""
-setuptools
-""""""""""
-
-Setuptools is a bit of a special case. If a package requires setuptools
-at run-time, how do they express this? They could add it to
-``install_requires``, but setuptools is imported long before this and is
-needed to read this line. And since you can't install the package
-without setuptools, the developers assume that setuptools will already
-be there, so they never mention when it is required. We don't want to
-add run-time dependencies if they aren't needed, so you need to
-determine whether or not setuptools is needed. Grep the installation
-directory for any files containing a reference to ``setuptools`` or
-``pkg_resources``. Both modules come from ``py-setuptools``.
-``pkg_resources`` is particularly common in scripts found in
-``prefix/bin``.
+``Requires-Python`` is equivalent to ``python_requires`` and
+``Requires-Dist`` is equivalent to ``install_requires``.
+``Provides-Extra`` is used to name optional features (variants) and
+a ``Requires-Dist`` with ``extra == 'foo'`` will list any
+dependencies needed for that feature.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Passing arguments to setup.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The default build and install phases should be sufficient to install
-most packages. However, you may want to pass additional flags to
-either phase.
+The default install phase should be sufficient to install most
+packages. However, the installation instructions for a package may
+suggest passing certain flags to the ``setup.py`` call. The
+``PythonPackage`` class has two techniques for doing this.
-You can view the available options for a particular phase with:
+""""""""""""""
+Global options
+""""""""""""""
-.. code-block:: console
+These flags are added directly after ``setup.py`` when pip runs
+``python setup.py install``. For example, the ``py-pyyaml`` package
+has an optional dependency on ``libyaml`` that can be enabled like so:
- $ python setup.py <phase> --help
+.. code-block:: python
+ def global_options(self, spec, prefix):
+ options = []
+ if '+libyaml' in spec:
+ options.append('--with-libyaml')
+ else:
+ options.append('--without-libyaml')
+ return options
-Each phase provides a ``<phase_args>`` function that can be used to
-pass arguments to that phase. For example,
-`py-numpy <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/py-numpy/package.py>`_
-adds:
-.. code-block:: python
+"""""""""""""""
+Install options
+"""""""""""""""
- def build_args(self, spec, prefix):
- args = []
+These flags are added directly after ``install`` when pip runs
+``python setup.py install``. For example, the ``py-pyyaml`` package
+allows you to specify the directories to search for ``libyaml``:
- # From NumPy 1.10.0 on it's possible to do a parallel build.
- if self.version >= Version('1.10.0'):
- # But Parallel build in Python 3.5+ is broken. See:
- # https://github.com/spack/spack/issues/7927
- # https://github.com/scipy/scipy/issues/7112
- if spec['python'].version < Version('3.5'):
- args = ['-j', str(make_jobs)]
+.. code-block:: python
- return args
+ def install_options(self, spec, prefix):
+ options = []
+ if '+libyaml' in spec:
+ options.extend([
+ spec['libyaml'].libs.search_flags,
+ spec['libyaml'].headers.include_flags,
+ ])
+ return options
^^^^^^^
@@ -669,9 +448,9 @@ a "package" is a directory containing files like:
whereas a "module" is a single Python file.
-The ``PythonPackage`` base class automatically detects these module
-names for you. If, for whatever reason, the module names detected
-are wrong, you can provide the names yourself by overriding
+The ``PythonPackage`` base class automatically detects these package
+and module names for you. If, for whatever reason, the module names
+detected are wrong, you can provide the names yourself by overriding
``import_modules`` like so:
.. code-block:: python
@@ -692,10 +471,8 @@ This can be expressed like so:
@property
def import_modules(self):
modules = ['yaml']
-
if '+libyaml' in self.spec:
modules.append('yaml.cyaml')
-
return modules
@@ -713,8 +490,8 @@ Unit tests
""""""""""
The package may have its own unit or regression tests. Spack can
-run these tests during the installation by adding phase-appropriate
-test methods.
+run these tests during the installation by adding test methods after
+installation.
For example, ``py-numpy`` adds the following as a check to run
after the ``install`` phase:
@@ -740,34 +517,14 @@ when testing is enabled during the installation (i.e., ``spack install
Setup file in a sub-directory
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-In order to be compatible with package managers like ``pip``, the package
-is required to place its ``setup.py`` in the root of the tarball. However,
-not every Python package cares about ``pip`` or PyPI. If you are installing
-a package that is not hosted on PyPI, you may find that it places its
-``setup.py`` in a sub-directory. To handle this, add the directory containing
-``setup.py`` to the package like so:
-
-.. code-block:: python
-
- build_directory = 'source'
-
-
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Alternate names for setup.py
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-As previously mentioned, packages need to call their setup script ``setup.py``
-in order to be compatible with package managers like ``pip``. However, some
-packages like
-`py-meep <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/py-meep/package.py>`_ and
-`py-adios <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/py-adios/package.py>`_
-come with multiple setup scripts, one for a serial build and another for a
-parallel build. You can override the default name to use like so:
+Many C/C++ libraries provide optional Python bindings in a
+subdirectory. To tell pip which directory to build from, you can
+override the ``build_directory`` attribute. For example, if a package
+provides Python bindings in a ``python`` directory, you can use:
.. code-block:: python
- def setup_file(self):
- return 'setup-mpi.py' if '+mpi' in self.spec else 'setup.py'
+ build_directory = 'python'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -781,10 +538,14 @@ on Python are not necessarily ``PythonPackage``'s.
Choosing a build system
"""""""""""""""""""""""
-First of all, you need to select a build system. ``spack create`` usually
-does this for you, but if for whatever reason you need to do this manually,
-choose ``PythonPackage`` if and only if the package contains a ``setup.py``
-file.
+First of all, you need to select a build system. ``spack create``
+usually does this for you, but if for whatever reason you need to do
+this manually, choose ``PythonPackage`` if and only if the package
+contains one of the following files:
+
+* ``pyproject.toml``
+* ``setup.py``
+* ``setup.cfg``
"""""""""""""""""""""""
Choosing a package name
@@ -857,10 +618,9 @@ having to add that module to ``PYTHONPATH``.
When deciding between ``extends`` and ``depends_on``, the best rule of
thumb is to check the installation prefix. If Python libraries are
-installed to ``prefix/lib/python2.7/site-packages`` (where 2.7 is the
-MAJOR.MINOR version of Python you used to install the package), then
-you should use ``extends``. If Python libraries are installed elsewhere
-or the only files that get installed reside in ``prefix/bin``, then
+installed to ``<prefix>/lib/pythonX.Y/site-packages``, then you
+should use ``extends``. If Python libraries are installed elsewhere
+or the only files that get installed reside in ``<prefix>/bin``, then
don't use ``extends``, as symlinking the package wouldn't be useful.
^^^^^^^^^^^^^^^^^^^^^
@@ -893,4 +653,17 @@ External documentation
^^^^^^^^^^^^^^^^^^^^^^
For more information on Python packaging, see:
-https://packaging.python.org/
+
+* https://packaging.python.org/
+
+For more information on build and installation frontend tools, see:
+
+* pip: https://pip.pypa.io/
+* build: https://pypa-build.readthedocs.io/
+* installer: https://installer.readthedocs.io/
+
+For more information on build backend tools, see:
+
+* setuptools: https://setuptools.pypa.io/
+* flit: https://flit.readthedocs.io/
+* poetry: https://python-poetry.org/