Skip to content

Instantly share code, notes, and snippets.

@mikofski
Last active July 10, 2016 04:40
Show Gist options
  • Select an option

  • Save mikofski/06580946420913067f56458f370f3e8f to your computer and use it in GitHub Desktop.

Select an option

Save mikofski/06580946420913067f56458f370f3e8f to your computer and use it in GitHub Desktop.
overloading ccompiler to build dylibs for mac osx with xcode
#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.
#include <Python.h>
int main()
{
return 0;
}
#if PY_MAJOR_VERSION >= 3
void PyInit_dummy() {}
#else
void initdummy() {}
#endif
"""
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