1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
|
# setup.py -- Build-Script for building Psychtoolbox-3 "mex" files as Python extensions.
#
# (c) 2018-2023 Mario Kleiner
# (c) 2020-2022 Alex Forrence
# (c) 2019 Jonathan Peirce
# (c) 2019 Richard HoĢchenberger
# (c) 2019 Eric Larson
# (c) 2019 Bernhard M. Wiedemann
#
# Licensed under the MIT license.
#
# To build Python extensions/wheels, you'll want pip >= 10.0 for pyproject.toml support.
# This file will allow pip to automatically download the required build dependencies (i.e. numpy).
# To build this wheel locally into the dist/ directory, you could use the following
# (assuming the current working directory is next to the setup.py file):
#
# > pip wheel . -w dist
#
# (see https://pip.pypa.io/en/stable/cli/pip_wheel/ for all options)
#
# To build and install in the same step:
#
# > pip install .
#
# (see https://pip.pypa.io/en/stable/cli/pip_install/ for all options)
#
# If you wish to build wheels for distribution, you may want to compile with Python's Limited API
# enabled. The resulting wheel can be used across multiple Python releases (see
# https://docs.python.org/3/c-api/stable.html for details).
# This requires Python >= 3.7, and PTB_LIMITED_WHEEL must be set to 1. For example,
#
# > PTB_LIMITED_WHEEL=1 pip wheel . -w dist
#
# Will produce a wheel that will work for Python versions >= 3.7.
# It is recommended to compile these on the oldest supported Python version (i.e. 3.7).
# from distutils.core import setup, Extension # Build system.
from setuptools import setup, Extension, find_packages
import os, fnmatch, shutil # Directory traversal, file list building.
import platform # OS detection.
import sys # cpu arch detection.
import numpy # To get include dir on macOS.
from wheel.bdist_wheel import bdist_wheel
# We borrowed the custom wheel configuration from https://github.com/joerick/python-abi3-package-sample
# It lives in a separate file for licensing simplicity
sys.path.append(os.path.join(os.path.dirname(__file__), 'PsychPython', 'psychtoolbox'))
from _abi3_wheel import abi3_wheel
is_64bits = sys.maxsize > 2**32
# unified version number, read from simple text file
def get_version():
import re
VERSIONFILE = "PsychPython/psychtoolbox/_version.py"
with open(VERSIONFILE, "rt") as fid:
verstrline = fid.read()
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
mo = re.search(VSRE, verstrline, re.M)
if mo:
verstr = mo.group(1)
else:
raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
return verstr
version = get_version()
def get_sourcefiles(path):
sources = []
pattern1 = '*.c'
pattern2 = '*.cpp'
for filename in sorted(os.listdir(path)):
if fnmatch.fnmatch(filename, pattern1) or fnmatch.fnmatch(filename, pattern2):
sources += [os.path.join(path,filename)]
# Fancy schmanzi, not needed atm. for recursive dir traversal:
# for root, dirs, files in os.walk(path):
# dirs.sort()
# for filename in sorted(files):
# sources += [os.path.join(root,filename)]
return(sources)
def get_basemacros(name, osname):
return([('PTBMODULE_' + name, None), ('PTBMODULENAME', name)] + base_macros)
def get_baseincludedirs(name, osname):
return(['PsychSourceGL/Source/Common/' + name] + baseincludes_common + ['PsychSourceGL/Source/' + osname + '/Base'] + ['PsychSourceGL/Source/' + osname + '/' + name])
def get_basesources(name, osname):
extrafiles = []
if os.access('./PsychSourceGL/Source/' + osname + '/' + name, os.F_OK):
extrafiles = get_sourcefiles('./PsychSourceGL/Source/' + osname + '/' + name)
return(basefiles_common + get_sourcefiles('./PsychSourceGL/Source/' + osname + '/Base/') + get_sourcefiles('./PsychSourceGL/Source/Common/' + name) + extrafiles)
# Treating some special cases like Octave seems to be the right thing to do,
# PSYCH_LANGUAGE setting is self-explanatory:
base_macros = [('PTBOCTAVE3MEX', None), ('PSYCH_LANGUAGE', 'PSYCH_PYTHON')]
# if desired by wheel-builder and building on py >= 3.7, build as a limited API wheel.
if sys.version_info >= (3, 7) and os.environ.get('PTB_LIMITED_WHEEL') == '1':
base_macros.append(('Py_LIMITED_API', '0x03070000'))
py_limited_api = True
bdist = abi3_wheel
else:
py_limited_api = False
bdist = bdist_wheel
# Common infrastructure and the scripting glue module for interfacing with the Python runtime:
basefiles_common = get_sourcefiles('./PsychSourceGL/Source/Common/Base') + ['PsychSourceGL/Source/Common/Base/PythonGlue/PsychScriptingGluePython.c']
baseincludes_common = [numpy.get_include(), 'PsychSourceGL/Source/Common/Base', 'PsychSourceGL/Source/Common/Screen']
# OS detection and file selection for the different OS specific backends:
print('Platform reported as: %s\n' % platform.system())
if platform.system() == 'Linux':
# Linux specific backend code:
print('Building for Linux...\n')
osname = 'Linux'
# All libraries to link to all modules:
base_libs = ['c', 'rt', 'dl']
# No "no reproducible builds" warning:
base_compile_args = ['-Wno-date-time']
# Extra OS specific libs for PsychPortAudio:
audio_libdirs = []
audio_extralinkargs = []
audio_libs = ['portaudio', 'asound']
audio_objects = []
# Extra OS specific libs for PsychHID:
psychhid_includes = ['/usr/include/libusb-1.0']
psychhid_libdirs = []
psychhid_libs = ['dl', 'usb-1.0', 'X11', 'Xi', 'util']
psychhid_extra_objects = []
psychhid_extralinkargs = []
# Extra files needed, e.g., libraries:
extra_files = {}
if platform.system() == 'Windows':
print('Building for Windows...\n')
osname = 'Windows'
base_libs = ['kernel32', 'user32', 'advapi32', 'winmm']
base_compile_args = []
# libusb includes:
psychhid_includes = ['PsychSourceGL/Cohorts/libusb1-win32/include/libusb-1.0']
psychhid_extralinkargs = []
if is_64bits:
# 64bit supports PsychPortAudio
psychhid_libdirs = ['PsychSourceGL/Cohorts/libusb1-win32/MS64/dll']
# and copy the files to the folder
shutil.copy('PsychSourceGL/Cohorts/libusb1-win32/MS64/dll/libusb-1.0.dll',
'PsychPython/psychtoolbox/')
shutil.copy('Psychtoolbox/PsychSound/portaudio_x64.dll',
'PsychPython/psychtoolbox/')
# list them so they get packaged
extra_files = {'psychtoolbox': ['portaudio_x64.dll', 'libusb-1.0.dll']}
# Extra OS specific libs for PsychPortAudio:
audio_libdirs = ['PsychSourceGL/Cohorts/PortAudio']
audio_extralinkargs = [] # No runtime delay loading atm. No benefit with current packaging method: ['/DELAYLOAD:portaudio_x64.dll']
audio_libs = ['delayimp', 'portaudio_x64']
audio_objects = []
else:
# for win32 we use a different libusb dll and the portaudio dll is not supported
psychhid_libdirs = ['PsychSourceGL/Cohorts/libusb1-win32/MS32/dll']
shutil.copy('PsychSourceGL/Cohorts/libusb1-win32/MS32/dll/libusb-1.0.dll',
'PsychPython/psychtoolbox/')
# list them so they get packaged
extra_files = {'psychtoolbox': ['libusb-1.0.dll']}
psychhid_libs = ['dinput8', 'libusb-1.0', 'setupapi']
psychhid_extra_objects = []
if platform.system() == 'Darwin':
print('Building for macOS...\n')
osname = 'OSX'
# These should go to extra_link_args in Extension() below, but apparently distutils
# always appends the extra_link_args at the end of the linker command line, which is
# wrong for -framework's, as they must be stated *before* the .o object files which
# want to use functions from them. A solution to this problem doesn't exist in distutils
# almost two decades after the debut of Mac OSX, because hey, take your time!
#
# The hack is to set the LDFLAGS environment variable to what we need, as LDFLAGS
# apparently gets prepended to the linker invocation arguments, so -framework statements
# precede the .o'bjects they should apply to and the linker is happy again.
#
# Downside is that now we have to pass the union of *all* -framework switches ever
# used for *any* extension module, as os.environ can't be changed during the build
# sequencing for a distribution package.
#
# Is this awful? Absolutely! And i thought the Octave/Matlab build process on macOS
# sucked big time, but apparently somebody in Pythonland shouted "Hold my beer!" :(
# Hopefully some new info about this issue will prove me wrong, and there is a sane
# and elegant solution, but this is the best i could find after hours of Googling and
# trying.
#
# The following would be the full list of frameworks, but apparently including -framework Carbon
# is enough. Maybe a catch-all including all other frameworks?
#
# -framework Carbon -framework CoreServices -framework CoreFoundation -framework CoreAudio -framework AudioToolbox -framework AudioUnit
# -framework ApplicationServices -framework OpenGL -framework CoreVideo -framework IOKit -framework SystemConfiguration
# -framework CoreText -framework Cocoa
#
os.environ['LDFLAGS'] = '-framework Carbon -framework CoreAudio'
base_libs = []
# No "no reproducible builds" warning. macOS minimum version 10.9 selected by Jon Peirce.
# May work for current modules, but is completely untested and unsupported by Psychtoolbox
# upstream as of v3.0.15+, which only allows 10.11 as minimum version for Psychtoolbox mex
# files and only tests with 10.13. Also note that already 10.11 is unsupported by Apple and
# therefore a security risk.
base_compile_args = ['-Wno-date-time', '-mmacosx-version-min=10.9']
# Extra OS specific libs for PsychPortAudio:
audio_libdirs = []
audio_extralinkargs = []
audio_libs = []
# Include our statically linked on-steroids version of PortAudio:
audio_objects = ['PsychSourceGL/Cohorts/PortAudio/libportaudio_osx_64.a']
# Include Apples open-source HID Utilities for all things USB-HID device handling, also libusb-1.0:
psychhid_includes = ['PsychSourceGL/Cohorts/HID_Utilities_64Bit/', 'PsychSourceGL/Cohorts/HID_Utilities_64Bit/IOHIDManager',
'PsychSourceGL/Cohorts/libusb1-win32/include/libusb-1.0']
psychhid_libdirs = []
psychhid_libs = []
# Weak-link libusb-1.0.dylib, so user does not need it on their local system as long as they
# don't use PsychHID functions like USBControlTransfer/USBInterruptTransfer/USBBulkTransfer:
psychhid_extralinkargs = ['-weak_library', 'PsychSourceGL/Cohorts/libusb1-win32/libusb-1.0.dylib']
# Extra objects for PsychHID - statically linked HID utilities:
psychhid_extra_objects = ['PsychSourceGL/Cohorts/HID_Utilities_64Bit/build/Release/libHID_Utilities64.a']
# Extra files needed, e.g., libraries:
extra_files = {}
ext_modules = []
# GetSecs module: Clock queries.
name = 'GetSecs'
GetSecs = Extension(name,
extra_compile_args = base_compile_args,
define_macros = get_basemacros(name, osname),
include_dirs = get_baseincludedirs(name, osname),
sources = get_basesources(name, osname),
libraries = base_libs,
py_limited_api = py_limited_api,
)
ext_modules.append(GetSecs)
# WaitSecs module: Timed waits.
name = 'WaitSecs'
WaitSecs = Extension(name,
extra_compile_args = base_compile_args,
define_macros = get_basemacros(name, osname),
include_dirs = get_baseincludedirs(name, osname),
sources = get_basesources(name, osname),
libraries = base_libs,
py_limited_api = py_limited_api,
)
ext_modules.append(WaitSecs)
# PsychPortAudio module: High precision, high reliability, multi-channel, multi-card audio i/o.
if is_64bits or platform.system() == 'Linux':
# This won't compile on 32bit windows or macOS. Linux also has 32 Bit non-Intel variants, e.g., RaspberryPi
name = 'PsychPortAudio'
PsychPortAudio = Extension(name,
extra_compile_args = base_compile_args,
define_macros = get_basemacros(name, osname),
include_dirs = get_baseincludedirs(name, osname),
sources = get_basesources(name, osname),
library_dirs = audio_libdirs,
libraries = base_libs + audio_libs,
extra_link_args = audio_extralinkargs,
extra_objects = audio_objects,
py_limited_api = py_limited_api,
)
ext_modules.append(PsychPortAudio)
# PsychHID module: Note the extra include_dirs and libraries:
name = 'PsychHID'
PsychHID = Extension(name,
extra_compile_args = base_compile_args,
define_macros = get_basemacros(name, osname),
include_dirs = get_baseincludedirs(name, osname) + psychhid_includes,
sources = get_basesources(name, osname),
library_dirs = psychhid_libdirs,
libraries = base_libs + psychhid_libs,
extra_link_args = psychhid_extralinkargs,
extra_objects = psychhid_extra_objects,
py_limited_api = py_limited_api,
)
ext_modules.append(PsychHID)
# IOPort module:
name = 'IOPort'
IOPort = Extension(name,
extra_compile_args = base_compile_args,
define_macros = get_basemacros(name, osname),
include_dirs = get_baseincludedirs(name, osname),
sources = get_basesources(name, osname),
libraries = base_libs,
py_limited_api = py_limited_api,
)
ext_modules.append(IOPort)
description = 'Pieces of Psychtoolbox-3 ported to CPython.'
setup (name = 'psychtoolbox',
version = version,
description = description,
long_description = description,
long_description_content_type = 'text/x-rst',
author = 'Mario Kleiner',
author_email = 'mario.kleiner.de@gmail.com',
url = 'http://psychtoolbox.org',
packages = ['psychtoolbox', 'psychtoolbox.demos'],
package_dir = {'' : 'PsychPython',
'psychtoolbox' : 'PsychPython/psychtoolbox',
'psychtoolbox.demos' : 'PsychPython/demos'},
package_data = extra_files,
ext_package = 'psychtoolbox',
ext_modules = ext_modules,
include_package_data=True, # Include files listed in MANIFEST.in
install_requires = ['numpy>=1.13.3'], # Oldest supported numpy on Python 3.6
cmdclass={"bdist_wheel": bdist},
)
if platform.system() == 'Windows':
# Get rid of the now no longer needed copies of dll's inside PsychPython,
# now that setup() has already copied them into the distribution.
if os.path.exists('PsychPython/psychtoolbox/portaudio_x64.dll'):
os.remove('PsychPython/psychtoolbox/portaudio_x64.dll')
os.remove('PsychPython/psychtoolbox/libusb-1.0.dll')
|