Skip to content

Commit 767d3e1

Browse files
committed
Merge branch 'support-python3'.
Add support for Python 3.4, 3.5, 3.6, stay compatible with 2.7. Test all supported versions on travis with conda. Test Python 2.7 with Linux system Python and Python 3.6 with homebrew Python on Mac OS X. This closes #10.
2 parents 4133e08 + a0ef415 commit 767d3e1

27 files changed

+226
-136
lines changed

.travis.yml

+45-14
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@ os:
88

99
env:
1010
- MYUSEMC=true MYPYTHON_VERSION=2.7
11+
- MYUSEMC=true MYPYTHON_VERSION=3.4
12+
- MYUSEMC=true MYPYTHON_VERSION=3.5
13+
- MYUSEMC=true MYPYTHON_VERSION=3.6
1114
- MYUSEMC=false
1215

13-
matrix:
14-
exclude:
15-
- os: osx
16-
env: MYUSEMC=false
17-
1816
git:
1917
depth: 999999
2018

@@ -26,10 +24,17 @@ before_install:
2624
- MYNAME=pyobjcryst
2725
- umask 022
2826
- git fetch origin --tags
27+
- MYPYTHON=python; MYPIP=pip
28+
- NOAPT=true; NOBREW=true; NOMC=true
2929
- if ${MYUSEMC}; then
30-
NOAPT=true; NOMC=false;
31-
else
32-
NOAPT=false; NOMC=true;
30+
NOMC=false;
31+
elif [[ ${TRAVIS_OS_NAME} == linux ]]; then
32+
NOAPT=false;
33+
MYPIPFLAGS="--user";
34+
elif [[ ${TRAVIS_OS_NAME} == osx ]]; then
35+
NOBREW=false;
36+
MYPYTHON=python3;
37+
MYPIP=pip3;
3338
MYPIPFLAGS="--user";
3439
fi
3540
- MYMCREPO=https://repo.continuum.io/miniconda
@@ -52,11 +57,16 @@ before_install:
5257
- $NOMC || wget --timestamping ${MYMCREPO}/${MYMCBUNDLE}
5358
- $NOMC || test -x ~/mc/bin/conda || bash ${MYMCBUNDLE} -b -f -p ~/mc
5459
- $NOMC || popd
55-
- $NOMC || export PATH="${HOME}/mc/bin:${PATH}"
60+
- $NOMC || source ~/mc/bin/activate root
5661
- $NOMC || conda update --yes conda
57-
- $NOMC || conda install --yes conda-build jinja2
62+
- $NOMC || conda install --yes conda-build jinja2 scons numpy
63+
# Make scons available globally.
64+
- $NOMC || mkdir -p ~/bin
65+
- $NOMC || cp ~/mc/bin/scons ~/bin/
5866
- $NOMC || conda create --name=testenv --yes python=${MYPYTHON_VERSION} coverage
5967
- $NOMC || conda config --add channels diffpy
68+
# FIXME - keep just the "diffpy" channel for public release.
69+
- $NOMC || conda config --add channels diffpy/channel/dev
6070

6171
- $NOAPT || test "${TRAVIS_OS_NAME}" = "linux" || exit $?
6272
- $NOAPT || sudo apt-get update -qq
@@ -70,6 +80,19 @@ before_install:
7080
- $NOAPT || git clone https://github.com/diffpy/libObjCryst.git
7181
- $NOAPT || popd
7282

83+
- $NOBREW || test "${TRAVIS_OS_NAME}" = "osx" || exit $?
84+
- $NOBREW || brew update
85+
- $NOBREW || brew install scons
86+
- $NOBREW || brew install python3
87+
- $NOBREW || brew install boost-python --with-python3
88+
- $NOBREW || brew tap homebrew/python
89+
- $NOBREW || brew install numpy --without-python --with-python3
90+
- $NOBREW || devutils/makesdist
91+
- $NOBREW || MYTARBUNDLE="$(ls -t "${PWD}"/dist/*.tar.gz | head -1)"
92+
- $NOBREW || pushd ~/pkgs
93+
- $NOBREW || git clone https://github.com/diffpy/libObjCryst.git
94+
- $NOBREW || popd
95+
7396
install:
7497
- $NOMC || conda build --python=${MYPYTHON_VERSION} --dirty conda-recipe
7598
- $NOMC || MYCONDATESTENV="$(ls -td ~/mc/conda-bld/${MYNAME}_*/_t_env* | head -1)"
@@ -78,20 +101,28 @@ install:
78101
- $NOMC || source activate testenv
79102
- $NOMC || conda install --yes --use-local --file=/tmp/mypackage.txt
80103

81-
- $NOAPT || pip install $MYPIPFLAGS coverage
104+
- $NOAPT || $MYPIP install $MYPIPFLAGS coverage
82105
- $NOAPT || sudo scons -C ~/pkgs/libObjCryst install
83-
- $NOAPT || easy_install --user "${MYTARBUNDLE}"
106+
- $NOAPT || $MYPIP install $MYPIPFLAGS --user "${MYTARBUNDLE}"
107+
108+
- $NOBREW || $MYPIP install $MYPIPFLAGS coverage
109+
- $NOBREW || scons -C ~/pkgs/libObjCryst install
110+
- $NOBREW || $MYPIP install $MYPIPFLAGS "${MYTARBUNDLE}"
84111

85112
- cd ${MYRUNDIR}
86-
- MYGIT_REV=$(python -c "import ${MYNAME}.version as v; print(v.__gitsha__)")
113+
- MYGIT_REV=$($MYPYTHON -c "import ${MYNAME}.version as v; print(v.__gitsha__)")
87114
- if [[ "${TRAVIS_COMMIT}" != "${MYGIT_REV}" ]]; then
88115
echo "Version mismatch ${TRAVIS_COMMIT} vs ${MYGIT_REV}.";
89116
exit 1;
90117
fi
91118

119+
before_script:
120+
- $NOBREW || USER_BASE="$(python3 -c 'import site; print(site.USER_BASE)')"
121+
- $NOBREW || PATH="${USER_BASE}/bin:${PATH}"
122+
92123
script:
93124
- coverage run --source ${MYNAME} -m ${MYNAME}.tests.run
94125

95126
after_success:
96-
- pip install $MYPIPFLAGS codecov
127+
- $MYPIP install $MYPIPFLAGS codecov
97128
- codecov

SConstruct

+9-7
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,18 @@ def subdictionary(d, keyset):
2929

3030
def getsyspaths(*names):
3131
s = os.pathsep.join(filter(None, map(os.environ.get, names)))
32-
return filter(os.path.exists, s.split(os.pathsep))
32+
rv = [p for p in s.split(os.pathsep) if os.path.exists(p)]
33+
return rv
3334

3435
def pyoutput(cmd):
3536
proc = subprocess.Popen([env['python'], '-c', cmd],
36-
stdout=subprocess.PIPE)
37+
stdout=subprocess.PIPE)
3738
out = proc.communicate()[0]
3839
return out.rstrip()
3940

4041
def pyconfigvar(name):
41-
cmd = '\n'.join((
42-
'from distutils.sysconfig import get_config_var',
43-
'print get_config_var(%r)' % name))
42+
cmd = ('from distutils.sysconfig import get_config_var\n'
43+
'print(get_config_var(%r))\n') % name
4444
return pyoutput(cmd)
4545

4646
# copy system environment variables related to compilation
@@ -93,7 +93,9 @@ env.MergeFlags([os.environ.get(n, '') for n in flagnames])
9393
good_python_flags = lambda n : (
9494
not isinstance(n, basestring) or
9595
not re.match(r'(-g|-Wstrict-prototypes|-O\d)$', n))
96-
env.ParseConfig("python-config --cflags")
96+
pyversion = pyoutput('import sys; print("%i.%i" % sys.version_info[:2])')
97+
pythonconfig = 'python' + pyversion + '-config'
98+
env.ParseConfig(pythonconfig + " --cflags")
9799
env.Replace(CCFLAGS=filter(good_python_flags, env['CCFLAGS']))
98100
env.Replace(CPPDEFINES='')
99101
# the CPPPATH directories are checked by scons dependency scanner
@@ -143,7 +145,7 @@ if env['profile']:
143145

144146
builddir = env.Dir('build/%s-%s' % (env['build'], platform.machine()))
145147

146-
Export('env', 'pyconfigvar', 'pyoutput')
148+
Export('env', 'pyconfigvar', 'pyoutput', 'pyversion')
147149

148150
if os.path.isfile('sconscript.local'):
149151
env.SConscript('sconscript.local')

conda-recipe/build.sh

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
#!/bin/bash
22

3-
export CPATH="$PREFIX/include:$CPATH"
4-
export LIBRARY_PATH="$PREFIX/lib:$LIBRARY_PATH"
5-
MYNCPU=$(( (CPU_COUNT > 8) ? 8 : CPU_COUNT ))
3+
export CPATH="${PREFIX}/include:$CPATH"
4+
export LIBRARY_PATH="${PREFIX}/lib:$LIBRARY_PATH"
5+
6+
MYNCPU=$(( (CPU_COUNT > 4) ? 4 : CPU_COUNT ))
67

78
if [ `uname` == Darwin ]; then
8-
export DYLD_FALLBACK_LIBRARY_PATH="$PREFIX/lib:$DYLD_FALLBACK_LIBRARY_PATH"
9+
export DYLD_FALLBACK_LIBRARY_PATH="${PREFIX}/lib"
910
fi
1011

12+
# Apply sconscript.local customizations.
13+
cp ${RECIPE_DIR}/sconscript.local ./
14+
15+
# Install package with scons to utilize multiple CPUs.
1116
scons -j $MYNCPU install prefix=$PREFIX
1217

1318
# Add more build steps here, if they are necessary.

conda-recipe/meta.yaml

+10-9
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ source:
99
git_url: ..
1010

1111
build:
12-
#preserve_egg_dir: True
13-
1412
# If this is a new build for the same version, increment the build
1513
# number. If you do not include this key, it defaults to 0.
1614
number: 0
@@ -19,17 +17,20 @@ requirements:
1917
build:
2018
- python
2119
- setuptools
22-
- numpy 1.9.2
23-
- libobjcryst 2015.1.*
24-
- boost-all 1.59.0
25-
- scons
20+
- numpy 1.11.2
21+
- libobjcryst 2015.1.* # [py2k]
22+
- boost-all 1.59.0 # [py2k]
23+
- libobjcryst 2017.1.* # [py3k]
24+
- boost 1.61.0 # [py3k]
2625

2726
run:
2827
- python
2928
- setuptools
30-
- numpy >=1.9.2
31-
- libobjcryst 2015.1.*
32-
- boost-all 1.59.0
29+
- numpy >=1.11.2
30+
- libobjcryst 2015.1.* # [py2k]
31+
- boost-all 1.59.0 # [py2k]
32+
- libobjcryst 2017.1.* # [py3k]
33+
- boost 1.61.0 # [py3k]
3334

3435
test:
3536
# Python imports

conda-recipe/sconscript.local

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Customize scons build environment.
2+
3+
Import('env')
4+
5+
import os
6+
7+
# Silence copious warnings from the boost headers.
8+
P = os.environ['PREFIX']
9+
env.Prepend(CCFLAGS=['-isystem{}/include'.format(P)])
10+
11+
# vim: ft=python

devutils/makesdist

+11-6
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@ vfb = versiondata.get('DEFAULT', 'version').split('.post')[0] + '.post0'
2121
emsg = "Invalid FALLBACK_VERSION. Expected %r got %r."
2222
assert vfb == FALLBACK_VERSION, emsg % (vfb, FALLBACK_VERSION)
2323

24-
print 'Run "setup.py sdist --formats=tar"',
25-
cmd_sdist = [sys.executable] + 'setup.py sdist --formats=tar'.split()
24+
def inform(s):
25+
sys.stdout.write(s)
26+
sys.stdout.flush()
27+
return
28+
29+
inform('Run "setup.py sdist --formats=tar" ')
30+
cmd_sdist = ([sys.executable, '-Wignore:Cannot detect name suffix'] +
31+
'setup.py sdist --formats=tar'.split())
2632
ec = subprocess.call(cmd_sdist, cwd=BASEDIR, stdout=open(os.devnull, 'w'))
2733
if ec: sys.exit(ec)
28-
print "[done]"
34+
inform("[done]\n")
2935

3036
tarname = max(glob.glob(BASEDIR + '/dist/*.tar'), key=os.path.getmtime)
3137

@@ -40,11 +46,10 @@ def fixtarinfo(tinfo):
4046
tinfo.mode &= ~0o022
4147
return tinfo
4248

43-
44-
print 'Filter %s --> %s.gz' % (2 * (os.path.basename(tarname),)),
49+
inform('Filter %s --> %s.gz ' % (2 * (os.path.basename(tarname),)))
4550
for ti in tfin:
4651
tfout.addfile(fixtarinfo(ti), tfin.extractfile(ti))
4752

4853
tfin.close()
4954
os.remove(tarname)
50-
print "[done]"
55+
inform("[done]\n")

doc/manual/source/conf.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
master_doc = 'index'
3535

3636
# General information about the project.
37-
project = u'pyobjcryst'
38-
copyright = u'%Y, Brookhaven National Laboratory'
37+
project = 'pyobjcryst'
38+
copyright = '%Y, Brookhaven National Laboratory'
3939

4040
# The version info for the project you're documenting, acts as replacement for
4141
# |version| and |release|, also used in various other places throughout the

setup.py

+17-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
"""
1010

1111
import os
12+
import re
13+
import sys
1214
import glob
1315
from setuptools import setup
1416
from setuptools import Extension
@@ -26,6 +28,8 @@
2628
'include_dirs' : get_numpy_include_dirs(),
2729
}
2830

31+
# determine if we run with Python 3.
32+
PY3 = (sys.version_info[0] == 3)
2933

3034
# Figure out which boost library to use. This doesn't appear to consult
3135
# LD_LIBRARY_PATH.
@@ -36,7 +40,7 @@ def get_boost_libraries():
3640
on the system. If required libraries are not found, an Exception will be
3741
thrown.
3842
"""
39-
baselib = "boost_python"
43+
baselib = "boost_python3" if PY3 else "boost_python"
4044
boostlibtags = ['', '-mt'] + ['']
4145
from ctypes.util import find_library
4246
for tag in boostlibtags:
@@ -52,7 +56,7 @@ def get_boost_libraries():
5256
if platform.system() == 'Darwin':
5357
ldevname = 'DYLD_FALLBACK_LIBRARY_PATH'
5458
wmsg = ("Cannot detect name suffix for the %r library. "
55-
"Consider setting %s.") % (baselib, ldevname)
59+
"Consider setting %s.") % (baselib, ldevname)
5660
warnings.warn(wmsg)
5761

5862
libs = [lib]
@@ -76,9 +80,10 @@ def create_extensions():
7680
versioncfgfile = os.path.join(MYDIR, 'src/pyobjcryst/version.cfg')
7781
gitarchivecfgfile = versioncfgfile.replace('version.cfg', 'gitarchive.cfg')
7882

83+
7984
def gitinfo():
8085
from subprocess import Popen, PIPE
81-
kw = dict(stdout=PIPE, cwd=MYDIR)
86+
kw = dict(stdout=PIPE, cwd=MYDIR, universal_newlines=True)
8287
proc = Popen(['git', 'describe', '--match=v[[:digit:]]*'], **kw)
8388
desc = proc.stdout.read()
8489
proc = Popen(['git', 'log', '-1', '--format=%H %at %ai'], **kw)
@@ -90,8 +95,10 @@ def gitinfo():
9095

9196

9297
def getversioncfg():
93-
import re
94-
from ConfigParser import RawConfigParser
98+
if PY3:
99+
from configparser import RawConfigParser
100+
else:
101+
from ConfigParser import RawConfigParser
95102
vd0 = dict(version=FALLBACK_VERSION, commit='', date='', timestamp=0)
96103
# first fetch data from gitarchivecfgfile, ignore if it is unexpanded
97104
g = vd0.copy()
@@ -120,7 +127,8 @@ def getversioncfg():
120127
cp.set('DEFAULT', 'commit', g['commit'])
121128
cp.set('DEFAULT', 'date', g['date'])
122129
cp.set('DEFAULT', 'timestamp', g['timestamp'])
123-
cp.write(open(versioncfgfile, 'w'))
130+
with open(versioncfgfile, 'w') as fp:
131+
cp.write(fp)
124132
return cp
125133

126134
versiondata = getversioncfg()
@@ -159,6 +167,9 @@ def getversioncfg():
159167
'Operating System :: Unix',
160168
'Programming Language :: C++',
161169
'Programming Language :: Python :: 2.7',
170+
'Programming Language :: Python :: 3.4',
171+
'Programming Language :: Python :: 3.5',
172+
'Programming Language :: Python :: 3.6',
162173
'Topic :: Scientific/Engineering :: Chemistry',
163174
'Topic :: Scientific/Engineering :: Physics',
164175
'Topic :: Software Development :: Libraries',

0 commit comments

Comments
 (0)