1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3"""Installs the OSKAR Python bindings.""" 4 5import os 6import platform 7try: 8 from setuptools import setup, Extension 9 from setuptools.command.build_ext import build_ext 10except ImportError: 11 from distutils.core import setup, Extension 12 from distutils.command.build_ext import build_ext 13 14# Define the versions of OSKAR this is compatible with. 15OSKAR_COMPATIBILITY_VERSION_MIN = '0x020700' 16OSKAR_COMPATIBILITY_VERSION_MAX = '0x0208FF' 17 18# Define the extension modules to build. 19MODULES = [ 20 ('_apps_lib', 'oskar_apps_lib.cpp'), 21 ('_binary_lib', 'oskar_binary_lib.c'), 22 ('_imager_lib', 'oskar_imager_lib.c'), 23 ('_measurement_set_lib', 'oskar_measurement_set_lib.c'), 24 ('_interferometer_lib', 'oskar_interferometer_lib.c'), 25 ('_settings_lib', 'oskar_settings_lib.cpp'), 26 ('_sky_lib', 'oskar_sky_lib.c'), 27 ('_telescope_lib', 'oskar_telescope_lib.c'), 28 ('_utils', 'oskar_utils.c'), 29 ('_vis_block_lib', 'oskar_vis_block_lib.c'), 30 ('_vis_header_lib', 'oskar_vis_header_lib.c'), 31 ('_bda_utils', 'oskar_bda_utils.c') 32] 33 34 35class BuildExt(build_ext): 36 """Class used to build OSKAR Python extensions. Inherits build_ext.""" 37 def __init__(self, *args, **kwargs): 38 """Initialise.""" 39 build_ext.__init__(self, *args, **kwargs) 40 self._checked_lib = False 41 self._checked_inc = False 42 43 @staticmethod 44 def find_file(name, dir_paths): 45 """Returns path of given file if it exists in the list of directories. 46 47 Args: 48 name (str): The name of the file to find. 49 dir_paths (array-like, str): List of directories to search. 50 """ 51 for directory in dir_paths: 52 directory = directory.strip('\"') 53 if os.path.exists(directory): 54 test_path = os.path.join(directory, name) 55 if os.path.isfile(test_path): 56 return test_path.strip('\"') 57 return None 58 59 @staticmethod 60 def dir_contains(name, dir_paths): 61 """Returns directory if name fragment is part of a directory listing. 62 63 Args: 64 name (str): The name fragment to search for. 65 dir_paths (array-like, str): List of directories to search. 66 """ 67 for directory in dir_paths: 68 directory = directory.strip('\"') 69 if os.path.exists(directory): 70 dir_contents = os.listdir(directory) 71 for item in dir_contents: 72 if name in item: 73 return directory 74 return None 75 76 @staticmethod 77 def get_oskar_version(version_file): 78 """Returns the version of OSKAR found on the system.""" 79 version_num = None 80 version_str = None 81 with open(version_file) as file_handle: 82 for line in file_handle: 83 if 'define OSKAR_VERSION_STR ' in line: 84 version_str = (line.split()[2]).replace('"', '') 85 elif 'define OSKAR_VERSION ' in line: 86 version_num = int(line.split()[2], base=16) 87 return (version_num, version_str) 88 89 @staticmethod 90 def check_oskar_version(version_num, version_str): 91 """Checks the version of OSKAR found is compatible.""" 92 if version_num < int(OSKAR_COMPATIBILITY_VERSION_MIN, base=16) or \ 93 version_num > int(OSKAR_COMPATIBILITY_VERSION_MAX, base=16): 94 raise RuntimeError( 95 "The version of OSKAR found is not compatible with oskarpy. " 96 "Found OSKAR %s (require %s < version < %s)." % ( 97 version_str, 98 OSKAR_COMPATIBILITY_VERSION_MIN, 99 OSKAR_COMPATIBILITY_VERSION_MAX) 100 ) 101 102 def run(self): 103 """Overridden method. Runs the build. 104 Library directories and include directories are checked here, first. 105 """ 106 # Check we can find the OSKAR library. 107 # For some reason, run() is sometimes called again after the build 108 # has already happened. 109 # Make sure not to fail the check the second time. 110 if not self._checked_lib: 111 self._checked_lib = True 112 if os.getenv('OSKAR_LIB_DIR'): 113 self.library_dirs.append(os.getenv('OSKAR_LIB_DIR')) 114 if platform.system() == 'Windows': 115 self.library_dirs.append('C:\\Program Files\\OSKAR\\lib') 116 for i, test_dir in enumerate(self.library_dirs): 117 self.library_dirs[i] = test_dir.strip('\"') 118 directory = self.dir_contains('oskar.', self.library_dirs) 119 if not directory: 120 raise RuntimeError( 121 "Could not find OSKAR library. " 122 "Check that OSKAR has already been installed on " 123 "this system, and either set the environment variable " 124 "OSKAR_LIB_DIR, or set the library path to build_ext " 125 "using -L or --library-dirs") 126 if platform.system() != 'Windows': 127 self.rpath.append(directory) 128 self.libraries.append('oskar') 129 self.libraries.append('oskar_apps') 130 self.libraries.append('oskar_binary') 131 self.libraries.append('oskar_settings') 132 if self.dir_contains('oskar_ms.', self.library_dirs): 133 self.libraries.append('oskar_ms') 134 135 # Check we can find the OSKAR headers. 136 if not self._checked_inc: 137 from numpy import get_include 138 self._checked_inc = True 139 if os.getenv('OSKAR_INC_DIR'): 140 self.include_dirs.append(os.getenv('OSKAR_INC_DIR')) 141 if platform.system() == 'Windows': 142 self.include_dirs.append('C:\\Program Files\\OSKAR\\include') 143 header = self.find_file( 144 os.path.join('oskar', 'oskar_version.h'), self.include_dirs) 145 if not header: 146 raise RuntimeError( 147 "Could not find oskar/oskar_version.h. " 148 "Check that OSKAR has already been installed on " 149 "this system, and either set the environment variable " 150 "OSKAR_INC_DIR, or set the include path to build_ext " 151 "using -I or --include-dirs") 152 self.include_dirs.insert(0, os.path.dirname(header)) 153 self.include_dirs.insert(0, get_include()) 154 for i, test_dir in enumerate(self.include_dirs): 155 self.include_dirs[i] = test_dir.strip('\"') 156 157 # Check the version of OSKAR is compatible. 158 version = self.get_oskar_version(header) 159 self.check_oskar_version(*version) 160 build_ext.run(self) 161 162 def build_extension(self, ext): 163 """Overridden method. Builds each Extension.""" 164 ext.runtime_library_dirs = self.rpath 165 166 # Unfortunately things don't work as they should on the Mac... 167 if platform.system() == 'Darwin': 168 for rpath in self.rpath: 169 ext.extra_link_args.append('-Wl,-rpath,'+rpath) 170 171 # Don't try to build MS extension if liboskar_ms is not found. 172 if 'measurement_set' in ext.name: 173 if not self.dir_contains('oskar_ms.', self.library_dirs): 174 return 175 build_ext.build_extension(self, ext) 176 177 178def get_oskarpy_version(): 179 """Get the version of oskarpy from the version file.""" 180 globals_ = {} 181 this_dir = os.path.dirname(__file__) 182 with open(os.path.join(this_dir, 'oskar', '_version.py')) as file_handle: 183 code = file_handle.read() 184 # pylint: disable=exec-used 185 exec(code, globals_) 186 return globals_['__version__'] 187 188 189# Call setup() with list of extensions to build. 190EXTENSIONS = [] 191for module in MODULES: 192 if platform.system() == 'Windows' and 'measurement_set' in module[0]: 193 continue 194 _, src_ext = os.path.splitext(module[1]) 195 extra_compile_args = [] 196 if src_ext == ".c" and platform.system() != 'Windows': 197 extra_compile_args = ["-std=c99"] 198 EXTENSIONS.append(Extension( 199 'oskar.' + module[0], 200 sources=[os.path.join('oskar', 'src', module[1])], 201 extra_compile_args=extra_compile_args)) 202setup( 203 name='oskarpy', 204 version=get_oskarpy_version(), 205 description='Radio interferometer simulation package (Python bindings)', 206 packages=['oskar'], 207 ext_modules=EXTENSIONS, 208 classifiers=[ 209 'Development Status :: 3 - Alpha', 210 'Environment :: Console', 211 'Intended Audience :: Science/Research', 212 'Topic :: Scientific/Engineering :: Astronomy', 213 'License :: OSI Approved :: BSD License', 214 'Operating System :: POSIX', 215 'Operating System :: MacOS :: MacOS X', 216 'Operating System :: Microsoft :: Windows', 217 'Programming Language :: C', 218 'Programming Language :: Python :: 2.7', 219 'Programming Language :: Python :: 3' 220 ], 221 author='University of Oxford', 222 url='https://github.com/OxfordSKA/OSKAR', 223 license='BSD', 224 install_requires=['numpy'], 225 setup_requires=['numpy'], 226 cmdclass={'build_ext': BuildExt} 227 ) 228