Skip to content

Commit

Permalink
Adding rudimentary support for Windows in setup.py.
Browse files Browse the repository at this point in the history
This is still a bit shaky. I.e. I can't get it to run outside
of the `git` bash shell. I know it has something to do with
the path for the installed `gfortran` which is in the same
Miniconda environment where `git` bash is installed.

Towards #26.
  • Loading branch information
dhermes committed Oct 9, 2017
1 parent eaec7cf commit 8538af4
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 36 deletions.
8 changes: 4 additions & 4 deletions .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ test_script:
- ps: if ($env:TEST_TYPE -eq "doctest")
{
sphinx-build -W `
-b doctest `
-d docs/build/doctrees `
docs `
docs/build/doctest;
-b doctest `
-d docs/build/doctrees `
docs `
docs/build/doctest;
}
5 changes: 3 additions & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
include README.rst LICENSE
include setup_helpers.py setup_helpers_osx.py
graft src/bezier
global-exclude *.so *.pyd
global-exclude *.o *.obj
global-exclude *.pyc __pycache__
global-exclude *.a *.def *.exp
global-exclude *.pyx
global-exclude *.so *.a
global-exclude *.pyc __pycache__
10 changes: 8 additions & 2 deletions nox.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@


NUMPY = 'numpy >= 1.13.3'
SCIPY = 'scipy >= 1.0.0rc1'
MOCK_DEP = 'mock >= 1.3.0'
SEABORN_DEP = 'seaborn >= 0.8'
BASE_DEPS = (
Expand Down Expand Up @@ -108,7 +109,7 @@ def unit(session, py):
local_deps = pypy_setup(BASE_DEPS, session)
else:
session.interpreter = 'python{}'.format(py)
local_deps = BASE_DEPS + ('scipy',)
local_deps = BASE_DEPS + (SCIPY,)

# Install all test dependencies.
session.install(*local_deps)
Expand All @@ -125,7 +126,7 @@ def cover(session):
session.interpreter = SINGLE_INTERP

# Install all test dependencies.
local_deps = BASE_DEPS + ('scipy', 'pytest-cov', 'coverage')
local_deps = BASE_DEPS + (SCIPY, 'pytest-cov', 'coverage')
session.install(*local_deps)
# Install this package.
session.install('.')
Expand Down Expand Up @@ -349,13 +350,18 @@ def clean(session):
get_path('docs', '__pycache__'),
get_path('docs', 'build'),
get_path('src', 'bezier', '__pycache__'),
get_path('src', 'bezier', 'extra-dll'),
get_path('src', 'bezier', 'lib'),
get_path('tests', '__pycache__'),
get_path('tests', 'functional', '__pycache__'),
get_path('tests', 'unit', '__pycache__'),
)
clean_globs = (
get_path('.coverage'),
get_path('*.mod'),
get_path('*.pyc'),
get_path('src', 'bezier', '*.pyc'),
get_path('src', 'bezier', '*.pyd'),
get_path('src', 'bezier', '*.so'),
get_path('src', 'bezier', 'quadpack', '*.o'),
get_path('src', 'bezier', '*.o'),
Expand Down
14 changes: 4 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import setup_helpers
import setup_helpers_osx
import setup_helpers_windows


VERSION = '0.5.0.dev1' # Also in codemeta.json
Expand All @@ -41,13 +42,6 @@
Skipping Fortran extension speedups.
"""
WINDOWS_MESSAGE = """\
Skipping Fortran extension speedups on Windows.
Sorry for this inconvenience. For more information or to help, visit:
https://github.com/dhermes/bezier/issues/26
"""
NO_EXTENSIONS_ENV = 'BEZIER_NO_EXTENSIONS'
NO_SPEEDUPS_MESSAGE = """\
The {} environment variable has been used to explicitly disable the
Expand Down Expand Up @@ -77,9 +71,6 @@ def extension_modules():
if NO_EXTENSIONS_ENV in os.environ:
print(NO_SPEEDUPS_MESSAGE)
return []
elif os.name == 'nt':
print(WINDOWS_MESSAGE, file=sys.stderr)
return []
elif setup_helpers.BuildFortranThenExt.has_f90_compiler():
return setup_helpers.extension_modules()
else:
Expand Down Expand Up @@ -113,6 +104,7 @@ def setup():
os.path.join('include', 'bezier', '*.h'),
os.path.join('lib', '*.a'),
os.path.join('lib', '*.lib'),
os.path.join('extra-dll', '*.dll'),
],
},
zip_safe=True,
Expand Down Expand Up @@ -147,6 +139,8 @@ def main():
setup_helpers_osx.patch_f90_compiler,
]

setup_helpers_windows.patch_cmd(setup_helpers.BuildFortranThenExt)

setup()


Expand Down
113 changes: 95 additions & 18 deletions setup_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import collections
import distutils.ccompiler
import os
import shutil
import subprocess
import sys

Expand Down Expand Up @@ -47,14 +48,15 @@
# specifically "What compiler options should I use for ...?".
# I have dropped ``-ffast-math`` because (for now) it causes a headache
# in tests and doesn't give an appreciable speed up.
FPIC = '-fPIC'
GFORTRAN_SHARED_FLAGS = ( # Used for both "DEBUG" and "OPTIMIZE"
'-Wall',
'-Wextra',
# ``-Wextra`` includes ``no-compare-reals``, which warns about
# ``value == 0.0_dp``
'-Wno-compare-reals',
'-Wimplicit-interface',
'-fPIC',
FPIC,
'-fmax-errors=1',
'-std=f2008',
)
Expand Down Expand Up @@ -224,10 +226,17 @@ def extension_modules():

mod_name = 'bezier._{}_speedup'.format(name)
path = SPEEDUP_FILENAME.format(name)
extra_objects = [
OBJECT_FILENAME.format(dependency)
for dependency in dependencies
]
if BuildFortranThenExt.USE_SHARED_LIBRARY:
# Here we don't depend on object files since the functionality
# is contained in the shared library.
extra_objects = []
else:
# NOTE: These may be treated as relative paths and replaced
# before the extension is actually built.
extra_objects = [
OBJECT_FILENAME.format(dependency)
for dependency in dependencies
]
extension = setuptools.Extension(
mod_name,
[path],
Expand Down Expand Up @@ -299,14 +308,25 @@ class BuildFortranThenExt(setuptools.command.build_ext.build_ext):
* Provides an optional "journaling" feature which allows commands invoked
during the compilation to be logged (or journaled) to a text file.
Provides a mutable ``PATCH_FUNCTIONS`` list to allow for "patching" when
first creating the Fortran compiler ``F90_COMPILER`` that will be
attached to the class (not the instances).
Provides mutable class attributes:
* ``PATCH_FUNCTIONS`` list to allow for "patching" when first creating
the Fortran compiler ``F90_COMPILER`` that will be attached to the
class (not the instances).
* ``CUSTOM_STATIC_LIB`` callable that takes a list of object files and
uses them to create a static / shared library. If not provided, then
:meth:`_default_static_lib` will be used.
* ``USE_SHARED_LIBRARY`` flag indicating if extensions will contain built
object files or if they will refer to a shared library.
* ``CLEANUP`` optional callable that cleans up at the end of :meth:`run`.
"""

# Will be set at runtime, not import time.
F90_COMPILER = None
PATCH_FUNCTIONS = []
CUSTOM_STATIC_LIB = None
USE_SHARED_LIBRARY = False
CLEANUP = None

def __init__(self, *args, **kwargs):
setuptools.command.build_ext.build_ext.__init__(self, *args, **kwargs)
Expand All @@ -324,6 +344,8 @@ def set_f90_compiler(cls):
c_compiler = distutils.ccompiler.new_compiler()
if c_compiler is None:
return
if c_compiler.compiler_type == 'msvc':
c_compiler.initialize()

f90_compiler = numpy.distutils.fcompiler.new_fcompiler(
requiref90=True, c_compiler=c_compiler)
Expand All @@ -347,7 +369,17 @@ def has_f90_compiler(cls):
@classmethod
def get_library_dirs(cls):
cls.set_f90_compiler()
return cls.F90_COMPILER.libraries, cls.F90_COMPILER.library_dirs
if cls.USE_SHARED_LIBRARY:
# NOTE: This assumes that the `libbezier` shared library will
# contain all libraries needed (e.g. there is no
# dependendence on ``libgfortran`` or similar). It's expected
# that ``library_dirs`` will be updated at run-time to have
# temporary build directories added.
libraries = ['bezier']
library_dirs = []
return libraries, library_dirs
else:
return cls.F90_COMPILER.libraries, cls.F90_COMPILER.library_dirs

def start_journaling(self):
"""Capture calls to the system by compilers.
Expand Down Expand Up @@ -421,6 +453,29 @@ def save_journal(self):
msg = BAD_JOURNAL.format(exc)
print(msg, file=sys.stderr)

def _default_static_lib(self, obj_files):
"""Create a static library (i.e. a ``.a`` / ``.lib`` file).
Args:
obj_files (List[str]): List of paths of compiled object files.
"""
c_compiler = self.F90_COMPILER.c_compiler

static_lib_dir = os.path.join(self.build_lib, 'bezier', 'lib')
if not os.path.exists(static_lib_dir):
os.makedirs(static_lib_dir)
c_compiler.create_static_lib(
obj_files, 'bezier', output_dir=static_lib_dir)

# NOTE: We must "modify" the paths for the ``extra_objects`` in
# each extension since they were compiled with
# ``output_dir=self.build_temp``.
for extension in self.extensions:
extension.extra_objects[:] = [
os.path.join(self.build_temp, rel_path)
for rel_path in extension.extra_objects
]

def compile_fortran_obj_files(self):
source_files_quadpack = [
QUADPACK_SOURCE_FILENAME.format(mod_name)
Expand All @@ -433,22 +488,42 @@ def compile_fortran_obj_files(self):
source_files = source_files_quadpack + source_files_bezier
obj_files = self.F90_COMPILER.compile(
source_files,
output_dir=None,
output_dir=self.build_temp,
macros=[],
include_dirs=[],
debug=None,
extra_postargs=[],
extra_postargs=[
'-J',
self.build_temp,
],
depends=[],
)

# Create a static library (i.e. a ``.a`` / ``.lib`` file).
c_compiler = self.F90_COMPILER.c_compiler
if self.CUSTOM_STATIC_LIB is None:
self._default_static_lib(obj_files)
else:
self.CUSTOM_STATIC_LIB(obj_files)

static_lib_dir = os.path.join(self.build_lib, 'bezier', 'lib')
if not os.path.exists(static_lib_dir):
os.makedirs(static_lib_dir)
c_compiler.create_static_lib(
obj_files, 'bezier', output_dir=static_lib_dir)
def _default_cleanup(self):
"""Default cleanup after :meth:`run`.
For in-place builds, moves the built shared library into the source
directory.
"""
if not self.inplace:
return

shutil.move(
os.path.join(self.build_lib, 'bezier', 'lib'),
os.path.join('src', 'bezier'),
)

def cleanup(self):
"""Cleanup after :meth:`run`."""
if self.CLEANUP is None:
self._default_cleanup()
else:
self.CLEANUP()

def run(self):
self.set_f90_compiler()
Expand All @@ -458,4 +533,6 @@ def run(self):

result = setuptools.command.build_ext.build_ext.run(self)
self.save_journal()
self.cleanup()

return result
Loading

0 comments on commit 8538af4

Please sign in to comment.