diff --git a/.gitignore b/.gitignore index efdbfa8f616..2cec8a0cf62 100644 --- a/.gitignore +++ b/.gitignore @@ -175,6 +175,16 @@ build/bin/sage-build-env-config /pkgs/*/*.egg-info /pkgs/*/.tox +/pkgs/sage-conf_pypi/sage_root/config.log +/pkgs/sage-conf_pypi/sage_root/config.status +/pkgs/sage-conf_pypi/sage_root/local/ +/pkgs/sage-conf_pypi/sage_root/logs/ +/pkgs/sage-conf_pypi/sage_root/prefix +/pkgs/sage-conf_pypi/sage_root/src/bin/sage-env-config +/pkgs/sage-conf_pypi/sage_root/src/bin/sage-src-env-config +/pkgs/sage-conf_pypi/sage_root/upstream/ +/pkgs/sage-conf_pypi/sage_root/venv + /pkgs/sagemath-objects/setup.cfg /pkgs/sagemath-bliss/setup.cfg /pkgs/sagemath-coxeter3/setup.cfg diff --git a/pkgs/sage-conf/README.rst b/pkgs/sage-conf/README.rst index c968612aef2..5730592e35a 100644 --- a/pkgs/sage-conf/README.rst +++ b/pkgs/sage-conf/README.rst @@ -78,10 +78,20 @@ sage_conf in the SageMath distribution The original version of the distribution package ``sage_conf`` is used internally in the SageMath distribution. It is provided in the directory `pkgs/sage-conf `_. -This version of the package is generated by the Sage distribution's ``configure`` +This version of the package is generated by the Sage distribution's ``./configure`` script. +sage_conf for conda +------------------- + +The version of the distribution package in the directory +`pkgs/sage-conf_conda `_ +is used in an experimental installation method of SageMath, where all packages +are provided by conda. This method is described in +https://doc.sagemath.org/html/en/installation/conda.html#using-conda-to-provide-all-dependencies-for-the-sage-library-experimental + + sage_conf in downstream distributions ------------------------------------- diff --git a/pkgs/sage-conf_conda/.gitignore b/pkgs/sage-conf_conda/.gitignore new file mode 100644 index 00000000000..6fdda73c500 --- /dev/null +++ b/pkgs/sage-conf_conda/.gitignore @@ -0,0 +1,6 @@ +/_sage_conf/_conf.py +/build +/dist +/*.egg-info +/.tox +/bin/sage-env-config diff --git a/pkgs/sage-conf_conda/MANIFEST.in b/pkgs/sage-conf_conda/MANIFEST.in new file mode 100644 index 00000000000..ea5f85f8c99 --- /dev/null +++ b/pkgs/sage-conf_conda/MANIFEST.in @@ -0,0 +1,53 @@ +prune .tox +include VERSION.txt +graft bin +exclude bin/sage-env-config # generated by configure + +prune sage_root +include sage_root/Makefile +include sage_root/README.md +include sage_root/VERSION.txt +include sage_root/bootstrap +include sage_root/bootstrap-conda +graft sage_root/build +prune sage_root/build/.tox +exclude sage_root/build/bin/sage-build-env-config # generated by configure +exclude sage_root/build/make/Makefile-auto # generated by configure +exclude sage_root/build/make/Makefile # generated by configure + +# These sources are not needed because individual distributions of these are made. +prune sage_root/build/pkgs/*/src* + +graft sage_root/config +include sage_root/configure +include sage_root/configure.ac +graft sage_root/m4 + +# Only these pkgs are needed (because of dependencies on source files, +# see "git grep SAGE_ROOT build/pkgs/*/dependencies") +graft sage_root/pkgs/sage-conf +prune sage_root/pkgs/sage-conf/build +prune sage_root/pkgs/sage-conf/dist +prune sage_root/pkgs/sage-conf/*.egg-info +exclude sage_root/pkgs/sage-conf/_sage_conf/_conf.py # generated by configure +graft sage_root/pkgs/sage-docbuild +prune sage_root/pkgs/sage-docbuild/build +prune sage_root/pkgs/sage-docbuild/dist +prune sage_root/pkgs/sage-docbuild/*.egg-info + +graft sage_root/src/_sage_conf +include sage_root/src/bin/sage-env +include sage_root/src/bin/sage-env-config.in +include sage_root/src/bin/sage-src-env-config.in +include sage_root/src/bin/sage-venv-config +include sage_root/src/bin/sage-version.sh +include sage_root/src/doc/bootstrap # FIXME: should move to builds/pkgs/sagemath_doc_html/ + +global-exclude .tox +global-exclude *~* +global-exclude *.bak +global-exclude *.orig +global-exclude __pycache__ +global-exclude *.py[co] +global-exclude *.so +global-exclude .DS_Store diff --git a/pkgs/sage-conf_conda/README.rst b/pkgs/sage-conf_conda/README.rst new file mode 120000 index 00000000000..feda886cd36 --- /dev/null +++ b/pkgs/sage-conf_conda/README.rst @@ -0,0 +1 @@ +../sage-conf/README.rst \ No newline at end of file diff --git a/pkgs/sage-conf_conda/VERSION.txt b/pkgs/sage-conf_conda/VERSION.txt new file mode 100644 index 00000000000..7786f0e63e4 --- /dev/null +++ b/pkgs/sage-conf_conda/VERSION.txt @@ -0,0 +1 @@ +10.2.beta5 diff --git a/pkgs/sage-conf_conda/_sage_conf b/pkgs/sage-conf_conda/_sage_conf new file mode 120000 index 00000000000..d92a91bef8c --- /dev/null +++ b/pkgs/sage-conf_conda/_sage_conf @@ -0,0 +1 @@ +../sage-conf/_sage_conf \ No newline at end of file diff --git a/pkgs/sage-conf_conda/bin b/pkgs/sage-conf_conda/bin new file mode 120000 index 00000000000..d7471f37a23 --- /dev/null +++ b/pkgs/sage-conf_conda/bin @@ -0,0 +1 @@ +../sage-conf_pypi/bin \ No newline at end of file diff --git a/pkgs/sage-conf_conda/pyproject.toml b/pkgs/sage-conf_conda/pyproject.toml new file mode 120000 index 00000000000..52c93c824e2 --- /dev/null +++ b/pkgs/sage-conf_conda/pyproject.toml @@ -0,0 +1 @@ +../sage-conf/pyproject.toml \ No newline at end of file diff --git a/pkgs/sage-conf_conda/sage_conf.py b/pkgs/sage-conf_conda/sage_conf.py new file mode 120000 index 00000000000..f4bca8cc55c --- /dev/null +++ b/pkgs/sage-conf_conda/sage_conf.py @@ -0,0 +1 @@ +../sage-conf/sage_conf.py \ No newline at end of file diff --git a/pkgs/sage-conf_conda/sage_root b/pkgs/sage-conf_conda/sage_root new file mode 120000 index 00000000000..c25bddb6dd4 --- /dev/null +++ b/pkgs/sage-conf_conda/sage_root @@ -0,0 +1 @@ +../.. \ No newline at end of file diff --git a/pkgs/sage-conf_conda/setup.cfg b/pkgs/sage-conf_conda/setup.cfg new file mode 120000 index 00000000000..93df2c80a4b --- /dev/null +++ b/pkgs/sage-conf_conda/setup.cfg @@ -0,0 +1 @@ +../sage-conf/setup.cfg \ No newline at end of file diff --git a/pkgs/sage-conf_conda/setup.py b/pkgs/sage-conf_conda/setup.py new file mode 100644 index 00000000000..9e8ac353f4f --- /dev/null +++ b/pkgs/sage-conf_conda/setup.py @@ -0,0 +1,121 @@ +import os +import sys +import shutil +import sysconfig +import platform +import fnmatch + +from pathlib import Path + +from setuptools import setup +from distutils.command.build_scripts import build_scripts as distutils_build_scripts +from setuptools.command.build_py import build_py as setuptools_build_py +from setuptools.command.editable_wheel import editable_wheel as setuptools_editable_wheel +from setuptools.errors import SetupError + + +class build_py(setuptools_build_py): + + def run(self): + + HERE = os.path.dirname(__file__) + if self.editable_mode: + SAGE_ROOT = os.path.join(HERE, 'sage_root') + else: + SAGE_ROOT = self._create_writable_sage_root() + + if not os.environ.get('CONDA_PREFIX', ''): + raise SetupError('No conda environment is active. ' + 'See https://doc.sagemath.org/html/en/installation/conda.html on how to get started.') + + if os.path.exists(os.path.join(SAGE_ROOT, 'config.status')): + print(f'Reusing configured SAGE_ROOT={SAGE_ROOT}') + else: + cmd = f"cd {SAGE_ROOT} && ./configure --enable-build-as-root --with-system-python3=force --disable-notebook --disable-sagelib --disable-sage_conf --disable-doc" + cmd += ' --with-python=$CONDA_PREFIX/bin/python --prefix="$CONDA_PREFIX"' + cmd += ' $(for pkg in $(PATH="build/bin:$PATH" build/bin/sage-package list :standard: --exclude rpy2 --has-file spkg-configure.m4 --has-file distros/conda.txt); do echo --with-system-$pkg=force; done)' + print(f"Running {cmd}") + sys.stdout.flush() + if os.system(cmd) != 0: + if os.path.exists(os.path.join(SAGE_ROOT, 'config.status')): + print("Warning: A configuration has been written, but the configure script has exited with an error. " + "Carefully check any messages above before continuing.") + else: + print(f"Error: The configure script has failed; this may be caused by missing build prerequisites.") + sys.stdout.flush() + PREREQ_SPKG = "_prereq bzip2 xz libffi" # includes python3 SPKG_DEPCHECK packages + os.system(f'cd {SAGE_ROOT} && export PACKAGES="$(build/bin/sage-get-system-packages conda {PREREQ_SPKG})" && [ -n "$PACKAGES" ] && echo "You can install the required build prerequisites using the following shell command" && echo "" && build/bin/sage-print-system-package-command conda --verbose --sudo install $PACKAGES && echo ""') + raise SetupError("configure failed") + + # In this mode, we never run "make". + + # Copy over files generated by the configure script + # (see configure.ac AC_CONFIG_FILES) + if self.editable_mode: + pass # same file + else: + shutil.copyfile(os.path.join(SAGE_ROOT, 'pkgs', 'sage-conf', '_sage_conf', '_conf.py'), + os.path.join(HERE, '_sage_conf', '_conf.py')) + shutil.copyfile(os.path.join(SAGE_ROOT, 'src', 'bin', 'sage-env-config'), + os.path.join(HERE, 'bin', 'sage-env-config')) + + setuptools_build_py.run(self) + + def _create_writable_sage_root(self): + HERE = os.path.dirname(__file__) + DOT_SAGE = os.environ.get('DOT_SAGE', os.path.join(os.environ.get('HOME'), '.sage')) + with open(os.path.join(HERE, 'VERSION.txt')) as f: + sage_version = f.read().strip() + # After #30534, SAGE_LOCAL no longer contains any Python. So we key the SAGE_ROOT only to Sage version + # and architecture. + system = platform.system() + machine = platform.machine() + arch_tag = f'{system}-{machine}' + # TODO: Should be user-configurable with config settings + SAGE_ROOT = os.path.join(DOT_SAGE, f'sage-{sage_version}-{arch_tag}-conda') + + def ignore(path, names): + # exclude all embedded src trees + if fnmatch.fnmatch(path, f'*/build/pkgs/*'): + return ['src'] + ### ignore more stuff --- .tox etc. + return [name for name in names + if name in ('.tox', '.git', '__pycache__', + 'prefix', 'local', 'venv', 'upstream', + 'config.status', 'config.log', 'logs')] + + if not os.path.exists(os.path.join(SAGE_ROOT, 'config.status')): + # config.status and other configure output has to be writable. + # So (until the Sage distribution supports VPATH builds - #21469), we have to make a copy of sage_root. + try: + shutil.copytree('sage_root', SAGE_ROOT, + ignore=ignore) # will fail if already exists + except Exception as e: + raise SetupError(f"the directory SAGE_ROOT={SAGE_ROOT} already exists but it is not configured ({e}). " + "Please either remove it and try again, or install in editable mode (pip install -e).") + + return SAGE_ROOT + + +class build_scripts(distutils_build_scripts): + + def run(self): + self.distribution.scripts.append(os.path.join('bin', 'sage-env-config')) + if not self.distribution.entry_points: + self.entry_points = self.distribution.entry_points = dict() + distutils_build_scripts.run(self) + + +class editable_wheel(setuptools_editable_wheel): + r""" + Customized so that exceptions raised by our build_py + do not lead to the "Customization incompatible with editable install" message + """ + _safely_run = setuptools_editable_wheel.run_command + + +setup( + cmdclass=dict(build_py=build_py, + build_scripts=build_scripts, + editable_wheel=editable_wheel) +) diff --git a/pkgs/sage-conf_pypi/pyproject.toml b/pkgs/sage-conf_pypi/pyproject.toml deleted file mode 100644 index 9787c3bdf00..00000000000 --- a/pkgs/sage-conf_pypi/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta" diff --git a/pkgs/sage-conf_pypi/pyproject.toml b/pkgs/sage-conf_pypi/pyproject.toml new file mode 120000 index 00000000000..52c93c824e2 --- /dev/null +++ b/pkgs/sage-conf_pypi/pyproject.toml @@ -0,0 +1 @@ +../sage-conf/pyproject.toml \ No newline at end of file diff --git a/pkgs/sage-conf_pypi/setup.py b/pkgs/sage-conf_pypi/setup.py index a689f4d617b..16686a848c9 100644 --- a/pkgs/sage-conf_pypi/setup.py +++ b/pkgs/sage-conf_pypi/setup.py @@ -3,40 +3,35 @@ import shutil import sysconfig import platform +import fnmatch from setuptools import setup from distutils.command.build_scripts import build_scripts as distutils_build_scripts from setuptools.command.build_py import build_py as setuptools_build_py -from setuptools.command.egg_info import egg_info as setuptools_egg_info -from distutils.errors import (DistutilsSetupError, DistutilsModuleError, - DistutilsOptionError) +from setuptools.command.editable_wheel import editable_wheel as setuptools_editable_wheel +from setuptools.errors import SetupError + class build_py(setuptools_build_py): def run(self): - DOT_SAGE = os.environ.get('DOT_SAGE', os.path.join(os.environ.get('HOME'), '.sage')) HERE = os.path.dirname(__file__) - with open(os.path.join(HERE, 'VERSION.txt')) as f: - sage_version = f.read().strip() + if self.editable_mode: + SAGE_ROOT = os.path.join(HERE, 'sage_root') + else: + SAGE_ROOT = self._create_writable_sage_root() + # For convenience, set up the homebrew env automatically. This is a no-op if homebrew is not present. - SETENV = '(. ./.homebrew-build-env 2> /dev/null || :)' - # After #30534, SAGE_LOCAL no longer contains any Python. So we key the SAGE_ROOT only to Sage version - # and architecture. - system = platform.system() - machine = platform.machine() - arch_tag = f'{system}-{machine}' - # TODO: These two should be user-configurable with options passed to "setup.py install" - SAGE_ROOT = os.path.join(DOT_SAGE, f'sage-{sage_version}-{arch_tag}') + if os.environ.get('CONDA_PREFIX', ''): + SETENV = ':' + else: + SETENV = '(. ./.homebrew-build-env 2> /dev/null || :)' + SAGE_LOCAL = os.path.join(SAGE_ROOT, 'local') + if os.path.exists(os.path.join(SAGE_ROOT, 'config.status')): - print(f'Reusing SAGE_ROOT={SAGE_ROOT}') + print(f'Reusing configured SAGE_ROOT={SAGE_ROOT}') else: - # config.status and other configure output has to be writable. - # So (until the Sage distribution supports VPATH builds - #21469), we have to make a copy of sage_root. - try: - shutil.copytree('sage_root', SAGE_ROOT) # will fail if already exists - except Exception: - raise DistutilsSetupError(f"the directory SAGE_ROOT={SAGE_ROOT} already exists but it is not configured. Please remove it and try again.") cmd = f"cd {SAGE_ROOT} && {SETENV} && ./configure --prefix={SAGE_LOCAL} --with-python={sys.executable} --enable-build-as-root --enable-download-from-upstream-url --with-system-python3=force --with-sage-venv --disable-notebook --disable-sagelib --disable-sage_conf --disable-doc" print(f"Running {cmd}") sys.stdout.flush() @@ -45,7 +40,18 @@ def run(self): sys.stdout.flush() PREREQ_SPKG = "_prereq bzip2 xz libffi" # includes python3 SPKG_DEPCHECK packages os.system(f'cd {SAGE_ROOT} && export SYSTEM=$(build/bin/sage-guess-package-system 2>/dev/null) && export PACKAGES="$(build/bin/sage-get-system-packages $SYSTEM {PREREQ_SPKG})" && [ -n "$PACKAGES" ] && echo "You can install the required build prerequisites using the following shell command" && echo "" && build/bin/sage-print-system-package-command $SYSTEM --verbose --sudo install $PACKAGES && echo ""') - raise DistutilsSetupError("configure failed") + raise SetupError("configure failed") + + # Copy over files generated by the configure script + # (see configure.ac AC_CONFIG_FILES) + if self.editable_mode: + pass # same file + else: + shutil.copyfile(os.path.join(SAGE_ROOT, 'pkgs', 'sage-conf', '_sage_conf', '_conf.py'), + os.path.join(HERE, '_sage_conf', '_conf.py')) + shutil.copyfile(os.path.join(SAGE_ROOT, 'src', 'bin', 'sage-env-config'), + os.path.join(HERE, 'bin', 'sage-env-config')) + # Here we run "make build" -- which builds everything except for sagelib because we # used configure --disable-sagelib # Alternative: @@ -61,13 +67,44 @@ def run(self): if os.system(cmd) != 0: raise DistutilsSetupError(f"make {TARGETS} failed") - # Install configuration - shutil.copyfile(os.path.join(SAGE_ROOT, 'pkgs', 'sage-conf', '_sage_conf', '_conf.py'), - os.path.join(HERE, '_sage_conf', '_conf.py')) - shutil.copyfile(os.path.join(SAGE_ROOT, 'src', 'bin', 'sage-env-config'), - os.path.join(HERE, 'bin', 'sage-env-config')) setuptools_build_py.run(self) + def _create_writable_sage_root(self): + HERE = os.path.dirname(__file__) + DOT_SAGE = os.environ.get('DOT_SAGE', os.path.join(os.environ.get('HOME'), '.sage')) + with open(os.path.join(HERE, 'VERSION.txt')) as f: + sage_version = f.read().strip() + # After #30534, SAGE_LOCAL no longer contains any Python. So we key the SAGE_ROOT only to Sage version + # and architecture. + system = platform.system() + machine = platform.machine() + arch_tag = f'{system}-{machine}' + # TODO: Should be user-configurable with config settings + SAGE_ROOT = os.path.join(DOT_SAGE, f'sage-{sage_version}-{arch_tag}') + + def ignore(path, names): + # exclude all embedded src trees + if fnmatch.fnmatch(path, f'*/build/pkgs/*'): + return ['src'] + ### ignore more stuff --- .tox etc. + return [name for name in names + if name in ('.tox', '.git', '__pycache__', + 'prefix', 'local', 'venv', 'upstream', + 'config.status', 'config.log', 'logs')] + + if not os.path.exists(os.path.join(SAGE_ROOT, 'config.status')): + # config.status and other configure output has to be writable. + # So (until the Sage distribution supports VPATH builds - #21469), we have to make a copy of sage_root. + try: + shutil.copytree('sage_root', SAGE_ROOT, + ignore=ignore) # will fail if already exists + except Exception as e: + raise SetupError(f"the directory SAGE_ROOT={SAGE_ROOT} already exists but it is not configured ({e}). " + "Please either remove it and try again, or install in editable mode (pip install -e).") + + return SAGE_ROOT + + class build_scripts(distutils_build_scripts): def run(self): @@ -76,6 +113,17 @@ def run(self): self.entry_points = self.distribution.entry_points = dict() distutils_build_scripts.run(self) + +class editable_wheel(setuptools_editable_wheel): + r""" + Customized so that exceptions raised by our build_py + do not lead to the "Customization incompatible with editable install" message + """ + _safely_run = setuptools_editable_wheel.run_command + + setup( - cmdclass=dict(build_py=build_py, build_scripts=build_scripts) + cmdclass=dict(build_py=build_py, + build_scripts=build_scripts, + editable_wheel=editable_wheel) ) diff --git a/src/doc/en/installation/conda.rst b/src/doc/en/installation/conda.rst index 5228611de3b..063a9fb06cf 100644 --- a/src/doc/en/installation/conda.rst +++ b/src/doc/en/installation/conda.rst @@ -135,24 +135,12 @@ Here we assume that you are using a git checkout. By default, the most recent version of Python supported by Sage is installed. You can use the additional option ``python=3.9`` in the above - ``env create`` command to select another Python version (here 3.9). - - - Run the ``configure`` script:: - - $ ./bootstrap - $ ./configure --with-python=$CONDA_PREFIX/bin/python \ - --prefix=$CONDA_PREFIX \ - $(for pkg in $(./sage -package list :standard: \ - --exclude rpy2 \ - --has-file spkg-configure.m4 \ - --has-file distros/conda.txt); do \ - echo --with-system-$pkg=force; \ - done) + ``env create`` command to select another Python version (here 3.9). - Install the build prerequisites and the Sage library:: - $ pip install --no-build-isolation -v -v --editable ./pkgs/sage-conf ./pkgs/sage-setup - $ pip install --no-build-isolation -v -v --editable ./src + $ pip install --no-build-isolation -v -v --editable ./pkgs/sage-conf_conda ./pkgs/sage-setup + $ pip install --no-build-isolation --config-settings editable_mode=compat -v -v --editable ./src - Verify that Sage has been installed:: @@ -183,3 +171,11 @@ To build the documentation, use:: $ pip install --no-build-isolation -v -v --editable ./pkgs/sage-docbuild $ sage --docbuild all html + +.. NOTE:: + + The switch ``--config-settings editable_mode=compat`` restores the + `legacy setuptools implementation of editable installations + `_. + Adventurous developers may omit this switch to try the modern, + PEP-660 implementation of editable installations, see :issue:`34209`.