Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build updates (move towards PEP-517 support) #1329

Merged
merged 22 commits into from
Aug 1, 2022

Conversation

DougBurke
Copy link
Contributor

@DougBurke DougBurke commented Oct 21, 2021

Summary

Move the Sherpa build system towards a more static configuration (PEP 517)

Details

October 22 2021 saw the release of setuptools 58.3.0 where the install method was marked as deprecated - https://setuptools.pypa.io/en/latest/history.html#v58-3-0 - so there's "more urgency" to this. Added April 14 2022 I've been involved in a discussion on how to build C extensions thread at https://discuss.python.org/t/modern-way-to-build-packages-with-c-cython-extensions/15050 which - at the time of writing - suggests that staying with python setup.py develop is reasonable for us (from one respondent).

This work was based on @hamogu's heroic #784 (but doesn't get to some of the good bits from that work, such as supporting tox or switching to setuptools_scm; they can come later). It has seen numerous rebases and reworks as I've tried to get parts of this work merged.

The main changes are:

  • perform some code cleanup and changes to address newer standards and build systems

    • the code in helpers/ has been re-arranged so that helpers.extensions.build_ext is a bit easier to use (it combines the "grab the settings" and the "build the actual extension" steps) and this happened because I realized we were including the wcs libs/headers when building the region code which is not needed (at least in the non-CIAO build)
  • bump to a minimum version of 49.1.2 for setuptools as it is in this release that they include the distutils code (which is technically needed to build nicely with >= python 3.10 but it's not obvious if it's required). As part of this we now extend the numpy.distutils.command.sdist command rather than distutils.command.sdist - this "feels" right but it could have unintended consequences and I can't know remember whether it was needed or not (it's been really hard to track down what any one change does to the build system...)

  • there are then two MANIFEST changes - the first one should be uncontroversial but the second one is a bit unclear to me as I am currently reading about how unclear all this is:

  • the next commit is Move basic options to setup.cfg which moves over much of the information from setup.py to setup.cfg, and removes some of the repetition with the use of the packages = find: option. Note that we'll come back to that in a commit or two...

  • aha: setup.cfg: explicit about packages and tests means I go back to being explicit about the packages and tests. This technically should not be needed but I was seeing some odd behavior and being explicit is "not necessarily bad" and potentially helps out with things like the settings queried by xspec_config.

  • back to code removal: Simplify setup.py removes some code that was to support building when you might not have setuptools - but we now require it. I also ensure setuptools is imported in setup.py as I was seeing "odd" issues with this - this may not be necessary with setuptools >=49.1.2 (or perhaps later) but I do know that setuptools does "fun" stuff with imports related to distutils. At this point I think you can still say 'python setup.py develop` but I don't want to bother worrying about that.

  • in an earlier commit I changed the clean command to allow it to work with setuptools >= 49.1.2, but we know that setuptools wants to remove such commands so in Remove the clean command from setup I have removed the clean command. I have never used the clean command, as I find git clean -dfx . to be "good enough" (and probably better than the current clean command). For reference

  • there are some more replacements of distutils functionality - it's a bit hit or miss on what seems to work, but that's in part because we are trying to use an old setuptools because of some work done in Improve several issues when building and testing Sherpa #1458

  • looking at the setuptools documentation - in particular https://setuptools.pypa.io/en/latest/userguide/declarative_config.html#options - I noted that the entry_points option was listed as added in version 51, so we now require this as a minimum version rather than 49.1.2. Now, it's not obvious to me that this is needed, since I could not obviously trigger problems with an older setuptools version, but I'm not sure it really matters (as version 51.0.0 of setuptools was released December 2020).

  • In looking at https://setuptools.pypa.io/en/latest/userguide/declarative_config.html#options I noted that the data_files option was marked as deprecated, so I then tried to address this. Unfortunately a combination of the documentation they point to https://setuptools.pypa.io/en/latest/userguide/datafiles.html and our own code made this a surprisingly-involved exploration, even though the actual code change is small. I'm relying on the fact that things still build and the tests still run to validate this change. As I note below, this leads to looking at issue group.so and stk.so are installed outside of the sherpa namespace #50, as this is one place we care about data files and locations and the like, but I think that can be looked at separately from this PR.

  • there is then a change to basically use super rather than the explicit class name. This is mainly a code-style change but there is technically a change in behavior in the announcing of building the stack and group libraries during a develop build - we now only report things when they are actually changed, and separate versions rather than a combined version - but I think these messages aren't seen by default anyway.

  • sdist change: I noted earlier that I did not know how to replace the numpy.distutils command get_data_files but my claim is that we don't need to, as I could not trigger this bit of code to file. I've replaced the method with something that falls over (rather than removing the method) so we will find out if we do actually use it. However, our CI run doesn't trigger it, and manual calls to python setup.py sdist doesn't seem to either.

  • sdist: revert the setuptools change. If you ran python setup.py sdist then you got a warning from numpy that the setuptools sdist command could cause problems - and links to Fix setuptools sdist numpy/numpy#7131 (found by @Marie-Terrell ). So I have reverted this change. However, see the Notes - sdist section below.

  • in an attempt to fix installing sherpa; interesting version differences #1545 I have re-implemented the change from do not change the XSPEC configuration when Sherpa is being built #1506 to always include the XSPEC code and tests, even when XSPEC is not being built. The assumption has been that 'import sherpa.astro.xspec' would error out cleanly if XSPEC support was not present, but I was able, in installing sherpa; interesting version differences #1545, get to a place where this was not true. I have no idea what causes this condition to happen, and it seems to depend on various versions of python/setuptools/..., but the fact that it can happen makes me worry about it. Now, with this change and an XSPEC-free build a call to 'import sherpa.astro.xspec' would trigger a confusing recursive-import message from ._xspec, so we now catch this and add a related XSPEC support is not enabled import error to be more-understandable (but keep the original error which is needed to debug potential XSPEC library issues for when we do have XSPEC support enabled).

  • finally I switch the GH workflow calls to build with pip rather than directly call setup.py

    • note that we use the pip --verbose flag to get the build output included in the log. This is not technically needed, and makes going through the logs slower (more to download) but does give us access to information on how the build works, which I think is worth it.

Notes

So, in the move to "declarative metadata"

and we are sticking with pyproject < 60 to follow the NumPy advice as they update their build system - https://numpy.org/doc/stable/reference/distutils_status_migration.html . I think we don't require anything "fancy", so should be able to move away from using numpy.distutils, but I still haven't seen any "to build an extension that uses the NumPy C API you just need to do this sparkly thing" information out there.

The release of NumPy 1.23 now causes this warning to be displayed when you build Sherpa:

/home/dburke/sherpa/sherpa-pr/setup.py:28: DeprecationWarning: 

  `numpy.distutils` is deprecated since NumPy 1.23.0, as a result
  of the deprecation of `distutils` itself. It will be removed for
  Python >= 3.12. For older Python versions it will remain present.
  It is recommended to use `setuptools < 60.0` for those Python versions.
  For more details, see:
    https://numpy.org/devdocs/reference/distutils_status_migration.html 


  from numpy.distutils.core import setup

Some of the changes, such as the data_files change, are related to issue #50 - it would be nice to address this, but it doesn't have to be in this PR.

Notes - sdist

Even with going back to the numpy sdist I still see a warning about setuptools, so I just give up (maybe it's saying we should use distutils not numpy.distutils here?)

% python setup.py sdist > log
/home/dburke/sherpa/sherpa-pr/setup.py:28: DeprecationWarning: 

  `numpy.distutils` is deprecated since NumPy 1.23.0, as a result
  of the deprecation of `distutils` itself. It will be removed for
  Python >= 3.12. For older Python versions it will remain present.
  It is recommended to use `setuptools < 60.0` for those Python versions.
  For more details, see:
    https://numpy.org/devdocs/reference/distutils_status_migration.html 


  from numpy.distutils.core import setup
/home/dburke/miniconda3/envs/sherpa-pr/lib/python3.9/site-packages/setuptools/_distutils/dist.py:987: UserWarning: 
`build_src` is being run, this may lead to missing
files in your sdist!  You want to use distutils.sdist
instead of the setuptools version:

    from distutils.command.sdist import sdist
    cmdclass={'sdist': sdist}"

See numpy's setup.py or gh-7131 for details.
  cmd_obj.run()
/home/dburke/miniconda3/envs/sherpa-pr/lib/python3.9/site-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.
  warnings.warn(

--

References

Configuration

We can see that setup.cfg now contains most of the configuration options that were in setup.py (the remaining fields are version, ext_modules, and cmdclass):

% bat setup.cfg
───────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: setup.cfg
───────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ [metadata]
   2   │ name = sherpa
   3   │ author = Smithsonian Astrophysical Observatory / Chandra X-Ray Center
   4   │ author_email = [email protected]
   5   │ url = http://cxc.harvard.edu/sherpa/
   6   │ project_urls =
   7   │     Issue Tracker = https://github.com/sherpa/sherpa/issues
   8   │
   9   │ description = Modeling and fitting package for scientific data analysis
  10   │ long_description = file: README.md
  11   │ long_description_content_type = text/markdown
  12   │ license = GNU GPL v3
  13   │ license_files = LICENSE
  14   │ platforms = Linux, Mac OS X
  15   │ classifiers=
  16   │     Intended Audience :: Science/Research
  17   │     License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
  18   │     Programming Language :: C
  19   │     Programming Language :: Python :: 3.7
  20   │     Programming Language :: Python :: 3.8
  21   │     Programming Language :: Python :: 3.9
  22   │     Programming Language :: Python :: 3.10
  23   │     Programming Language :: Python :: Implementation :: CPython
  24   │     Topic :: Scientific/Engineering :: Astronomy
  25   │     Topic :: Scientific/Engineering :: Physics
  26   │
  27   │ [options]
  28   │ python_requires = ~=3.7
  29   │ zip_safe = False
  30   │ install_requires =
  31   │     numpy
  32   │
  33   │ tests_require =
  34   │     pytest-xvfb
  35   │     pytest>=5.0,!=5.23
  36   │
  37   │ # Be explicit as something doesn't seem quite right when using find.
  38   │ #
  39   │ # find sherpa -name __init__.py | sort | sed -e "s|/__init__.py||" -e "s|/|.|g"
  40   │ packages =
  41   │     sherpa.astro.datastack
  42   │     sherpa.astro
  43   │     sherpa.astro.io
  44   │     sherpa.astro.models
  45   │     sherpa.astro.optical
...

Things you can now do

PEP 517 builds

The following seem to work, but have seem limited testing (also, the builds used to fail but some changes we've done now mean that even with the main branch you can build a wheel; it's just that when you come to test the result the main branch may fail):

Build using a PEP 517 tool - e.g. build

% pip install build
% python -m build .
...
% pip install dist/sherpa-4.14.0+399.g3329a683-cp310-cp310-linux_x86_64.whl   # actual file name may be different ;-)
...

I tried changing setup.cfg to enable XSPEC support and used python -m build .; pip install dist/.....whl and it built and installed an XSPEC-capable Sherpa (at least once I fixed the MANIFEST.in file).

You can also try

% pip install --use-pep517 . --use-feature=in-tree-build

and then the following should work

% cd $HOME    # just to make sure you are out of the source directory
% sherpa_test

how to test

This is now the suggested way to test an editable build (ie not an installed one):

% pip install -e .
% pytest

You can test an installed version (which is different to python setup.py test) by saying

% pytest --pyargs sherpa

although we also have the sherpa_test command that essentially does this for us.

@DougBurke DougBurke marked this pull request as draft October 21, 2021 17:26
@DougBurke
Copy link
Contributor Author

@Marie-Terrell @harlantc - here's the build rework I recently mentioned. I've left this as a draft as it needs #1328 to land first and there's likely more cleanup to do (including rewording the current WIP commit....)

@DougBurke DougBurke force-pushed the pep-517-changes branch 8 times, most recently from 579eaf3 to 855b730 Compare October 22, 2021 19:09
@DougBurke DougBurke mentioned this pull request Oct 23, 2021
@DougBurke DougBurke force-pushed the pep-517-changes branch 3 times, most recently from 73b77ee to 054f75d Compare October 26, 2021 13:28
@DougBurke DougBurke force-pushed the pep-517-changes branch 2 times, most recently from 0d710ed to 6a84504 Compare October 26, 2021 21:18
@hamogu
Copy link
Contributor

hamogu commented Oct 27, 2021

Just so that I don't forget when talking about build systems: Scipy has decided that setuptools is not enough for them and they are moving to meson: https://labs.quansight.org/blog/2021/07/moving-scipy-to-meson/
Only partially related to this PR, but if I don't put the link somewhere, I will never find it again!

@harlantc
Copy link
Contributor

that page seems to be MIA now, @hamogu , but I found Scipy's git discussion on it. scipy/scipy#13615

@DougBurke
Copy link
Contributor Author

There is also this [old] post about using meson / other systems (which ends up linking to the scipy discussion) at https://discuss.python.org/t/should-python-packaging-aim-for-meson-as-build-system-in-case-of-extension-modules/2579

@codecov
Copy link

codecov bot commented Oct 29, 2021

Codecov Report

Merging #1329 (24154c1) into main (1766dd0) will decrease coverage by 12.63%.
The diff coverage is 80.00%.

Impacted file tree graph

@@             Coverage Diff             @@
##             main    #1329       +/-   ##
===========================================
- Coverage   78.95%   66.32%   -12.64%     
===========================================
  Files          72       72               
  Lines       26673    26676        +3     
  Branches     4496     4496               
===========================================
- Hits        21060    17692     -3368     
- Misses       5420     8877     +3457     
+ Partials      193      107       -86     
Impacted Files Coverage Δ
sherpa/__init__.py 40.07% <0.00%> (ø)
sherpa/astro/xspec/__init__.py 0.33% <ø> (-72.90%) ⬇️
sherpa/astro/xspec/utils.py 10.20% <100.00%> (-28.93%) ⬇️
sherpa/astro/sim/fullbayes.py 18.03% <0.00%> (-77.05%) ⬇️
sherpa/astro/sim/pragbayes.py 17.93% <0.00%> (-69.57%) ⬇️
sherpa/image/DS9.py 18.36% <0.00%> (-48.47%) ⬇️
sherpa/image/ds9_backend.py 3.92% <0.00%> (-46.08%) ⬇️
sherpa/image/__init__.py 55.15% <0.00%> (-30.91%) ⬇️
... and 24 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1766dd0...24154c1. Read the comment docs.

DougBurke added 14 commits July 28, 2022 14:26
Remove a number of warning checks (for un-installed modules) since
it's assumed that this is now handled by setup.cfg.

We can still 'python setup.py develop' but do we care about this?
The setuptools-specific commands are being removed [1] so remove
the clean command as

1. we can use 'git clean -dfx .' to remove extra files

2. it's not at all obvious that setuptools will still support this
   command (it was from distutils but this code has been moved into
   setuptools and is being "simplified")

[1] https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html
numpy.distutils is now deprecated, so replace the easy-to-identify
changes. One way I have used to check is to use the command

    python -m build

to build the code following PEP 517.

Several cases where we currently can't replace the numpy.distutils
version are noted.
It appears that options.entry_points in setup.cfg was added in
version 51.0.0 of setuptools, so bump the minimum version.
Limited testing didn't show a problem if I forced a setuptools
earlier than 51.0.0, but
https://setuptools.pypa.io/en/latest/userguide/declarative_config.html#options
lists 51.0.0 as the version options.entry_points was added
The setuptools document at
https://setuptools.pypa.io/en/latest/userguide/declarative_config.html#options
points out that options.data_files is deprecated, and we should
do something else, but - as with everything python-packaging
related - when I go to the "read here how to do it" page I do not
understand what they are saying. It looks like I can just use
options.package_data to refer to the sherpa[-standalone].rc files,
but it triggers build issues. This appears to be because

a) helpers/sherpa_config.py implicitly requires on the data_files
   option (one issue in doing this is understanding how a
   configuration option maps to various fields in the setuptools
   Command object, how thy are meant to be used, and how we are
   using them)

b) the reason we use the data_files field is to include the
   stack and group "modules" if needed (primarily if in a non-CIAO
   build), and this is something we've wanted to address for a
   long time:
   sherpa#50
Instead of explicitly mentioning the parent class, use super().

There is also a small change in the develop routine so that

a) we announce the stk and group extensions separately
b) we only announce them when they are actually built

However it looks like we don't see these messages by default,
so most users will not see any difference.
I had noted that sdist used numpy.distutils.misc_util.get_data_files
and what should be the replacement for this. In testing, I've found
that I can't trigger the add_defaults method which calls this
routine, so I have replaced it with a method that errors out if
called (in case my testing, which has been limited, was not
sufficient).
Numpy warns if you use the setuptools sdist - see
numpy/numpy#7131 - so revert this change
for now.
Prior to this commit the XSPEC relevant code and tests were
dropped when XSPEC support was disabled. This made sense, but
there have been times when the installation could still install
the XSPEC tests, with the result that 'import sherpa.astro.xspec'
would not fail even though XSPEC support was not enabled. This
behavior seems to have been fixed with the PEP 517 changes, but it
is not-at-all-clear why it happened, so it seems better to guard
against this feature of the build infrastructure. See
sherpa#1545
for more information.

Instead, we now ensure the XSPEC code is installed, even when XSPEC
support is disabled. This can lead to an unsettling error message
from 'import sherpa.astro.xspec', so the XSPEC initialization code
has been tweaked to provide a nicer message in this case:

% python -c 'import sherpa.astro.xspec'
Traceback (most recent call last):
  File "/home/dburke/miniconda3/envs/sherpa-pr/lib/python3.9/site-packages/sherpa/astro/xspec/utils.py", line 24, in <module>
    from . import _xspec
ImportError: cannot import name '_xspec' from partially initialized module 'sherpa.astro.xspec' (most likely due to a circular import) (/home/dburke/miniconda3/envs/sherpa-pr/lib/python3.9/site-packages/sherpa/astro/xspec/__init__.py)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/dburke/miniconda3/envs/sherpa-pr/lib/python3.9/site-packages/sherpa/astro/xspec/__init__.py", line 113, in <module>
    from .utils import ModelMeta, version_at_least, equal_or_greater_than
  File "/home/dburke/miniconda3/envs/sherpa-pr/lib/python3.9/site-packages/sherpa/astro/xspec/utils.py", line 31, in <module>
    raise ImportError("XSPEC support is not enabled") from ie
ImportError: XSPEC support is not enabled

Note that we retain the original "hard to understand for most users"
error, but only as a "secondary" one - the main error tells the
user the problem. We keep the original error in case there is a
problem in XSPEC initialization, which we'd like to know about.
The --verbose flag is included as it can be useful to see how the
build was done when there's a problem.
I don't know why the _xpsec.cc file needs to be included but the
other files do not.
Looking cosely at the message from NumPy, it looks like we have
to use distutils after all. In doing so, we trigger the add_defaults
behavior. In checking out the old method, it was only being used
to add in the stack and group dynamic libraries, and this doesn't
actually work (they get rejected). So I removed the add_defaults
method.

If sherpa#50 gets fixed up we may have to do something, but I don't
think so because they are both compiled code and the sdist is not
for those files (at least as far as I know).
As requested by @Marie-Terrell, I have restored the check on the
Python version just in case people with old systems try to build
Sherpa. As mentioned in the comments, I

a) would prefer to rely on the metadata in setup.cfg to catch this,
   but if people are using old tool chains they will not pick up
   on this

b) I would hope this is now an uncommon situation so it's not worth
   time on updating the error message (i.e. once we drop Python 3.7
   support).
There's no functional change here, but the code uses more-modern
Python idioms (the decision of when to stop is somewhat arbitrary,
I basically got to the point of understanding what is going on and
then stopping).
@DougBurke
Copy link
Contributor Author

I've re-based as I wasn't sure whether we had any "build" changes needed in main.

I've put back the version check that @Marie-Terrell was asking for (I am personally less bothered about this as python 3.6 is now "old" and I wanted to remove as much cruft as possible but I don't see this as requirement).

I also added a minor clean-up of helpers/deps/__init__.py based on trying to understand #1548 (but they do not address this issue and I don't think it's a particularly important issue to look at).

@DougBurke
Copy link
Contributor Author

For @Marie-Terrell - as I mention in the details section of the PR, the XSPEC change is because of

in an attempt to fix #1545 I have re-implemented the change from #1506 to always include the XSPEC code and tests, even when XSPEC is not being built. The assumption has been that 'import sherpa.astro.xspec' would error out cleanly if XSPEC support was not present, but I was able, in #1545, get to a place where this was not true. I have no idea what causes this condition to happen, and it seems to depend on various versions of python/setuptools/..., but the fact that it can happen makes me worry about it. Now, with this change and an XSPEC-free build a call to 'import sherpa.astro.xspec' would trigger a confusing recursive-import message from ._xspec, so we now catch this and add a related XSPEC support is not enabled import error to be more-understandable (but keep the original error which is needed to debug potential XSPEC library issues for when we do have XSPEC support enabled).

What I need to know if this is good, bad, or indifferent.

Copy link
Contributor

@Marie-Terrell Marie-Terrell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the rest of my review, see my comment from July 18th.
The xspec change isn't ideal, but I think since this catches a case with issues while still throwing a more explanatory error than what was seen before #1458 it is a great compromise (which makes it the best option currently).
Ran through a couple quick checks of the helpers/deps/init.py change just to confirm that nothing unexpected changed.
This looks good. Thank you!

]
make("install", opts, check=True)

# Create the built file to indicate the dependecies have been
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Create the built file to indicate the dependecies have been
# Create the built file to indicate the dependencies have been

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wmclaugh - let me know when you have finished so I don't accidentally add your fixes before you've finished. As that may have just happened here already....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

installing sherpa; interesting version differences
5 participants