Last active
July 10, 2016 04:40
-
-
Save mikofski/06580946420913067f56458f370f3e8f to your computer and use it in GitHub Desktop.
overloading ccompiler to build dylibs for mac osx with xcode
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define macros | |
| SOLPOSAM = libsolposAM.dylib | |
| SOLPOSAM_LIB = solposAM | |
| SPECTRL2 = libspectrl2.dylib | |
| SPECTEST = spectest | |
| STEST00 = stest00 | |
| SRC_DIR = src | |
| BUILD_DIR = build | |
| SOLPOSAM_SRC = \ | |
| $(SRC_DIR)/solposAM.c \ | |
| $(SRC_DIR)/solpos.c | |
| SPECTRL2_SRC = \ | |
| $(SRC_DIR)/spectrl2.c \ | |
| $(SRC_DIR)/spectrl2_2.c \ | |
| $(SRC_DIR)/solpos.c | |
| SPECTEST_SRC = \ | |
| $(SRC_DIR)/spectest.c \ | |
| $(SRC_DIR)/spectrl2_2.c \ | |
| $(SRC_DIR)/solpos.c | |
| STEST00_SRC = \ | |
| $(SRC_DIR)/stest00.c \ | |
| $(SRC_DIR)/solpos.c | |
| SOLPOSAM_LOG = $(BUILD_DIR)/solposAM.log | |
| SPECTRL2_LOG = $(BUILD_DIR)/spectrl2.log | |
| all: uninstall clean solposAM spectrl2 install test | |
| clean: | |
| rm -rf $(BUILD_DIR) | |
| create_dirs: | |
| mkdir -p $(BUILD_DIR) | |
| solposAM: create_dirs | |
| cc -Wl,-rpath,@loader_path/ -shared -fPIC -Wall $(SOLPOSAM_SRC) \ | |
| -o $(BUILD_DIR)/$(SOLPOSAM) -install_name @rpath/$(SOLPOSAM) | |
| spectrl2: create_dirs | |
| cc -L$(BUILD_DIR) -Wl,-rpath,@loader_path/ -shared -fPIC -Wall \ | |
| $(SPECTRL2_SRC) -o $(BUILD_DIR)/$(SPECTRL2) -l$(SOLPOSAM_LIB) \ | |
| -install_name @rpath/$(SPECTRL2) | |
| install: | |
| install $(BUILD_DIR)/*.dylib ./ | |
| uninstall: | |
| rm -f $(SOLPOSAM) $(SPECTRL2) | |
| spectest: | |
| cc -fPIC -Wall $(SPECTEST_SRC) -o $(BUILD_DIR)/$(SPECTEST) | |
| stest00: | |
| cc -fPIC -Wall $(STEST00_SRC) -o $(BUILD_DIR)/$(STEST00) | |
| test: spectest stest00 | |
| $(BUILD_DIR)/$(SPECTEST) >> SPECTRL2_LOG | |
| $(BUILD_DIR)/$(STEST00) >> SOLPOSAM_LOG | |
| # https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html | |
| # -Wl,option | |
| # Pass option as an option to the linker. If option contains commas, it is | |
| # split into multiple options at the commas. You can use this syntax to pass | |
| # an argument to the option. For example, -Wl,-Map,output.map passes | |
| # -Map output.map to the linker. When using the GNU linker, you can also get | |
| # the same effect with -Wl,-Map=output.map. | |
| # https://gcc.gnu.org/onlinedocs/gcc/Darwin-Options.html | |
| # -install_name <path> | |
| # https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/RunpathDependentLibraries.html | |
| # -install_name <path> | |
| # https://developer.apple.com/library/ios/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkBinding.html | |
| # otool | |
| # https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/xcode-select.1.html | |
| # install_name_tool | |
| # https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dyld.1.html | |
| # @loader_path/ | |
| # This variable is replaced with the path to the directory containing the mach-o binary which | |
| # contains the load command using @loader_path. Thus, in every binary, @loader_path resolves to | |
| # a different path, whereas @executable_path always resolves to the same path. @loader_path is | |
| # useful as the load path for a framework/dylib embedded in a plug-in, if the final file system | |
| # location of the plugin-in unknown (so absolute paths cannot be used) or if the plug-in is used | |
| # by multiple applications (so @executable_path cannot be used). If the plug-in mach-o file is | |
| # at /some/path/Myfilter.plugin/Contents/MacOS/Myfilter and a framework dylib file is at | |
| # /some/path/Myfilter.plugin/Contents/Frameworks/Foo.framework/Versions/A/Foo, then the frame-work framework | |
| # work load path could be encoded as @loader_path/../Frameworks/Foo.framework/Versions/A/Foo and | |
| # the Myfilter.plugin directory could be moved around in the file system and dyld will still be | |
| # able to load the embedded framework. | |
| # | |
| # @rpath/ | |
| # Dyld maintains a current stack of paths called the run path list. When @rpath is encountered | |
| # it is substituted with each path in the run path list until a loadable dylib if found. The | |
| # run path stack is built from the LC_RPATH load commands in the depencency chain that lead to | |
| # the current dylib load. You can add an LC_RPATH load command to an image with the -rpath | |
| # option to ld(1). You can even add a LC_RPATH load command path that starts with | |
| # @loader_path/, and it will push a path on the run path stack that relative to the image con-taining containing | |
| # taining the LC_RPATH. The use of @rpath is most useful when you have a complex directory | |
| # structure of programs and dylibs which can be installed anywhere, but keep their relative | |
| # positions. This scenario could be implemented using @loader_path, but every client of a dylib | |
| # could need a different load path because its relative position in the file system is differ-ent. different. | |
| # ent. The use of @rpath introduces a level of indirection that simplies things. You pick a | |
| # location in your directory structure as an anchor point. Each dylib then gets an install path | |
| # that starts with @rpath and is the path to the dylib relative to the anchor point. Each main | |
| # executable is linked with -rpath @loader_path/zzz, where zzz is the path from the executable | |
| # to the anchor point. At runtime dyld sets it run path to be the anchor point, then each dylib | |
| # is found relative to the anchor point. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <Python.h> | |
| int main() | |
| { | |
| return 0; | |
| } | |
| #if PY_MAJOR_VERSION >= 3 | |
| void PyInit_dummy() {} | |
| #else | |
| void initdummy() {} | |
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| SolarUtils Package Setup | |
| Copyright (c) 2016 SunPower Corp. | |
| """ | |
| import sys | |
| import os | |
| import shutil | |
| from distutils import unixccompiler | |
| try: | |
| from setuptools import setup, distutils, Extension | |
| except ImportError: | |
| sys.exit('setuptools was not detected - please install setuptools and pip') | |
| from solar_utils import __version__ as VERSION, __name__ as NAME | |
| from solar_utils.tests import test_cdlls | |
| import logging | |
| logging.basicConfig(level=logging.DEBUG) | |
| LOGGER = logging.getLogger('SETUP') | |
| # set platform constants | |
| CCFLAGS, RPATH, INSTALL_NAME, LDFLAGS, MACROS = None, None, None, None, None | |
| PYVERSION = sys.version_info | |
| PLATFORM = sys.platform | |
| if PLATFORM == 'win32': | |
| LIB_FILE = '%s.dll' | |
| MACROS = [('WIN32', None)] | |
| if PYVERSION.major >= 3 and PYVERSION.minor >= 5: | |
| LDFLAGS = ['/DLL'] | |
| elif PLATFORM == 'darwin': | |
| LIB_FILE = 'lib%s.dylib' | |
| RPATH = "-Wl,-rpath,@loader_path/" | |
| INSTALL_NAME = "@rpath/" + LIB_FILE | |
| CCFLAGS = LDFLAGS = ['-fPIC'] | |
| elif PLATFORM in ['linux', 'linux2']: | |
| PLATFORM = 'linux' | |
| LIB_FILE = 'lib%s.so' | |
| RPATH = "-Wl,-rpath,${ORIGIN}" | |
| CCFLAGS = LDFLAGS = ['-fPIC'] | |
| else: | |
| sys.exit('Platform "%s" is unknown or unsupported.' % PLATFORM) | |
| def make_ldflags(ldflags=LDFLAGS, rpath=RPATH): | |
| """ | |
| Make LDFLAGS with rpath, install_name and lib_name. | |
| """ | |
| if ldflags and rpath: | |
| ldflags.extend([rpath]) | |
| elif rpath: | |
| ldflags = [rpath] | |
| return ldflags | |
| def make_install_name(lib_name, install_name=INSTALL_NAME): | |
| """ | |
| Make INSTALL_NAME with and lib_name. | |
| """ | |
| if install_name: | |
| return ['-install_name', install_name % lib_name] | |
| else: | |
| return None | |
| def dylib_monkeypatch(self): | |
| """ | |
| Monkey patch :class:`distutils.UnixCCompiler` for darwin so libraries use | |
| '.dylib' instead of '.so'. | |
| """ | |
| def link_dylib_lib(self, objects, output_libname, output_dir=None, | |
| libraries=None, library_dirs=None, | |
| runtime_library_dirs=None, export_symbols=None, | |
| debug=0, extra_preargs=None, extra_postargs=None, | |
| build_temp=None, target_lang=None): | |
| """implementation of link_shared_lib""" | |
| self.link("shared_library", objects, | |
| self.library_filename(output_libname, lib_type='dylib'), | |
| output_dir, | |
| libraries, library_dirs, runtime_library_dirs, | |
| export_symbols, debug, | |
| extra_preargs, extra_postargs, build_temp, target_lang) | |
| self.link_so = self.link_shared_lib | |
| self.link_shared_lib = link_dylib_lib | |
| return self | |
| # use dummy to get correct platform metadata | |
| PKG_DATA = [] | |
| DUMMY = Extension('%s.dummy' % NAME, sources=[os.path.join(NAME, 'dummy.c')]) | |
| SRC_DIR = os.path.join(NAME, 'src') | |
| BUILD_DIR = os.path.join(NAME, 'build') | |
| TESTS = '%s.tests' % NAME | |
| TEST_DATA = ['test_spectrl2_data.json'] | |
| SOLPOS = 'solpos.c' | |
| SOLPOSAM = 'solposAM.c' | |
| SOLPOSAM_LIB = 'solposAM' | |
| SOLPOSAM_LIB_FILE = LIB_FILE % SOLPOSAM_LIB | |
| SPECTRL2 = 'spectrl2.c' | |
| SPECTRL2_2 = 'spectrl2_2.c' | |
| SPECTRL2_LIB = 'spectrl2' | |
| SPECTRL2_LIB_FILE = LIB_FILE % SPECTRL2_LIB | |
| SOLPOS = os.path.join(SRC_DIR, SOLPOS) | |
| SOLPOSAM = os.path.join(SRC_DIR, SOLPOSAM) | |
| SPECTRL2 = os.path.join(SRC_DIR, SPECTRL2) | |
| SPECTRL2_2 = os.path.join(SRC_DIR, SPECTRL2_2) | |
| SOLPOSAM_LIB_PATH = os.path.join(NAME, SOLPOSAM_LIB_FILE) | |
| SPECTRL2_LIB_PATH = os.path.join(NAME, SPECTRL2_LIB_FILE) | |
| LIB_FILES_EXIST = all([ | |
| os.path.exists(SOLPOSAM_LIB_PATH), | |
| os.path.exists(SPECTRL2_LIB_PATH) | |
| ]) | |
| # run clean or build libraries if they don't exist | |
| if 'clean' in sys.argv: | |
| try: | |
| os.remove(SOLPOSAM_LIB_PATH) | |
| os.remove(SPECTRL2_LIB_PATH) | |
| except OSError as err: | |
| sys.stderr.write('%s\n' % err) | |
| elif 'sdist' in sys.argv: | |
| for plat in ('win32', 'linux', 'darwin'): | |
| PKG_DATA.append('%s.mk' % plat) | |
| PKG_DATA.append(os.path.join('src', '*.*')) | |
| PKG_DATA.append(os.path.join('src', 'orig', 'solpos', '*.*')) | |
| PKG_DATA.append(os.path.join('src', 'orig', 'spectrl2', '*.*')) | |
| elif not LIB_FILES_EXIST: | |
| PKG_DATA = ['%s.mk' % PLATFORM, | |
| SOLPOSAM_LIB_FILE, SPECTRL2_LIB_FILE, | |
| os.path.join('src', '*.*'), | |
| os.path.join('src', 'orig', 'solpos', '*.*'), | |
| os.path.join('src', 'orig', 'spectrl2', '*.*')] | |
| # clean build directory | |
| if os.path.exists(BUILD_DIR): | |
| shutil.rmtree(BUILD_DIR) # delete entire directory tree | |
| os.mkdir(BUILD_DIR) # make build directory | |
| # compile NREL source code | |
| if PLATFORM == 'darwin': | |
| CCOMPILER = unixccompiler.UnixCCompiler | |
| OSXCCOMPILER = dylib_monkeypatch(CCOMPILER) | |
| CC = OSXCCOMPILER(verbose=3) | |
| else: | |
| CC = distutils.ccompiler.new_compiler() # initialize compiler object | |
| CC.add_include_dir(SRC_DIR) # set includes directory | |
| # compile solpos and solposAM objects into build directory | |
| OBJS = CC.compile([SOLPOS, SOLPOSAM], output_dir=BUILD_DIR, | |
| extra_preargs=CCFLAGS, macros=MACROS) | |
| # link objects and make shared library in build directory | |
| CC.link_shared_lib(OBJS, SOLPOSAM_LIB, output_dir=BUILD_DIR, | |
| extra_preargs=make_ldflags(), | |
| extra_postargs=make_install_name(SOLPOSAM_LIB)) | |
| # compile spectrl2 objects into build directory | |
| OBJS = CC.compile([SPECTRL2, SPECTRL2_2, SOLPOS], output_dir=BUILD_DIR, | |
| extra_preargs=CCFLAGS, macros=MACROS) | |
| CC.add_library(SOLPOSAM_LIB) # set linked libraries | |
| CC.add_library_dir(BUILD_DIR) # set library directories | |
| # link objects and make shared library in build directory | |
| CC.link_shared_lib(OBJS, SPECTRL2_LIB, output_dir=BUILD_DIR, | |
| extra_preargs=make_ldflags(), | |
| extra_postargs=make_install_name(SPECTRL2_LIB)) | |
| # copy files from build to library folder | |
| shutil.copy(os.path.join(BUILD_DIR, SOLPOSAM_LIB_FILE), NAME) | |
| shutil.copy(os.path.join(BUILD_DIR, SPECTRL2_LIB_FILE), NAME) | |
| # test libraries | |
| test_cdlls.test_solposAM() | |
| test_cdlls.test_spectrl2() | |
| setup(name=NAME, | |
| version=VERSION, | |
| packages=[NAME, TESTS], | |
| package_data={NAME: PKG_DATA, TESTS: TEST_DATA}, | |
| description='Python wrapper around NREL SOLPOS and SPECTRL2', | |
| ext_modules=[DUMMY]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment