Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "SConsChecks"]
path = SConsChecks
url = https://github.com/ChrislS/SConsChecks.git
branch = master
6 changes: 5 additions & 1 deletion README
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ Linux and CMake on Windows.
Building with SCons should be as simple as running "scons" and "scons
install", but you may need to use the "--with-boost*" options (see
"scons --help") to specify where to find Boost. The Python that is
used by SCons will be the one built against.
used by SCons will be the one built against. Additionally, the
SConsChecks submodule must be initialized by git before building
by running

git submodule update --init

Please see libs/numpy/doc/cmakeBuild.rst for more information on
building with CMake.
Expand Down
1 change: 1 addition & 0 deletions SConsChecks
Submodule SConsChecks added at b2af1e
233 changes: 42 additions & 191 deletions SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -6,192 +6,21 @@
# http://www.boost.org/LICENSE_1_0.txt)

# Big thanks to Mike Jarvis for help with the configuration prescriptions below.
# Integration of SConsChecks for platform independent building
# by Christoph Lassner.

from __future__ import print_function
import os
import sys
import subprocess
import sysconfig
from SCons.SConf import CheckContext
from SConsChecks import AddLibOptions, GetLibChecks

def setupPaths(env, prefix, include, lib):
if prefix is not None:
if include is None:
include = os.path.join(prefix, "include")
if lib is None:
lib = os.path.join(prefix, "lib")
if include:
env.PrependUnique(CPPPATH=[include])
if lib:
env.PrependUnique(LIBPATH=[lib])
AddMethod(Environment, setupPaths)

def checkLibs(context, try_libs, source_file):
init_libs = context.env.get('LIBS', [])
context.env.PrependUnique(LIBS=[try_libs])
result = context.TryLink(source_file, '.cpp')
if not result :
context.env.Replace(LIBS=init_libs)
return result
AddMethod(CheckContext, checkLibs)

def CheckPython(context):
python_source_file = """
#include "Python.h"
int main()
{
Py_Initialize();
Py_Finalize();
return 0;
}
"""
context.Message('Checking if we can build against Python... ')
try:
import distutils.sysconfig
except ImportError:
context.Result(0)
print('Failed to import distutils.sysconfig.')
return False
context.env.AppendUnique(CPPPATH=[distutils.sysconfig.get_python_inc()])
libDir = distutils.sysconfig.get_config_var("LIBDIR")
context.env.AppendUnique(LIBPATH=[libDir])
libfile = distutils.sysconfig.get_config_var("LIBRARY")
import re
match = re.search("(python.*)\.(a|so|dylib)", libfile)
if match:
context.env.AppendUnique(LIBS=[match.group(1)])
if match.group(2) == 'a':
flags = distutils.sysconfig.get_config_var('LINKFORSHARED')
if flags is not None:
context.env.AppendUnique(LINKFLAGS=flags.split())
flags = [f for f in " ".join(distutils.sysconfig.get_config_vars("MODLIBS", "SHLIBS")).split()
if f != "-L"]
context.env.MergeFlags(" ".join(flags))
result, output = context.TryRun(python_source_file,'.cpp')
if not result and context.env["PLATFORM"] == 'darwin':
# Sometimes we need some extra stuff on Mac OS
frameworkDir = libDir # search up the libDir tree for the proper home for frameworks
while frameworkDir and frameworkDir != "/":
frameworkDir, d2 = os.path.split(frameworkDir)
if d2 == "Python.framework":
if not "Python" in os.listdir(os.path.join(frameworkDir, d2)):
context.Result(0)
print((
"Expected to find Python in framework directory %s, but it isn't there"
% frameworkDir))
return False
break
context.env.AppendUnique(LINKFLAGS="-F%s" % frameworkDir)
result, output = context.TryRun(python_source_file,'.cpp')
if not result:
context.Result(0)
print("Cannot run program built with Python.")
return False
if context.env["PLATFORM"] == "darwin":
context.env["LDMODULESUFFIX"] = ".so"
context.Result(1)
return True

def CheckNumPy(context):
numpy_source_file = """
#include "Python.h"
#include "numpy/arrayobject.h"
#if PY_MAJOR_VERSION == 2
static void wrap_import_array() {
import_array();
}
#else
static void * wrap_import_array() {
import_array();
}
#endif
void doImport() {
wrap_import_array();
}
int main()
{
int result = 0;
Py_Initialize();
doImport();
if (PyErr_Occurred()) {
result = 1;
} else {
npy_intp dims = 2;
PyObject * a = PyArray_SimpleNew(1, &dims, NPY_INT);
if (!a) result = 1;
Py_DECREF(a);
}
Py_Finalize();
return result;
}
"""
context.Message('Checking if we can build against NumPy... ')
try:
import numpy
except ImportError:
context.Result(0)
print('Failed to import numpy.')
print('Things to try:')
print('1) Check that the command line python (with which you probably installed numpy):')
print(' ', end=' ')
sys.stdout.flush()
subprocess.call('which python',shell=True)
print(' is the same as the one used by SCons:')
print(' ',sys.executable)
print(' If not, then you probably need to reinstall numpy with %s.' % sys.executable)
print(' Alternatively, you can reinstall SCons with your preferred python.')
print('2) Check that if you open a python session from the command line,')
print(' import numpy is successful there.')
return False
context.env.Append(CPPPATH=numpy.get_include())
result = context.checkLibs([''],numpy_source_file)
if not result:
context.Result(0)
print("Cannot build against NumPy.")
return False
result, output = context.TryRun(numpy_source_file,'.cpp')
if not result:
context.Result(0)
print("Cannot run program built with NumPy.")
return False
context.Result(1)
return True

def CheckBoostPython(context):
bp_source_file = """
#include "boost/python.hpp"
class Foo { public: Foo() {} };
int main()
{
Py_Initialize();
boost::python::object obj;
boost::python::class_< Foo >("Foo", boost::python::init<>());
Py_Finalize();
return 0;
}
"""
context.Message('Checking if we can build against Boost.Python... ')
context.env.setupPaths(
prefix = GetOption("boost_prefix"),
include = GetOption("boost_include"),
lib = GetOption("boost_lib")
)
boost_python_lib = GetOption ('boost_python_lib')
result = (
context.checkLibs([''], bp_source_file) or
context.checkLibs([boost_python_lib], bp_source_file) or
context.checkLibs([boost_python_lib+'_mt'], bp_source_file)
)
if not result:
context.Result(0)
print("Cannot build against Boost.Python.")
return False
result, output = context.TryRun(bp_source_file, '.cpp')
if not result:
context.Result(0)
print ("Cannot run program built against Boost.Python.")
return False
context.Result(1)
return True
_libs = ['boost.python',
'python',
'numpy']
_checks = GetLibChecks(_libs)

# Setup command-line options
def setupOptions():
Expand All @@ -203,7 +32,7 @@ def setupOptions():
metavar="DIR", help="location to install libraries (overrides --prefix for libraries)")
AddOption("--with-boost", dest="boost_prefix", type="string", nargs=1, action="store",
metavar="DIR", default=os.environ.get("BOOST_DIR"),
help="prefix for Boost libraries; should have 'include' and 'lib' subdirectories")
help="prefix for Boost libraries; should have 'include' and 'lib' subdirectories, 'boost' and 'stage\\lib' subdirectories on Windows")
AddOption("--with-boost-include", dest="boost_include", type="string", nargs=1, action="store",
metavar="DIR", help="location of Boost header files")
AddOption("--with-boost-lib", dest="boost_lib", type="string", nargs=1, action="store",
Expand All @@ -213,7 +42,10 @@ def setupOptions():
AddOption("--boost-python-lib", dest="boost_python_lib", type="string", action="store",
help="name of boost_python_lib", default='boost_python')
variables = Variables()
variables.Add("CCFLAGS", default=os.environ.get("CCFLAGS", "-O2 -g"), help="compiler flags")
defaultflags = "-O2 -g"
if os.name == 'nt':
defaultflags = "/O2"
variables.Add("CCFLAGS", default=os.environ.get("CCFLAGS", defaultflags), help="compiler flags")
return variables

def makeEnvironment(variables):
Expand All @@ -227,15 +59,36 @@ def makeEnvironment(variables):
custom_rpath = GetOption("custom_rpath")
if custom_rpath is not None:
env.AppendUnique(RPATH=custom_rpath)
boost_lib = GetOption ('boost_lib')
if boost_lib is not None:
env.PrependUnique(LIBPATH=boost_lib)
if env['CC'] == 'cl':
# C++ exception handling,
# multithread-supporting, dynamically linked system libraries,
# generate debug information.
env.AppendUnique(CPPFLAGS=['/EHsc', '/MD', '/Zi'])
return env

def setupTargets(env, root="."):
lib = SConscript(os.path.join(root, "libs", "numpy", "src", "SConscript"), exports='env')
example = SConscript(os.path.join(root, "libs", "numpy", "example", "SConscript"), exports='env')
test = SConscript(os.path.join(root, "libs", "numpy", "test", "SConscript"), exports='env')
# Determine file extensions.
VERSION = sys.version_info.major
if os.name == 'nt':
EXT_SUFFIX = '.dll'
LIB_SUFFIX = '.lib'
PY_SUFFIX = '.pyd'
else:
EXT_SUFFIX = sysconfig.get_config_var("EXT_SUFFIX")
if VERSION == 2 and EXT_SUFFIX == 'None' or EXT_SUFFIX==None:
EXT_SUFFIX = '.so'
elif VERSION == 3 and EXT_SUFFIX == b'None' or EXT_SUFFIX==None:
EXT_SUFFIX = '.so'
LIB_SUFFIX = EXT_SUFFIX
PY_SUFFIX = EXT_SUFFIX
OBJ_SUFFIX = EXT_SUFFIX.replace ('.so', '.os')

lib = SConscript(os.path.join(root, "libs", "numpy", "src", "SConscript"),
exports=['env', 'EXT_SUFFIX', 'LIB_SUFFIX', 'OBJ_SUFFIX'])
example = SConscript(os.path.join(root, "libs", "numpy", "example", "SConscript"),
exports='env')
test = SConscript(os.path.join(root, "libs", "numpy", "test", "SConscript"),
exports=['env', 'lib', 'EXT_SUFFIX', 'LIB_SUFFIX', 'OBJ_SUFFIX', 'PY_SUFFIX'])
prefix = Dir(GetOption("prefix")).abspath
install_headers = GetOption('install_headers')
install_lib = GetOption('install_lib')
Expand All @@ -244,14 +97,12 @@ def setupTargets(env, root="."):
if not install_lib:
install_lib = os.path.join(prefix, "lib")
env.Alias("install", env.Install(install_lib, lib))
for header in ("dtype.hpp", "invoke_matching.hpp", "matrix.hpp",
for header in ("dtype.hpp", "invoke_matching.hpp", "matrix.hpp",
"ndarray.hpp", "numpy_object_mgr_traits.hpp",
"scalars.hpp", "ufunc.hpp",):
env.Alias("install", env.Install(os.path.join(install_headers, "boost", "numpy"),
os.path.join(root, "boost", "numpy", header)))
env.Alias("install", env.Install(os.path.join(install_headers, "boost"),
os.path.join(root, "boost", "numpy.hpp")))

checks = {"CheckPython": CheckPython, "CheckNumPy": CheckNumPy, "CheckBoostPython": CheckBoostPython}

Return("setupOptions", "makeEnvironment", "setupTargets", "checks")
Return("setupOptions", "makeEnvironment", "setupTargets", "_checks", "_libs")
9 changes: 6 additions & 3 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)

setupOptions, makeEnvironment, setupTargets, checks = SConscript("SConscript")
from SConsChecks import GetLibChecks

setupOptions, makeEnvironment, setupTargets, checks, libnames = SConscript("SConscript")

variables = setupOptions()

Expand All @@ -14,8 +16,9 @@ env.AppendUnique(CPPPATH="#.")

if not GetOption("help") and not GetOption("clean"):
config = env.Configure(custom_tests=checks)
if not (config.CheckPython() and config.CheckNumPy() and config.CheckBoostPython()):
Exit(1)
checknames = GetLibChecks(libnames).keys()
if False in (config.__dict__[checkname]() for checkname in checknames):
Exit(1)
env = config.Finish()

setupTargets(env)
6 changes: 4 additions & 2 deletions boost/numpy/ndarray.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* @brief Object manager and various utilities for numpy.ndarray.
*/

#include <cstddef>

#include <boost/python.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_integral.hpp>
Expand Down Expand Up @@ -87,10 +89,10 @@ class ndarray : public python::object
ndarray copy() const;

/// @brief Return the size of the nth dimension.
int const shape(int n) const { return get_shape()[n]; }
Py_ssize_t const shape(int n) const { return get_shape()[n]; }

/// @brief Return the stride of the nth dimension.
int const strides(int n) const { return get_strides()[n]; }
Py_ssize_t const strides(int n) const { return get_strides()[n]; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the typedef we want to switch to here is Python's Py_ssize_t, not std::size_t. I'm pretty sure the former is actually a signed type, but that's actually what we want, at least for strides (which can be negative). And I think it makes sense to make the shape signed as well, to avoid having to constantly work around compiler warnings about signed/unsigned comparisons.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, you're right! I did a little bit of research on the matter and found http://legacy.python.org/dev/peps/pep-0353/. Quoting from there: "A new type Py_ssize_t is introduced, which has the same size as the compiler's size_t type, but is signed. It will be a typedef for ssize_t where available." It is additionally noted that "All occurrences of index and length parameters and results are changed to use Py_ssize_t".

The reasoning behind this is that negative indexing to indicate counting from the end would otherwise require significant code overhead. As a sidenote: I had to make a similar decision for the ndarray project. I decided to use std::size_t for array shapes and strides, but std::ptrdiff_t for iterator strides (making backwards iteration possible). This seemed most reasonable for me, but is of course debatable as well.

I do not have any experience with changing a pull request. Should I update the line in my branch and resend the request?

/**
* @brief Return the array's raw data pointer.
Expand Down
28 changes: 6 additions & 22 deletions libs/numpy/src/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,16 @@

import sys
import os
VERSION = sys.version_info.major
import sysconfig
if os.name == 'nt':
EXT_SUFFIX = '.dll'
LIB_SUFFIX = '.lib'
else:
EXT_SUFFIX = sysconfig.get_config_var("EXT_SUFFIX")
LIB_SUFFIX = EXT_SUFFIX

if VERSION == 2 and EXT_SUFFIX == 'None' or EXT_SUFFIX==None:
EXT_SUFFIX = '.so'
elif VERSION == 3 and EXT_SUFFIX == b'None' or EXT_SUFFIX==None:
EXT_SUFFIX = '.so'
print ('EXT:', EXT_SUFFIX)

OBJ_SUFFIX = EXT_SUFFIX.replace ('.so', '.os')

Import("env")
Import(['env', 'EXT_SUFFIX', 'LIB_SUFFIX', 'OBJ_SUFFIX'])

LIB_BOOST_NUMPY = ('boost_numpy' + LIB_SUFFIX)
mods = [g.name.replace('.cpp', '') for g in Glob("*.cpp")]
for m in mods:
env.SharedObject (target=m+OBJ_SUFFIX, source=m+'.cpp')
sourcefiles = Glob("*.cpp")
if os.name == 'nt':
lib = env.StaticLibrary(LIB_BOOST_NUMPY, source=[m+OBJ_SUFFIX for m in mods])
lib = env.StaticLibrary(LIB_BOOST_NUMPY, source=sourcefiles)
else:
mods = [g.name.replace('.cpp', '') for g in sourcefiles]
for m in mods:
env.SharedObject (target=m+OBJ_SUFFIX, source=m+'.cpp')
lib = env.SharedLibrary(LIB_BOOST_NUMPY, source=[m+OBJ_SUFFIX for m in mods])

Return("lib")
Loading