Skip to content

Commit

Permalink
Trac #27040: Cleanup sage.env
Browse files Browse the repository at this point in the history
1. `sage.all` should be importable and mostly usable even if no
environment variables like `SAGE_ROOT` or `SAGE_LOCAL` are set. This
requires a new function `join` which is like `os.path.join` except that
`None` values are propagated.

2. Several unneeded variables are removed.

3. Get rid of the over-engineered `$VAR` replacement stuff.

4. In some places, `SAGE_LIB` (installed location of Sage) can be used
instead of `SAGE_SRC`.

5. Determine `SAGE_LIB` not from `site-packages` but from
`sage.__file__`

6. Rename `_add_variable_or_fallback` -> `var` to shorten line lengths.

There is a [https://groups.google.com/forum/#!topic/sage-
devel/3ng87YVZjug thread on sage-devel] that complains about the failure
in Debian of `import sage.all` from system Python.

URL: https://trac.sagemath.org/27040
Reported by: Snark
Ticket author(s): Jeroen Demeyer
Reviewer(s): Erik Bray
  • Loading branch information
Release Manager authored and vbraun committed Jan 27, 2019
2 parents d029e5b + 3b0a094 commit 3b64308
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 158 deletions.
295 changes: 143 additions & 152 deletions src/sage/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>
# Copyright (C) 2019 Jeroen Demeyer <[email protected]>
#
# 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://[email protected]: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
Expand All @@ -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:
Expand All @@ -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.,")
Expand All @@ -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):
"""
Expand All @@ -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():
Expand Down
Loading

0 comments on commit 3b64308

Please sign in to comment.