From 3b0a0942b06750089bfae780fc07c32519f2c2aa Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Fri, 11 Jan 2019 10:14:58 +0100 Subject: [PATCH] Clean up sage.env --- src/sage/env.py | 295 ++++++++++++++--------------- src/sage/misc/lazy_import_cache.py | 7 +- src/sage/misc/sageinspect.py | 6 +- 3 files changed, 150 insertions(+), 158 deletions(-) diff --git a/src/sage/env.py b/src/sage/env.py index 3a64c2df7b4..985fcdfb078 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -5,175 +5,187 @@ - \R. Andrew Ohana (2012): Initial version. +Verify that Sage can be started without any ``SAGE_`` environment +variables:: + + sage: env = {k:v for (k,v) in os.environ.items() if not k.startswith("SAGE_")} + sage: import subprocess + sage: cmd = "from sage.all import SAGE_ROOT; print(SAGE_ROOT)" + sage: res = subprocess.call(["python", "-c", cmd], env=env) # long time + None """ -######################################################################## + +#***************************************************************************** # Copyright (C) 2013 R. Andrew Ohana +# Copyright (C) 2019 Jeroen Demeyer # -# Distributed under the terms of the GNU General Public License (GPL) -# as published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# -# http://www.gnu.org/licenses/ -######################################################################## +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +#***************************************************************************** + from __future__ import absolute_import +import sage +import sys import glob import os import socket -import site from . import version -opj = os.path.join -# set default values for sage environment variables -# every variable can be overwritten by os.environ +# All variables set by var() appear in this SAGE_ENV dict and also +# appear as module global (contained in __all__). SAGE_ENV = dict() +__all__ = ['sage_include_directories', 'cython_aliases'] -# Helper to build the SAGE_ENV dictionary -def _add_variable_or_fallback(key, fallback, force=False): + +def join(*args): + """ + Join paths like ``os.path.join`` except that the result is ``None`` + if any of the components is ``None``. + + EXAMPLES:: + + sage: from sage.env import join + sage: print(join("hello", "world")) + hello/world + sage: print(join("hello", None)) + None + """ + if any(a is None for a in args): + return None + return os.path.join(*args) + + +def var(key, *fallbacks, **kwds): """ Set ``SAGE_ENV[key]``. - If ``key`` is an environment variable, this is the - value. Otherwise, the ``fallback`` is used. + If ``key`` is an environment variable, this is the value. + Otherwise, the ``fallbacks`` are tried until one is found which + is not ``None``. If the environment variable is not set and all + fallbacks are ``None``, then the final value is ``None``. INPUT: - ``key`` -- string. - - ``fallback`` -- anything. + - ``fallbacks`` -- tuple containing ``str`` or ``None`` values. - - ``force`` -- boolean (optional, default is ``False``). Whether - to always use the fallback, regardless of environment variables. + - ``force`` -- boolean (optional, default is ``False``). If + ``True``, skip the environment variable and only use the + fallbacks. EXAMPLES:: sage: import os, sage.env sage: sage.env.SAGE_ENV = dict() sage: os.environ['SAGE_FOO'] = 'foo' - sage: sage.env._add_variable_or_fallback('SAGE_FOO', '---$SAGE_URL---') + sage: sage.env.var('SAGE_FOO', 'unused') sage: sage.env.SAGE_FOO 'foo' sage: sage.env.SAGE_ENV['SAGE_FOO'] 'foo' - If the environment variable does not exist, the fallback is - used. Previously-declared variables are replaced if they are - prefixed with a dollar sign:: + If the environment variable does not exist, the fallbacks (if any) + are used. In most typical uses, there is exactly one fallback:: sage: _ = os.environ.pop('SAGE_BAR', None) # ensure that SAGE_BAR does not exist - sage: sage.env._add_variable_or_fallback('SAGE_BAR', '---$SAGE_FOO---') + sage: sage.env.var('SAGE_BAR', 'bar') sage: sage.env.SAGE_BAR - '---foo---' + 'bar' sage: sage.env.SAGE_ENV['SAGE_BAR'] - '---foo---' + 'bar' - Test that :trac:`23758` has been resolved:: + Test multiple fallbacks:: - sage: sage.env._add_variable_or_fallback('SAGE_BA', '---hello---') - sage: sage.env._add_variable_or_fallback('SAGE_QUX', '$SAGE_BAR') - sage: sage.env.SAGE_ENV['SAGE_QUX'] - '---foo---' - """ - global SAGE_ENV - import six - try: - import os - value = os.environ[key] - except KeyError: - value = fallback - if force: - value = fallback - if isinstance(value, six.string_types): - # Now do the variable replacement. First treat 'value' as if - # it were a path and do the substitution on each of the - # components. This is to avoid the sloppiness in the second - # round of substitutions: if VAR and VAR_NEW are both in - # SAGE_ENV, then when doing substitution on the string - # "$VAR_NEW/a/b", we want to match VAR_NEW, not VAR, if - # possible. - for sep in set([os.path.sep, '/']): - components = [] - for s in value.split(sep): - if s.startswith('$'): - components.append(SAGE_ENV.get(s[1:], s)) - else: - components.append(s) - value = sep.join(components) - # Now deal with any remaining substitutions. The following is - # sloppy, as mentioned above: if $VAR and $VAR_NEW are both in - # SAGE_ENV, the substitution for "$VAR_NEw" depends on which - # of the two appears first when iterating over - # SAGE_ENV.items(). - for k,v in SAGE_ENV.items(): - if isinstance(v, six.string_types): - value = value.replace('$'+k, v) - SAGE_ENV[key] = value - globals()[key] = value + sage: sage.env.var('SAGE_BAR', None, 'yes', 'no') + sage: sage.env.SAGE_BAR + 'yes' -# system info -_add_variable_or_fallback('UNAME', os.uname()[0]) -_add_variable_or_fallback('HOSTNAME', socket.gethostname()) -_add_variable_or_fallback('LOCAL_IDENTIFIER','$HOSTNAME.%s'%os.getpid()) + If all fallbacks are ``None``, the result is ``None``:: -# bunch of sage directories and files -_add_variable_or_fallback('SAGE_ROOT', None) -_add_variable_or_fallback('SAGE_LOCAL', None) -_add_variable_or_fallback('SAGE_ETC', opj('$SAGE_LOCAL', 'etc')) -_add_variable_or_fallback('SAGE_INC', opj('$SAGE_LOCAL', 'include')) -_add_variable_or_fallback('SAGE_SHARE', opj('$SAGE_LOCAL', 'share')) + sage: sage.env.var('SAGE_BAR') + sage: print(sage.env.SAGE_BAR) + None + sage: sage.env.var('SAGE_BAR', None) + sage: print(sage.env.SAGE_BAR) + None -_add_variable_or_fallback('SAGE_SRC', opj('$SAGE_ROOT', 'src')) + Test the ``force`` keyword:: -try: - sitepackages_dirs = site.getsitepackages() -except AttributeError: # in case of use inside virtualenv - sitepackages_dirs = [os.path.join(os.path.dirname(site.__file__), - 'site-packages')] -_add_variable_or_fallback('SITE_PACKAGES', sitepackages_dirs) + sage: os.environ['SAGE_FOO'] = 'foo' + sage: sage.env.var('SAGE_FOO', 'forced', force=True) + sage: sage.env.SAGE_FOO + 'forced' + sage: sage.env.var('SAGE_FOO', 'forced', force=False) + sage: sage.env.SAGE_FOO + 'foo' + """ + if kwds.get("force"): + value = None + else: + value = os.environ.get(key) + # Try all fallbacks in order as long as we don't have a value + for f in fallbacks: + if value is not None: + break + value = f + SAGE_ENV[key] = value + globals()[key] = value + __all__.append(key) -_add_variable_or_fallback('SAGE_LIB', SITE_PACKAGES[0]) -# Used by sage/misc/package.py. Should be SAGE_SRC_ROOT in VPATH. -_add_variable_or_fallback('SAGE_PKGS', opj('$SAGE_ROOT', 'build', 'pkgs')) +# system info +var('UNAME', os.uname()[0]) +var('HOSTNAME', socket.gethostname()) +var('LOCAL_IDENTIFIER', "{}.{}".format(HOSTNAME, os.getpid())) +# version info +var('SAGE_VERSION', version.version) +var('SAGE_DATE', version.date) +var('SAGE_VERSION_BANNER', version.banner) -_add_variable_or_fallback('SAGE_EXTCODE', opj('$SAGE_SHARE', 'sage', 'ext')) -_add_variable_or_fallback('SAGE_LOGS', opj('$SAGE_ROOT', 'logs', 'pkgs')) -_add_variable_or_fallback('SAGE_SPKG_INST', opj('$SAGE_LOCAL', 'var', 'lib', 'sage', 'installed')) -_add_variable_or_fallback('SAGE_DOC_SRC', opj('$SAGE_SRC', 'doc')) -_add_variable_or_fallback('SAGE_DOC', opj('$SAGE_SHARE', 'doc', 'sage')) -_add_variable_or_fallback('DOT_SAGE', opj(os.environ.get('HOME','$SAGE_ROOT'), '.sage')) -_add_variable_or_fallback('SAGE_DOT_GIT', opj('$SAGE_ROOT', '.git')) -_add_variable_or_fallback('SAGE_DISTFILES', opj('$SAGE_ROOT', 'upstream')) +# bunch of sage directories and files +var('SAGE_LOCAL', os.path.abspath(sys.prefix)) +var('SAGE_ETC', join(SAGE_LOCAL, 'etc')) +var('SAGE_INC', join(SAGE_LOCAL, 'include')) +var('SAGE_SHARE', join(SAGE_LOCAL, 'share')) +var('SAGE_DOC', join(SAGE_SHARE, 'doc', 'sage')) +var('SAGE_SPKG_INST', join(SAGE_LOCAL, 'var', 'lib', 'sage', 'installed')) +var('SAGE_LIB', os.path.dirname(os.path.dirname(sage.__file__))) + +var('SAGE_ROOT') # no fallback for SAGE_ROOT +var('SAGE_SRC', join(SAGE_ROOT, 'src'), SAGE_LIB) +var('SAGE_DOC_SRC', join(SAGE_SRC, 'doc')) +var('SAGE_PKGS', join(SAGE_ROOT, 'build', 'pkgs')) + +var('DOT_SAGE', join(os.environ.get('HOME'), '.sage')) +var('SAGE_STARTUP_FILE', join(DOT_SAGE, 'init.sage')) + +# installation directories for various packages +var('SAGE_EXTCODE', join(SAGE_SHARE, 'sage', 'ext')) +var('CONWAY_POLYNOMIALS_DATA_DIR', join(SAGE_SHARE, 'conway_polynomials')) +var('GRAPHS_DATA_DIR', join(SAGE_SHARE, 'graphs')) +var('ELLCURVE_DATA_DIR', join(SAGE_SHARE, 'ellcurves')) +var('POLYTOPE_DATA_DIR', join(SAGE_SHARE, 'reflexive_polytopes')) +var('GAP_ROOT_DIR', join(SAGE_SHARE, 'gap')) +var('THEBE_DIR', join(SAGE_SHARE, 'thebe')) +var('COMBINATORIAL_DESIGN_DATA_DIR', join(SAGE_SHARE, 'combinatorial_designs')) +var('CREMONA_MINI_DATA_DIR', join(SAGE_SHARE, 'cremona')) +var('CREMONA_LARGE_DATA_DIR', join(SAGE_SHARE, 'cremona')) +var('JMOL_DIR', join(SAGE_SHARE, 'jmol')) +var('JSMOL_DIR', join(SAGE_SHARE, 'jsmol')) +var('MATHJAX_DIR', join(SAGE_SHARE, 'mathjax')) +var('THREEJS_DIR', join(SAGE_SHARE, 'threejs')) +var('MAXIMA_FAS') # misc -_add_variable_or_fallback('SAGE_URL', 'http://sage.math.washington.edu/sage/') -_add_variable_or_fallback('REALM', 'sage.math.washington.edu') -_add_variable_or_fallback('TRAC_SERVER_URI', 'https://trac.sagemath.org') -_add_variable_or_fallback('SAGE_REPO_AUTHENTICATED', 'ssh://git@trac.sagemath.org:2222/sage.git') -_add_variable_or_fallback('SAGE_REPO_ANONYMOUS', 'git://trac.sagemath.org/sage.git') -_add_variable_or_fallback('SAGE_VERSION', version.version) -_add_variable_or_fallback('SAGE_DATE', version.date) -_add_variable_or_fallback('SAGE_VERSION_BANNER', version.banner) -_add_variable_or_fallback('SAGE_BANNER', '') -_add_variable_or_fallback('SAGE_IMPORTALL', 'yes') - -# additional packages locations -_add_variable_or_fallback('CONWAY_POLYNOMIALS_DATA_DIR', opj('$SAGE_SHARE','conway_polynomials')) -_add_variable_or_fallback('GRAPHS_DATA_DIR', opj('$SAGE_SHARE','graphs')) -_add_variable_or_fallback('ELLCURVE_DATA_DIR',opj('$SAGE_SHARE','ellcurves')) -_add_variable_or_fallback('POLYTOPE_DATA_DIR',opj('$SAGE_SHARE','reflexive_polytopes')) -_add_variable_or_fallback('GAP_ROOT_DIR', opj('$SAGE_SHARE','gap')) -_add_variable_or_fallback('THEBE_DIR', opj('$SAGE_SHARE','thebe')) -_add_variable_or_fallback('COMBINATORIAL_DESIGN_DATA_DIR', opj('$SAGE_SHARE', 'combinatorial_designs')) -_add_variable_or_fallback('CREMONA_MINI_DATA_DIR', opj('$SAGE_SHARE', 'cremona')) -_add_variable_or_fallback('CREMONA_LARGE_DATA_DIR', opj('$SAGE_SHARE', 'cremona')) -_add_variable_or_fallback('JMOL_DIR', opj('$SAGE_SHARE', 'jmol')) -_add_variable_or_fallback('JSMOL_DIR', opj('$SAGE_SHARE', 'jsmol')) -_add_variable_or_fallback('MATHJAX_DIR', opj('$SAGE_SHARE', 'mathjax')) -_add_variable_or_fallback('THREEJS_DIR', opj('$SAGE_SHARE', 'threejs')) -_add_variable_or_fallback('MAXIMA_FAS', None) +var('SAGE_BANNER', '') +var('SAGE_IMPORTALL', 'yes') # locate singular shared object @@ -188,7 +200,7 @@ def _add_variable_or_fallback(key, fallback, force=False): # library name changed from libsingular to libSingular btw 3.x and 4.x SINGULAR_SO = SAGE_LOCAL+"/lib/libSingular."+extension -_add_variable_or_fallback('SINGULAR_SO', SINGULAR_SO) +var('SINGULAR_SO', SINGULAR_SO) # post process if ' ' in DOT_SAGE: @@ -197,7 +209,7 @@ def _add_variable_or_fallback(key, fallback, force=False): # to have a space in it. Fortunately, users also have # write privileges to c:\cygwin\home, so we just put # .sage there. - _add_variable_or_fallback('DOT_SAGE', "/home/.sage", force=True) + var('DOT_SAGE', "/home/.sage", force=True) else: print("Your home directory has a space in it. This") print("will probably break some functionality of Sage. E.g.,") @@ -216,16 +228,6 @@ def _add_variable_or_fallback(key, fallback, force=False): if m: CYGWIN_VERSION = tuple(map(int, m.group(1).split('.'))) - del m - del _uname, re - -# things that need DOT_SAGE -_add_variable_or_fallback('PYTHON_EGG_CACHE', opj('$DOT_SAGE', '.python-eggs')) -_add_variable_or_fallback('SAGE_STARTUP_FILE', opj('$DOT_SAGE', 'init.sage')) - -# delete temporary variables used for setting up sage.env -del opj, os, socket, version, site - def sage_include_directories(use_sources=False): """ @@ -243,35 +245,24 @@ def sage_include_directories(use_sources=False): EXAMPLES: - Expected output while using sage - - :: + Expected output while using Sage:: sage: import sage.env sage: sage.env.sage_include_directories() ['.../include', + '.../python.../site-packages/sage/ext', '.../include/python...', - '.../python.../numpy/core/include', - '.../python.../site-packages', - '.../python.../site-packages/sage/ext'] + '.../python.../numpy/core/include'] """ - import os, numpy + import numpy import distutils.sysconfig - opj = os.path.join - - include_directories = [SAGE_INC, - distutils.sysconfig.get_python_inc(), - numpy.get_include()] - - if use_sources : - include_directories.extend([SAGE_SRC, - opj(SAGE_SRC, 'sage', 'ext')]) - else: - include_directories.extend([SAGE_LIB, - opj(SAGE_LIB, 'sage', 'ext')]) + TOP = SAGE_SRC if use_sources else SAGE_LIB - return include_directories + return [SAGE_INC, + os.path.join(TOP, 'sage', 'ext'), + distutils.sysconfig.get_python_inc(), + numpy.get_include()] def cython_aliases(): diff --git a/src/sage/misc/lazy_import_cache.py b/src/sage/misc/lazy_import_cache.py index 7688a9565e5..0f9d7de8aca 100644 --- a/src/sage/misc/lazy_import_cache.py +++ b/src/sage/misc/lazy_import_cache.py @@ -6,11 +6,12 @@ import os -from ..env import SAGE_SRC, DOT_SAGE +from ..env import SAGE_LIB, DOT_SAGE def get_cache_file(): """ - Returns a per-branch file for caching names of lazily imported modules. + Return the canonical filename for caching names of lazily imported + modules. EXAMPLES:: @@ -30,6 +31,6 @@ def get_cache_file(): True sage: sage.misc.lazy_import_cache.DOT_SAGE = OLD """ - mangled = os.path.realpath(SAGE_SRC).replace(os.sep, '_') + mangled = os.path.realpath(SAGE_LIB).replace(os.sep, '_') return os.path.join(DOT_SAGE, 'cache', "%s-lazy_import_cache.pickle" % mangled) diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index a2315028edd..f0e85107b77 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -125,7 +125,7 @@ def foo(unsigned int x=1, a=')"', b={not (2+1==3):'bar'}, *args, **kwds): return import tokenize import re EMBEDDED_MODE = False -from sage.env import SAGE_SRC +from sage.env import SAGE_LIB def loadable_module_extension(): r""" @@ -249,7 +249,7 @@ def _extract_embedded_position(docstring): # 2) Module compiled by Sage's inline cython() compiler from sage.misc.misc import SPYX_TMP try_filenames = [ - os.path.join(SAGE_SRC, raw_filename), + os.path.join(SAGE_LIB, raw_filename), os.path.join(SPYX_TMP, '_'.join(raw_filename.split('_')[:-1]), raw_filename) ] @@ -2452,7 +2452,7 @@ def test3(b, # 12 Test _extract_embedded_position: - We cannot test the filename since it depends on SAGE_SRC. + We cannot test the filename since it depends on ``SAGE_LIB``. Make sure things work with no trailing newline::