diff --git a/.gitignore b/.gitignore index 2a5de48..81d3280 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,9 @@ *.app config.txt .DS_Store + +# CMake build folder +*-build +*__pycache__* + +notes* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9d28be0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,124 @@ +cmake_minimum_required(VERSION 3.24) +project(py VERSION 0.1.0 LANGUAGES C CXX) + +include(CMakePrintHelpers) + +# set(CMAKE_INSTALL_RPATH_USE_LINK_PATH 1) + +find_package(Python3 REQUIRED COMPONENTS Interpreter Development NumPy) + +if(VERBOSE) + cmake_print_variables(Python3_INCLUDE_DIRS) + cmake_print_variables(Python3_NumPy_INCLUDE_DIRS) +endif() + +# TODO: make a FindPd.cmake find module to address the following PD_* items: +execute_process( + COMMAND "${Python3_EXECUTABLE}" -c "import platform; print('.pd_' + platform.system().lower())" + OUTPUT_VARIABLE PD_EXTERNAL_SUFFIX + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +if(VERBOSE) + cmake_print_variables(PD_EXTERNAL_SUFFIX) +endif() + +set(PD_EXTERNALS_PATH "~/Documents/Pd/externals" CACHE PATH "Pd externals path") + +if(UNIX AND NOT APPLE) + set(PD_SOURCE_PATH_DEFAULT /usr/local/pd/src) +elseif(UNIX AND APPLE) + set(PD_SOURCE_PATH_DEFAULT /Applications/Pd-0.54-0.app/Contents/Resources/src) +elseif(WIN32) + set(PD_SOURCE_PATH_DEFAULT c:/pd/src) +endif() + +set(PD_SOURCE_PATH ${PD_SOURCE_PATH_DEFAULT} CACHE PATH "Pd src path") +set(PD_INSTANCE OFF CACHE BOOL "Builds with PDINSTANCE defined") + +set(STRIP_TARGET OFF CACHE BOOL "Strip unneeded symbols from compiled library (saves some disk space)") + +set(FLEXT_SOURCE_PATH /usr/local/include/flext CACHE PATH "Flext source path") +set(FLEXT_BUILD_TYPE SHARED CACHE STRING "Flext build type") +set_property(CACHE FLEXT_BUILD_TYPE PROPERTY STRINGS "SINGLE" "SHARED" "MULTI") + +add_library(objects OBJECT + source/bound.cpp + source/clmeth.cpp + source/main.cpp + source/modmeth.cpp + source/py.cpp + source/pyargs.cpp + source/pyatom.cpp + source/pybase.cpp + source/pybuffer.cpp + source/pybundle.cpp + source/pycompat.cpp + source/pydsp.cpp + source/pyext.cpp + source/pymeth.cpp + source/pysymbol.cpp + source/register.cpp) + +target_compile_definitions(objects PRIVATE PD) +target_compile_definitions(objects PRIVATE FLEXT_INLINE) +target_compile_definitions(objects PRIVATE FLEXT_ATTRIBUTES=1) + +if(FLEXT_BUILD_TYPE STREQUAL MULTI) + target_compile_definitions(objects PRIVATE FLEXT_THREADS) +elseif(FLEXT_BUILD_TYPE STREQUAL SHARED) + target_compile_definitions(objects PRIVATE FLEXT_SHARED) +endif() + +if(${PD_INSTANCE}) + target_compile_definitions(objects PRIVATE PDINSTANCE) + target_compile_definitions(objects PRIVATE PDTHREAD) +endif() + +if(UNIX) + target_compile_definitions(objects PRIVATE UNIX) +endif() + +target_compile_options(objects PRIVATE -O3 -Wl,--trace -fPIC -fcheck-new -Wall -Wextra -Wshadow -Winline -Wstrict-aliasing -ffast-math -funroll-loops -fomit-frame-pointer) +target_include_directories(objects PRIVATE ${PD_SOURCE_PATH}) +target_include_directories(objects PRIVATE ${FLEXT_SOURCE_PATH}) +target_link_libraries(objects PRIVATE Python3::Python Python3::NumPy) + +# ---------------------------------------------- # +# BUILDING PD EXTERNAL PY/PYEXT +add_library(${PROJECT_NAME} SHARED $) +target_compile_options(${PROJECT_NAME} PRIVATE -rdynamic -shared -fPIC -Wl,-rpath,"\$ORIGIN",--enable-new-dtags) +target_link_libraries(${PROJECT_NAME} + PRIVATE -lc + PRIVATE -lm + PRIVATE -lstdc++ +) +target_link_libraries(${PROJECT_NAME} PRIVATE Python3::Python Python3::NumPy) + +if(FLEXT_BUILD_TYPE STREQUAL MULTI) + target_compile_definitions(${PROJECT_NAME} PRIVATE FLEXT_THREADS) +elseif(FLEXT_BUILD_TYPE STREQUAL SHARED) + target_compile_definitions(${PROJECT_NAME} PRIVATE FLEXT_SHARED) +endif() +if(${PD_INSTANCE}) + target_compile_definitions(${PROJECT_NAME} PRIVATE PDINSTANCE) + target_compile_definitions(${PROJECT_NAME} PRIVATE PDTHREAD) +endif() + +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX "${PD_EXTERNAL_SUFFIX}") + +if(STRIP_TARGET) + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND strip --strip-unneeded ${CMAKE_BINARY_DIR}/${PROJECT_NAME}${PD_EXTERNAL_SUFFIX} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Stripping file..." + ) +endif() + +# install to Pd externals directory (run `cmake --install ` to install) +if(NOT ${PD_INSTANCE}) + install(FILES ${CMAKE_BINARY_DIR}/${PROJECT_NAME}${PD_EXTERNAL_SUFFIX} DESTINATION ${PD_EXTERNALS_PATH}/${PROJECT_NAME}) +else() + install(FILES ${CMAKE_BINARY_DIR}/${PROJECT_NAME}${PD_EXTERNAL_SUFFIX} DESTINATION ${PD_EXTERNALS_PATH}instance/${PROJECT_NAME}) +endif() diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0a5cea5 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +NAME=py +cflags=-DFLEXT_INLINE -DFLEXT_ATTRIBUTES=1 + +# source files +$(NAME).class.sources = $(wildcard source/*.cpp) + +# help files +datafiles = $(wildcard pd/*.pd scripts/*.py) + +# include Makefile.pdlibbuilder from submodule directory 'pd-lib-builder' +PDLIBBUILDER_DIR=./pd-lib-builder/ +include $(PDLIBBUILDER_DIR)/Makefile.pdlibbuilder diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ea5964 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# py/pyext for Python 3 + +## Credits + +### py/pyext - python script objects for PD and Max/MSP + +Copyright (c)2002-2020 Thomas Grill (gr@grrrr.org) + +For information on usage and redistribution, and for a DISCLAIMER OF ALL +WARRANTIES, see the file, "license.txt," in this distribution. + +Donations for further development of the package are highly appreciated. +Visit https://www.paypal.com/xclick/business=gr%40grrrr.org&item_name=pyext&no_note=1&tax=0¤cy_code=EUR + +More info in the previous `readme.txt`. +## Installation + +### Pre-requisite +* A python3 environment; +* An installation of [flext](https://github.com/grrrr/flext/); +* cmake ; +* ninja (optional). +### Build with cmake + +Thanks to the work of @fferri, the `CMakeLists.txt` has been largely purified. +The library can be build with wathever python3 environment ; to build with a conda environment for example, first activate that environment before running the following commands: +```bash +mkdir cmake-build +cd cmake-build +cmake .. \ +-GNinja \ # optional, builds faster +-DFLEXT_BUILD_TYPE=[SINGLE | MULTI | SHARED] # SINGLE is default +``` + +The variable `FLEXT_BUILD_TYPE` allows the user to choose which version of flext to use for the build. `MULTI` and `SHARED` enable thread usage. + +### Build with pd-lib-builder + +See `readme.txt`. +## Pd configuration & usage + +### Configuration + +You should create a folder to hold your python scripts - we'll call it the pd script folder; ideally, place it alongside the folder that natively contains the pd externals (for example at `~/Documents/Pd/externals`). + +* Open puredata and go to `File > Preferences > Edit Preferences` (*might be something along the lines of `Paths` for older pd versions*); +* Add the script folder to your search path. + +If you want to work with scripts that are located somewhere else on your machine, you can create a link to that file in the pd script folder. That way, you can modify your file in place and the changes will be taken into account in puredata without having to move your file over and over again. + +### Usage + +There are plenty examples of patches the `pd` folder of this repo. Try them out ! \ No newline at end of file diff --git a/build/config-lnx.def b/build/config-lnx.def index 86477fc..cfdbcd6 100644 --- a/build/config-lnx.def +++ b/build/config-lnx.def @@ -1,8 +1,13 @@ # what is the base prefix of the Python installation? +ifdef PY_CONDA_ROOT +PYTHONPREFIX=$(PY_CONDA_ROOT) +else PYTHONPREFIX=/usr +endif # which Python version do you want to compile against? -PYTHONVERSION=2.4 +PY_MAJOR_VERSION=3 +PY_MINOR_VERSION=7 # uncomment if numpy/numarray/numeric support should be compiled in # for info see http://numeric.scipy.org @@ -15,3 +20,6 @@ PY_USE_GIL=1 # use inofficial (pure data) functionality # PY_USE_INOFFICIAL=1 + +# use python with pymalloc (look for "pythonX.Ym" files) +PY_USE_PYMALLOC=1 diff --git a/build/config-mac.def b/build/config-mac.def index 0a59aa9..a86906f 100644 --- a/build/config-mac.def +++ b/build/config-mac.def @@ -1,6 +1,9 @@ -# set PY_DEFAULT=0 if you manually installed Python on your system -# (overriding the one that came with OS X by default) -PY_DEFAULT=1 +# which kind of Python installation to use +# system - macOS system default +# local - local installation +# conda - conda environment; specify the environment root dir using the +# PY_CONDA_ROOT environment variable +PY_KIND=conda ######################################################################### @@ -10,8 +13,8 @@ PY_DEFAULT=1 # Mac OSX 10.5 -> default Python version (major.minor) = 2.5 # Mac OSX 10.6 -> default Python version (major.minor) = 2.6 -PY_MAJOR_VERSION=2 -PY_MINOR_VERSION=6 +PY_MAJOR_VERSION=3 +PY_MINOR_VERSION=7 ######################################################################### @@ -19,7 +22,7 @@ PY_MINOR_VERSION=6 # for info see http://numeric.scipy.org # numarray and numeric are outdated -# PY_NUMPY=1 +PY_NUMPY=1 # PY_NUMARRAY=1 # PY_NUMERIC=1 @@ -30,3 +33,6 @@ PY_USE_GIL=1 # use inofficial (pure data) functionality # PY_USE_INOFFICIAL=1 + +# use python with pymalloc (look for "pythonX.Ym" files) +PY_USE_PYMALLOC=1 diff --git a/build/gnumake-lnx-gcc.inc b/build/gnumake-lnx-gcc.inc index 7bbde1f..e9177b4 100644 --- a/build/gnumake-lnx-gcc.inc +++ b/build/gnumake-lnx-gcc.inc @@ -1,5 +1,19 @@ +ifdef PY_USE_PYMALLOC +PYTHONVERSION=$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)m +else +PYTHONVERSION=$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION) +endif + DEFS += -DPY_EXPORTS + INCPATH += -I$(PYTHONPREFIX)/include/python$(PYTHONVERSION) + +ifdef PY_CONDA_ROOT +LIBPATH += -L$(PY_CONDA_ROOT)/lib +LDFLAGS += -Wl,-R$(PY_CONDA_ROOT)/lib +DEFS += -DPY_INTERPRETER=$(PY_CONDA_ROOT)/bin/python +endif + LIBS += -lpython$(PYTHONVERSION) ifdef PY_NUMARRAY @@ -7,6 +21,7 @@ DEFS += -DPY_NUMARRAY endif ifdef PY_NUMPY DEFS += -DPY_NUMPY +INCPATH += -I$(PYTHONPREFIX)/lib/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)/site-packages/numpy/core/include endif ifdef PY_NUMERIC DEFS += -DPY_NUMERIC diff --git a/build/gnumake-mac-gcc.inc b/build/gnumake-mac-gcc.inc index 7ae6eb2..3bd383e 100644 --- a/build/gnumake-mac-gcc.inc +++ b/build/gnumake-mac-gcc.inc @@ -1,3 +1,50 @@ +DEFS += -DPY_EXPORTS + +ifdef PY_NUMPY +DEFS += -DPY_NUMPY +endif + +ifdef PY_NUMARRAY +DEFS += -DPY_NUMARRAY +endif + +ifdef PY_NUMERIC +DEFS += -DPY_NUMERIC +endif + +ifdef PY_USE_GIL +DEFS += -DPY_USE_GIL +endif + +ifdef PY_USE_INOFFICIAL +DEFS += -DPY_USE_INOFFICIAL +endif + +ifeq ($(PY_KIND),conda) + +ifndef PY_CONDA_ROOT +$(error PY_CONDA_ROOT is undefined) +endif + +DEFS += -DPY_INTERPRETER=$(PY_CONDA_ROOT)/bin/python +INCPATH += -I$(PY_CONDA_ROOT)/include +ifdef PY_USE_PYMALLOC +INCPATH += -I$(PY_CONDA_ROOT)/include/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)m +LIBS += $(PY_CONDA_ROOT)/lib/libpython$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)m.dylib +else +INCPATH += -I$(PY_CONDA_ROOT)/include/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION) +LIBS += $(PY_CONDA_ROOT)/lib/libpython$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION).dylib +endif +LDFLAGS += -rpath $(PY_CONDA_ROOT)/lib + +ifdef PY_NUMPY +INCPATH += -I$(PY_CONDA_ROOT)/lib/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)/site-packages/numpy/core/include +endif + +else + +DEFS += -DPY_USE_FRAMEWORK + # don't use -framework Python, since this will stick to the default system version _LOCAL_FRAMEWORK := /Library/Frameworks/Python.framework/Versions/$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION) @@ -5,10 +52,9 @@ _SYSTEM_FRAMEWORK := /System/Library/Frameworks/Python.framework/Versions/$(PY_M _LOCAL_LIBRARY := /Library/Python/$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION) _SYSTEM_LIBRARY := /System/Library/Python/$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION) -DEFS += -DPY_EXPORTS -#INCPATH += -F/Library/Frameworks -framework Python +INCPATH += -F/Library/Frameworks -framework Python -ifeq ($(PY_DEFAULT),1) +ifeq ($(PY_KIND),system) LIBS += $(_SYSTEM_FRAMEWORK)/Python INCPATH += -I$(_SYSTEM_FRAMEWORK)/Headers else @@ -16,27 +62,18 @@ LIBS += $(_LOCAL_FRAMEWORK)/Python INCPATH += -I$(_LOCAL_FRAMEWORK)/Headers endif -ifdef PY_NUMARRAY -DEFS += -DPY_NUMARRAY -endif ifdef PY_NUMPY -DEFS += -DPY_NUMPY INCPATH += -I$(_LOCAL_LIBRARY)/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)/site-packages/numpy/core/include -ifeq ($(PY_DEFAULT),1) + +ifeq ($(PY_KIND),system) INCPATH += -I$(_SYSTEM_FRAMEWORK)/lib/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)/site-packages/numpy/core/include INCPATH += -I$(_SYSTEM_FRAMEWORK)/Extras/lib/python/numpy/core/include else INCPATH += -I$(_LOCAL_FRAMEWORK)/lib/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)/site-packages/numpy/core/include endif endif -ifdef PY_NUMERIC -DEFS += -DPY_NUMERIC -endif -ifdef PY_USE_GIL -DEFS += -DPY_USE_GIL endif -ifdef PY_USE_INOFFICIAL -DEFS += -DPY_USE_INOFFICIAL -endif +#DEBUG = 1 +#DFLAGS += -DFLEXT_DEBUG diff --git a/maxmsp/py.maxhelp b/maxmsp/py.maxhelp index 05a60d2..e6b5d93 100644 --- a/maxmsp/py.maxhelp +++ b/maxmsp/py.maxhelp @@ -133,7 +133,7 @@ , { "box" : { "maxclass" : "message", - "text" : "gain 0.5", + "text" : "set gain 0.5", "fontsize" : 12.0, "numinlets" : 2, "patching_rect" : [ 100.77256, 1262.583496, 53.0, 18.0 ], diff --git a/package.txt b/package.txt index 9002aa8..2a19933 100644 --- a/package.txt +++ b/package.txt @@ -11,6 +11,6 @@ SRCS= \ py.cpp pyext.cpp modmeth.cpp clmeth.cpp \ register.cpp bound.cpp pyargs.cpp \ pysymbol.cpp pybuffer.cpp pybundle.cpp pydsp.cpp \ - pyatom.cpp pybase.cpp pymeth.cpp + pyatom.cpp pybase.cpp pymeth.cpp pycompat.cpp -HDRS= pyprefix.h main.h pyext.h pysymbol.h pybuffer.h pybundle.h pyatom.h pybase.h +HDRS= pyprefix.h main.h pyext.h pysymbol.h pybuffer.h pybundle.h pyatom.h pybase.h pycompat.h diff --git a/pd-lib-builder/CHANGELOG.txt b/pd-lib-builder/CHANGELOG.txt new file mode 100644 index 0000000..5bdfce6 --- /dev/null +++ b/pd-lib-builder/CHANGELOG.txt @@ -0,0 +1,104 @@ +Changelog for Makefile.pdlibbuilder. + +v0.6.0, dated 2019-12-21 +- detect target platform (OS and architecture) rather than build platform (#55) +- introduce optional user variable 'PLATFORM' for cross compilation +- no longer build OSX/MacOS fat binaries by default (#21, #50) +- do build fat binaries when 'extension=d_fat' is specified for OSX/MacOS +- fix bug where minimum OSX/MacOS version wasn't defined, and set it to 10.6 + +v0.5.1, dated 2018-03-15 +Fixes and improvements for Windows builds: +- properly evaluate variables 'PDDIR' and 'PDBINDIR' to find pd.dll +- define default path of 32 bit Pd on 64 bit Windows +- link C++ externals with standard C libs on Windows, they don't load otherwise +- strip installed Windows binaries by default +(issues #34, #39, #41, #42 respectively) +Warning for all platforms: variable 'PD_PATH' is no longer supported, use the +equivalent 'PDDIR'. + +v0.5.0, dated 2018-01-23 +Implement target architecture detection for Windows builds, +and set appropriate options for 32 and 64 bit (used to be for 32 bit only). +(feature, issue #37 #38, merge commit 215bf3e) + +v0.4.4, dated 2016-11-22 +Use variable 'system' when evaluating 'for{Linux,Darwin,Windows}' +(bugfix, issue #31, commit 2c14110) + +v0.4.3, dated 2016-11-02 +Replace flags '-fpic' by 'fPIC'. +(bugfix, issue #29, commit 426b38b) + +v0.4.2, dated 2016-10-30 +Fix issue where incorrect message about m_pd.h is given. +(bugfix, commit 2e13d8f) + +v0.4.1, dated 2016-10-27 +Respect cflag for minimum OSX version when defined by lib makefile. +(bugfix, pull request #22, commit 48c4127) + +v0.4.0, dated 2016-10-14 +Introduced path variables PDDIR, PDINCLUDEDIR, PDBINDIR, PDLIBDIR which can +also be defined in environment. +(feature, issue #27, commit b0dab72) + +v0.3.1, dated 2016-10-13 +Fix bug where pd.dll wouldn't be found. +(bugfix, commit a0c87be) + +v0.3.0, dated 2016-10-09 +Variable 'PD_PATH' introduced for pd-extended / pd-l2ork compatibility. +(feature, issue #26, commit 41e9743) + +v0.2.8, dated 2016-10-09 +Allow installed files to contain weird characters (notably '$'). +(bugfix, pull request #20, commit 5b920b1) + +v0.2.7, dated 2016-10-04 +Remove all default pd search paths except vanilla's. +(discussion, issue #25, commit a6a89dc) + +v0.2.6, dated 2016-09-20 +Redefined dependency checking so it won't stall rebuilds on OSX. +(bugfix, issue #16, commit 9fd1795) + +v0.2.5, dated 2016-06-26 +Fixed dependency checking for object files in other directories. +(bugfix, commit f06e550) + +v0.2.4, dated 2016-06-25 +Fixed regression bug that disabled all dependency checking. +(bugfix, commit 1d7bb5e) + +v0.2.3, dated 2016-03-29 +Disabled dependency checking for OSX <= 10.5 because it stalled rebuilds. +(bugfix, issue #16, commit eb614fd) + +v0.2.2, dated 2016-03-28 +Removed target 'pre' because it forced rebuild of everything in 'all'. +(bugfix, issue #17, commit c989c8e) + +v0.2.1, dated 2015-12-27 +Implement / respect 'CPPFLAGS','CFLAGS'and 'LDFLAGS'. +(bugfix, issue #5, commit 98f3582) + +v0.2.0, dated 2015-12-19 +Added per-platform multiline defines 'forLinux', 'forDarwin', 'forWindows'. +(feature, pull request #9, commit 3946ea5) + +v0.1.0, dated 2015-12-08 +Added targets 'pre' and 'post' to automatically run before and after 'all'. +(feature, pull request #4, commit a5678ac) + +v0.0.2, dated 2015-12-06 +Improved methods for searching pd paths. +(bugfix, commit ed37e6b) + +v0.0.1, dated 2015-10-31 +Fixed expansion of variable 'lib.version'. +(bugfix, issue #1, commit 974b617) + +v0.0.0, dated 2015-06-24 +Initial version. +(commit 16517a2) diff --git a/pd-lib-builder/Makefile.pdlibbuilder b/pd-lib-builder/Makefile.pdlibbuilder new file mode 100644 index 0000000..7d6c11c --- /dev/null +++ b/pd-lib-builder/Makefile.pdlibbuilder @@ -0,0 +1,1336 @@ +# Makefile.pdlibbuilder dated 2019-12-21 +version = 0.6.0 + +# Helper makefile for Pure Data external libraries. +# Written by Katja Vetter March-June 2015 for the public domain. No warranties. +# Inspired by Hans Christoph Steiner's Makefile Template and Stephan Beal's +# ShakeNMake. +# +# Grab the newest version of Makefile.pdlibbuilder from +# https://github.com/pure-data/pd-lib-builder/ +# +# GNU make version >= 3.81 required. +# +# +#=== characteristics =========================================================== +# +# +# - defines build settings based on autodetected OS and architecture +# - defines rules to build Pd class- or lib executables from C or C++ sources +# - defines rules for libdir installation +# - defines convenience targets for developer and user +# - evaluates implicit dependencies for non-clean builds +# +# +#=== basic usage =============================================================== +# +# +# In your Makefile, define your Pd lib name and class files, and include +# Makefile.pdlibbuilder at the end of the Makefile. Like so: +# +# ________________________________________________________________________ +# +# # Makefile for mylib +# +# lib.name = mylib +# +# class.sources = myclass1.c myclass2.c +# +# datafiles = myclass1-help.pd myclass2-help.pd README.txt LICENSE.txt +# +# include Makefile.pdlibbuilder +# ________________________________________________________________________ +# +# +# For files in class.sources it is assumed that class basename == source file +# basename. The default target builds all classes as individual executables +# with Pd's default extension for the platform. For anything more than the +# most basic usage, continue reading. +# +# +#=== list of Makefile.pdlibbuilder API variables =============================== +# +# +# Variables available for definition in your library Makefile: +# +# - lib.name +# - lib.setup.sources +# - class.sources +# - common.sources +# - shared.sources +# - .class.sources +# - .class.ldflags +# - .class.ldlibs +# - cflags +# - ldflags +# - ldlibs +# - datafiles +# - datadirs +# - makefiles +# - makefiledirs +# - externalsdir +# +# Optional multiline defines evaluated per operating system: +# +# - forLinux +# - forDarwin +# - forWindows +# +# Variables available for your makefile or make command line: +# +# - make-lib-executable +# - suppress-wunused +# +# Path variables for make command line or environment: +# +# - PDDIR +# - PDINCLUDEDIR +# - PDBINDIR +# - PDLIBDIR +# +# Standard make variables for make command line or environment: +# +# - CPPFLAGS +# - CFLAGS +# - LDFLAGS +# - CC +# - CXX +# - INSTALL +# - STRIP +# - DESTDIR +# +# Optional user variables for make command line or environment: +# +# - PLATFORM +# +# Deprecated path variables: +# +# - pdincludepath +# - pdbinpath +# - objectsdir +# +# +#=== descriptions of Makefile.pdlibbuilder API variables ======================= +# +# +# lib.name: +# Name of the library directory as it will be installed / distributed. Also the +# name of the lib executable in the case where all classes are linked into +# a single binary. +# +# lib.setup.sources: +# Source file(s) (C or C++) which must be compiled only when linking all classes +# into a single lib binary. +# +# class.sources: +# All sources files (C or C++) for which the condition holds that +# class name == source file basename. +# +# .class.sources: +# Source file(s) (C or C++) specific to class . Use this for +# multiple-source classes or when class name != source file basename. +# +# common.sources: +# Source file(s) which must be statically linked to each class in the library. +# +# shared.sources: +# Source file(s) (C or C++) to build a shared dynamic link lib, to be linked +# with all class executables. +# +# cflags, ldflags, ldlibs: +# Define cflags (preprocessor&compiler), ldflags (linker) and ldlibs (dynamic +# link libs) for the whole library. These flags are added to platform-specific +# flags defined by Makefile.pdlibbuilder. +# +# .class.ldflags and .class.ldlibs: +# Define ldflags resp. ldlibs specific to class . These flags are +# added to platform-specific flags defined by Makefile.pdlibbuilder, and flags +# defined in your Makefile for the whole library. Note: cflags can not be +# defined per class in the current implementation. +# +# datafiles and datadirs: +# All extra files you want to include in binary distributions of the +# library: abstractions and help patches, example patches, meta patch, readme +# and license texts, manuals, sound files, etcetera. Use 'datafiles' for all +# files that should go into your lib rootdir and 'datadirs' for complete +# directories you want to copy from source to distribution. +# +# forLinux, forDarwin, forWindows: +# Shorthand for 'variable definitions for Linux only' etc. Use like: +# define forLinux +# cflags += -DLINUX +# class.sources += linuxthing.c +# endef +# +# makefiles and makefiledirs: +# Extra makefiles or directories with makefiles that should be made in sub-make +# processes. +# +# make-lib-executable: +# When this variable is defined 'yes' in your makefile or as command argument, +# Makefile.pdlibbuilder will try to build all classes into a single library +# executable (but it will force exit if lib.setup.sources is undefined). +# If your makefile defines 'make-lib-executable=yes' as the library default, +# this can still be overridden with 'make-lib-executable=no' as command argument +# to build individual class executables (the Makefile.pdlibbuilder default.) +# +# suppress-wunused: +# When this variable is defined ('yes' or any other value), -Wunused-variable, +# -Wunused-parameter, -Wunused-value and -Wunused-function are suppressed, +# but the other warnings from -Wall are retained. +# +# PDDIR: +# Root directory of 'portable' pd package. When defined, PDINCLUDEDIR and +# PDBINDIR will be evaluated as $(PDDIR)/src and $(PDDIR)/bin. +# +# PDINCLUDEDIR: +# Directory where Pd API m_pd.h should be found, and other Pd header files. +# Overrides the default search path. +# +# PDBINDIR: +# Directory where pd.dll should be found for linking (Windows only). Overrides +# the default search path. +# +# PDLIBDIR: +# Root directory for installation of Pd library directories. Overrides the +# default install location. +# +# DESTDIR: +# Prepended path component for staged install. +# +# PLATFORM: +# Target platform for cross compilation in the form of GNU triplet: +# cpu-vendor-os. Example: x86_64-w64-mingw32. This specifies the tool chain that +# pdlibbuilder will use, if installed and locatable. System and architecture +# will then be autodefined accordingly. In most cases no other variables need to +# be overridden. +# +# CPPFLAGS: +# Preprocessor flags which are not strictly required for building. +# +# CFLAGS: +# Compiler flags which are not strictly required for building. Compiler flags +# defined by Makefile.pdlibbuilder for warning, optimization and architecture +# specification are overriden by CFLAGS. +# +# LDFLAGS: +# Linker flags which are not strictly required for building. Linker flags +# defined by Makefile.pdlibbuilder for architecture specification are overriden +# by LDFLAGS. +# +# CC and CXX: +# C and C++ compiler programs as defined in your build environment. +# +# INSTALL +# Definition of install program. +# +# STRIP +# Name of strip program. Default 'strip' can be overridden in cross compilation +# environments. +# +# objectsdir: +# Root directory for installation of Pd library directories, like PDLIBDIR but +# not overridable by environment. Supported for compatibility with pd-extended +# central makefile, but deprecated otherwise. +# +# pdincludepath, pdbinpath: +# As PDINCLUDEDIR and PDBINDIR but not overridable by environment. Deprecated +# as user variables. +# +# +#=== paths ===================================================================== +# +# +# Source files in directories other than current working directory must be +# prefixed with their relative path. Do not rely on VPATH or vpath. +# Object (.o) files are built in the directory of their source files. +# Executables are built in current working directory. +# +# Default search path for m_pd.h and other API header files is platform +# dependent, and overridable by PDINCLUDEDIR: +# +# Linux: /usr/include/pd +# +# OSX: /Applications/Pd*.app/Contents/Resources/src +# +# Windows: %PROGRAMFILES%/Pd/src +# %PROGRAMFILES(X86)%/Pd/src (32 bit builds on 64 bit Windows) +# +# Default search path for binary pd.dll (Windows), overridable by PDBINDIR +# +# %PROGRAMFILES%/Pd/bin +# %PROGRAMFILES(X86)%/Pd/bin (32 bit builds on 64 bit Windows) +# +# Default location to install pd libraries is platform dependent, and +# overridable by PDLIBDIR: +# +# Linux: /usr/local/lib/pd-externals +# OSX: ~/Library/Pd +# Windows: %APPDATA%/Pd +# +# https://puredata.info/docs/faq/how-do-i-install-externals-and-help-files +# The rationale for not installing to ~/pd-externals by default on Linux +# is that some people share the home dir between 32 and 64 bit installations. +# +# +#=== targets =================================================================== +# +# +# all: build $(executables) plus optional post target +# post: target to build after $(executables) +# alldebug: build all with -g option turned on for debug symbols +# : force clean build of an individual class +# .pre: make preprocessor output file in current working directory +# .lst: make asm/source output file in current working directory +# +# install: install executables and data files +# clean: remove build products from source tree +# +# help: print help text +# vars: print makefile variables +# allvars: print all variables +# depend: print generated prerequisites +# dumpmachine: print compiler output of option '-dumpmachine' +# coffee: dummy target +# +# Variable $(executables) expands to class executables plus optional shared lib, +# or alternatively to single lib executable when make-lib-executable=true. +# Targets pre and post can be defined by library makefile. Make sure to include +# Makefile.pdlibbuilder first so default target all will not be redefined. +# +# +#=== Pd-extended libdir concept ================================================ +# +# +# For libdir layout as conceived by Hans-Christoph Steiner, see: +# +# https://puredata.info/docs/developer/Libdir +# +# Files README.txt, LICENSE.txt and -meta.pd are part of the libdir +# convention. Help patches for each class and abstraction are supposed to be +# available. Makefile.pdlibbuilder does not force the presence of these files +# however. It does not automatically include such files in libdir installations. +# Data files you want to include in distributions must be defined explicitly in +# your Makefile. +# +# +#=== Makefile.pdlibbuilder syntax conventions ================================== +# +# +# Makefile.pdlibbuilder variable names are lower case. Default make variables, +# environment variables, and standard user variables (CC, CXX, CFLAGS, DESTDIR) +# are upper case. Use target 'allvars' to print all variables and their values. +# +# 'Fields' in data variables are separated by dots, like in 'foo.class.sources'. +# Words in variables expressing a function or command are separated by dashes, +# like in 'make-lib-executable'. +# +# +#=== useful make options ======================================================= +# +# +# Use 'make -d ' to print debug details of the make process. +# Use 'make -p ' to print make's database. +# +# +#=== TODO ====================================================================== +# +# +# - decide whether to use -static-libgcc or shared dll in MinGW +# - cygwin support +# - android support +# - figure out how to handle '$' in filenames +# - add makefile template targets dpkg-source dist libdir distclean tags? +# +# +#=== end of documentation sections ============================================= +# +# +################################################################################ +################################################################################ +################################################################################ + + +# GNU make version 3.81 (2006) or higher is required because of the following: +# - function 'info' +# - variable '.DEFAULT_GOAL' + +# force exit when make version is < 3.81 +ifneq ($(firstword $(sort 3.81 $(MAKE_VERSION))), 3.81) + $(error GNU make version 3.81 or higher is required) +endif + +# Relative path to externals root dir in multi-lib source tree like +# pd-extended SVN. Default is parent of current working directory. May be +# defined differently in including makefile. +externalsdir ?= .. + +# variable you can use to check if Makefile.pdlibbuilder is already included +Makefile.pdlibbuilder = true + + +################################################################################ +### variables: library name and version ######################################## +################################################################################ + + +# strip possibles spaces from lib.name, they mess up calculated file names +lib.name := $(strip $(lib.name)) + +# if meta file exists, check library version +metafile := $(wildcard $(lib.name)-meta.pd) + +ifdef metafile + lib.version := $(shell sed -n \ + 's|^\#X text [0-9][0-9]* [0-9][0-9]* VERSION \(.*\);|\1|p' \ + $(metafile)) +endif + + +################################################################################ +### variables: files ########################################################### +################################################################################ + + +#=== sources =================================================================== + + +# (re)define .class.sources using file names in class.sources + +define add-class-source +$(notdir $(basename $v)).class.sources += $v +endef + +$(foreach v, $(class.sources), $(eval $(add-class-source))) + +# derive class names from .class.sources variables +sourcevariables := $(filter %.class.sources, $(.VARIABLES)) +classes := $(basename $(basename $(sourcevariables))) + +# accumulate all source files specified in makefile +classes.sources := $(sort $(foreach v, $(sourcevariables), $($v))) +all.sources := $(classes.sources) $(lib.setup.sources) \ + $(shared.sources) $(common.sources) + + +#=== object files ============================================================== + + +# construct object filenames from all C and C++ source file names +classes.objects := $(addsuffix .o, $(basename $(classes.sources))) +common.objects := $(addsuffix .o, $(basename $(common.sources))) +shared.objects := $(addsuffix .o, $(basename $(shared.sources))) +lib.setup.objects := $(addsuffix .o, $(basename $(lib.setup.sources))) +all.objects = $(classes.objects) $(common.objects) $(shared.objects) \ + $(lib.setup.objects) + + +#=== executables =============================================================== + + +# use recursive variables here because executable extension is not yet known + +# construct class executable names from class names +classes.executables = $(addsuffix .$(extension), $(classes)) + +# construct shared lib executable name if shared sources are defined +ifdef shared.sources + shared.lib = lib$(lib.name).$(shared.extension) +else + shared.lib = +endif + + +################################################################################ +### target platform detection ################################################## +################################################################################ + + +#=== target platform =========================================================== + + +# PLATFORM: optional user variable to define target platform for cross +# compilation. Redefine build tools accordingly. PLATFORM should match +# the exact target prefix of tools present in $PATH, like x86_64-w64-mingw32, +# x86_64-apple-darwin12 etc. Tool definitions are exported to ensure submakes +# will get the same. + +ifneq ($(PLATFORM),) + ifneq ($(findstring darwin, $(PLATFORM)),) + export CC = $(PLATFORM)-cc + export CXX = $(PLATFORM)-c++ + export CPP = $(PLATFORM)-cc + else + export CC = $(PLATFORM)-gcc + export CXX = $(PLATFORM)-g++ + export CPP = $(PLATFORM)-cpp + endif + STRIP = $(PLATFORM)-strip +endif + +# Let (native or cross-) compiler report target triplet and isolate individual +# words therein to facilitate later processing. +target.triplet := $(subst -, ,$(shell $(CC) -dumpmachine)) + + +#=== operating system ========================================================== + + +# The following systems are defined: Linux, Darwin, Windows. GNU and +# GNU/kFreeBSD are treated as Linux to get the same options. + +ifneq ($(filter linux gnu% kfreebsd, $(target.triplet)),) + system = Linux +endif + +ifneq ($(filter darwin%, $(target.triplet)),) + system = Darwin +endif + +ifneq ($(filter mingw% cygwin%, $(target.triplet)),) + system = Windows +endif + +# evaluate possible system-specific multiline defines from library makefile +$(eval $(for$(system))) + + +# TODO: Cygwin, Android + + +#=== architecture ============================================================== + + +# The following CPU names can be processed by pdlibbuilder: +# i*86 Intel 32 bit +# x86_64 Intel 64 bit +# arm ARM 32 bit +# aarch64 ARM 64 bit + +target.arch := $(firstword $(target.triplet)) + + +################################################################################ +### variables per platform ##################################################### +################################################################################ + + +#=== flags per architecture ==================================================== + + +# Set architecture-dependent cflags, mainly for Linux. For Mac and Windows, +# arch.c.flags are overriden below. To see gcc's default architecture flags: +# $ gcc -Q --help=target + +# ARMv6: Raspberry Pi 1st gen, not detectable from target.arch +ifeq ($(shell uname), armv6l) + arch.c.flags = -march=armv6 -mfpu=vfp -mfloat-abi=hard + +# ARMv7: Beagle, Udoo, RPi2 etc. +else ifeq ($(target.arch), arm) + arch.c.flags = -march=armv7-a -mfpu=vfpv3 -mfloat-abi=hard + +# ARMv8 64 bit, not tested yet +else ifeq ($(target.arch), aarch64) + arch.c.flags = -mcpu=cortex-a53 + +# Intel 32 bit, build with SSE and SSE2 instructions +else ifneq ($(filter i%86, $(target.arch)),) + arch.c.flags = -march=pentium4 -mfpmath=sse -msse -msse2 + +# Intel/AMD 64 bit, build with SSE, SSE2 and SSE3 instructions +else ifeq ($(target.arch), x86_64) + arch.c.flags = -march=core2 -mfpmath=sse -msse -msse2 -msse3 + +# if none of the above architectures detected +else + arch.c.flags = +endif + + +#=== flags and paths for Linux ================================================= + + +ifeq ($(system), Linux) + prefix = /usr/local + libdir := $(prefix)/lib + pkglibdir = $(libdir)/pd-externals + pdincludepath := $(wildcard /usr/include/pd) + extension = pd_linux + cpp.flags := -DUNIX + c.flags := -fPIC + c.ldflags := -rdynamic -shared -fPIC -Wl,-rpath,"\$$ORIGIN",--enable-new-dtags + c.ldlibs := -lc -lm + cxx.flags := -fPIC -fcheck-new + cxx.ldflags := -rdynamic -shared -fPIC -Wl,-rpath,"\$$ORIGIN",--enable-new-dtags + cxx.ldlibs := -lc -lm -lstdc++ + shared.extension = so + shared.ldflags := -rdynamic -fPIC -shared -Wl,-soname,$(shared.lib) +endif + + +#=== flags and paths for Darwin ================================================ + + +# LLVM-clang doesn't support -fcheck-new, therefore this flag is only used when +# compiling with g++. + +ifeq ($(system), Darwin) + pkglibdir = $(HOME)/Library/Pd + pdincludepath := $(firstword $(wildcard \ + /Applications/Pd*.app/Contents/Resources/src)) + extension = pd_darwin + cpp.flags := -DUNIX -DMACOSX -I /sw/include + c.flags := + c.ldflags := -undefined suppress -flat_namespace -bundle + c.ldlibs := -lc + cxx.ldflags := -undefined suppress -flat_namespace -bundle + cxx.ldlibs := -lc + shared.extension = dylib + shared.ldflags = -dynamiclib -undefined dynamic_lookup \ + -install_name @loader_path/$(shared.lib) \ + -compatibility_version 1 -current_version 1.0 + ifneq ($(filter %g++, $(CXX)),) + cxx.flags := -fcheck-new + endif + ifeq ($(extension), d_fat) + arch := i386 x86_64 + else + arch := $(target.arch) + endif + ifneq ($(filter -mmacosx-version-min=%, $(cflags)),) + version.flag := $(filter -mmacosx-version-min=%, $(cflags)) + else + version.flag = -mmacosx-version-min=10.6 + endif + arch.c.flags := $(addprefix -arch , $(arch)) $(version.flag) + arch.ld.flags := $(arch.c.flags) +endif + + +#=== flags and paths for Windows =============================================== + + +# Standard paths on Windows contain spaces, and GNU make functions treat such +# paths as lists, with unintended effects. Therefore we must use shell function +# ls instead of make's wildcard when probing for a path, and use double quotes +# when specifying a path in a command argument. + +# Default paths in Mingw / Mingw-w64 environments. 'PROGRAMFILES' is standard +# location for builds with native architecture, 'ProgramFiles(x86)' for i686 +# builds on x86_64 Windows (detection method by Lucas Cordiviola). Curly braces +# required because of parentheses in variable name. +ifeq ($(system), Windows) + pkglibdir := $(APPDATA)/Pd + ifeq ($(target.arch), i686) + programfiles := ${ProgramFiles(x86)} + else + programfiles := $(PROGRAMFILES) + endif + pdbinpath := $(programfiles)/Pd/bin + pdincludepath := $(programfiles)/Pd/src +endif + +# Store default path to pd.dll in PDBINDIR if the latter is not user-defined. +# For include path this is done in the platform-independent paths section below, +# but for PDBINDIR it is done here so ld flags can be evaluated as immediate +# variables. +ifeq ($(system), Windows) + ifdef PDDIR + PDBINDIR := $(PDDIR)/bin + endif + PDBINDIR ?= $(pdbinpath) +endif + +# TODO: decide whether -mms-bitfields should be specified. +ifeq ($(system), Windows) + cpp.flags := -DMSW -DNT + ifeq ($(target.arch), i686) + arch.c.flags := -march=pentium4 -msse -msse2 -mfpmath=sse + else ifeq ($(target.arch), x86_64) + cpp.flags := -DMSW -DNT -DPD_LONGINTTYPE=__int64 + arch.c.flags := -march=core2 -msse -msse2 -msse3 -mfpmath=sse + else + arch.c.flags = + endif + extension = dll + c.flags := + c.ldflags := -static-libgcc -shared \ + -Wl,--enable-auto-import "$(PDBINDIR)/pd.dll" + c.ldlibs := + cxx.flags := -fcheck-new + cxx.ldflags := -static-libgcc -static-libstdc++ -shared \ + -Wl,--enable-auto-import "$(PDBINDIR)/pd.dll" + cxx.ldlibs := + shared.extension = dll + shared.ldflags := -static-libgcc -shared "$(PDBINDIR)/pd.dll" + stripflags = --strip-all +endif + + +#=== paths ===================================================================== + + +# Platform-dependent default paths are specified above, but overridable. +# Path variables in upper case can be defined as make command argument or in the +# environment. Variable 'objectsdir' is supported for compatibility with +# the build system that pd-l2ork has inherited from pd-extended. + +PDINCLUDEDIR ?= $(pdincludepath) +PDLIBDIR ?= $(firstword $(objectsdir) $(pkglibdir)) + +ifdef PDDIR + PDINCLUDEDIR := $(wildcard $(PDDIR)/src) +endif + +# base path where all components of the lib will be installed by default +installpath := $(DESTDIR)$(PDLIBDIR)/$(lib.name) + +# check if include path contains spaces (as is often the case on Windows) +# if so, store the path so we can later do checks with it +pdincludepathwithspaces := $(if $(word 2, $(PDINCLUDEDIR)), $(PDINCLUDEDIR)) + + +#=== accumulated build flags =================================================== + + +# From GNU make docs: 'Users expect to be able to specify CFLAGS freely +# themselves.' So we use CFLAGS to define options which are not strictly +# required for compilation: optimizations, architecture specifications, and +# warnings. CFLAGS can be safely overriden using a make command argument. +# Variables cflags, ldflags and ldlibs may be defined in including makefile. + +optimization.flags = -O3 -ffast-math -funroll-loops -fomit-frame-pointer +warn.flags = -Wall -Wextra -Wshadow -Winline -Wstrict-aliasing + +# suppress -Wunused-variable & Co if you don't want to clutter a build log +ifdef suppress-wunused + warn.flags += $(addprefix -Wno-unused-, function parameter value variable) +endif + +CFLAGS = $(warn.flags) $(optimization.flags) $(arch.c.flags) + +# preprocessor flags +cpp.flags := -DPD -I "$(PDINCLUDEDIR)" $(cpp.flags) $(CPPFLAGS) + +# flags for dependency checking (cflags from makefile may define -I options) +depcheck.flags := $(cpp.flags) $(cflags) + +# architecture specifications for linker are overridable by LDFLAGS +LDFLAGS := $(arch.ld.flags) + +# now add the same ld flags to shared dynamic lib +shared.ldflags := $(shared.ldflags) $(LDFLAGS) + +# accumulated flags for C compiler / linker +c.flags := $(cpp.flags) $(c.flags) $(cflags) $(CFLAGS) +c.ldflags := $(c.ldflags) $(ldflags) $(LDFLAGS) +c.ldlibs := $(c.ldlibs) $(ldlibs) + +# accumulated flags for C++ compiler / linker +cxx.flags := $(cpp.flags) $(cxx.flags) $(cflags) $(CFLAGS) +cxx.ldflags := $(cxx.ldflags) $(ldflags) $(LDFLAGS) +cxx.ldlibs := $(cxx.ldlibs) $(ldlibs) + + +################################################################################ +### variables: tools ########################################################### +################################################################################ + + +# aliases so we can later define 'compile-$1' and set 'c' or 'cxx' as argument +compile-c := $(CC) +compile-cxx := $(CXX) + + +################################################################################ +### checks ##################################################################### +################################################################################ + + +# At this point most variables are defined. Now do some checks and info's +# before rules begin. + +# print Makefile.pdlibbuilder version before possible termination +$(info ++++ info: using Makefile.pdlibbuilder version $(version)) + +# Terminate if target triplet remained empty, to avoid all sorts of confusing +# scenarios and spurious bugs. +ifeq ($(target.triplet),) + $(error Command "$(CC) -dumpmachine" did not return a target triplet, \ + needed for a build. \ + Is compiler "$(CC)" installed in your PATH? ($(PATH)). \ + Does compiler "$(CC)" support option "-dumpmachine"?) +endif + +# 'forward declaration' of default target, needed to do checks +all: + +# To avoid unpredictable results, make sure the default target is not redefined +# by including makefile. +ifneq ($(.DEFAULT_GOAL), all) + $(error Default target must be 'all'.) +endif + +# find out which target(s) will be made +ifdef MAKECMDGOALS + goals := $(MAKECMDGOALS) +else + goals := all +endif + +# store path to Pd API m_pd.h if it is found +ifdef PDINCLUDEDIR + mpdh := $(shell ls "$(PDINCLUDEDIR)/m_pd.h") +endif + +# store path to pd.dll; if not found, ls will give a useful error +ifeq ($(system), Windows) + pddll := $(shell ls "$(PDBINDIR)/pd.dll") +endif + +# when making target all, check if m_pd.h is found and print info about it +ifeq ($(goals), all) + $(if $(mpdh), \ + $(info ++++ info: using Pd API $(mpdh)), \ + $(warning Where is Pd API m_pd.h? Do 'make help' for info.)) +endif + +# print target info +$(info ++++ info: making target $(goals) $(if $(lib.name),in lib $(lib.name))) + +# when installing, print installpath info +$(if $(filter install install-lib, $(goals)), $(info ++++ info: \ + installpath is '$(installpath)')) + + +#=== define executables ======================================================== + + +# By default we build class executables, and optionally a shared dynamic link +# lib. When make-lib-executable=yes we build all classes into a single lib +# executable, on the condition that variable lib.setup.sources is defined. + +ifeq ($(make-lib-executable),yes) + $(if $(lib.setup.sources), ,\ + $(error Can not build library blob because lib.setup.sources is undefined)) + executables := $(lib.name).$(extension) +else + executables := $(classes.executables) $(shared.lib) +endif + + +################################################################################ +### rules: special targets ##################################################### +################################################################################ + + +# Disable built-in rules. If some target can't be built with the specified +# rules, it should not be built at all. +MAKEFLAGS += --no-builtin-rules + +.PRECIOUS: +.SUFFIXES: +.PHONY: all post build-lib \ + $(classes) $(makefiledirs) $(makefiles) \ + install install-executables install-datafiles install-datadirs \ + force clean vars allvars depend help + + +################################################################################ +### rules: build targets ####################################################### +################################################################################ + + +# Target all forces the build of targets [$(executables) post] in +# deterministic order. Target $(executables) builds class executables plus +# optional shared lib or alternatively a single lib executable when +# make-lib-executable=true. Target post is optionally defined by +# library makefile. + +all: post +post: $(executables) + +all: + $(info ++++info: target all in lib $(lib.name) completed) + +# build all with -g option turned on for debug symbols +alldebug: c.flags += -g +alldebug: cxx.flags += -g +alldebug: all + + +#=== class executable ========================================================== + + +# recipe for linking objects in class executable +# argument $1 = compiler type (c or cxx) +# argument $2 = class basename +define link-class + $(compile-$1) \ + $($1.ldflags) $($2.class.ldflags) \ + -o $2.$(extension) \ + $(addsuffix .o, $(basename $($2.class.sources))) \ + $(addsuffix .o, $(basename $(common.sources))) \ + $($1.ldlibs) $($2.class.ldlibs) $(shared.lib) +endef + +# general rule for linking object files in class executable +%.$(extension): $(shared.lib) + $(info ++++ info: linking objects in $@ for lib $(lib.name)) + $(if $(filter %.cc %.cpp, $($*.class.sources)), \ + $(call link-class,cxx,$*), \ + $(call link-class,c,$*)) + + +#=== library blob ============================================================== + + +# build all classes into single executable +build-lib: $(lib.name).$(extension) + $(info ++++ info: library blob $(lib.name).$(extension) completed) + +# recipe for linking objects in lib executable +# argument $1 = compiler type (c or cxx) +define link-lib + $(compile-$1) \ + $($1.ldflags) $(lib.ldflags) \ + -o $(lib.name).$(extension) $(all.objects) \ + $($1.ldlibs) $(lib.ldlibs) +endef + +# rule for linking objects in lib executable +# declared conditionally to avoid name clashes +ifeq ($(make-lib-executable),yes) +$(lib.name).$(extension): $(all.objects) + $(if $(filter %.cc %.cpp, $(all.sources)), \ + $(call link-lib,cxx), \ + $(call link-lib,c)) +endif + + +#=== shared dynamic lib ======================================================== + + +# recipe for linking objects in shared executable +# argument $1 = compiler type (c or cxx) +define link-shared + $(compile-$1) \ + $(shared.ldflags) \ + -o lib$(lib.name).$(shared.extension) $(shared.objects) \ + $($1.ldlibs) $(shared.ldlibs) +endef + +# rule for linking objects in shared executable +# build recipe is in macro 'link-shared' +lib$(lib.name).$(shared.extension): $(shared.objects) + $(info ++++ info: linking objects in shared lib $@) + $(if $(filter %.cc %.cpp, $(shared.sources)), \ + $(call link-shared,cxx), \ + $(call link-shared,c)) + + +#=== object files ============================================================== + + +# recipe to make .o file from source +# argument $1 is compiler type (c or cxx) +define make-object-file + $(info ++++ info: making $@ in lib $(lib.name)) + $(compile-$1) \ + $($1.flags) \ + -o $@ -c $< +endef + +# Three rules to create .o files. These are double colon 'terminal' rules, +# meaning they are the last in a rules chain. + +%.o:: %.c + $(call make-object-file,c) + +%.o:: %.cc + $(call make-object-file,cxx) + +%.o:: %.cpp + $(call make-object-file,cxx) + + +#=== explicit prerequisites for class executables ============================== + + +# For class executables, prerequisite rules are declared in run time. Target +# 'depend' prints these rules for debugging purposes. + +# declare explicit prerequisites rule like 'class: class.extension' +# argument $v is class basename +define declare-class-target +$v: $v.$(extension) +endef + +# declare explicit prerequisites rule like 'class.extension: object1.o object2.o' +# argument $v is class basename +define declare-class-executable-target +$v.$(extension): $(addsuffix .o, $(basename $($v.class.sources))) \ + $(addsuffix .o, $(basename $(common.sources))) +endef + +# evaluate explicit prerequisite rules for all classes +$(foreach v, $(classes), $(eval $(declare-class-target))) +$(foreach v, $(classes), $(eval $(declare-class-executable-target))) + + +#=== implicit prerequisites for class executables ============================== + + +# Evaluating implicit prerequisites (header files) with help from the +# preprocessor is 'expensive' so this is done conditionally and selectively. +# Note that it is also possible to trigger a build via install targets, in +# which case implicit prerequisites are not checked. + +# When the Pd include path contains spaces it will mess up the implicit +# prerequisites rules. +disable-dependency-tracking := $(strip $(pdincludepathwithspaces)) + +ifndef disable-dependency-tracking + must-build-everything := $(filter all, $(goals)) + must-build-class := $(filter $(classes), $(goals)) + must-build-sources := $(foreach v, $(must-build-class), $($v.class.sources)) +endif + +# declare implicit prerequisites rule like 'object.o: header1.h header2.h ...' +# argument $1 is input source file(s) +# dir is explicitly added because option -MM strips it by default +define declare-object-target +$(dir $1)$(filter %.o: %.h, $(shell $(CPP) $(depcheck.flags) -MM $1)) $(MAKEFILE_LIST) +endef + +# evaluate implicit prerequisite rules when rebuilding everything +ifdef must-build-everything + $(if $(wildcard $(all.objects)), \ + $(info ++++ info: evaluating implicit prerequisites in lib $(lib.name).....) \ + $(foreach v, $(all.sources), $(eval $(call declare-object-target, $v)))) +endif + +# evaluate implicit prerequisite rules when selectively building classes +ifdef must-build-class + $(foreach v, $(must-build-sources), \ + $(eval $(call declare-object-target, $v))) + $(foreach v, $(shared.sources), \ + $(eval $(call declare-object-target, $v))) +endif + + +################################################################################ +### rules: preprocessor and assembly files ##################################### +################################################################################ + + +# Preprocessor and assembly output files for bug tracing etc. They are not part +# of the build processes for executables. By default these files are created in +# the current working directory. Dependency tracking is not performed, the build +# is forced instead to make sure it's up to date. + +force: + + +#=== preprocessor file ========================================================= + + +# make preprocessor output file with extension .pre +# argument $1 = compiler type (c or cxx) +define make-preprocessor-file + $(info ++++ info: making preprocessor output file $(notdir $*.pre) \ + in current working directory) + $(compile-$1) -E $< $(c.flags) $($1.flags) -o $(notdir $*.pre) +endef + +%.pre:: %.c force + $(call make-preprocessor-file,c) + +%.pre:: %.cc force + $(call make-preprocessor-file,cxx) + +%.pre:: %.cpp force + $(call make-preprocessor-file,cxx) + + +#=== assembly file ============================================================= + + +# make C / assembly interleaved output file with extension .lst +# argument $1 = compiler type (c or cxx) +define make-assembly-file + $(info ++++ info: making assembly output file $(notdir $*.lst) \ + in current working directory) + $(compile-$1) \ + -c -Wa,-a,-ad -fverbose-asm \ + $($1.flags) \ + $< > $(notdir $*.lst) +endef + +%.lst:: %.c force + $(call make-assembly-file,c) + +%.lst:: %.cc force + $(call make-assembly-file,cxx) + +%.lst:: %.cpp force + $(call make-assembly-file,cxx) + + +################################################################################ +### rules: installation targets ################################################ +################################################################################ + + +#=== strip ===================================================================== + + +# Stripping of installed binaries will only be done when variable 'stripflags' +# is defined non-empty. No default definition is provided except for Windows +# where the unstripped binaries are large, especially in the case of Mingw-w64. + +# Note: while stripping all symbols ('-s' or '--strip-all') is possible for +# Linux and Windows, in the case of OSX only non-global symbols can be stripped +# (option '-x' or '--discard-all'). + +# Make definition of strip command overridable so it can be defined in an +# environment for cross-compilation. +STRIP ?= strip + +# Commands in 'strip-executables' will be executed conditionally in the rule for +# target 'install-executables'. +strip-executables = cd "$(installpath)" && \ + $(foreach v, $(executables), $(STRIP) $(stripflags) '$v';) + + +#=== install =================================================================== + + +# Install targets depend on successful exit status of target all because nothing +# must be installed in case of a build error. + +# -p = preserve time stamps +# -m = set permission mode (as in chmod) +# -d = create all components of specified directories +INSTALL = install +INSTALL_PROGRAM := $(INSTALL) -p -m 644 +INSTALL_DATA := $(INSTALL) -p -m 644 +INSTALL_DIR := $(INSTALL) -m 755 -d + +# strip spaces from file names +executables := $(strip $(executables)) +datafiles := $(strip $(datafiles)) +datadirs := $(strip $(datadirs)) + +# Do not make any install sub-target with empty variable definition because the +# install program would exit with an error. +install: $(if $(executables), install-executables) +install: $(if $(datafiles), install-datafiles) +install: $(if $(datadirs), install-datadirs) + +install-executables: all + $(INSTALL_DIR) -v "$(installpath)" + $(foreach v, $(executables), \ + $(INSTALL_PROGRAM) '$v' "$(installpath)";) + $(info ++++ info: executables of lib $(lib.name) installed \ + from $(CURDIR) to $(installpath)) + $(if $(stripflags), $(strip-executables),) + +install-datafiles: all + $(INSTALL_DIR) -v "$(installpath)" + $(foreach v, $(datafiles), \ + $(INSTALL_DATA) '$(v)' "$(installpath)";) + $(info ++++ info: data files of lib $(lib.name) installed \ + from $(CURDIR) to $(installpath)) + +install-datadirs: all + $(foreach v, $(datadirs), $(INSTALL_DIR) "$(installpath)/$v";) + $(foreach v, $(datadirs), \ + $(INSTALL_DATA) $(wildcard $v/*) "$(installpath)/$v";) + $(info ++++ info: data directories of lib $(lib.name) installed \ + from $(CURDIR) to $(installpath)) + + +################################################################################ +### rules: distribution targets ################################################ +################################################################################ + + +# TODO +# These targets are implemented in Makefile Template, but I have to figure out +# how to do it under the not-so-strict conditions of Makefile.pdlibbuilder. + +# make source package +dist: + @echo "target dist not yet implemented" + +# make Debian source package +dpkg-source: + @echo "target dpkg-source not yet implemented" + +$(ORIGDIR): + +$(DISTDIR): + + +################################################################################ +### rules: clean targets ####################################################### +################################################################################ + + +# delete build products from build tree +clean: + rm -f $(all.objects) + rm -f $(classes.executables) $(lib.name).$(extension) $(shared.lib) + rm -f *.pre *.lst + +# remove distribution directories and tarballs from build tree +distclean: clean + @echo "target distclean not yet implemented" + + +################################################################################ +### rules: submake targets ##################################################### +################################################################################ + + +# Iterate over sub-makefiles or makefiles in other directories. + +# When 'continue-make=yes' is set, sub-makes will report 'true' to the parent +# process regardless of their real exit status. This prevents the parent make +# from being aborted by a sub-make error. Useful when you want to quickly find +# out which sub-makes from a large set will succeed. +ifeq ($(continue-make),yes) + continue = || true +endif + +# These targets will trigger sub-make processes for entries in 'makefiledirs' +# and 'makefiles'. +all alldebug install clean distclean dist dkpg-source: \ + $(makefiledirs) $(makefiles) + +# this expands to identical rules for each entry in 'makefiledirs' +$(makefiledirs): + $(MAKE) --directory=$@ $(MAKECMDGOALS) $(continue) + +# this expands to identical rules for each entry in 'makefiles' +$(makefiles): + $(MAKE) --directory=$(dir $@) --makefile=$(notdir $@) $(MAKECMDGOALS) $(continue) + + +################################################################################ +### rules: convenience targets ################################################# +################################################################################ + + +#=== show variables ============================================================ + + +# Several 'function' macro's cause errors when expanded within a rule or without +# proper arguments. Variables which are set with the define directive are only +# shown by name for that reason. +functions = \ +add-class-source \ +declare-class-target \ +declare-class-executable-target \ +declare-object-target \ +link-class \ +link-lib \ +link-shared \ +make-object-file \ +make-preprocessor-file \ +make-assembly-file + + +# show variables from makefiles +vars: + $(info ++++ info: showing makefile variables:) + $(foreach v,\ + $(sort $(filter-out $(functions) functions, $(.VARIABLES))),\ + $(if $(filter file, $(origin $v)),\ + $(info variable $v = $($v)))) + $(foreach v, $(functions), $(info 'function' name: $v)) + @echo + +# show all variables +allvars: + $(info ++++ info: showing default, automatic and makefile variables:) + $(foreach v, \ + $(sort $(filter-out $(functions) functions, $(.VARIABLES))), \ + $(info variable ($(origin $v)) $v = $($v))) + $(foreach v, $(functions), $(info 'function' name: $v)) + @echo + + +#=== show dependencies ========================================================= + + +# show generated prerequisites rules +depend: + $(info ++++ info: generated prerequisite rules) + $(foreach v, $(classes), $(info $(declare-class-target))) + $(foreach v, $(classes), $(info $(declare-class-executable-target))) + $(foreach v, $(all.sources), $(info $(call declare-object-target, $v))) + @echo + + +#=== show help text ============================================================ + + +# brief info about targets and paths + +ifdef mpdh + mpdhinfo := $(mpdh) +else + mpdhinfo := m_pd.h was not found. Is Pd installed? +endif + +help: + @echo + @echo " Main targets:" + @echo " all: build executables (default target)" + @echo " install: install all components of the library" + @echo " vars: print makefile variables for troubleshooting" + @echo " allvars: print all variables for troubleshooting" + @echo " help: print this help text" + @echo + @echo " Pd API m_pd.h:" + @echo " $(mpdhinfo)" + @echo " You may specify your preferred Pd include directory as argument" + @echo " to the make command, like 'PDINCLUDEDIR=path/to/pd/src'." + @echo + @echo " Path for installation of your libdir(s):" + @echo " $(PDLIBDIR)" + @echo " Alternatively you may specify your path for installation as argument" + @echo " to the make command, like 'PDLIBDIR=path/to/pd-externals'." + @echo + @echo " Default paths are listed in the doc sections in Makefile.pdlibbuilder." + @echo + + +#=== platform test ============================================================= + + +# This target can be used to test if the compiler for specified PLATFORM is +# correctly defined and available. + +dumpmachine: + @$(CC) -dumpmachine + + +#=== dummy target ============================================================== + + +coffee: + @echo "Makefile.pdlibbuilder: Can not make coffee. Sorry." + + +################################################################################ +### end of rules sections ###################################################### +################################################################################ + + +# for syntax highlighting in vim and github +# vim: set filetype=make: + diff --git a/pd-lib-builder/README.md b/pd-lib-builder/README.md new file mode 100644 index 0000000..187b132 --- /dev/null +++ b/pd-lib-builder/README.md @@ -0,0 +1,162 @@ + + +### Makefile.pdlibbuilder ### + +Helper makefile for Pure Data external libraries. Written by Katja Vetter +March-June 2015 for the public domain and since then developed as a Pd +community project. No warranties. Inspired by Hans Christoph Steiner's Makefile +Template and Stephan Beal's ShakeNMake. + +GNU make version >= 3.81 required. + + +### characteristics ### + + +* defines build settings based on autodetected target platform +* defines rules to build Pd class- or lib executables from C or C++ sources +* defines rules for libdir installation +* defines convenience targets for developer and user +* evaluates implicit dependencies for non-clean builds + + +### basic usage ### + + +In your Makefile, define your Pd lib name and class files, and include +Makefile.pdlibbuilder at the end of the Makefile. Like so: + + + # Makefile for mylib + + lib.name = mylib + + class.sources = myclass1.c myclass2.c + + datafiles = myclass1-help.pd myclass2-help.pd README.txt LICENSE.txt + + include Makefile.pdlibbuilder + + +For files in class.sources it is assumed that class name == source file +basename. The default target builds all classes as individual executables +with Pd's default extension for the platform. For anything more than the +most basic usage, read the documentation sections in Makefile.pdlibbuilder. + + +### paths ### + + +Makefile.pdlibbuilder >= v0.4.0 supports pd path variables which can be +defined not only as make command argument but also in the environment, to +override platform-dependent defaults: + +PDDIR: +Root directory of 'portable' pd package. When defined, PDINCLUDEDIR and +PDBINDIR will be evaluated as $(PDDIR)/src and $(PDDIR)/bin. + +PDINCLUDEDIR: +Directory where Pd API m_pd.h should be found, and other Pd header files. +Overrides the default search path. + +PDBINDIR: +Directory where pd.dll should be found for linking (Windows only). Overrides +the default search path. + +PDLIBDIR: +Root directory for installation of Pd library directories. Overrides the +default install location. + + +### documentation ### + + +This README.md provides only basic information. A large comment section inside +Makefile.pdlibbuilder lists and explains the available user variables, default +paths, and targets. The internal documentation reflects the exact functionality +of the particular version. For suggestions about project maintenance and +advanced compilation see tips-tricks.md. + + +### versioning ### + + +The project is versioned in MAJOR.MINOR.BUGFIX format (see http://semver.org), +and maintained at https://github.com/pure-data/pd-lib-builder. Pd lib developers +are invited to regulary check for updates, and to contribute and discuss +improvements here. If you really need to distribute a personalized version with +your library, rename Makefile.pdlibbuilder to avoid confusion. + + +### examples ### + +The list of projects using pd-lib-builder can be helpful if you are looking for +examples, from the simplest use case to more complex implementations. + +- helloworld: traditional illustration of simplest use case +- pd-windowing: straightforward real world use case of a small library +- pd-nilwind / pd-cyclone: more elaborate source tree +- zexy: migrated from autotools to pd-lib-builder + + +### projects using pd-lib-builder ### + +non-exhaustive list + +https://github.com/pure-data/helloworld + +https://github.com/electrickery/pd-nilwind + +https://github.com/electrickery/pd-maxlib + +https://github.com/electrickery/pd-sigpack + +https://github.com/electrickery/pd-tof + +https://github.com/electrickery/pd-windowing + +https://github.com/electrickery/pd-smlib + +https://github.com/porres/pd-cyclone + +https://github.com/porres/pd-else + +https://github.com/porres/pd-psycho + +https://git.iem.at/pd/comport + +https://git.iem.at/pd/hexloader + +https://git.iem.at/pd/iemgui + +https://git.iem.at/pd/iemguts + +https://git.iem.at/pd/iemlib + +https://git.iem.at/pd/iemnet + +https://git.iem.at/pd/iem_ambi + +https://git.iem.at/pd/iem_tab + +https://git.iem.at/pd/iem_adaptfilt + +https://git.iem.at/pd/iem_roomsim + +https://git.iem.at/pd/iem_spec2 + +https://git.iem.at/pd/mediasettings + +https://git.iem.at/pd/zexy + +https://git.iem.at/pd-gui/punish + +https://github.com/residuum/PuRestJson + +https://github.com/libpd/abl_link + +https://github.com/wbrent/timbreID + +https://github.com/MetaluNet/moonlib + + diff --git a/pd-lib-builder/tips-tricks.md b/pd-lib-builder/tips-tricks.md new file mode 100644 index 0000000..c1795f4 --- /dev/null +++ b/pd-lib-builder/tips-tricks.md @@ -0,0 +1,230 @@ +pd-lib-builder cheatsheet +========================= + +# Creating special builds + +## cross-compiling on linux x86_64 for other platforms + +Using pd-lib-builder >=0.6.0 we can define variable `PLATFORM` to specify a +target triplet for cross-compilation. Example to build W32 binaries (assuming +package `mingw-w64` is installed and a W32 package for Pd is unzipped into a +path `${PDWIN32}`: + + make PLATFORM=x86_64-w64-mingw32 PDDIR="${PDWIN32}" + +#### older pd-lib-builder versions + +Using pd-lib-builder < 0.6.0, in the absence of variable `PLATFORM`, you would +instead override variables `system`, `target.arch`, `CC` and / or `CXX`, +`STRIP`. Example: + + make system=Windows target.arch=i686 CC=i686-w64-mingw32-gcc STRIP=i686-w64-mingw32-strip PDDIR="${PDWIN32}" + +#### toolchains + +Cross toolchains for relevant platforms in Debian Buster (install g++ +with dependencies for a given platform to get the whole tool chain): + +- `arm-linux-gnueabihf` +- `aarch64-linux-gnu` +- `i686-linux-gnu` +- `i686-w64-mingw32` and `x86_64-w64-mingw32` (install `mingw-w64`) + +OSX/MacOS cross tool chains are not distributed by Debian. Use project +`osxcross` from Thomas Poechtraeger to create the tools. + +## building double-precision externals + +At the time of writing (2018-02) there is no official Pd that supports +double-precision numbers yet. +However, if you do get hold of an experimental double-precision Pd, you can +easily build your externals for 64-bit numbers: + + make CPPFLAGS="-DPD_FLOATSIZE=64" + +## building externals for W64 (64-bit Windows) + +At the time of writing (2018-02) there is no official Pd that supports +W64 yet. +However, if you do get hold of an experimental W64 Pd, you can +easily build your externals for this environment with + + make CPPFLAGS="-DPD_LONGINTTYPE=__int64" CC=x86_64-w64-mingw32-gcc + + +To build a double-precision external for W64, use something like: + + make CPPFLAGS="-DPD_LONGINTTYPE=__int64 -DPD_FLOATSIZE=64" CC=x86_64-w64-mingw32-gcc + + +## TODO universal binaries on OSX + + +# Project management + +In general it is advised to put the `Makefile.pdlibbuilder` into a separate +subdirectory (e.g. `pd-lib-builder/`). +This makes it much easier to update the `Makefile.pdlibbuilder` later + +You *should* also use a variable to the actual path of the Makefile.pdlibbuilder +(even if you keep it in the root-directory), as this allows easy experimenting +with newer (or older) (or site-specific) versions of the pd-lib-builder +Makefile. + +~~~make +PDLIBBUILDER_DIR=pd-lib-builder/ +include $(PDLIBBUILDER_DIR)/Makefile.pdlibbuilder +~~~ + +## Keeping pd-lib-builder up-to-date + +### `git subtree` + +With git-subtrees, you make the pd-lib-builder repository (or any other +repository for that matter) part of your own repository - with full history and +everything - put nicely into a distinct subdirectory. + +Support for *manipulating* subtrees has been added with Git-v1.7.11 (May 2012). +The nice thing however is, that from "outside" the subtree is part of your +repository like any other directory. E.g. older versions of Git can clone your +repository with the full subtree (and all it's history) just fine. +You can also use git-archive to make a complete snapshot of your repository +(including the subtree) - nice, if you e.g. want self-contained downloads of +your project from git hosting platforms (like Github, Gitlab, Bitbucket,...) + +In short, `git subtree` is the better `git submodule`. + +So here's how to do it: + +#### Initial setup/check-out +This will create a `pd-lib-builder/` directory containing the full history of +the pd-lib-builder repository up to its release `v0.5.0` + +~~~sh +git subtree add --prefix=pd-lib-builder/ https://github.com/pure-data/pd-lib-builder v0.5.0 +~~~ + +This will automatically merge the `pd-lib-builder/` history into your current +branch, so everything is ready to go. + +#### Cloning your repository with the subtree +Nothing special, really. +Just clone your repository as always: + +~~~sh +git clone https://git.example.org/pd/superbonk~.git +~~~ + +#### Updating the subtree +Time passes and sooner or later you will find, that there is a shiny new +pd-lib-builder with plenty of bugfixes and new features. +To update your local copy to pd-lib-builder's current `master`, simply run: + +~~~sh +git subtree pull --prefix pd-lib-builder/ https://github.com/pure-data/pd-lib-builder master +~~~ + +#### Pulling the updated subtree into existing clones +Again, nothing special. +Just pull as always: + +~~~sh +git pull +~~~ + + +#### Further reading +More on the power of `git subtree` can be found online +- https://medium.com/@v/git-subtrees-a-tutorial-6ff568381844 +- https://www.atlassian.com/blog/git/alternatives-to-git-submodule-git-subtree +- ... + +### ~~`git submodule`~~ [DISCOURAGED] + + +#### Initial setup/check-out +To add a new submodule to your repository, just run `git submodule add` and +commit the changes: + +~~~sh +git submodule add https://github.com/pure-data/pd-lib-builder +git commit .gitmodules pd-lib-builder/ -m "Added pd-lib-builder as git-submodule" +~~~ + +#### Cloning your repository with the submodule + +When doing a fresh clone of your repository, pass the `--recursive` option to +automatically fetch all submodules: + +~~~sh +git clone --recursive https://git.example.org/pd/superbonk~.git +~~~ + +If you've cloned non-recursively, you can initialize and update the submodules +manually: + +~~~sh +git submodule init +git submodule update +~~~ + +#### Updating the submodule +Submodules are usually fixed to a given commit in their repository. +To update the `pd-lib-builder` submodule to the current `master` do something +like: + +~~~sh +cd pd-lib-builder +git checkout master +git pull +cd .. +git status pd-lib-builder +git commit pd-lib-builder -m "Updated pd-lib-builder to current master" +~~~ + +#### Pulling the updated submodule into existing clones +After you have pushed the submodule updates in your repository, other clones of +the repository can be updated as follows: + +~~~sh +git pull +~~~ + +The above will make your repository aware, that the submodule is out-of-sync. + +~~~sh +$ LANG=C git status pd-lib-builder +On branch master +Your branch is up to date with 'origin/master'. + +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git checkout -- ..." to discard changes in working directory) + + modified: pd-lib-builder (new commits) +$ +~~~ + +In order to sync the submodule to the correct commit, run the following: + +~~~sh +git submodule update +~~~ + +#### Drawbacks +`git submodule` has a number of drawbacks: +- it requires special commands to synchronize the submodules, in addition to + synching your repository. +- you must make sure to use an URL for the submodule that is accessible to your + potential users. e.g. using `git@github.com:pure-data/pd-lib-builder` is bad, + because it requires everybody who wants to checkout your sources to have a + github-account - even if they could checkout *your* repository anonymously. +- submodules will be excluded from `git archive`. This means, that if you use a + mainstream git provider (like Github, GitLab, Bitbucket,...) and make releases + by creating a `git tag`, the automatically generated zipfiles with the sources + will lack the submodule - and your users will not be able to compile your + source code. + +In general, I would suggest to **avoid** `git submodule`, and instead use the +better `git subtree` (above). + diff --git a/pd/methods-1.pd b/pd/methods-1.pd index 5b85c43..253fa6a 100644 --- a/pd/methods-1.pd +++ b/pd/methods-1.pd @@ -1,24 +1,24 @@ -#N canvas 540 469 734 369 12; -#X obj 16 13 cnv 15 650 40 empty empty py/pyext 10 22 0 24 -260818 --1 0; -#X text 235 32 http://grrrr.org/ext; -#X symbolatom 21 139 10 0 0 0 - - -; -#X symbolatom 25 298 10 0 0 0 - - -; -#X obj 22 179 py .str @py 1; -#X text 145 170 convert the symbol to a Python string; -#X text 35 216 pass it as a true Python object; -#X symbolatom 364 295 10 0 0 0 - - -; -#X text 462 269 use module function; -#X text 23 119 enter some text; -#X text 145 187 using the built-in str function; -#X obj 25 252 pym swapcase; -#X text 63 270 use swapcase method; -#X obj 363 250 py string.swapcase; -#X text 235 16 Python script objects \, (C)2003-2006 Thomas Grill; -#X text 21 73 Py can act on Python objects in an object-oriented manner -; -#X connect 2 0 4 1; -#X connect 4 0 11 1; -#X connect 4 0 13 1; -#X connect 11 0 3 0; -#X connect 13 0 7 0; +#N canvas 540 469 734 369 12; +#X obj 16 13 cnv 15 650 40 empty empty py/pyext 10 22 0 24 -260818 +-1 0; +#X text 235 32 http://grrrr.org/ext; +#X symbolatom 21 139 10 0 0 0 - - -; +#X symbolatom 25 298 10 0 0 0 - - -; +#X obj 22 179 py .str @py 1; +#X text 145 170 convert the symbol to a Python string; +#X text 35 216 pass it as a true Python object; +#X symbolatom 364 295 10 0 0 0 - - -; +#X text 462 269 use module function; +#X text 23 119 enter some text; +#X text 145 187 using the built-in str function; +#X obj 25 252 pym swapcase; +#X text 63 270 use swapcase method; +#X text 235 16 Python script objects \, (C)2003-2006 Thomas Grill; +#X text 21 73 Py can act on Python objects in an object-oriented manner +; +#X obj 363 250 py string.capwords; +#X connect 2 0 4 1; +#X connect 4 0 11 1; +#X connect 4 0 15 1; +#X connect 11 0 3 0; +#X connect 15 0 7 0; diff --git a/readme.txt b/readme.txt index 75d3f73..dd272a0 100644 --- a/readme.txt +++ b/readme.txt @@ -1,6 +1,6 @@ py/pyext - python script objects for PD and Max/MSP -Copyright ©2002-2012 Thomas Grill (gr@grrrr.org) +Copyright (c)2002-2020 Thomas Grill (gr@grrrr.org) For information on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES, see the file, "license.txt," in this distribution. @@ -57,8 +57,24 @@ BUILDING from source You will need the flext C++ layer for PD and Max/MSP externals to compile this. See http://grrrr.org/ext/flext -Download, install and compile the package. -Afterwards you can proceed with building this external. +Download and unzip, or git-clone the package. + + +Pure data - any platform supporting gcc-compatible compilers + +------------------------------------------------------------ + + +The pd-lib-builder project (https://github.com/pure-data/pd-lib-builder) is used to compile the project. +A git subtree of this project is already present. + +The compilation is done using the GNU make tool and it will need additional information about the location of the flext source files, and possibly, Pure data, if a specific version should be used. + +This could be an example: +make CPPFLAGS="-I ../flext/source" PDDIR="../../pure-data" + +For OS X, further flags can be needed: +CFLAGS="-mmacosx-version-min=10.9" LDFLAGS="-mmacosx-version-min=10.9" pd/Max - Windows - Microsoft Visual C, Borland C++, MinGW: diff --git a/scripts/buffer.py b/scripts/buffer.py index b58ea42..401cc6b 100644 --- a/scripts/buffer.py +++ b/scripts/buffer.py @@ -12,18 +12,20 @@ Numeric, numarray and numpy (for all of them see http://numeric.scipy.org) """ +from __future__ import print_function + import sys try: import pyext except: - print "ERROR: This script must be loaded by the PD/Max py/pyext external" + print("ERROR: This script must be loaded by the PD/Max py/pyext external") try: # numpy is assumed here... numeric and numarray are considered deprecated import numpy as N except: - print "Failed importing numpy module:",sys.exc_value + print("Failed importing numpy module:",sys.exc_value) def mul(*args): # create buffer objects diff --git a/scripts/script.py b/scripts/script.py index ff41730..0ef6492 100644 --- a/scripts/script.py +++ b/scripts/script.py @@ -7,14 +7,17 @@ """Several functions to show the py script functionality""" +from __future__ import print_function + +from functools import reduce import sys -print "Script initialized" +print("Script initialized") try: - print "Script arguments: ",sys.argv + print("Script arguments: ",sys.argv) except: - print + print() def numargs(*args): # variable argument list """Return the number of arguments""" diff --git a/scripts/sendrecv.py b/scripts/sendrecv.py index 9d873ba..25596a7 100644 --- a/scripts/sendrecv.py +++ b/scripts/sendrecv.py @@ -18,10 +18,12 @@ """ +from __future__ import print_function + try: - import pyext + import pyext except: - print "ERROR: This script must be loaded by the PD/Max pyext external" + print("ERROR: This script must be loaded by the PD/Max pyext external") from time import sleep @@ -29,92 +31,92 @@ ################################################################# def recv_gl(arg): - """This is a global receive function, it has no access to class members.""" - print "GLOBAL",arg + """This is a global receive function, it has no access to class members.""" + print("GLOBAL",arg) class ex1(pyext._class): - """Example of a class which receives and sends messages + """Example of a class which receives and sends messages - It has two creation arguments: a receiver and a sender name. - There are no inlets and outlets. - Python functions (one global function, one class method) are bound to PD's or Max/MSP's receive symbols. - The class method sends the received messages out again. - """ + It has two creation arguments: a receiver and a sender name. + There are no inlets and outlets. + Python functions (one global function, one class method) are bound to PD's or Max/MSP's receive symbols. + The class method sends the received messages out again. + """ - # no inlets and outlets - _inlets=1 - _outlets=0 + # no inlets and outlets + _inlets=1 + _outlets=0 - recvname="" - sendname="" + recvname="" + sendname="" - def recv(self,*arg): - """This is a class-local receive function, which has access to class members.""" + def recv(self,*arg): + """This is a class-local receive function, which has access to class members.""" - # print some stuff - print "CLASS",self.recvname,arg + # print some stuff + print("CLASS",self.recvname,arg) - # send data to specified send address - self._send(self.sendname,arg) + # send data to specified send address + pyext._send(self.sendname,arg) - def __init__(self,*args): - """Class constructor""" + def __init__(self,*args): + """Class constructor""" - # store sender/receiver names - if len(args) >= 1: self.recvname = args[0] - if len(args) >= 2: self.sendname = args[1] + # store sender/receiver names + if len(args) >= 1: self.recvname = args[0] + if len(args) >= 2: self.sendname = args[1] - self.bind_1() + self.bind_1() - def bind_1(self): - # bind functions to receiver names - # both are called upon message - self._bind(self.recvname,self.recv) - self._bind(self.recvname,recv_gl) + def bind_1(self): + # bind functions to receiver names + # both are called upon message + self._bind(self.recvname,self.recv) + self._bind(self.recvname,recv_gl) - def unbind_1(self): - self._unbind(self.recvname,self.recv) - self._unbind(self.recvname,recv_gl) + def unbind_1(self): + self._unbind(self.recvname,self.recv) + self._unbind(self.recvname,recv_gl) - def __del__(self): - """Class destructor""" + def __del__(self): + """Class destructor""" - # unbinding is automatically done at destruction - pass + # unbinding is automatically done at destruction + pass ################################################################# class ex2(pyext._class): - """Example of a class which receives a message and forwards it to an outlet + """Example of a class which receives a message and forwards it to an outlet - It has one creation argument: the receiver name. - """ + It has one creation argument: the receiver name. + """ - # define inlets and outlets - _inlets=0 - _outlets=1 + # define inlets and outlets + _inlets=0 + _outlets=1 - recvname="" + recvname="" - def recv(self,*arg): - """This is a class-local receive function""" + def recv(self,*arg): + """This is a class-local receive function""" - # send received data to outlet - self._outlet(1,arg) + # send received data to outlet + self._outlet(1,arg) - def __init__(self,rname): - """Class constructor""" + def __init__(self,rname): + """Class constructor""" - # store receiver names - self.recvname = rname + # store receiver names + self.recvname = rname - # bind function to receiver name - self._bind(self.recvname,self.recv) + # bind function to receiver name + self._bind(self.recvname,self.recv) ################################################################# @@ -124,57 +126,57 @@ def __init__(self,rname): from random import random,randint class ex3(pyext._class): - """Example of a class which does some object manipulation by scripting""" - - - # define inlets and outlets - _inlets=1 - _outlets=0 - - def __init__(self): - """Class constructor""" - - # called scripting method should run on its own thread - if self._isthreaded: - print "Threading is on" - self._detach(1) - - def bang_1(self): - """Do some scripting - PD only!""" - - num = 12 # number of objects - ori = complex(150,180) # origin - rad = 100 # radius - l = range(num) # initialize list - - # make flower - self._tocanvas("obj",ori.real,ori.imag,"bng",20,250,50,0,"empty","yeah","empty",0,-6,64,8,-24198,-1,-1) - for i in xrange(num): - l[i] = ori+rad*exp(complex(0,i*2*pi/num)) - self._tocanvas("obj",l[i].real,l[i].imag,"bng",15,250,50,0,"empty","yeah"+str(i),"empty",0,-6,64,8,0,-1,-1) - self._tocanvas("connect",6,0,7+i,0) - - # blink - for i in range(10): - self._send("yeah","bang") - sleep(1./(i+1)) - - # move objects around - for i in xrange(200): - ix = randint(0,num-1) - l[ix] = ori+rad*complex(2*random()-1,2*random()-1) - self._send("yeah"+str(ix),"pos",l[ix].real,l[ix].imag) - sleep(0.02) - - # now delete - # this is not well-done... from time to time an object remains - self._tocanvas("editmode",1) - for i in xrange(num): - self._tocanvas("mouse",l[i].real,l[i].imag,0,0) - self._tocanvas("cut") - - self._tocanvas("mouse",ori.real+1,ori.imag+1,0,0) - self._tocanvas("cut") - - self._tocanvas("editmode",0) + """Example of a class which does some object manipulation by scripting""" + + + # define inlets and outlets + _inlets=1 + _outlets=0 + + def __init__(self): + """Class constructor""" + + # called scripting method should run on its own thread + if self._isthreaded: + print("Threading is on") + self._detach(1) + + def bang_1(self): + """Do some scripting - PD only!""" + + num = 12 # number of objects + ori = complex(150,180) # origin + rad = 100 # radius + l = list(range(num)) # initialize list + + # make flower + self._tocanvas("obj",ori.real,ori.imag,"bng",20,250,50,0,"empty","yeah","empty",0,-6,64,8,-24198,-1,-1) + for i in range(num): + l[i] = ori+rad*exp(complex(0,i*2*pi/num)) + self._tocanvas("obj",l[i].real,l[i].imag,"bng",15,250,50,0,"empty","yeah"+str(i),"empty",0,-6,64,8,0,-1,-1) + self._tocanvas("connect",6,0,7+i,0) + + # blink + for i in range(10): + pyext._send("yeah","bang") + sleep(1./(i+1)) + + # move objects around + for i in range(200): + ix = randint(0,num-1) + l[ix] = ori+rad*complex(2*random()-1,2*random()-1) + pyext._send("yeah"+str(ix),"pos",l[ix].real,l[ix].imag) + sleep(0.02) + + # now delete + # this is not well-done... from time to time an object remains + self._tocanvas("editmode",1) + for i in range(num): + self._tocanvas("mouse",l[i].real,l[i].imag,0,0) + self._tocanvas("cut") + + self._tocanvas("mouse",ori.real+1,ori.imag+1,0,0) + self._tocanvas("cut") + + self._tocanvas("editmode",0) diff --git a/scripts/sig.py b/scripts/sig.py index 6bebcb3..a7de91e 100644 --- a/scripts/sig.py +++ b/scripts/sig.py @@ -1,6 +1,6 @@ # py/pyext - python script objects for PD and MaxMSP # -# Copyright (c) 2002-2012 Thomas Grill (gr@grrrr.org) +# Copyright (c) 2002-2017 Thomas Grill (gr@grrrr.org) # For information on usage and redistribution, and for a DISCLAIMER OF ALL # WARRANTIES, see the file, "license.txt," in this distribution. # @@ -11,15 +11,17 @@ It will probably once be replaced by Numeric(3) """ +from __future__ import print_function + try: import pyext except: - print "ERROR: This script must be loaded by the PD/Max py/pyext external" + print("ERROR: This script must be loaded by the PD/Max py/pyext external") try: import psyco psyco.full() - print "Using JIT compilation" + print("Using JIT compilation") except: # don't care pass @@ -29,30 +31,32 @@ try: import numpy as N except: - print "Failed importing numpy module:",sys.exc_value + print("Failed importing numpy module:",sys.exc_value) class gain(pyext._class): """Just a simple gain stage""" - gain = 0 + def __init__(self): + self.gain = 0 def _signal(self): # Multiply input vector by gain and copy to output - try: - self._outvec(0)[:] = self._invec(0)*self.gain - except: - pass + try: + self._outvec(0)[:] = self._invec(0)*self.gain + except: + pass class gain2(pyext._class): """More optimized version""" - gain = 0 + def __init__(self): + self.gain = 0 def _dsp(self): - if not self._arraysupport(): - print "No DSP support" + if not pyext._arraysupport(): + print("No DSP support") return False # cache vectors in this scope diff --git a/scripts/simple.py b/scripts/simple.py index 1aa211c..9e57f10 100644 --- a/scripts/simple.py +++ b/scripts/simple.py @@ -13,202 +13,211 @@ - Inherit your class from pyext._class - Specfiy the number of inlets and outlets: - Use the class members (variables) _inlets and _outlets - If not given they default to 1 - You can also use class methods with the same names to return the respective number + Use the class members (variables) _inlets and _outlets + If not given they default to 1 + You can also use class methods with the same names to return the respective number - Constructors/Destructors - You can specify an __init__ constructor and/or an __del__ destructor. - The constructor will be called with the object's arguments + You can specify an __init__ constructor and/or an __del__ destructor. + The constructor will be called with the object's arguments - e.g. if your PD or MaxMSP object looks like - [pyext script class arg1 arg2 arg3] + e.g. if your PD or MaxMSP object looks like + [pyext script class arg1 arg2 arg3] - then the __init__(self,*args) function will be called with a tuple argument - args = (arg1,arg2,arg3) - With this syntax, you will have to give at least one argument. - By defining the constructor as __init__(self,*args) you can also initialize - the class without arguments. + then the __init__(self,*args) function will be called with a tuple argument + args = (arg1,arg2,arg3) + With this syntax, you will have to give at least one argument. + By defining the constructor as __init__(self,*args) you can also initialize + the class without arguments. - Methods called by pyext - The general format is 'tag_inlet(self,arg)' resp. 'tag_inlet(self,*args)': - tag is the PD or MaxMSP message header.. either bang, float, list etc. - inlet is the inlet (starting from 1) from which messages are received. - args is a tuple which corresponds to the content of the message. args can be omitted. + The general format is 'tag_inlet(self,arg)' resp. 'tag_inlet(self,*args)': + tag is the PD or MaxMSP message header.. either bang, float, list etc. + inlet is the inlet (starting from 1) from which messages are received. + args is a tuple which corresponds to the content of the message. args can be omitted. - The inlet index can be omitted. The method name then has the format 'tag_(self,inlet,args)'. - Here, the inlet index is a additional parameter to the method + The inlet index can be omitted. The method name then has the format 'tag_(self,inlet,args)'. + Here, the inlet index is a additional parameter to the method - You can also set up methods which react on any message. These have the special forms - _anything_inlet(self,*args) - or - _anything_(self,inlet,*args) + You can also set up methods which react on any message. These have the special forms + _anything_inlet(self,*args) + or + _anything_(self,inlet,*args) - Please see below for examples. + Please see below for examples. - Any return values are ignored - use _outlet (see below). + Any return values are ignored - use _outlet (see below). - Generally, you should avoid method_, method_xx forms for your non-pyext class methods. - Identifiers (variables and functions) with leading underscores are reserved for pyext. + Generally, you should avoid method_, method_xx forms for your non-pyext class methods. + Identifiers (variables and functions) with leading underscores are reserved for pyext. - Send messages to outlets: - Use the inherited _outlet method. - You can either use the form - self._outlet(outlet,arg1,arg2,arg3,arg4) ... where all args are atoms (no sequence types!) - or - self._outlet(outlet,arg) ... where arg is a sequence containing only atoms - + Use the inherited _outlet method. + You can either use the form + self._outlet(outlet,arg1,arg2,arg3,arg4) ... where all args are atoms (no sequence types!) + or + self._outlet(outlet,arg) ... where arg is a sequence containing only atoms + Do not use _outlet inside __init__, since the outlets have not been created at that time. - Use pyext functions and methods: - See the __doc__ strings of the pyext module and the pyext._class base class. + See the __doc__ strings of the pyext module and the pyext._class base class. """ +from __future__ import print_function + try: - import pyext + import pyext except: - print "ERROR: This script must be loaded by the PD/Max pyext external" + print("ERROR: This script must be loaded by the PD/Max pyext external") +try: + # Python 2 + _long = long + del _long +except NameError: + long = int + ################################################################# class ex1(pyext._class): - """Example of a simple class which receives messages and prints to the console""" - - # number of inlets and outlets - _inlets=3 - _outlets=0 + """Example of a simple class which receives messages and prints to the console""" + + # number of inlets and outlets + _inlets=3 + _outlets=0 + # methods for first inlet - # methods for first inlet + def bang_1(self): + print("Bang into first inlet") - def bang_1(self): - print "Bang into first inlet" + def int_1(self,f): + print("Integer",f,"into first inlet") - def int_1(self,f): - print "Integer",f,"into first inlet" + def float_1(self,f): + print("Float",f,"into first inlet") - def float_1(self,f): - print "Float",f,"into first inlet" + def list_1(self,*s): + print("List",s,"into first inlet") - def list_1(self,*s): - print "List",s,"into first inlet" + # methods for second inlet - # methods for second inlet + def hey_2(self): + print("Tag 'hey' into second inlet") - def hey_2(self): - print "Tag 'hey' into second inlet" + def ho_2(self): + print("Tag 'ho' into second inlet") - def ho_2(self): - print "Tag 'ho' into second inlet" + def lets_2(self): + print("Tag 'lets' into second inlet") - def lets_2(self): - print "Tag 'lets' into second inlet" + def go_2(self): + print("Tag 'go' into second inlet") - def go_2(self): - print "Tag 'go' into second inlet" + def _anything_2(self,*args): + print("Some other message into second inlet:",args) - def _anything_2(self,*args): - print "Some other message into second inlet:",args + # methods for third inlet - # methods for third inlet + def onearg_3(self,a): + print("Tag 'onearg' into third inlet:",a) - def onearg_3(self,a): - print "Tag 'onearg' into third inlet:",a + def twoargs_3(self,*a): + if len(a) == 2: + print("Tag 'twoargs' into third inlet:",a[0],a[1]) + else: + print("Tag 'twoargs': wrong number of arguments") - def twoargs_3(self,*a): - if len(a) == 2: - print "Tag 'twoargs' into third inlet:",a[0],a[1] - else: - print "Tag 'twoargs': wrong number of arguments" + def threeargs_3(self,*a): + if len(a) == 3: + print("Tag 'threeargs' into third inlet",a[0],a[1],a[2]) + else: + print("Tag 'threeargs': wrong number of arguments") - def threeargs_3(self,*a): - if len(a) == 3: - print "Tag 'threeargs' into third inlet",a[0],a[1],a[2] - else: - print "Tag 'threeargs': wrong number of arguments" + def varargs_3(self,*args): + # with *args there can be arguments or not - def varargs_3(self,*args): - # with *args there can be arguments or not - - print "Tag 'varargs' into third inlet",args + print("Tag 'varargs' into third inlet",args) ################################################################# class ex2(pyext._class): - """Example of a simple class which receives messages and writes to outlets""" + """Example of a simple class which receives messages and writes to outlets""" - # number of inlets and outlets - _inlets=3 - _outlets=2 + # number of inlets and outlets + _inlets=3 + _outlets=2 - # methods for all inlets + # methods for all inlets - def hello_(self,n): - print "Tag 'hello' into inlet",n + def hello_(self,n): + print("Tag 'hello' into inlet",n) - def _anything_(self,n,*args): - print "Message into inlet",n,":",args + def _anything_(self,n,*args): + print("Message into inlet",n,":",args) - # methods for first inlet + # methods for first inlet - def float_1(self,f): - self._outlet(2,f) + def float_1(self,f): + self._outlet(2,f) - # methods for second inlet + # methods for second inlet - def float_2(self,f): - self._outlet(1,f) + def float_2(self,f): + self._outlet(1,f) ################################################################# # helper function - determine whether argument is a numeric type def isNumber(value): - import types - if type(value) in (types.FloatType, types.IntType, types.LongType): - return 1 - else: - return 0 + import types + if type(value) in (float, int, long): + return 1 + else: + return 0 class ex3(pyext._class): - """Example of a simple class doing a typical number addition - - It uses a constructor and a class member as temporary storage. - """ + """Example of a simple class doing a typical number addition + + It uses a constructor and a class member as temporary storage. + """ + + # number of inlets and outlets + _inlets=2 + _outlets=1 - # number of inlets and outlets - _inlets=2 - _outlets=1 + # temporary storage + tmp=0 - # temporary storage - tmp=0 + # constructor + def __init__(self,*args): + if len(args) == 1: + if isNumber(args[0]): + self.tmp = args[0] + else: + print("ex3: __init__ has superfluous arguments") - # constructor - def __init__(self,*args): - if len(args) == 1: - if isNumber(args[0]): - self.tmp = args[0] - else: - print "ex3: __init__ has superfluous arguments" + # methods - # methods + def float_1(self,f): + self._outlet(1,self.tmp+f) - def float_1(self,f): - self._outlet(1,self.tmp+f) + def float_2(self,f): + self.tmp = f - def float_2(self,f): - self.tmp = f + # handlers for MaxMSP int type + def int_1(self,f): + self.float_1(f) - # handlers for MaxMSP int type - def int_1(self,f): - self.float_1(f) + def int_2(self,f): + self.float_2(f) - def int_2(self,f): - self.float_2(f) diff --git a/scripts/tcltk.py b/scripts/tcltk.py index 0813199..a02b7ef 100644 --- a/scripts/tcltk.py +++ b/scripts/tcltk.py @@ -7,10 +7,12 @@ """This is an example script for showing a nonsense tcl/tk application.""" +from __future__ import print_function + try: import pyext except: - print "ERROR: This script must be loaded by the PD/Max pyext external" + print("ERROR: This script must be loaded by the PD/Max pyext external") from Tkinter import * import random @@ -74,7 +76,7 @@ def __init__(self): self._detach(1) def bang_1(self): - self._priority(-3) + pyext._priority(-3) # display the tcl/tk dialog app = Application(self) app.mainloop() diff --git a/scripts/threads.py b/scripts/threads.py index b029910..769127f 100755 --- a/scripts/threads.py +++ b/scripts/threads.py @@ -16,13 +16,22 @@ """ +from __future__ import print_function + try: import pyext except: - print "ERROR: This script must be loaded by the PD/Max pyext external" + print("ERROR: This script must be loaded by the PD/Max pyext external") from time import sleep +try: + # Python 2 + range = xrange +except NameError: + # Python 3 + pass + ################################################################# class ex1(pyext._class): @@ -37,10 +46,10 @@ class ex1(pyext._class): # method for bang to any inlet def bang_(self,n): - for i in xrange(self.loops): + for i in range(self.loops): # if _shouldexit is true, the thread ought to stop if self._shouldexit: - print "BREAK" + print("BREAK") break self._outlet(n,i) diff --git a/source/bound.cpp b/source/bound.cpp index 419760e..4ba7700 100644 --- a/source/bound.cpp +++ b/source/bound.cpp @@ -81,12 +81,12 @@ bool pyext::boundmeth(flext_base *th,t_symbol *sym,int argc,t_atom *argv,void *d return true; } -PyObject *pyext::pyext_bind(PyObject *,PyObject *args) +PyObject *pyext::pyext_bind(PyObject *self, PyObject *args) { - PyObject *self,*meth,*name; - if(!PyArg_ParseTuple(args, "OOO:pyext_bind", &self,&name,&meth)) // borrowed references + PyObject *meth,*name; + if(!PyArg_ParseTuple(args, "OO:pyext_bind",&name,&meth)) // borrowed references post("py/pyext - Wrong arguments!"); - else if(!PyInstance_Check(self) || !PyCallable_Check(meth)) { + else if(!PyCallable_Check(meth)) { post("py/pyext - Wrong argument types!"); } else { @@ -126,12 +126,12 @@ PyObject *pyext::pyext_bind(PyObject *,PyObject *args) return Py_None; } -PyObject *pyext::pyext_unbind(PyObject *,PyObject *args) +PyObject *pyext::pyext_unbind(PyObject *self, PyObject *args) { - PyObject *self,*meth,*name; - if(!PyArg_ParseTuple(args, "OOO:pyext_bind", &self,&name,&meth)) // borrowed references + PyObject *meth,*name; + if(!PyArg_ParseTuple(args, "OO:pyext_bind",&name,&meth)) // borrowed references post("py/pyext - Wrong arguments!"); - else if(!PyInstance_Check(self) || !PyCallable_Check(meth)) { + else if(!PyCallable_Check(meth)) { post("py/pyext - Wrong argument types!"); } else { diff --git a/source/clmeth.cpp b/source/clmeth.cpp index a7e6d8d..77f1a73 100644 --- a/source/clmeth.cpp +++ b/source/clmeth.cpp @@ -29,17 +29,11 @@ PyMethodDef pyext::meth_tbl[] = #endif { "_invec", pyext::pyext_invec, METH_VARARGS,"Get input vector" }, { "_outvec", pyext::pyext_outvec, METH_VARARGS,"Get output vector" }, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - -PyMethodDef pyext::attr_tbl[] = -{ { "__setattr__", pyext::pyext_setattr, METH_VARARGS,"Set class attribute" }, { "__getattr__", pyext::pyext_getattr, METH_VARARGS,"Get class attribute" }, - { NULL, NULL,0,NULL }, + {NULL, NULL, 0, NULL} /* Sentinel */ }; - const char *pyext::pyext_doc = "py/pyext - python external object for PD and Max/MSP, (C)2002-2008 Thomas Grill\n" "\n" @@ -58,33 +52,8 @@ const char *pyext::pyext_doc = #endif ; -/* -PyObject* pyext::pyext__init__(PyObject *,PyObject *args) -{ -// post("pyext.__init__ called"); - - Py_INCREF(Py_None); - return Py_None; -} - -PyObject* pyext::pyext__del__(PyObject *,PyObject *args) -{ -// post("pyext.__del__ called"); - - Py_INCREF(Py_None); - return Py_None; -} -*/ - -PyObject* pyext::pyext__str__(PyObject *,PyObject *args) -{ - PyObject *self; - if(!PyArg_ParseTuple(args, "O:pyext__str__",&self)) { - // handle error - ERRINTERNAL(); - return NULL; - } - +PyObject* pyext::pyext__str__(PyObject *self, PyObject *args) +{ return #if PY_MAJOR_VERSION < 3 PyString_FromFormat @@ -94,15 +63,17 @@ PyObject* pyext::pyext__str__(PyObject *,PyObject *args) ("", self); } -PyObject* pyext::pyext_setattr(PyObject *,PyObject *args) +PyObject* pyext::pyext_setattr(PyObject *self, PyObject *args) { - PyObject *self,*name,*val; - if(!PyArg_ParseTuple(args, "OOO:pyext_setattr", &self,&name,&val)) { + PyObject *name,*val; + if(!PyArg_ParseTuple(args, "OO:pyext_setattr",&name,&val)) + { // handle error ERRINTERNAL(); return NULL; } - + + bool handled = false; /* @@ -114,29 +85,35 @@ PyObject* pyext::pyext_setattr(PyObject *,PyObject *args) } */ if(!handled) { - if(PyInstance_Check(self)) - PyDict_SetItem(((PyInstanceObject *)self)->in_dict, name,val); - else + if(PyObject_GenericSetAttr(self, name, val) < 0) { ERRINTERNAL(); + return NULL; + } } Py_INCREF(Py_None); return Py_None; } -PyObject* pyext::pyext_getattr(PyObject *,PyObject *args) +PyObject* pyext::pyext_getattr(PyObject *self, PyObject *args) { - PyObject *self, *name, *ret = NULL; - if(!PyArg_ParseTuple(args, "OO:pyext_getattr", &self, &name)) { + PyObject *name, *ret = NULL; + if(!PyArg_ParseTuple(args, "O:pyext_getattr", &name)) + { // handle error ERRINTERNAL(); + return NULL; } #if PY_MAJOR_VERSION < 3 - if(PyString_Check(name)) { + if(PyString_Check(name)) +#else + if(PyUnicode_Check(name)) +#endif + { +#if PY_MAJOR_VERSION < 3 const char *sname = PyString_AS_STRING(name); #else - if(PyUnicode_Check(name)) { const char *sname = PyUnicode_AsUTF8(name); #endif if(sname) { @@ -162,6 +139,16 @@ PyObject* pyext::pyext_getattr(PyObject *,PyObject *args) ret = Py_False; #endif } + else if(!strcmp(sname, "_canvas_dir")) { + pyext *ext = GetThis(self); + char dir[1024]; + ext->GetCanvasDir(dir, sizeof(dir)); +#if PY_MAJOR_VERSION < 3 + ret = PyString_InternFromString(dir); +#else + ret = PyUnicode_InternFromString(dir); +#endif + } } } @@ -178,7 +165,7 @@ PyObject* pyext::pyext_getattr(PyObject *,PyObject *args) } //! Send message to outlet -PyObject *pyext::pyext_outlet(PyObject *,PyObject *args) +PyObject *pyext::pyext_outlet(PyObject *self, PyObject *args) { bool ok = false; @@ -188,12 +175,11 @@ PyObject *pyext::pyext_outlet(PyObject *,PyObject *args) int sz = PyTuple_GET_SIZE(args); // borrowed references! - PyObject *self,*outl; + PyObject *outl; if( - sz >= 2 && - (self = PyTuple_GET_ITEM(args,0)) != NULL && PyInstance_Check(self) && - (outl = PyTuple_GET_ITEM(args,1)) != NULL && + sz >= 1 && + (outl = PyTuple_GET_ITEM(args,0)) != NULL && #if PY_MAJOR_VERSION < 3 PyInt_Check(outl) #else @@ -208,8 +194,8 @@ PyObject *pyext::pyext_outlet(PyObject *,PyObject *args) PyObject *val; #if 0 - if(sz == 3) { - val = PyTuple_GET_ITEM(args,2); // borrow reference + if(sz == 2) { + val = PyTuple_GET_ITEM(args,1); // borrow reference Py_INCREF(val); tp = PySequence_Check(val); } @@ -217,14 +203,14 @@ PyObject *pyext::pyext_outlet(PyObject *,PyObject *args) tp = false; if(!tp) - val = PySequence_GetSlice(args,2,sz); // new ref + val = PySequence_GetSlice(args,1,sz); // new ref #else - if(sz == 3) { - val = PyTuple_GET_ITEM(args,2); // borrow reference + if(sz == 2) { + val = PyTuple_GET_ITEM(args,1); // borrow reference Py_INCREF(val); } else - val = PyTuple_GetSlice(args,2,sz); // new ref + val = PyTuple_GetSlice(args,1,sz); // new ref #endif int o; @@ -259,11 +245,12 @@ PyObject *pyext::pyext_outlet(PyObject *,PyObject *args) #ifdef FLEXT_THREADS //! Detach threads -PyObject *pyext::pyext_detach(PyObject *,PyObject *args) +PyObject *pyext::pyext_detach(PyObject *self, PyObject *args) { - PyObject *self; int val; - if(!PyArg_ParseTuple(args, "Oi:pyext_detach",&self,&val)) { + if( + !PyArg_ParseTuple(args, "i:pyext_detach",&val) + ) { // handle error PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _detach(self,[0/1/2])"); return NULL; @@ -287,11 +274,12 @@ PyObject *pyext::pyext_detach(PyObject *,PyObject *args) } //! Stop running threads -PyObject *pyext::pyext_stop(PyObject *,PyObject *args) +PyObject *pyext::pyext_stop(PyObject *self, PyObject *args) { - PyObject *self; int val = -1; - if(!PyArg_ParseTuple(args, "O|i:pyext_stop",&self,&val)) { + if( + !PyArg_ParseTuple(args, "|i:pyext_stop",&val) + ) { // handle error PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _stop(self,{wait time})"); return NULL; @@ -323,72 +311,67 @@ PyObject *pyext::pyext_stop(PyObject *,PyObject *args) #if FLEXT_SYS == FLEXT_SYS_PD //! Send message to canvas -PyObject *pyext::pyext_tocanvas(PyObject *,PyObject *args) +PyObject *pyext::pyext_tocanvas(PyObject *self, PyObject *args) { FLEXT_ASSERT(PyTuple_Check(args)); int sz = PyTuple_GET_SIZE(args); bool ok = false; - PyObject *self; // borrowed ref - if( - sz >= 1 && - (self = PyTuple_GET_ITEM(args,0)) != NULL && PyInstance_Check(self) - ) { - pyext *ext = GetThis(self); - if(!ext) { - PyErr_SetString(PyExc_RuntimeError,"pyext - _tocanvas: instance not associated with pd object"); - return NULL; - } - - PyObject *val; - - bool tp = - sz == 2 && - PySequence_Check( - val = PyTuple_GET_ITEM(args,1) // borrowed ref - ); - - if(!tp) - val = PyTuple_GetSlice(args,1,sz); // new ref + pyext *ext = GetThis(self); + if(!ext) { + PyErr_SetString(PyExc_RuntimeError,"pyext - _tocanvas: instance not associated with pd object"); + return NULL; + } - flext::AtomListStatic<16> lst; - const t_symbol *sym = GetPyArgs(lst,val); - if(sym) { - t_glist *gl = ext->thisCanvas(); - if(gl) { - // \TODO find a flext-based non-locking method - sys_lock(); - pd_forwardmess((t_class **)gl,lst.Count(),lst.Atoms()); - sys_unlock(); - } + PyObject *val; + + bool tp = + sz == 2 && + PySequence_Check( + val = PyTuple_GET_ITEM(args,0) // borrowed ref + ); + + if(!tp) + val = PyTuple_GetSlice(args,0,sz); // new ref + + flext::AtomListStatic<16> lst; + const t_symbol *sym = GetPyArgs(lst,val); + if(sym) { + t_glist *gl = ext->thisCanvas(); + if(gl) { + // \TODO find a flext-based non-locking method + sys_lock(); + pd_forwardmess((t_class **)gl,lst.Count(),lst.Atoms()); + sys_unlock(); + } #ifdef FLEXT_DEBUG - else - post("pyext - no parent canvas?!"); + else + post("pyext - no parent canvas?!"); #endif - ok = true; - } - else - post("py/pyext - No data to send"); - - if(!tp) Py_DECREF(val); + ok = true; } + else + post("py/pyext - No data to send"); - if(!ok) { - PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _tocanvas(self,args...)"); - return NULL; - } + if(!tp) Py_DECREF(val); - Py_INCREF(Py_None); - return Py_None; +if(!ok) { + PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _tocanvas(self,args...)"); + return NULL; + } + +Py_INCREF(Py_None); +return Py_None; } #endif -PyObject *pyext::pyext_invec(PyObject *,PyObject *args) +PyObject *pyext::pyext_invec(PyObject *self, PyObject *args) { - PyObject *self; int val = -1; - if(!PyArg_ParseTuple(args, "O|i:pyext_invec",&self,&val)) { + if( + !PyArg_ParseTuple(args, "|i:pyext_invec",&val) + ) { // handle error PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _invec(self,inlet)"); return NULL; @@ -413,11 +396,12 @@ PyObject *pyext::pyext_invec(PyObject *,PyObject *args) return Py_None; } -PyObject *pyext::pyext_outvec(PyObject *,PyObject *args) +PyObject *pyext::pyext_outvec(PyObject *self, PyObject *args) { - PyObject *self; int val = -1; - if(!PyArg_ParseTuple(args, "O|i:pyext_outvec",&self,&val)) { + if( + !PyArg_ParseTuple(args, "|i:pyext_outvec",&val) + ) { // handle error PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _outvec(self,inlet)"); return NULL; diff --git a/source/modmeth.cpp b/source/modmeth.cpp index 7d7497a..2325f10 100644 --- a/source/modmeth.cpp +++ b/source/modmeth.cpp @@ -52,6 +52,20 @@ const char *pybase::py_doc = "_tuple(args...): Make a tuple from args\n" ; +#if PY_MAJOR_VERSION >= 3 +PyModuleDef pybase::pyext_module_def = { + PyModuleDef_HEAD_INIT, // PyModuleDef_Base m_base + PYEXT_MODULE, // const char *m_name + py_doc, // const char *m_doc + -1, // Py_ssize_t m_size + func_tbl, // PyMethodDef *m_methods + NULL, // PyModuleDef_Slot *m_slots + NULL, // traverseproc m_traverse + NULL, // inquiry m_clear + NULL // freefunc m_free +}; +#endif + #ifdef FLEXT_THREADS void pybase::tick(void *) { @@ -235,10 +249,10 @@ PyObject *pybase::py_getvalue(PyObject *self,PyObject *args) PyObject *ret; if( - sz == 1 && + sz == 1 && (sym = pyObject_AsSymbol(PyTuple_GET_ITEM(args,0))) != NULL ) { - float f; + t_float f; if(value_getfloat(const_cast(sym),&f)) { post("py/pyext - Could not get value '%s'",GetString(sym)); Py_INCREF(Py_None); diff --git a/source/pyargs.cpp b/source/pyargs.cpp index 6845f98..0dd3c15 100644 --- a/source/pyargs.cpp +++ b/source/pyargs.cpp @@ -162,8 +162,9 @@ const t_symbol *pybase::getone(t_atom &at,PyObject *arg) else if(pySymbol_Check(arg)) { flext::SetSymbol(at,pySymbol_AS_SYMBOL(arg)); return flext::sym_symbol; } #if PY_MAJOR_VERSION < 3 else if(PyString_Check(arg)) { flext::SetString(at,PyString_AS_STRING(arg)); return flext::sym_symbol; } -#endif +#else else if(PyUnicode_Check(arg)) { flext::SetString(at,PyUnicode_AsUTF8(arg)); return flext::sym_symbol; } +#endif else { PyObject *tp = PyObject_Type(arg); PyObject *stp = tp?PyObject_Str(tp):NULL; diff --git a/source/pybase.cpp b/source/pybase.cpp index abaf583..961fe34 100644 --- a/source/pybase.cpp +++ b/source/pybase.cpp @@ -8,16 +8,22 @@ WARRANTIES, see the file, "license.txt," in this distribution. #include "pybase.h" #include - +#include #if FLEXT_OS == FLEXT_OS_WIN #include #elif FLEXT_OS == FLEXT_OS_MAC #include #endif +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define WSTRINGIFY(x) L ## #x +#define TOWSTRING(x) WSTRINGIFY(x) + static PyMethodDef StdOut_Methods[] = { { "write", pybase::StdOut_Write, 1 }, + { "flush", pybase::StdOut_Flush, 1 }, { NULL, NULL, } }; @@ -79,6 +85,8 @@ void pybase::FreeThreadState() PyFifo pybase::qufifo; flext::ThrCond pybase::qucond; +std::atomic pybase::qurunning; +ThrCtrl pybase::qucondctrl {&pybase::qucond, &pybase::qurunning}; // attaching the qucond to the controller to send a signal when closing pd #endif @@ -98,14 +106,76 @@ void initsymbol(); void initsamplebuffer(); void initbundle(); +MOD_INIT(pyext) +{ + PyObject *init_ret = pybase::pyext_init(); + return MOD_SUCCESS_VAL(init_ret); +} + +PyObject *pybase::pyext_init() +{ + if(module_obj == NULL) { + PyObject *m; + + MOD_DEF(m, PYEXT_MODULE, py_doc, func_tbl); + + PyModule_AddStringConstant(m,"__doc__",(char *)py_doc); + + // add symbol type + initsymbol(); + PyModule_AddObject(m,"Symbol",(PyObject *)&pySymbol_Type); + + // pre-defined symbols + PyModule_AddObject(m,"_s_",(PyObject *)pySymbol__); + PyModule_AddObject(m,"_s_bang",(PyObject *)pySymbol_bang); + PyModule_AddObject(m,"_s_list",(PyObject *)pySymbol_list); + PyModule_AddObject(m,"_s_symbol",(PyObject *)pySymbol_symbol); + PyModule_AddObject(m,"_s_float",(PyObject *)pySymbol_float); + PyModule_AddObject(m,"_s_int",(PyObject *)pySymbol_int); + + // add samplebuffer type + initsamplebuffer(); + PyModule_AddObject(m,"Buffer",(PyObject *)&pySamplebuffer_Type); + + // add message bundle type + initbundle(); + PyModule_AddObject(m,"Bundle",(PyObject *)&pyBundle_Type); + + module_obj = m; + module_dict = PyModule_GetDict(m); // borrowed reference + } + + return module_obj; +} + void pybase::lib_setup() -{ +{ +#ifdef PY_INTERPRETER + { +#if PY_MAJOR_VERSION < 3 + static char py_program_name[] = TOSTRING(PY_INTERPRETER); +#else + static wchar_t py_program_name[] = TOWSTRING(PY_INTERPRETER); +#endif + Py_SetProgramName(py_program_name); + } + +#endif + post(""); post("------------------------------------------------"); post("py/pyext %s - python script objects",PY__VERSION); post("(C)2002-2019 Thomas Grill - http://grrrr.org/ext"); post(""); post("using Python %s",Py_GetVersion()); + #ifdef FLEXT_THREADS + post(""); + post("Built with multithreaded flext"); + #endif + + #ifdef PY_USE_GIL + post("Using Python GIL"); + #endif #ifdef FLEXT_DEBUG post(""); @@ -124,6 +194,8 @@ void pybase::lib_setup() // ------------------------------------------------------------- + PyImport_AppendInittab(PYEXT_MODULE, MOD_INIT_NAME(pyext)); + Py_Initialize(); #ifdef FLEXT_DEBUG @@ -150,22 +222,30 @@ void pybase::lib_setup() #endif // sys.argv must be set to empty tuple +#if PY_MAJOR_VERSION < 3 const char *nothing = ""; PySys_SetArgv(0,const_cast(¬hing)); +#else + const wchar_t *nothing = L""; + PySys_SetArgv(0,const_cast(¬hing)); +#endif - // register/initialize pyext module only once! - module_obj = Py_InitModule(const_cast(PYEXT_MODULE), func_tbl); - module_dict = PyModule_GetDict(module_obj); // borrowed reference - - PyModule_AddStringConstant(module_obj,"__doc__",(char *)py_doc); - + // import the pyext module to ensure init + PyImport_ImportModule(PYEXT_MODULE); + // redirect stdout PyObject* py_out; - py_out = Py_InitModule(const_cast("stdout"), StdOut_Methods); + + { + MOD_DEF(py_out, "stdout", "pyext standard output", StdOut_Methods); + } PySys_SetObject(const_cast("stdout"), py_out); - py_out = Py_InitModule(const_cast("stderr"), StdOut_Methods); + + { + MOD_DEF(py_out, "stderr", "pyext standard error", StdOut_Methods); + } PySys_SetObject(const_cast("stderr"), py_out); - + // get garbage collector function PyObject *gcobj = PyImport_ImportModule("gc"); if(gcobj) { @@ -173,29 +253,13 @@ void pybase::lib_setup() Py_DECREF(gcobj); } +#if PY_MAJOR_VERSION < 3 builtins_obj = PyImport_ImportModule("__builtin__"); +#else + builtins_obj = PyImport_ImportModule("builtins"); +#endif builtins_dict = PyModule_GetDict(builtins_obj); // borrowed reference - // add symbol type - initsymbol(); - PyModule_AddObject(module_obj,"Symbol",(PyObject *)&pySymbol_Type); - - // pre-defined symbols - PyModule_AddObject(module_obj,"_s_",(PyObject *)pySymbol__); - PyModule_AddObject(module_obj,"_s_bang",(PyObject *)pySymbol_bang); - PyModule_AddObject(module_obj,"_s_list",(PyObject *)pySymbol_list); - PyModule_AddObject(module_obj,"_s_symbol",(PyObject *)pySymbol_symbol); - PyModule_AddObject(module_obj,"_s_float",(PyObject *)pySymbol_float); - PyModule_AddObject(module_obj,"_s_int",(PyObject *)pySymbol_int); - - // add samplebuffer type - initsamplebuffer(); - PyModule_AddObject(module_obj,"Buffer",(PyObject *)&pySamplebuffer_Type); - - // add message bundle type - initbundle(); - PyModule_AddObject(module_obj,"Bundle",(PyObject *)&pyBundle_Type); - // ------------------------------------------------------------- #if FLEXT_SYS == FLEXT_SYS_PD && defined(PD_DEVEL_VERSION) && defined(PY_USE_INOFFICIAL) // add PD paths @@ -238,6 +302,7 @@ FLEXT_LIB_SETUP(py,pybase::lib_setup) pybase::pybase() : module(NULL) , dict(NULL) + , respond(false) #ifdef FLEXT_THREADS , thrcount(0) , shouldexit(false),stoptick(0) @@ -409,7 +474,7 @@ void pybase::OpenEditor() #else // thanks to Tim Blechmann - char *editor = getenv("EDITOR"); + const char *editor = getenv("EDITOR"); if(!editor) { // || !strcmp(editor, "/usr/bin/nano") || !strcmp(editor, "/usr/bin/pico") || !strcmp(editor, "/usr/bin/vi")) { // no environment variable or console text editor found ... use idle instead (should have come with Python) @@ -432,13 +497,32 @@ void pybase::SetArgs() // script arguments int argc = args.Count(); const t_atom *argv = args.Atoms(); +#if PY_MAJOR_VERSION < 3 char **sargv = new char *[argc+1]; +#else + wchar_t **sargv = new wchar_t *[argc+1]; +#endif for(int i = 0; i <= argc; ++i) { +#if PY_MAJOR_VERSION < 3 sargv[i] = new char[256]; - if(!i) +#else + sargv[i] = new wchar_t[256]; +#endif + if(!i) { +#if PY_MAJOR_VERSION < 3 strcpy(sargv[i],"py/pyext"); - else +#else + wcscpy(sargv[i],L"py/pyext"); +#endif + } else { +#if PY_MAJOR_VERSION < 3 GetAString(argv[i-1],sargv[i],255); +#else + char *arg = new char[256]; + GetAString(argv[i-1],arg,255); + mbstowcs(sargv[i],arg,255); +#endif + } } // the arguments to the module are only recognized once! (at first use in a patcher) @@ -494,7 +578,7 @@ static bool getmodulesub(const char *mod,char *dir,int len,const char *ext) return name != NULL; #elif FLEXT_SYS == FLEXT_SYS_MAX short path; - long type; + unsigned int type; char smod[1024]; strcpy(smod,mod); strcat(smod,ext); @@ -829,6 +913,12 @@ PyObject* pybase::StdOut_Write(PyObject* self, PyObject* args) return Py_None; } +// dummy flush method, since some logging libraries call this +PyObject* pybase::StdOut_Flush(PyObject* self, PyObject* args) +{ + Py_INCREF(Py_None); + return Py_None; +} class work_data { @@ -930,7 +1020,7 @@ void pybase::quworker(thr_params *) FifoEl *el; ThrState my = FindThreadState(); - for(;;) { + while(qurunning) { while((el = qufifo.Get())) { ++el->th->thrcount; // \todo this should be atomic { @@ -944,7 +1034,6 @@ void pybase::quworker(thr_params *) } qucond.Wait(); } - // we never end if(false) { ThrLock lock(my); @@ -981,7 +1070,11 @@ bool pybase::collect() PyObject *ret = PyObject_CallObject(gcollect,NULL); if(ret) { #ifdef FLEXT_DEBUG +#if PY_MAJOR_VERSION < 3 int refs = PyInt_AsLong(ret); +#else + int refs = PyLong_AsLong(ret); +#endif if(refs) post("py/pyext - Garbage collector reports %i unreachable objects",refs); #endif Py_DECREF(ret); diff --git a/source/pybase.h b/source/pybase.h index 8a3105e..9716bf7 100644 --- a/source/pybase.h +++ b/source/pybase.h @@ -13,6 +13,16 @@ WARRANTIES, see the file, "license.txt," in this distribution. #include "pysymbol.h" #include "pybuffer.h" #include "pybundle.h" +#include "ceval.h" +#include "thrctrl.h" +#include +#include + +#if PY_MAJOR_VERSION >= 3 + #ifdef Py_BEGIN_ALLOW_THREADS + #define PY_USE_GIL + #endif // Py_BEGIN_ALLOW_THREADS +#endif #ifdef FLEXT_THREADS # ifdef PY_USE_GIL @@ -24,6 +34,26 @@ WARRANTIES, see the file, "license.txt," in this distribution. typedef int ThrState; // dummy #endif +#if PY_MAJOR_VERSION < 3 +#define MOD_ERROR_VAL +#define MOD_SUCCESS_VAL(val) +#define MOD_INIT_NAME(name) init##name +#define MOD_INIT(name) void MOD_INIT_NAME(name)(void) +#define MOD_DEF(ob, name, doc, methods) \ + ob = Py_InitModule3(name, methods, doc); +#else +#define MOD_ERROR_VAL NULL +#define MOD_SUCCESS_VAL(val) val +#define MOD_INIT_NAME(name) PyInit_##name +#define MOD_INIT(name) PyMODINIT_FUNC MOD_INIT_NAME(name)(void) +#define MOD_DEF(ob, name, doc, methods) \ + static struct PyModuleDef moduledef = { \ + PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \ + ob = PyModule_Create(&moduledef); +#endif + +MOD_INIT(pyext); + class pybase : public flext { @@ -38,6 +68,7 @@ class pybase static const t_symbol *GetPyArgs(AtomList &lst,PyObject *pValue,int offs = 0); static const t_symbol *GetPyAtom(AtomList &lst,PyObject *pValue); + static PyObject *pyext_init(); static void lib_setup(); protected: @@ -110,7 +141,10 @@ class pybase static PyObject *module_obj,*module_dict; static PyObject *builtins_obj,*builtins_dict; - static PyMethodDef func_tbl[],attr_tbl[]; + static PyMethodDef func_tbl[]; +#if PY_MAJOR_VERSION >= 3 + static PyModuleDef pyext_module_def; +#endif static PyObject *py__doc__(PyObject *,PyObject *args); static PyObject *py_send(PyObject *,PyObject *args); @@ -189,6 +223,9 @@ class pybase static PyFifo qufifo; static ThrCond qucond; + /* The two following variables provide means of shutting the thread system down. */ + static std::atomic qurunning; + static ThrCtrl qucondctrl; #ifndef PY_USE_GIL static ThrState pythrsys; @@ -269,6 +306,7 @@ class pybase }; static PyObject* StdOut_Write(PyObject* Self, PyObject* Args); + static PyObject* StdOut_Flush(PyObject* Self, PyObject* Args); }; #endif diff --git a/source/pybuffer.cpp b/source/pybuffer.cpp index ab3bcfb..86c975f 100644 --- a/source/pybuffer.cpp +++ b/source/pybuffer.cpp @@ -7,18 +7,22 @@ WARRANTIES, see the file, "license.txt," in this distribution. */ #include "pybase.h" - #undef PY_ARRAYS - -#if defined(PY_NUMERIC) || defined(PY_NUMPY) || defined(PY_NUMARRAY) - #define PY_ARRAYS 1 +#if PY_MAJOR_VERSION >= 3 + #include + #define PY_ARRAYS defined(NUMPY_CORE_INCLUDE_NUMPY_ARRAYOBJECT_H_) + #define PY_NUMPY PY_ARRAYS +#else + #if defined(PY_NUMERIC) || defined(PY_NUMPY) || defined(PY_NUMARRAY) + #define PY_ARRAYS 1 + #endif #endif #ifdef PY_ARRAYS #ifdef PY_NUMARRAY -# if FLEXT_OS == FLEXT_OS_MAC +# ifdef PY_USE_FRAMEWORK # include # else # include @@ -30,8 +34,13 @@ inline bool arrsupport() { return numtype != tAny; } #else # if defined(PY_NUMPY) # include +# if _FLEXT_NEED_SAMPLE_CONV +# define PY_NUMPY_BUFFER_FORMAT "f" +# else +# define PY_NUMPY_BUFFER_FORMAT "d" +# endif # else -# if FLEXT_OS == FLEXT_OS_MAC +# ifdef PY_USE_FRAMEWORK # include # else # include @@ -43,6 +52,7 @@ inline bool arrsupport() { return numtype != tAny; } #endif #endif +#include "pycompat.h" PyObject *pybase::py_arraysupport(PyObject *self,PyObject *args) { @@ -57,14 +67,12 @@ PyObject *pybase::py_arraysupport(PyObject *self,PyObject *args) } -// PD defines a T_OBJECT symbol +// undefine PD's T_OBJECT to avoid conflict with Python's #undef T_OBJECT -#if FLEXT_OS == FLEXT_OS_MAC -#include "Python/bufferobject.h" +#ifdef PY_USE_FRAMEWORK #include "Python/structmember.h" #else -#include "bufferobject.h" #include "structmember.h" #endif @@ -108,9 +116,10 @@ static int buffer_init(PyObject *obj, PyObject *args, PyObject *kwds) #if PY_MAJOR_VERSION < 3 else if(PyString_Check(arg)) self->sym = flext::MakeSymbol(PyString_AS_STRING(arg)); -#endif +#else else if(PyUnicode_Check(arg)) self->sym = flext::MakeSymbol(PyUnicode_AsUTF8(arg)); +#endif else ret = -1; Py_DECREF(arg); @@ -168,7 +177,8 @@ static PyObject *buffer_dirty(PyObject *obj) static PyObject *buffer_resize(PyObject *obj,PyObject *args,PyObject *kwds) { - flext::buffer *b = ((pySamplebuffer *)obj)->buf; + pySamplebuffer *self = reinterpret_cast(obj); + flext::buffer *b = self->buf; if(b) { int frames,keep = 1,zero = 1; static char const *kwlist[] = {"frames", "keep", "zero", NULL}; @@ -192,8 +202,6 @@ static PyMethodDef buffer_methods[] = { {NULL} /* Sentinel */ }; - - // support the buffer protocol static Py_ssize_t buffer_readbuffer(PyObject *obj, Py_ssize_t segment, void **ptrptr) @@ -228,11 +236,55 @@ static Py_ssize_t buffer_charbuffer(PyObject *obj, Py_ssize_t segment, return b->Channels()*b->Frames()*sizeof(t_sample); } +static int buffer_getbuffer(PyObject *obj, Py_buffer *view, int flags) { + pySamplebuffer *self = reinterpret_cast(obj); + flext::buffer *b = self->buf; + const Py_ssize_t len = b->Channels()*b->Frames()*sizeof(t_sample); + + if(!(flags & PyBUF_STRIDES)) { + view->obj = NULL; + PyErr_SetString(PyExc_BufferError, "PyBUF_STRIDES is required"); + return -1; + } + + if(!(flags & PyBUF_FORMAT)) { + view->obj = NULL; + PyErr_SetString(PyExc_BufferError, "PyBUF_FORMAT is required"); + return -1; + } + + std::pair *shape_strides = new std::pair(); + shape_strides->first = b->Channels() * b->Frames(); + shape_strides->second = sizeof(FLEXT_ARRAYTYPE); + + view->buf = (void *) b->Data(); + view->obj = obj; + view->len = len; + view->readonly = false; + view->itemsize = sizeof(t_sample); + // adapt to newer versions of numpy (PY_NUMPY_BUFFER_FORMAT is deleted in versions >= 1.7.0) + #ifndef PY_NUMPY_BUFFER_FORMAT + view->format = (flags & PyBUF_FORMAT) ? ((Py_buffer*)view)->format : NULL; + #else + view->format = (flags & PyBUF_FORMAT) ? (char *) PY_NUMPY_BUFFER_FORMAT : NULL; + #endif // !1 + view->ndim = 1; + view->shape = &shape_strides->first; + view->strides = &shape_strides->second; + view->suboffsets = NULL; + view->internal = (void *) shape_strides; + + Py_INCREF(self); + return 0; +} + +static void buffer_releasebuffer(PyObject *obj, Py_buffer *view) { + delete (std::pair *) view->internal; +} + static PyBufferProcs buffer_as_buffer = { - buffer_readbuffer, - buffer_writebuffer, - buffer_segcount, - buffer_charbuffer + .bf_getbuffer = buffer_getbuffer, + .bf_releasebuffer = buffer_releasebuffer }; static Py_ssize_t buffer_length(PyObject *s) @@ -246,6 +298,9 @@ static PyObject *buffer_item(PyObject *s,Py_ssize_t i) pySamplebuffer *self = reinterpret_cast(s); PyObject *ret; if(self->buf) { + if(i < 0) { + i += self->buf->Frames(); + } if (i < 0 || i >= self->buf->Frames()) { PyErr_SetString(PyExc_IndexError,"Index out of range"); ret = NULL; @@ -279,16 +334,32 @@ PyObject *arrayfrombuffer(PyObject *buf,int c,int n) #ifdef PY_NUMARRAY arr = (PyObject *)NA_NewAllFromBuffer(c == 1?1:2,shape,numtype,buf,0,0,NA_ByteOrder(),1,1); #else - void *data; - Py_ssize_t len; - int err = PyObject_AsWriteBuffer(buf,&data,&len); + Py_buffer view; + int err = PyObject_GetBuffer(buf, &view, PyBUF_WRITABLE | PyBUF_FORMAT | PyBUF_STRIDES); if(!err) { - FLEXT_ASSERT(len <= n*c*sizeof(t_sample)); + FLEXT_ASSERT(view.len <= n*c*sizeof(t_sample)); // Py_INCREF(buf); // ATTENTION... this won't be released any more!! # ifdef PY_NUMPY - arr = PyArray_NewFromDescr(&PyArray_Type,PyArray_DescrNewFromType(numtype),c == 1?1:2,shape,0,(char *)data,NPY_WRITEABLE|NPY_C_CONTIGUOUS,NULL); + // the results of PyObject_GetBuffer() differ a bit depending on + // whether we're dealing with a pySamplebuffer (Pd array) or a numpy + // array (Pd signal). + // + // for pySamplebuffer, we get stride information that must be used + // to correctly deal with float32 Pd. + // + // for numpy arrays, we get strides=1 (due to format="B"), which + // breaks PyArray_NewFromDescr(), so instead we pass strides=NULL. + bool use_strides = pySamplebuffer_Check(buf); + npy_intp strides_arr[2] = {0, 0}; + npy_intp *strides = NULL; + if(use_strides) { + strides_arr[0] = *view.strides; + strides = strides_arr; + } + arr = PyArray_NewFromDescr(&PyArray_Type, PyArray_DescrNewFromType(numtype), + c == 1 ? 1 : 2, shape, strides, (char *) view.buf, NPY_ARRAY_WRITEABLE | NPY_ARRAY_C_CONTIGUOUS, NULL); # else - arr = PyArray_FromDimsAndData(c == 1?1:2,shape,numtype,(char *)data); + arr = PyArray_FromDimsAndData(c == 1?1:2,shape,numtype,(char *)view.buf); # endif } else { @@ -460,15 +531,140 @@ static PyObject *buffer_repeat(PyObject *s,Py_ssize_t rep) return NULL; } +static PyObject *buffer_subscript(PyObject *s, PyObject *item) +{ + pySamplebuffer *self = reinterpret_cast(s); + PyObject *ret; + +#ifdef PY_ARRAYS + if(arrsupport()) { + if(self->buf) { + const int n = self->buf->Frames(); + const int c = self->buf->Channels(); + + PyObject *nobj = arrayfrombuffer((PyObject *) self, c, n); + ret = PyObject_GetItem(nobj, item); + Py_DECREF(nobj); + } else { + Py_INCREF(Py_None); + ret = Py_None; + } + } + else +#endif + if(PyIndex_Check(item)) { + Py_ssize_t i; + i = PyNumber_AsSsize_t(item, PyExc_IndexError); + if(i == -1 && PyErr_Occurred()) { + ret = NULL; + } else { + if(i < 0) + i += PyList_GET_SIZE(self); + + ret = buffer_item(s, i); + } + } else if(PySlice_Check(item)) { + PyErr_SetString(PyExc_RuntimeError, "No array support"); + ret = NULL; + } else { + PyErr_Format( + PyExc_TypeError, + "buffer indices must be integers or slices, not %.200s", + item->ob_type->tp_name + ); + ret = NULL; + } + + return ret; +} + +static int buffer_ass_subscript(PyObject *s, PyObject *item, PyObject *value) +{ + pySamplebuffer *self = reinterpret_cast(s); + int ret; + + if(PyIndex_Check(item)) { + Py_ssize_t i; + i = PyNumber_AsSsize_t(item, PyExc_IndexError); + if(i == -1 && PyErr_Occurred()) { + ret = -1; + } else { + ret = buffer_ass_item(s, i, value); + } + } else if(PySlice_Check(item)) { +#ifdef PY_ARRAYS + if(arrsupport()) { + if(self->buf) { + const int n = self->buf->Frames(); + const int c = self->buf->Channels(); + + PyArrayObject *out = (PyArrayObject *) PyArray_ContiguousFromObject(value, numtype, 1, 2); + + if(!out) { + // exception already set + ret = -1; + } else if(out->nd != 1) { + PyErr_SetString(PyExc_NotImplementedError, "Multiple dimensions not supported yet"); + ret = -1; + } else { + const t_sample *src = (t_sample *) out->data; + + Py_ssize_t ilow, ihigh, istep; + + if(PySlice_Unpack(item, &ilow, &ihigh, &istep) < 0) { + ret = -1; + } else { + Py_ssize_t dlen = PySlice_AdjustIndices(n, &ilow, &ihigh, istep); + int slen = out->dimensions[0]; + int cnt = slen < dlen ? slen : dlen; + flext::buffer::Element *dst = self->buf->Data() + ilow; + for(int i = 0; i < cnt; i += istep) { + dst[i] = src[i]; + } + + self->dirty = true; + ret = 0; + } + } + + Py_XDECREF(out); + } else { + PyErr_SetString(PyExc_ValueError,"Buffer is not assigned"); + ret = -1; + } + } + else +#endif + { + PyErr_SetString(PyExc_RuntimeError, "No array support"); + ret = -1; + } + } else { + PyErr_Format( + PyExc_TypeError, + "buffer indices must be integers or slices, not %.200s", + item->ob_type->tp_name + ); + ret = -1; + } + + return ret; +} static PySequenceMethods buffer_as_seq = { - buffer_length, /* inquiry sq_length; __len__ */ - buffer_concat, /* __add__ */ - buffer_repeat, /* __mul__ */ - buffer_item, /* intargfunc sq_item; __getitem__ */ - buffer_slice, /* intintargfunc sq_slice; __getslice__ */ - buffer_ass_item, /* intobjargproc sq_ass_item; __setitem__ */ - buffer_ass_slice, /* intintobjargproc sq_ass_slice; __setslice__ */ + buffer_length, /* lenfunc sq_length __len__ */ + buffer_concat, /* binaryfunc sq_concat __add__ */ + buffer_repeat, /* ssizeargfunc sq_repeat __mul__ */ + NULL, /* ssizeargfunc sq_item; __getitem__ */ + NULL, /* intintargfunc sq_slice; __getslice__ */ + NULL, /* ssizeobjargproc sq_ass_item __setitem__ */ + NULL, /* intintobjargproc sq_ass_slice; __setslice__ */ +}; + +static PyMappingMethods buffer_as_mapping = { + buffer_length, // lenfunc mp_length + buffer_subscript, // binaryfunc mp_subscript + buffer_ass_subscript // objobjargproc mp_ass_subscript }; static PyObject *buffer_iter(PyObject *s) @@ -723,6 +919,20 @@ static PyObject *buffer_inplace_divide(PyObject *s,PyObject *op) } #endif +static PyObject *buffer_inplace_true_divide(PyObject *s,PyObject *op) +{ + pySamplebuffer *self = reinterpret_cast(s); + PyObject *nobj = buffer_slice(s); + if(nobj) { + PyObject *ret = PyNumber_InPlaceTrueDivide(nobj,op); + if(ret == nobj) self->dirty = true; + Py_DECREF(nobj); + return ret; + } + else + return NULL; +} + static PyObject *buffer_inplace_floor_divide(PyObject *s,PyObject *op) { pySamplebuffer *self = reinterpret_cast(s); @@ -768,57 +978,59 @@ static PyObject *buffer_inplace_power(PyObject *s,PyObject *op1,PyObject *op2) static PyNumberMethods buffer_as_number = { - (binaryfunc)buffer_add, /*nb_add*/ - (binaryfunc)buffer_subtract, /*nb_subtract*/ - (binaryfunc)buffer_multiply, /*nb_multiply*/ + (binaryfunc)buffer_add, // binaryfunc nb_add + (binaryfunc)buffer_subtract, // binaryfunc nb_subtract + (binaryfunc)buffer_multiply, // binaryfunc nb_multiply #if PY_MAJOR_VERSION < 3 - (binaryfunc)buffer_divide, /*nb_divide*/ -#else - 0, /*nb_divide not supported */ + (binaryfunc)buffer_divide, // binaryfunc nb_divide #endif - (binaryfunc)buffer_remainder, /*nb_remainder*/ - (binaryfunc)buffer_divmod, /*nb_divmod*/ - (ternaryfunc)buffer_power, /*nb_power*/ - (unaryfunc)buffer_negative, - (unaryfunc)buffer_pos, /*nb_pos*/ - (unaryfunc)buffer_absolute, /* (unaryfunc)buffer_abs, */ - 0, //(inquiry)buffer_nonzero, /*nb_nonzero*/ - 0, /*nb_invert*/ - 0, /*nb_lshift*/ - 0, /*nb_rshift*/ - 0, /*nb_and*/ - 0, /*nb_xor*/ - 0, /*nb_or*/ - (coercion)buffer_coerce, /*nb_coerce*/ - 0, /*nb_int*/ - 0, /*nb_long*/ - 0, /*nb_float*/ - 0, /*nb_oct*/ - 0, /*nb_hex*/ - (binaryfunc)buffer_inplace_add, /* nb_inplace_add */ - (binaryfunc)buffer_inplace_subtract, /* nb_inplace_subtract */ - (binaryfunc)buffer_inplace_multiply, /* nb_inplace_multiply */ + (binaryfunc)buffer_remainder, // nb_binaryfunc remainder + (binaryfunc)buffer_divmod, // binaryfunc nb_divmod + (ternaryfunc)buffer_power, // ternaryfunc nb_power + (unaryfunc)buffer_negative, // unaryfunc nb_negative + (unaryfunc)buffer_pos, // unaryfunc nb_pos + (unaryfunc)buffer_absolute, // unaryfunc np_absolute + 0, //(inquiry)buffer_nonzero, // inquiry nb_nonzero + 0, // unaryfunc nb_invert + 0, // binaryfunc nb_lshift + 0, // binaryfunc nb_rshift + 0, // binaryfunc nb_and + 0, // binaryfunc nb_xor + 0, // binaryfunc nb_or #if PY_MAJOR_VERSION < 3 - (binaryfunc)buffer_inplace_divide, /* nb_inplace_divide */ + (coercion)buffer_coerce, // coercion nb_coerce +#endif + 0, // unaryfunc nb_int +#if PY_MAJOR_VERSION < 3 + 0, // unaryfunc nb_long + 0, // unaryfunc nb_float + 0, // unaryfunc nb_oct + 0, // unaryfunc nb_hex #else - 0, /* nb_inplace_divide not supported */ + 0, // void *nb_reserved + 0, // unaryfunc nb_float +#endif + (binaryfunc)buffer_inplace_add, // binaryfunc nb_inplace_add + (binaryfunc)buffer_inplace_subtract, // binaryfunc nb_inplace_subtract + (binaryfunc)buffer_inplace_multiply, // binaryfunc nb_inplace_multiply +#if PY_MAJOR_VERSION < 3 + (binaryfunc)buffer_inplace_divide, // binaryfunc nb_inplace_divide #endif - (binaryfunc)buffer_inplace_remainder, /* nb_inplace_remainder */ - (ternaryfunc)buffer_inplace_power, /* nb_inplace_power */ - 0, /* nb_inplace_lshift */ - 0, /* nb_inplace_rshift */ - 0, /* nb_inplace_and */ - 0, /* nb_inplace_xor */ - 0, /* nb_inplace_or */ - (binaryfunc)buffer_floor_divide, /* nb_floor_divide */ - (binaryfunc)buffer_true_divide, /* nb_true_divide */ - (binaryfunc)buffer_inplace_floor_divide, /* nb_inplace_floor_divide */ -// buffer_inplace_div, /* nb_inplace_true_divide */ + (binaryfunc)buffer_inplace_remainder, // binaryfunc nb_inplace_remainder + (ternaryfunc)buffer_inplace_power, // ternaryfunc nb_inplace_power + 0, // binaryfunc nb_inplace_lshift + 0, // binaryfunc nb_inplace_rshift + 0, // binaryfunc nb_inplace_and + 0, // binaryfunc nb_inplace_xor + 0, // binaryfunc nb_inplace_or + (binaryfunc)buffer_floor_divide, // binaryfunc nb_floor_divide + (binaryfunc)buffer_true_divide, // binaryfunc nb_true_divide + (binaryfunc)buffer_inplace_floor_divide, // binaryfunc nb_inplace_floor_divide + (binaryfunc)buffer_inplace_true_divide, // binaryfunc nb_inplace_true_divide }; PyTypeObject pySamplebuffer_Type = { - PyObject_HEAD_INIT(NULL) - 0, /*ob_size*/ + PyVarObject_HEAD_INIT(NULL, 0) "Buffer", /*tp_name*/ sizeof(pySamplebuffer), /*tp_basicsize*/ 0, /*tp_itemsize*/ @@ -830,14 +1042,18 @@ PyTypeObject pySamplebuffer_Type = { buffer_repr, /*tp_repr*/ &buffer_as_number, /*tp_as_number*/ &buffer_as_seq, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ + &buffer_as_mapping, /*tp_as_mapping*/ buffer_hash, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ &buffer_as_buffer, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT /*| Py_TPFLAGS_BASETYPE*/, /*tp_flags*/ + Py_TPFLAGS_DEFAULT /*| Py_TPFLAGS_BASETYPE*/ /*tp_flags*/ +#if PY_MAJOR_VERSION < 3 + | Py_TPFLAGS_HAVE_NEWBUFFER +#endif + , "Samplebuffer objects", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ @@ -860,14 +1076,26 @@ PyTypeObject pySamplebuffer_Type = { // Must have this as a function because the import_array macro in numpy version 1.01 strangely has a return statement included. // Furthermore the import error printout from this macro is ugly, but we accept that for now, waiting for later numpy updates to fix all of this. +// The situation is further complicated by Python 3, where numpy's import_array returns NULL... #ifdef PY_ARRAYS -static void __import_array__() +#ifdef PY_NUMARRAY +#define IMPORT_ARRAY_RET_TYPE void +#define IMPORT_ARRAY_RET_VALUE +#elif PY_MAJOR_VERSION < 3 +#define IMPORT_ARRAY_RET_TYPE void +#define IMPORT_ARRAY_RET_VALUE +#else +#define IMPORT_ARRAY_RET_TYPE void * +#define IMPORT_ARRAY_RET_VALUE NULL +#endif +static IMPORT_ARRAY_RET_TYPE __import_array__() { #ifdef PY_NUMARRAY import_libnumarray(); #else import_array(); #endif + return IMPORT_ARRAY_RET_VALUE; } #endif diff --git a/source/pybuffer.h b/source/pybuffer.h index d405a22..8c98255 100644 --- a/source/pybuffer.h +++ b/source/pybuffer.h @@ -15,7 +15,7 @@ WARRANTIES, see the file, "license.txt," in this distribution. #error You need at least flext version 0.5.0 #endif -#if FLEXT_OS == FLEXT_OS_MAC +#ifdef PY_USE_FRAMEWORK #include #else #include @@ -60,10 +60,10 @@ inline PyObject *pySamplebuffer_FromString(PyObject *str) #if PY_MAJOR_VERSION < 3 if(PyString_Check(str)) cstr = PyString_AsString(str); - else -#endif +#else if(PyUnicode_Check(str)) cstr = PyUnicode_AsUTF8(str); +#endif else PyErr_SetString(PyExc_TypeError, "Type must be string or unicode"); return pySamplebuffer_FromString(cstr); diff --git a/source/pybundle.cpp b/source/pybundle.cpp index 8f136ec..b2512d6 100644 --- a/source/pybundle.cpp +++ b/source/pybundle.cpp @@ -108,7 +108,7 @@ static PyObject *bundle_append(PyObject *self,PyObject *args) int o; if(sz > 2 && - (tg = PyTuple_GET_ITEM(args,0)) != NULL && PyInstance_Check(tg) && + (tg = PyTuple_GET_ITEM(args,0)) != NULL && (outl = PyTuple_GET_ITEM(args,1)) != NULL && #if PY_MAJOR_VERSION < 3 PyInt_Check(outl) @@ -195,8 +195,7 @@ static PyMethodDef bundle_methods[] = { PyTypeObject pyBundle_Type = { - PyObject_HEAD_INIT(NULL) - 0, /*ob_size*/ + PyVarObject_HEAD_INIT(NULL, 0) "Bundle", /*tp_name*/ sizeof(pyBundle), /*tp_basicsize*/ 0, /*tp_itemsize*/ diff --git a/source/pybundle.h b/source/pybundle.h index b397ffb..f862235 100644 --- a/source/pybundle.h +++ b/source/pybundle.h @@ -15,7 +15,7 @@ WARRANTIES, see the file, "license.txt," in this distribution. #error You need at least flext version 0.5.0 #endif -#if FLEXT_OS == FLEXT_OS_MAC +#ifdef PY_USE_FRAMEWORK #include #else #include diff --git a/source/pycompat.cpp b/source/pycompat.cpp new file mode 100644 index 0000000..809c830 --- /dev/null +++ b/source/pycompat.cpp @@ -0,0 +1,93 @@ +#include "pycompat.h" + +// copied from Python 3.8.2 for compatibility with pre-3.6.1 versions because +// doing the right thing with the older slice functions seems difficult... + +#if PY_VERSION_HEX < 0x03060100 +int +PySlice_Unpack(PyObject *_r, + Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step) +{ + PySliceObject *r = (PySliceObject*)_r; + /* this is harder to get right than you might think */ + + Py_BUILD_ASSERT(PY_SSIZE_T_MIN + 1 <= -PY_SSIZE_T_MAX); + + if (r->step == Py_None) { + *step = 1; + } + else { + if (!_PyEval_SliceIndex(r->step, step)) return -1; + if (*step == 0) { + PyErr_SetString(PyExc_ValueError, + "slice step cannot be zero"); + return -1; + } + /* Here *step might be -PY_SSIZE_T_MAX-1; in this case we replace it + * with -PY_SSIZE_T_MAX. This doesn't affect the semantics, and it + * guards against later undefined behaviour resulting from code that + * does "step = -step" as part of a slice reversal. + */ + if (*step < -PY_SSIZE_T_MAX) + *step = -PY_SSIZE_T_MAX; + } + + if (r->start == Py_None) { + *start = *step < 0 ? PY_SSIZE_T_MAX : 0; + } + else { + if (!_PyEval_SliceIndex(r->start, start)) return -1; + } + + if (r->stop == Py_None) { + *stop = *step < 0 ? PY_SSIZE_T_MIN : PY_SSIZE_T_MAX; + } + else { + if (!_PyEval_SliceIndex(r->stop, stop)) return -1; + } + + return 0; +} + +Py_ssize_t +PySlice_AdjustIndices(Py_ssize_t length, + Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t step) +{ + /* this is harder to get right than you might think */ + + assert(step != 0); + assert(step >= -PY_SSIZE_T_MAX); + + if (*start < 0) { + *start += length; + if (*start < 0) { + *start = (step < 0) ? -1 : 0; + } + } + else if (*start >= length) { + *start = (step < 0) ? length - 1 : length; + } + + if (*stop < 0) { + *stop += length; + if (*stop < 0) { + *stop = (step < 0) ? -1 : 0; + } + } + else if (*stop >= length) { + *stop = (step < 0) ? length - 1 : length; + } + + if (step < 0) { + if (*stop < *start) { + return (*start - *stop - 1) / (-step) + 1; + } + } + else { + if (*start < *stop) { + return (*stop - *start - 1) / step + 1; + } + } + return 0; +} +#endif diff --git a/source/pycompat.h b/source/pycompat.h new file mode 100644 index 0000000..3f5cb26 --- /dev/null +++ b/source/pycompat.h @@ -0,0 +1,40 @@ +#ifndef __PYCOMPAT_H +#define __PYCOMPAT_H + +#ifdef PY_USE_FRAMEWORK +#include +#else +#include +#endif + +// copied from Python 3.8.2 for compatibility with pre-3.6.1 versions because +// doing the right thing with the older slice functions seems difficult... + +#if PY_VERSION_HEX < 0x03060100 +#if PY_MAJOR_VERSION < 3 +/* Assert a build-time dependency, as an expression. + Your compile will fail if the condition isn't true, or can't be evaluated + by the compiler. This can be used in an expression: its value is 0. + Example: + #define foo_to_char(foo) \ + ((char *)(foo) \ + + Py_BUILD_ASSERT_EXPR(offsetof(struct foo, string) == 0)) + Written by Rusty Russell, public domain, http://ccodearchive.net/ */ +#define Py_BUILD_ASSERT_EXPR(cond) \ + (sizeof(char [1 - 2*!(cond)]) - 1) + +#define Py_BUILD_ASSERT(cond) do { \ + (void)Py_BUILD_ASSERT_EXPR(cond); \ + } while(0) +#endif + +int +PySlice_Unpack(PyObject *_r, + Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step); + +Py_ssize_t +PySlice_AdjustIndices(Py_ssize_t length, + Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t step); +#endif + +#endif diff --git a/source/pydsp.cpp b/source/pydsp.cpp index 49e6aee..db8a8ad 100644 --- a/source/pydsp.cpp +++ b/source/pydsp.cpp @@ -38,6 +38,7 @@ FLEXT_LIB_DSP_V("pyext~ pyext.~ pyx~ pyx.~",pydsp) pydsp::pydsp(int argc,const t_atom *argv) : pyext(argc,argv,true) , dspfun(NULL),sigfun(NULL) + , buffers(NULL) {} bool pydsp::DoInit() @@ -83,7 +84,15 @@ void pydsp::NewBuffers() for(i = 0; i < ins; ++i) { Py_XDECREF(buffers[i]); - PyObject *b = PyBuffer_FromReadWriteMemory(insigs[i],n*sizeof(t_sample)); + PyObject *b = +#if PY_MAJOR_VERSION < 3 + PyBuffer_FromReadWriteMemory(insigs[i],n*sizeof(t_sample)); +#elif PY_MINOR_VERSION >= 3 + PyMemoryView_FromMemory(reinterpret_cast(insigs[i]), n*sizeof(t_sample), PyBUF_WRITE); +#else +#error "TODO" +#endif + buffers[i] = arrayfrombuffer(b,1,n); Py_DECREF(b); } @@ -95,7 +104,15 @@ void pydsp::NewBuffers() Py_XINCREF(buffers[i]); } else { - PyObject *b = PyBuffer_FromReadWriteMemory(outsigs[i],n*sizeof(t_sample)); + PyObject *b = +#if PY_MAJOR_VERSION < 3 + PyBuffer_FromReadWriteMemory(outsigs[i],n*sizeof(t_sample)); +#elif PY_MINOR_VERSION >= 3 + PyMemoryView_FromMemory(reinterpret_cast(outsigs[i]), n*sizeof(t_sample), PyBUF_WRITE); +#else +#error "TODO" +#endif + buffers[ins+i] = arrayfrombuffer(b,1,n); Py_DECREF(b); } diff --git a/source/pyext.cpp b/source/pyext.cpp index 0deb2e9..9b19bd2 100644 --- a/source/pyext.cpp +++ b/source/pyext.cpp @@ -9,6 +9,14 @@ WARRANTIES, see the file, "license.txt," in this distribution. #include "pyext.h" #include +// undefine PD's T_OBJECT to avoid conflict with Python's +#undef T_OBJECT + +#ifdef PY_USE_FRAMEWORK +#include "Python/structmember.h" +#else +#include "structmember.h" +#endif FLEXT_LIB_V("pyext pyext. pyx pyx.",pyext) @@ -47,41 +55,19 @@ void pyext::Setup(t_classid c) // ---------------------------------------------------- // register/initialize pyext base class along with module - class_dict = PyDict_New(); - PyObject *className; -#if PY_MAJOR_VERSION < 3 - className = PyString_FromString(PYEXT_CLASS); -#else - className = PyUnicode_FromString(PYEXT_CLASS); -#endif - PyMethodDef *def; - // add setattr/getattr to class - for(def = attr_tbl; def->ml_name; def++) { - PyObject *func = PyCFunction_New(def, NULL); - PyDict_SetItemString(class_dict, def->ml_name, func); - Py_DECREF(func); + if(PyType_Ready(&pyPyext_Type) < 0) { + PyErr_Print(); + return; } - - class_obj = PyClass_New(NULL, class_dict, className); - Py_DECREF(className); - - // add methods to class - for (def = meth_tbl; def->ml_name != NULL; def++) { - PyObject *func = PyCFunction_New(def, NULL); - PyObject *method = PyMethod_New(func, NULL, class_obj); // increases class_obj ref count by 1 - PyDict_SetItemString(class_dict, def->ml_name, method); - Py_DECREF(func); - Py_DECREF(method); - } - -#if PY_VERSION_HEX >= 0x02020000 + +#if 0 && PY_VERSION_HEX >= 0x02020000 // not absolutely necessary, existent in python 2.2 upwards // make pyext functions available in class scope PyDict_Merge(class_dict,module_dict,0); #endif - // after merge so that it's not in class_dict as well... - PyDict_SetItemString(module_dict, PYEXT_CLASS,class_obj); // increases class_obj ref count by 1 + + PyDict_SetItemString(module_dict, PYEXT_CLASS, (PyObject *) &pyPyext_Type); // increases class_obj ref count by 1 PyObject *str; #if PY_MAJOR_VERSION < 3 @@ -89,12 +75,12 @@ void pyext::Setup(t_classid c) #else str = PyUnicode_FromString(pyext_doc); #endif - PyDict_SetItemString(class_dict, "__doc__", str); } pyext *pyext::GetThis(PyObject *self) { PyObject *th = PyObject_GetAttrString(self,"_this"); + if(th) { pyext *ret = static_cast(PyLong_AsVoidPtr(th)); Py_DECREF(th); @@ -119,9 +105,6 @@ void pyext::ClearThis() FLEXT_ASSERT(ret != -1); } -PyObject *pyext::class_obj = NULL; -PyObject *pyext::class_dict = NULL; - pyext::pyext(int argc,const t_atom *argv,bool sig): methname(NULL), pyobj(NULL), @@ -179,7 +162,13 @@ pyext::pyext(int argc,const t_atom *argv,bool sig): PyErr_SetString(PyExc_ValueError,"Invalid module name"); // check for alias creation names - if(dotted) clname = scr; + // ...this seems wrong? + // e.g. "pyext. a.b c" becomes the equivalent of: + // from a.b import a.b + // instead of + // from a.b import c + // so let's disable it for now. + //if(dotted) clname = scr; } Register(GetRegistry(REGNAME)); @@ -207,7 +196,9 @@ bool pyext::Init() if(methname) { MakeInstance(); - if(pyobj) InitInOut(inlets,outlets); + if(pyobj) { + InitInOut(inlets,outlets); + } } else inlets = outlets = 0; @@ -276,25 +267,11 @@ bool pyext::DoInit() bool ok = true; SetThis(); - - PyObject *init = PyObject_GetAttrString(pyobj,"__init__"); // get ref - if(init) { - if(PyMethod_Check(init)) { - PyObject *res = PyObject_CallObject(init,pargs); - if(!res) { - // exception is set - ok = false; - // we want to know why __init__ failed... - PyErr_Print(); - } - else - Py_DECREF(res); - } - Py_DECREF(init); + + if(pyobj->ob_type->tp_init(pyobj, pargs, NULL) < 0) { + ok = false; + PyErr_Print(); } - else - // __init__ has not been found - don't care - PyErr_Clear(); Py_DECREF(pargs); return ok; @@ -376,10 +353,10 @@ bool pyext::InitInOut(int &inl,int &outl) #if PY_MAJOR_VERSION < 3 if(PyInt_Check(res)) inl = PyInt_AS_LONG(res); - else -#endif +#else if(PyLong_Check(res)) inl = PyLong_AS_LONG(res); +#endif else PyErr_SetString(PyExc_TypeError, "Type must be integer"); Py_DECREF(res); @@ -400,10 +377,10 @@ bool pyext::InitInOut(int &inl,int &outl) #if PY_MAJOR_VERSION < 3 if(PyInt_Check(res)) outl = PyInt_AS_LONG(res); - else -#endif +#else if(PyLong_Check(res)) outl = PyLong_AS_LONG(res); +#endif else PyErr_SetString(PyExc_TypeError, "Type must be integer"); @@ -425,15 +402,17 @@ bool pyext::MakeInstance() if(!pref) PyErr_Print(); else { -#if PY_MAJOR_VERSION < 3 - if(PyClass_Check(pref)) { - // make instance, but don't call __init__ - pyobj = PyInstance_NewRaw(pref, NULL); -#else - if(PyObject_IsInstance(pref, (PyObject *)&PyType_Type)) { - // pyobj = PyBaseObject_Type.tp_new() - // TODO: correctly initialize instance -#endif + if(PyType_Check(pref) && PyType_IsSubtype((PyTypeObject *) pref, &pyPyext_Type)) { + PyTypeObject *pytypeobj = (PyTypeObject *) pref; + PyObject *pargs = MakePyArgs(NULL, initargs.Count(), initargs.Atoms()); + + if(!pargs) { + PyErr_Print(); + } else { + pyobj = pytypeobj->tp_new(pytypeobj, pargs, NULL); + + Py_DECREF(pargs); + } if(!pyobj) PyErr_Print(); } else @@ -687,3 +666,50 @@ void pyext::DumpOut(const t_symbol *sym,int argc,const t_atom *argv) { ToOutAnything(GetOutAttr(),sym?sym:thisTag(),argc,argv); } + +static PyObject *pyext_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + pyPyext *self = (pyPyext *) type->tp_alloc(type, 0); + + self->this_ptr = 0; + + PyObject *ret = (PyObject *) self; + + if(self) { + PyObject *this_long = kwds != NULL + ? PyDict_GetItemString(kwds, "_pyext_this") + : NULL; + + if(this_long) { + if(!PyLong_Check(this_long)) { + self->this_ptr = PyLong_AsLong(this_long); + } else { + Py_DECREF(self); + ret = NULL; + } + + Py_DECREF(this_long); + } + } else { + ret = NULL; + } + + return (PyObject *) ret; +} + +static PyMemberDef pyPyext_members[] = { + {"_this", T_OBJECT_EX, offsetof(pyPyext, this_ptr), 0, "pointer to pyext object"}, + {NULL} +}; + +PyTypeObject pyPyext_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = PYEXT_MODULE "." PYEXT_CLASS, + .tp_basicsize = sizeof(pyPyext), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = pyext::pyext_doc, + .tp_methods = pyext::meth_tbl, + .tp_members = pyPyext_members, + .tp_new = pyext_new +}; diff --git a/source/pyext.h b/source/pyext.h index 61b892a..92e5649 100644 --- a/source/pyext.h +++ b/source/pyext.h @@ -20,30 +20,32 @@ class pyext public: pyext(int argc,const t_atom *argv,bool sig = false); - static PyObject *pyext__str__(PyObject *,PyObject *args); + static PyObject *pyext__str__(PyObject *self, PyObject *args); - static PyObject *pyext_outlet(PyObject *,PyObject *args); + static PyObject *pyext_outlet(PyObject *self, PyObject *args); #if FLEXT_SYS == FLEXT_SYS_PD - static PyObject *pyext_tocanvas(PyObject *,PyObject *args); + static PyObject *pyext_tocanvas(PyObject *self, PyObject *args); #endif - static PyObject *pyext_setattr(PyObject *,PyObject *args); - static PyObject *pyext_getattr(PyObject *,PyObject *args); + static PyObject *pyext_setattr(PyObject *self, PyObject *args); + static PyObject *pyext_getattr(PyObject *self, PyObject *args); - static PyObject *pyext_detach(PyObject *,PyObject *args); - static PyObject *pyext_stop(PyObject *,PyObject *args); - static PyObject *pyext_isthreaded(PyObject *,PyObject *); + static PyObject *pyext_detach(PyObject *self, PyObject *args); + static PyObject *pyext_stop(PyObject *self, PyObject *args); - static PyObject *pyext_inbuf(PyObject *,PyObject *args); - static PyObject *pyext_invec(PyObject *,PyObject *args); - static PyObject *pyext_outbuf(PyObject *,PyObject *args); - static PyObject *pyext_outvec(PyObject *,PyObject *args); + static PyObject *pyext_inbuf(PyObject *self, PyObject *args); + static PyObject *pyext_invec(PyObject *self, PyObject *args); + static PyObject *pyext_outbuf(PyObject *self, PyObject *args); + static PyObject *pyext_outvec(PyObject *self, PyObject *args); int Inlets() const { return inlets; } int Outlets() const { return outlets; } static pyext *GetThis(PyObject *self); + static PyMethodDef meth_tbl[]; + static const char *pyext_doc; + protected: virtual bool Init(); @@ -65,7 +67,7 @@ class pyext void ms_initargs(const AtomList &a) { m_reload_(a.Count(),a.Atoms()); } void m_dir_() { m__dir(pyobj); } void mg_dir_(AtomList &lst) { GetDir(pyobj,lst); } - void m_doc_() { m__doc(((PyInstanceObject *)pyobj)->in_class->cl_dict); } + void m_doc_() { m__doc(pyobj); } void m_get(const t_symbol *s); void m_set(int argc,const t_atom *argv); @@ -98,13 +100,9 @@ class pyext bool MakeInstance(); bool InitInOut(int &inlets,int &outlets); - static PyObject *class_obj,*class_dict; - static PyMethodDef attr_tbl[],meth_tbl[]; - static const char *pyext_doc; - // -------- bind stuff ------------------ - static PyObject *pyext_bind(PyObject *,PyObject *args); - static PyObject *pyext_unbind(PyObject *,PyObject *args); + static PyObject *pyext_bind(PyObject *self, PyObject *args); + static PyObject *pyext_unbind(PyObject *self, PyObject *args); // --------------------------- @@ -151,4 +149,13 @@ class pyext #endif }; +typedef struct { + PyObject_HEAD + long this_ptr; +} pyPyext; + +PY_EXPORT extern PyTypeObject pyPyext_Type; + +#define pyPyext_Check(op) PyObject_TypeCheck((op), &pyPyext_Type) + #endif diff --git a/source/pymeth.cpp b/source/pymeth.cpp index 0ff87ed..42cbfca 100644 --- a/source/pymeth.cpp +++ b/source/pymeth.cpp @@ -382,7 +382,11 @@ bool pymeth::CbMethodResort(int n,const t_symbol *s,int argc,const t_atom *argv) else if(self != objects[0]) { // type hasn't changed, but object has PyObject *f = function; +#if PY_MAJOR_VERSION < 3 function = PyMethod_New(PyMethod_GET_FUNCTION(f),objects[0],PyMethod_GET_CLASS(f)); +#else + function = PyMethod_New(PyMethod_GET_FUNCTION(f),objects[0]); +#endif Py_DECREF(f); } } diff --git a/source/pyprefix.h b/source/pyprefix.h index 66d1820..7da820d 100644 --- a/source/pyprefix.h +++ b/source/pyprefix.h @@ -16,7 +16,7 @@ WARRANTIES, see the file, "license.txt," in this distribution. // otherwise some functions don't get defined #include -#if 0 //FLEXT_OS == FLEXT_OS_MAC +#ifdef PY_USE_FRAMEWORK #include #else #include @@ -47,4 +47,27 @@ extern "C" { typedef int Py_ssize_t; #endif +// these are copied from the Python 3.8.2 source because doing the right thing +// with the pre-3.6.1 slice functions seems difficult... + +#if PY_MAJOR_VERSION < 3 +/* Assert a build-time dependency, as an expression. + Your compile will fail if the condition isn't true, or can't be evaluated + by the compiler. This can be used in an expression: its value is 0. + Example: + #define foo_to_char(foo) \ + ((char *)(foo) \ + + Py_BUILD_ASSERT_EXPR(offsetof(struct foo, string) == 0)) + Written by Rusty Russell, public domain, http://ccodearchive.net/ */ +#define Py_BUILD_ASSERT_EXPR(cond) \ + (sizeof(char [1 - 2*!(cond)]) - 1) + +#define Py_BUILD_ASSERT(cond) do { \ + (void)Py_BUILD_ASSERT_EXPR(cond); \ + } while(0) +#endif + +#if PY_VERSION_HEX < 0x03060100 +#endif + #endif diff --git a/source/pysymbol.cpp b/source/pysymbol.cpp index 743207f..d03cb2f 100644 --- a/source/pysymbol.cpp +++ b/source/pysymbol.cpp @@ -35,9 +35,10 @@ static int symbol_init(PyObject *self, PyObject *args, PyObject *kwds) #if PY_MAJOR_VERSION < 3 else if(PyString_Check(arg)) ((pySymbol *)self)->sym = flext::MakeSymbol(PyString_AS_STRING(arg)); -#endif +#else else if(PyUnicode_Check(arg)) ((pySymbol *)self)->sym = flext::MakeSymbol(PyUnicode_AsUTF8(arg)); +#endif else { PyErr_SetString(PyExc_TypeError, "string, unicode or symbol argument expected"); ret = -1; @@ -178,13 +179,14 @@ static PyObject *symbol_repeat(PyObject *s,Py_ssize_t rep) } static PySequenceMethods symbol_as_seq = { - symbol_length, /* inquiry sq_length; __len__ */ - symbol_concat, /* __add__ */ - symbol_repeat, /* __mul__ */ - symbol_item, /* intargfunc sq_item; __getitem__ */ - symbol_slice, /* intintargfunc sq_slice; __getslice__ */ - NULL, /* intobjargproc sq_ass_item; __setitem__ */ - NULL, /* intintobjargproc sq_ass_slice; __setslice__ */ + symbol_length, /* lenfunc sq_length __len__ */ + symbol_concat, /* binaryfunc sq_concat __add__ */ + symbol_repeat, /* ssizeargfunc sq_repeat __mul__ */ + symbol_item, /* ssizeargfunc sq_item; __getitem__ */ + NULL, /* ssizeobjargproc sq_ass_item __setitem__ */ + NULL, /* objobjproc sq_contains __contains__ */ + NULL, /* binaryfunc sq_inplace_concat __iadd__ */ + NULL /* ssizeargfunc sq_inplace_repeat __imul */ }; static PyObject *symbol_iter(PyObject *s) @@ -203,45 +205,44 @@ static PyObject *symbol_iter(PyObject *s) PyTypeObject pySymbol_Type = { - PyObject_HEAD_INIT(NULL) - 0, /*ob_size*/ - "Symbol", /*tp_name*/ - sizeof(pySymbol), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - 0, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - symbol_repr, /*tp_repr*/ - 0, /*tp_as_number*/ - &symbol_as_seq, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - symbol_hash, /*tp_hash */ - 0, /*tp_call*/ - symbol_str, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT /*| Py_TPFLAGS_BASETYPE*/, /*tp_flags*/ - "Symbol objects", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ + PyVarObject_HEAD_INIT(NULL, 0) + "Symbol", /* tp_name */ + sizeof(pySymbol), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + symbol_repr, /* tp_repr */ + 0, /* tp_as_number */ + &symbol_as_seq, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + symbol_hash, /* tp_hash */ + 0, /* tp_call */ + symbol_str, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT /* | Py_TPFLAGS_BASETYPE*/, /* tp_flags */ + "Symbol objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ symbol_richcompare, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - symbol_iter, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - 0, /* tp_members */ + 0, /* tp_weaklistoffset */ + symbol_iter, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ - symbol_init, /* tp_init */ + symbol_init, /* tp_init */ 0, /* tp_alloc */ - symbol_new, /* tp_new */ + symbol_new, /* tp_new */ }; pySymbol *pySymbol__; diff --git a/source/pysymbol.h b/source/pysymbol.h index f033c46..5e7353c 100644 --- a/source/pysymbol.h +++ b/source/pysymbol.h @@ -15,7 +15,7 @@ WARRANTIES, see the file, "license.txt," in this distribution. #error You need at least flext version 0.5.0 #endif -#if FLEXT_OS == FLEXT_OS_MAC +#ifdef PY_USE_FRAMEWORK #include #else #include @@ -65,10 +65,10 @@ inline PyObject *pySymbol_FromString(PyObject *str) #if PY_MAJOR_VERSION < 3 if(PyString_Check(str)) cstr = PyString_AsString(str); - else -#endif +#else if(PyUnicode_Check(str)) cstr = PyUnicode_AsUTF8(str); +#endif else PyErr_SetString(PyExc_TypeError, "Type must be string or unicode"); @@ -95,10 +95,10 @@ inline const t_symbol *pyObject_AsSymbol(PyObject *op) #if PY_MAJOR_VERSION < 3 if(PyString_Check(op)) return flext::MakeSymbol(PyString_AS_STRING(op)); - else -#endif +#else if(PyUnicode_Check(op)) return flext::MakeSymbol(PyUnicode_AsUTF8(op)); +#endif else return pySymbol_AsSymbol(op); } diff --git a/source/thrctrl.h b/source/thrctrl.h new file mode 100644 index 0000000..edbd978 --- /dev/null +++ b/source/thrctrl.h @@ -0,0 +1,34 @@ +#ifndef __THRCTRL_H +#define __THRCTRL_H + +#include +#include +#include "flsupport.h" + +#ifdef FLEXT_THREADS +class ThrCtrl +{ +public: + explicit ThrCtrl(flext::ThrCond * thr, std::atomic * ctrlbool, bool init = true) + : _attached_boolean { ctrlbool } + , _attached_thrcond { thr } + { + *_attached_boolean = init; + } + + ~ThrCtrl() + { + *_attached_boolean = !*_attached_boolean; + if(!*_attached_boolean) + { + _attached_thrcond->Signal(); + fprintf(stderr, "Shutting down py/pyext thread pool.\n"); + } + } + +private: + std::atomic * _attached_boolean; + flext::ThrCond * _attached_thrcond; +}; +#endif +#endif // !__THRCTRL_H \ No newline at end of file