diff --git a/.gitignore b/.gitignore index b92bed604e..d0e77ccdd8 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,9 @@ scratch.sparsebundle *.DS_Store __ecl_lib_path.py __ecl_lib_info.py + +/venv/ +/_skbuild/ +/python/ecl/version.py +.* +*.egg-info/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 347bed92bd..1cde211725 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,47 +24,55 @@ set( ECL_VERSION_MICRO "not-available" ) execute_process(COMMAND date "+%Y-%m-%d %H:%M:%S" OUTPUT_VARIABLE ECL_BUILD_TIME ) string(STRIP "${ECL_BUILD_TIME}" ECL_BUILD_TIME) -find_package(Git) - -if(GIT_FOUND) - execute_process( - COMMAND ${GIT_EXECUTABLE} "--git-dir=${CMAKE_SOURCE_DIR}/.git" status - RESULT_VARIABLE RESULT - OUTPUT_QUIET - ERROR_QUIET - ) - - if(NOT "${RESULT}" STREQUAL 0) - set(GIT_FOUND OFF) +if(ECL_VERSION) + # Have we been provided with an explicitly-set version? + string( REGEX REPLACE "^([^.]+)\\.([^.]+)\\.(.+)$" "\\1" ECL_VERSION_MAJOR "${ECL_VERSION}") + string( REGEX REPLACE "^([^.]+)\\.([^.]+)\\.(.+)$" "\\2" ECL_VERSION_MINOR "${ECL_VERSION}") + string( REGEX REPLACE "^([^.]+)\\.([^.]+)\\.(.+)$" "\\3" ECL_VERSION_MICRO "${ECL_VERSION}") +else() + # Otherwise try to discover it via git + find_package(Git) + + if(GIT_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} "--git-dir=${CMAKE_SOURCE_DIR}/.git" status + RESULT_VARIABLE RESULT + OUTPUT_QUIET + ERROR_QUIET + ) + + if(NOT "${RESULT}" STREQUAL 0) + set(GIT_FOUND OFF) + endif() endif() -endif() - -if(GIT_FOUND) - execute_process( - COMMAND ${GIT_EXECUTABLE} "--git-dir=${CMAKE_SOURCE_DIR}/.git" rev-parse HEAD - OUTPUT_VARIABLE GIT_COMMIT - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - execute_process( - COMMAND ${GIT_EXECUTABLE} "--git-dir=${CMAKE_SOURCE_DIR}/.git" rev-parse --short HEAD - OUTPUT_VARIABLE GIT_COMMIT_SHORT - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - execute_process( - COMMAND ${GIT_EXECUTABLE} "--git-dir=${CMAKE_SOURCE_DIR}/.git" describe --tags - OUTPUT_VARIABLE GIT_TAG - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - string( REGEX REPLACE "^([^.]+)\\.([^.]+)\\.(.+)$" "\\1" ECL_VERSION_MAJOR "${GIT_TAG}") - string( REGEX REPLACE "^([^.]+)\\.([^.]+)\\.(.+)$" "\\2" ECL_VERSION_MINOR "${GIT_TAG}") - string( REGEX REPLACE "^([^.]+)\\.([^.]+)\\.(.+)$" "\\3" ECL_VERSION_MICRO "${GIT_TAG}") -else() + if(GIT_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} "--git-dir=${CMAKE_SOURCE_DIR}/.git" rev-parse HEAD + OUTPUT_VARIABLE GIT_COMMIT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + execute_process( + COMMAND ${GIT_EXECUTABLE} "--git-dir=${CMAKE_SOURCE_DIR}/.git" rev-parse --short HEAD + OUTPUT_VARIABLE GIT_COMMIT_SHORT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + execute_process( + COMMAND ${GIT_EXECUTABLE} "--git-dir=${CMAKE_SOURCE_DIR}/.git" describe --tags + OUTPUT_VARIABLE GIT_TAG + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + string( REGEX REPLACE "^([^.]+)\\.([^.]+)\\.(.+)$" "\\1" ECL_VERSION_MAJOR "${GIT_TAG}") + string( REGEX REPLACE "^([^.]+)\\.([^.]+)\\.(.+)$" "\\2" ECL_VERSION_MINOR "${GIT_TAG}") + string( REGEX REPLACE "^([^.]+)\\.([^.]+)\\.(.+)$" "\\3" ECL_VERSION_MICRO "${GIT_TAG}") + else() set( GIT_COMMIT "unknown (git not found!)") set( GIT_COMMIT_SHORT "unknown (git not found!)") message( WARNING "Git not found. Build will not contain correct version info." ) + endif() endif() message( STATUS "libecl version: ${ECL_VERSION_MAJOR}.${ECL_VERSION_MINOR}.${ECL_VERSION_MICRO}" ) @@ -375,6 +383,9 @@ if (ENABLE_PYTHON) endif() endif() -install(EXPORT ecl-config DESTINATION share/cmake/ecl) -export(TARGETS ecl FILE eclConfig.cmake) -export(PACKAGE ecl) +if (NOT SKBUILD) + # Avoid installing when calling from python setup.py + install(EXPORT ecl-config DESTINATION share/cmake/ecl) + export(TARGETS ecl FILE eclConfig.cmake) + export(PACKAGE ecl) +endif() diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 39edc9d855..cd1d27b474 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -204,10 +204,10 @@ install(TARGETS ecl LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(DIRECTORY include/ - DESTINATION include + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/ - DESTINATION include + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) if (NOT BUILD_TESTS) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..8280b3eeec --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[build-system] +requires = ["setuptools", "setuptools_scm", "wheel", "scikit-build", "cmake", "ninja"] diff --git a/python/ecl/__init__.py b/python/ecl/__init__.py index b6636bf1af..9689097b21 100644 --- a/python/ecl/__init__.py +++ b/python/ecl/__init__.py @@ -64,74 +64,112 @@ module=r'ecl|ert', ) -from cwrap import load as cwrapload from cwrap import Prototype -try: - import ert_site_init -except ImportError: - pass +ECL_LEGACY_INSTALLED = not os.path.isdir(os.path.join(os.path.dirname(__file__), ".libs")) +if not ECL_LEGACY_INSTALLED: + from .version import version as __version__ -required_version_hex = 0x02070000 + def get_include(): + return os.path.join(os.path.dirname(__file__), ".include") -ecl_lib_path = None -ert_so_version = "" -__version__ = "0.0.0" + def dlopen_libecl(): + import ctypes + import platform -# 1. Try to load the __ecl_lib_info module; this module has been -# configured by cmake during the build configuration process. The -# module should contain the variable lib_path pointing to the -# directory with shared object files. -try: - from .__ecl_lib_info import EclLibInfo - ecl_lib_path = EclLibInfo.lib_path - ert_so_version = EclLibInfo.so_version - __version__ = EclLibInfo.__version__ -except ImportError: - pass -except AttributeError: - pass + path = os.path.join(os.path.dirname(__file__), ".libs") + if platform.system() == "Linux": + path = os.path.join(path, "libecl.so") + elif platform.system() == "Darwin": + path = os.path.join(path, "libecl.dylib") + else: + raise NotImplementedError("Invalid platform") + return ctypes.CDLL(path, ctypes.RTLD_GLOBAL) -# 2. Using the environment variable ERT_LIBRARY_PATH it is possible to -# override the default algorithms. If the ERT_LIBRARY_PATH is set -# to a non existing directory a warning will go to stderr and the -# setting will be ignored. -env_lib_path = os.getenv("ERT_LIBRARY_PATH") -if env_lib_path: - if os.path.isdir( env_lib_path ): - ert_lib_path = os.getenv("ERT_LIBRARY_PATH") - else: - sys.stderr.write("Warning: Environment variable ERT_LIBRARY_PATH points to nonexisting directory:%s - ignored" % env_lib_path) + class EclPrototype(Prototype): + lib = dlopen_libecl() -# Check that the final ert_lib_path setting corresponds to an existing -# directory. -if ecl_lib_path: - if not os.path.isabs(ecl_lib_path): - ecl_lib_path = os.path.abspath(os.path.join(os.path.dirname(__file__), ecl_lib_path)) + def __init__(self, prototype, bind=True): + super(EclPrototype, self).__init__(EclPrototype.lib, prototype, bind=bind) +else: + # + # If installed via CMake directly (legacy) + # + from cwrap import load as cwrapload - if not os.path.isdir( ecl_lib_path ): - ecl_lib_path = None + try: + import ert_site_init + except ImportError: + pass + + + required_version_hex = 0x02070000 -if sys.hexversion < required_version_hex: - raise Exception("ERT Python requires Python 2.7.") + ecl_lib_path = None + ert_so_version = "" + __version__ = "0.0.0" -def load(name): + + # 1. Try to load the __ecl_lib_info module; this module has been + # configured by cmake during the build configuration process. The + # module should contain the variable lib_path pointing to the + # directory with shared object files. try: - return cwrapload(name, path=ecl_lib_path, so_version=ert_so_version) + from .__ecl_lib_info import EclLibInfo + ecl_lib_path = EclLibInfo.lib_path + ert_so_version = EclLibInfo.so_version + __version__ = EclLibInfo.__version__ except ImportError: - # For pip installs, setup.py puts the shared lib in this directory - own_dir=os.path.dirname(os.path.abspath(__file__)) - return cwrapload(name, path=own_dir, so_version=ert_so_version) + pass + except AttributeError: + pass + + + # 2. Using the environment variable ERT_LIBRARY_PATH it is possible to + # override the default algorithms. If the ERT_LIBRARY_PATH is set + # to a non existing directory a warning will go to stderr and the + # setting will be ignored. + env_lib_path = os.getenv("ERT_LIBRARY_PATH") + if env_lib_path: + if os.path.isdir( env_lib_path ): + ert_lib_path = os.getenv("ERT_LIBRARY_PATH") + else: + sys.stderr.write("Warning: Environment variable ERT_LIBRARY_PATH points to nonexisting directory:%s - ignored" % env_lib_path) + -class EclPrototype(Prototype): - lib = load("libecl") + # Check that the final ert_lib_path setting corresponds to an existing + # directory. + if ecl_lib_path: + if not os.path.isabs(ecl_lib_path): + ecl_lib_path = os.path.abspath(os.path.join(os.path.dirname(__file__), ecl_lib_path)) - def __init__(self, prototype, bind=True): - super(EclPrototype, self).__init__(EclPrototype.lib, prototype, bind=bind) + if not os.path.isdir( ecl_lib_path ): + ecl_lib_path = None + + if sys.hexversion < required_version_hex: + raise Exception("ERT Python requires Python 2.7.") + + def load(name): + try: + return cwrapload(name, path=ecl_lib_path, so_version=ert_so_version) + except ImportError: + # For pip installs, setup.py puts the shared lib in this directory + own_dir=os.path.dirname(os.path.abspath(__file__)) + return cwrapload(name, path=own_dir, so_version=ert_so_version) + + class EclPrototype(Prototype): + lib = load("libecl") + + def __init__(self, prototype, bind=True): + super(EclPrototype, self).__init__(EclPrototype.lib, prototype, bind=bind) + +# +# Common +# from .ecl_type import EclTypeEnum, EclDataType from .ecl_util import EclFileEnum, EclFileFlagEnum, EclPhaseEnum, EclUnitTypeEnum , EclUtil @@ -147,4 +185,3 @@ def root(): Will print the filesystem root of the current ert package. """ return os.path.abspath( os.path.join( os.path.dirname( __file__ ) , "../")) - diff --git a/python/tests/global_tests/CMakeLists.txt b/python/tests/global_tests/CMakeLists.txt index 25fb9e4263..0745999d5e 100644 --- a/python/tests/global_tests/CMakeLists.txt +++ b/python/tests/global_tests/CMakeLists.txt @@ -1,12 +1,8 @@ set(TEST_SOURCES __init__.py - test_import.py test_pylint.py ) add_python_package("python.tests.global_tests" "${PYTHON_INSTALL_PREFIX}/tests/global_tests" "${TEST_SOURCES}" False) -addPythonTest(tests.global_tests.test_import.ImportEcl) addPythonTest(tests.global_tests.test_pylint.LintErt) - - diff --git a/requirements.txt b/requirements.txt index 303f7ff42b..6555d6cc30 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,14 @@ cwrap -numpy -pandas -sphinx +functools32;python_version=='2.7' future +ninja +numpy;python_version>='3.0' +numpy<=1.16.6;python_version=='2.7' +pandas;python_version>='3.0' +pandas<=0.25.3;python_version=='2.7' +scikit-build +setuptools +setuptools_scm six -functools32;python_version=='2.7' +sphinx +wheel diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000000..31ad82b6a0 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test = pytest diff --git a/setup.py b/setup.py index ac71393d32..6df3bc7110 100644 --- a/setup.py +++ b/setup.py @@ -1,52 +1,65 @@ -import os -import subprocess - -from setuptools import setup, Extension, find_packages -from setuptools.command.build_ext import build_ext -from distutils.version import LooseVersion - +#!/usr/bin/env python -class CMakeExtension(Extension): - def __init__(self, name, sourcedir=''): - Extension.__init__(self, name, sources=[]) - self.sourcedir = os.path.abspath(sourcedir) +import os +import skbuild +import setuptools +from setuptools_scm import get_version -class CMakeBuild(build_ext): - def run(self): - for ext in self.extensions: - self.build_extension(ext) +version = get_version(relative_to=__file__, write_to="python/ecl/version.py") - def build_extension(self, ext): - cmake_args = [] # Fill in extra stuff we may need - extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) - cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir] - cfg = 'Debug' if self.debug else 'Release' - cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] - build_args = ['--config', cfg, '--', '-j2'] - if not os.path.exists(self.build_temp): - os.makedirs(self.build_temp) - env = os.environ.copy() - subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env) - subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) +with open("README.md") as f: + long_description = f.read() -setup( - name='equinor_libecl', - version='0.1.1', - author_email='chandan.nath@gmail.com', - description='libecl', - long_description=open("README.md", "r").read(), +skbuild.setup( + name="libecl", + author="Equinor ASA", + description="Package for reading and writing the result files from the ECLIPSE reservoir simulator", + long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/equinor/libecl", - license="GNU General Public License, Version 3, 29 June 2007", - packages=find_packages(where='python', exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), - package_dir={'': 'python'}, - ext_package='ecl', - ext_modules=[CMakeExtension('libecl')], - cmdclass=dict(build_ext=CMakeBuild), + packages=setuptools.find_packages(where='python', exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), + package_dir={"": "python"}, + license="GPL-3.0", + platforms="any", install_requires=[ - 'cwrap', - 'numpy', - 'pandas', + "cwrap", + "functools32;python_version=='2.7'", + "future", + "numpy;python_version>='3.0'", + "numpy<=1.16.6;python_version=='2.7'", + "pandas;python_version>='3.0'", + "pandas<=0.25.3;python_version=='2.7'", + "six" + ], + tests_require=["pytest"], + cmake_args=[ + "-DECL_VERSION=" + version, + "-DCMAKE_INSTALL_LIBDIR=python/ecl/.libs", + "-DCMAKE_INSTALL_INCLUDEDIR=python/ecl/.include", + # we can safely pass OSX_DEPLOYMENT_TARGET as it's ignored on + # everything not OS X. We depend on C++11, which makes our minimum + # supported OS X release 10.9 + "-DCMAKE_OSX_DEPLOYMENT_TARGET=10.9" + ], + # skbuild's test imples develop, which is pretty obnoxious instead, use a + # manually integrated pytest. + cmdclass={"test": setuptools.command.test.test}, + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Other Environment", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Natural Language :: English", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Physics", + "Topic :: Software Development :: Libraries", + "Topic :: Utilities" ], + version=version )