1#  ___________________________________________________________________________
2#
3#  Pyomo: Python Optimization Modeling Objects
4#  Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
5#  Under the terms of Contract DE-NA0003525 with National Technology and
6#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
7#  rights in this software.
8#  This software is distributed under the 3-clause BSD License.
9#  ___________________________________________________________________________
10
11import errno
12import os
13import shutil
14import stat
15import sys
16import tempfile
17
18import pyomo.common.envvar as envvar
19from pyomo.common.fileutils import this_file_dir, find_executable
20
21def handleReadonly(function, path, excinfo):
22    excvalue = excinfo[1]
23    if excvalue.errno == errno.EACCES:
24        os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
25        function(path)
26    else:
27        raise
28
29def build_pynumero(user_args=[], parallel=None):
30    import distutils.core
31    from setuptools import Extension
32    from distutils.command.build_ext import build_ext
33
34    class _CMakeBuild(build_ext, object):
35        def run(self):
36            project_dir = self.extensions[0].project_dir
37
38            cmake_config = 'Debug' if self.debug else 'Release'
39            cmake_args = [
40                '-DCMAKE_INSTALL_PREFIX=' + envvar.PYOMO_CONFIG_DIR,
41                '-DBUILD_AMPLMP_IF_NEEDED=ON',
42                #'-DCMAKE_BUILD_TYPE=' + cmake_config,
43            ] + user_args
44
45            try:
46                # Redirect all stderr to stdout (to prevent powershell
47                # from inadvertently failing builds)
48                sys.stderr.flush()
49                sys.stdout.flush()
50                old_stderr = os.dup(sys.stderr.fileno())
51                os.dup2(sys.stdout.fileno(), sys.stderr.fileno())
52                old_environ = dict(os.environ)
53                if parallel:
54                    # --parallel was only added in cmake 3.12.  Use an
55                    # environment variable so that we don't have to bump
56                    # the minimum cmake version.
57                    os.environ['CMAKE_BUILD_PARALLEL_LEVEL'] = str(parallel)
58
59                cmake = find_executable('cmake')
60                if cmake is None:
61                    raise IOError("cmake not found in the system PATH")
62                self.spawn([cmake, project_dir] + cmake_args)
63                if not self.dry_run:
64                    # Skip build and go straight to install: the build
65                    # harness should take care of dependencies and this
66                    # will prevent repeated builds in MSVS
67                    #
68                    #self.spawn(['cmake', '--build', '.',
69                    #            '--config', cmake_config])
70                    self.spawn([cmake, '--build', '.',
71                                '--target', 'install',
72                                '--config', cmake_config])
73            finally:
74                # Restore stderr
75                sys.stderr.flush()
76                sys.stdout.flush()
77                os.dup2(old_stderr, sys.stderr.fileno())
78                os.environ = old_environ
79
80    class CMakeExtension(Extension, object):
81        def __init__(self, name):
82            # don't invoke the original build_ext for this special extension
83            super(CMakeExtension, self).__init__(name, sources=[])
84            self.project_dir = os.path.join(this_file_dir(), name)
85
86    sys.stdout.write("\n**** Building PyNumero libraries ****\n")
87    package_config = {
88        'name': 'pynumero_libraries',
89        'packages': [],
90        'ext_modules': [CMakeExtension("src")],
91        'cmdclass': {'build_ext': _CMakeBuild},
92    }
93    dist = distutils.core.Distribution(package_config)
94    try:
95        basedir = os.path.abspath(os.path.curdir)
96        tmpdir = os.path.abspath(tempfile.mkdtemp())
97        os.chdir(tmpdir)
98        dist.run_command('build_ext')
99        install_dir = os.path.join(envvar.PYOMO_CONFIG_DIR, 'lib')
100    finally:
101        os.chdir(basedir)
102        shutil.rmtree(tmpdir, onerror=handleReadonly)
103    sys.stdout.write("Installed PyNumero libraries to %s\n" % ( install_dir, ))
104
105
106class PyNumeroBuilder(object):
107    def __call__(self, parallel):
108        return build_pynumero(parallel=parallel)
109
110if __name__ == "__main__":
111    build_pynumero(sys.argv[1:])
112
113