# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2024
# National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________
import errno
import os
import shutil
import stat
import sys
import tempfile
import pyomo.common.envvar as envvar
from pyomo.common.fileutils import this_file_dir, find_executable
[docs]
def handleReadonly(function, path, excinfo):
excvalue = excinfo[1]
if excvalue.errno == errno.EACCES:
os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) # 0777
function(path)
else:
raise
[docs]
def build_cmake_project(
targets, package_name=None, description=None, user_args=[], parallel=None
):
from setuptools import Extension, Distribution
from setuptools.command.build_ext import build_ext
class _CMakeBuild(build_ext, object):
def run(self):
for cmake_ext in self.extensions:
self._cmake_build_target(cmake_ext)
def _cmake_build_target(self, cmake_ext):
cmake_config = 'Debug' if self.debug else 'Release'
cmake_args = [
'-DCMAKE_INSTALL_PREFIX=' + envvar.PYOMO_CONFIG_DIR,
#'-DCMAKE_BUILD_TYPE=' + cmake_config,
] + cmake_ext.user_args
try:
# Redirect all stderr to stdout (to prevent powershell
# from inadvertently failing builds)
sys.stderr.flush()
sys.stdout.flush()
old_stderr = os.dup(sys.stderr.fileno())
os.dup2(sys.stdout.fileno(), sys.stderr.fileno())
old_environ = dict(os.environ)
if cmake_ext.parallel:
# --parallel was only added in cmake 3.12. Use an
# environment variable so that we don't have to bump
# the minimum cmake version.
os.environ['CMAKE_BUILD_PARALLEL_LEVEL'] = str(cmake_ext.parallel)
cmake = find_executable('cmake')
if cmake is None:
raise IOError("cmake not found in the system PATH")
self.spawn([cmake, cmake_ext.target_dir] + cmake_args)
if not self.dry_run:
# Skip build and go straight to install: the build
# harness should take care of dependencies and this
# will prevent repeated builds in MSVS
#
# self.spawn(['cmake', '--build', '.',
# '--config', cmake_config])
self.spawn(
[
cmake,
'--build',
'.',
'--target',
'install',
'--config',
cmake_config,
]
)
finally:
# Restore stderr
sys.stderr.flush()
sys.stdout.flush()
os.dup2(old_stderr, sys.stderr.fileno())
os.environ = old_environ
class CMakeExtension(Extension, object):
def __init__(self, target_dir, user_args, parallel):
# don't invoke the original build_ext for this special extension
super(CMakeExtension, self).__init__(
self.__class__.__qualname__, sources=[]
)
self.target_dir = target_dir
self.user_args = user_args
self.parallel = parallel
if package_name is None:
package_name = 'build_cmake'
if description is None:
description = package_name
# Get the source directory from the caller's frame for use in
# resolving any relative target paths
caller_dir = this_file_dir(2)
ext_modules = [
CMakeExtension(os.path.join(caller_dir, target), user_args, parallel)
for target in targets
]
sys.stdout.write(f"\n**** Building {description} ****\n")
package_config = {
'name': package_name,
'packages': [],
'ext_modules': ext_modules,
'cmdclass': {'build_ext': _CMakeBuild},
}
dist = Distribution(package_config)
basedir = os.path.abspath(os.path.curdir)
try:
tmpdir = os.path.abspath(tempfile.mkdtemp())
os.chdir(tmpdir)
dist.run_command('build_ext')
install_dir = envvar.PYOMO_CONFIG_DIR
finally:
os.chdir(basedir)
shutil.rmtree(tmpdir, onerror=handleReadonly)
sys.stdout.write(f"Installed {description} to {install_dir}\n")