From 29620fec21b230f9ab5b8714e2c471259af74b96 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 6 Jun 2023 14:54:45 +0200 Subject: [PATCH] Add a script to build BornAgain for multiple platforms and different Python versions --- devtools/deploy/multipy/build_multipy.py | 205 +++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 devtools/deploy/multipy/build_multipy.py diff --git a/devtools/deploy/multipy/build_multipy.py b/devtools/deploy/multipy/build_multipy.py new file mode 100644 index 00000000000..9664bcea873 --- /dev/null +++ b/devtools/deploy/multipy/build_multipy.py @@ -0,0 +1,205 @@ +""" +Build BornAgain with multiple versions of Python +""" + +import os, platform, re, subprocess as subp +import re +from timeit import default_timer as timer +from collections import namedtuple + +# all environmental variables +ENV = os.environ +BUILD_DIR = "build" + +# separator line +SEPLINE = "-" * 40 + +# directories needed for the full build +BuildDirs = namedtuple("BuildDirectories", ["source", "build", "package", "python"]) + +class WindowsMake: + """ Build on Windows """ + PLATFORM = "windows" + ARCH = "amd64" + NPROC = 8 + # initial path + PATH_INI = ENV["PATH"] + OPT_DIR = "C:/opt/x64" + FFTW3_INCLUDE_DIR = OPT_DIR + "/include" + FFTW3_LIB = OPT_DIR + "/lib/libfftw3-3.lib" + BOOST_DIR = OPT_DIR + "/boost_current" + BOOST_INCLUDE_DIR = BOOST_DIR + "/include" + BOOST_LIB_DIR = BOOST_DIR + "/lib" + QT_MSVC_DIR = "C:/Qt/6.2.4/msvc2019_64" + QTCMake_DIR = QT_MSVC_DIR + "/lib/cmake" + PY_PLATFORM_BASE = "C:/opt/multipython/Python" + # supported Python platforms: default (3.9), 3.8, 3.10, 3.11 + SUPPORTED_PY_VERSIONS = ("", "38", "310", "311") + + def configure(dirs:BuildDirs): + """ + Configure the build with a given source and build directory + and a given Python platform + """ + + # NOTE: The command is equivalent to the following shell command: + # $ cmake -G "Visual Studio 17 2022" -A x64 -T host=x64 ... \ + # -DCMAKE_PREFIX_PATH="optdir;qtcmakedir" ... + # Notice that the double-quotation marks should not be included + # in the passed arguments. + conf_prc = subp.run( + ["cmake", "-G", "Visual Studio 17 2022", + "-A x64", + "-T host=x64", + "-DCMAKE_PREFIX_PATH=" + WindowsMake.OPT_DIR + ";" + WindowsMake.QTCMake_DIR, + "-DFFTW3_INCLUDE_DIR=" + WindowsMake.FFTW3_INCLUDE_DIR, + "-DFFTW3_LIBRARY=" + WindowsMake.FFTW3_LIB, + "-DCMAKE_INCLUDE_PATH=" + WindowsMake.OPT_DIR + "/include;" + + WindowsMake.BOOST_INCLUDE_DIR, + "-DCMAKE_LIBRARY_PATH=" + WindowsMake.OPT_DIR + "/lib;" + + WindowsMake.BOOST_LIB_DIR, + "-DBA_PY_PACKAGE=ON", + "-DBA_PY_PLATFORM=" + dirs.python, + "-DCMAKE_C_COMPILER=cl.exe", + "-DCMAKE_CXX_COMPILER=cl.exe", + "-B", dirs.build, "-S", dirs.source]) + + return conf_prc + + def build(dirs:BuildDirs): + """ Build in the given build directory """ + build_prc = subp.run(["cmake", "--build", dirs.build, "--config Release", "--", + "/fl", "/flp:logfile=BornAgainMSBuild.log", "/verbosity:minimal"]) + return build_prc + + def test(dirs:BuildDirs): + """ Perform tests """ + os.chdir(dirs.build) + # Windows: change the system PATH temporarily + ENV["PATH"] = WindowsMake.QT_MSVC_DIR + "/bin;" + + dirs.python + ";" + + WindowsMake.PATH_INI + test_prc = subp.run(["ctest", "-C", "Release", + "--parallel %i" % WindowsMake.NPROC, "--output-on-failure", + ]) + return test_prc + + def pack(dirs:BuildDirs): + """ Build package via NSIS """ + pack_prc = subp.run(["cpack", dirs.build, "-C", "Release", "-B", dirs.package]) + return pack_prc + + +def make(maker, source_dir, python_version): + """ Make for the given version of Python """ + print("#--- DIAGNOSTICS ---") + # list all environmental variables + for key, val in ENV.items(): + print(key, "=", val) + + print(SEPLINE) + + # set build-related directories + source_dir = os.path.abspath(source_dir) + build_dir = source_dir + "/" + BUILD_DIR + package_dir = "installer" + python_platform = "" + python_version = python_version.strip() + if python_version: + build_dir = build_dir + "_py" + python_version + package_dir = package_dir + "_py" + python_version + python_platform = maker.PY_PLATFORM_BASE + python_version + + dirs = BuildDirs(source_dir, build_dir, + build_dir + "/" + package_dir, python_platform) + + print("* build directories:", dirs) + + # make the CMake build directory and switch to it + if os.path.exists(dirs.build): + raise RuntimeError("Build directory '%s' already exists" % dirs.build) + + os.mkdir(dirs.build) + os.chdir(dirs.build) + + print("\n#--- CONFIGURE ---") + print(subp.check_output(["cmake", "--version"], text=True, encoding="utf-8")) + print("current path:" , os.getcwd()) + conf_prc = maker.configure(dirs) + # NOTE: check_returncode() raises a `CalledProcessError` exception if the process fails + conf_prc.check_returncode() + + print("\n#--- BUILD ---") + build_start = timer() + build_prc = maker.build(dirs) + build_prc.check_returncode() + build_elapsed = timer() - build_start + print(SEPLINE) + + print("\n#--- TEST ---") + test_start = timer() + test_prc = maker.test(dirs) + test_prc.check_returncode() + test_elapsed = timer() - test_start + print(SEPLINE) + + print("\n#--- PACKAGE ---") + print("package directory: '%s'" % package_dir) + pack_start = timer() + pack_prc = maker.pack(dirs) + pack_prc.check_returncode() + pack_elapsed = timer() - pack_start + print(SEPLINE) + + print() + print("#--- Total build time = {:.3f} sec".format(build_elapsed)) + print("#--- Total test time = {:.3f} sec".format(test_elapsed)) + print("#--- Total packaging time = {:.3f} sec".format(pack_elapsed)) + print() + + +def make_all(source_dir="."): + """ Make for all versions of Python """ + # supported platforms; see `platform.system` documentation + SUPPORTED_PLATFORMS = (WindowsMake.PLATFORM, MacMake_x64.PLATFORM, + MacMake_arm.PLATFORM, LinuxMake.PLATFORM) + + # switch to the proper make commands for the platform + platform_ = platform.system().lower() + arch_ = platform.machine().lower() + + if not platform_ in SUPPORTED_PLATFORMS: + raise NotImplementedError( + "MuliPythonBuild: Platform '%s' is not in the supported platforms %s" + % (platform_, SUPPORTED_PLATFORMS)) + + if platform_ == LinuxMake.PLATFORM and arch_ == LinuxMake.ARCH: + maker = LinuxMake + elif platform_ == WindowsMake.PLATFORM and arch_ == WindowsMake.ARCH: + maker = WindowsMake + elif platform_ == MacMake_x64.PLATFORM: + if arch_ == MacMake_x64.ARCH: + maker = MacMake_x64 + elif arch_ == MacMake_arm.ARCH: + maker = MacMake_arm + else: + raise NotImplementedError( + "MuliPythonBuild: Architecture '%s' on platform '%s' is not supported" + % (arch_, platform_)) + else: + raise NotImplementedError( + "MuliPythonBuild: Platform '%s' or architecture '%s' is not supported" + % (platform_, arch_)) + + source_dir = os.path.abspath(source_dir) + for python_version in maker.SUPPORTED_PY_VERSIONS: + py_ver_str = python_version if python_version else "default" + print(SEPLINE) + print("*** Build for Python version '%s' ***" % py_ver_str) + make(maker, source_dir, python_version) + print("*** END: Build for Python version '%s' ***" % py_ver_str) + print(SEPLINE) + + +if __name__ == "__main__": + make_all() -- GitLab