| #!/usr/bin/env python3 |
| |
| import enum |
| import multiprocessing |
| import os |
| import subprocess |
| import sys |
| |
| @enum.unique |
| class Host(enum.Enum): |
| """Enumeration of supported hosts.""" |
| Darwin = 'darwin' |
| Linux = 'linux' |
| |
| |
| def get_default_host(): |
| """Returns the Host matching the current machine.""" |
| if sys.platform.startswith('linux'): |
| return Host.Linux |
| elif sys.platform.startswith('darwin'): |
| return Host.Darwin |
| else: |
| raise RuntimeError('Unsupported host: {}'.format(sys.platform)) |
| |
| |
| def build_autoconf_target(host, python_src, out_dir): |
| print('## Building Python ##') |
| print('## Out Dir : {}'.format(out_dir)) |
| print('## Python Src : {}'.format(python_src)) |
| sys.stdout.flush() |
| |
| build_dir = os.path.join(out_dir, 'build') |
| install_dir = os.path.join(out_dir, 'install') |
| os.makedirs(build_dir, exist_ok=True) |
| os.makedirs(install_dir, exist_ok=True) |
| |
| cflags = ['-Wno-unused-command-line-argument'] |
| ldflags = ['-s'] |
| config_cmd = [ |
| os.path.join(python_src, 'configure'), |
| '--prefix={}'.format(install_dir), |
| '--enable-shared', |
| ] |
| if host == Host.Darwin: |
| MAC_MIN_VERSION = '10.9' |
| cflags.append('-mmacosx-version-min={}'.format(MAC_MIN_VERSION)) |
| cflags.append('-DMACOSX_DEPLOYMENT_TARGET={}'.format(MAC_MIN_VERSION)) |
| ldflags.append("-Wl,-rpath,'@loader_path/../lib'") |
| |
| # Disable functions to support old macOS. See https://bugs.python.org/issue31359 |
| # Fails the build if any new API is used. |
| cflags.append('-Werror=unguarded-availability') |
| # Disables unavailable functions. |
| disable_funcs = [ |
| # New in 10.13 |
| 'utimensat', 'futimens', |
| # New in 10.12 |
| 'getentropy', 'clock_getres', 'clock_gettime', 'clock_settime', |
| # New in 10.10 |
| 'fstatat', 'faccessat', 'fchmodat', 'fchownat', 'linkat', 'fdopendir', |
| 'mkdirat', 'renameat', 'unlinkat', 'readlinkat', 'symlinkat', 'openat', |
| ] |
| config_cmd.extend('ac_cv_func_{}=no'.format(f) for f in disable_funcs) |
| elif host == Host.Linux: |
| ldflags.append("-Wl,-rpath,'$$ORIGIN/../lib'") |
| |
| env = dict(os.environ) |
| env.update({ |
| 'CC': ' '.join(['cc'] + cflags + ldflags), |
| 'CXX': ' '.join(['c++'] + cflags + ldflags), |
| }) |
| |
| subprocess.check_call(config_cmd, cwd=build_dir, env=env) |
| |
| if host == Host.Darwin: |
| # By default, LC_ID_DYLIB for libpython will be set to an absolute path. |
| # Linker will embed this path to all binaries linking this library. |
| # Since configure does not give us a chance to set -install_name, we have |
| # to edit the library afterwards. |
| libpython = 'libpython3.8.dylib' |
| subprocess.check_call(['make', |
| '-j{}'.format(multiprocessing.cpu_count()), |
| libpython], |
| cwd=build_dir) |
| subprocess.check_call(['install_name_tool', '-id', '@rpath/' + libpython, |
| libpython], cwd=build_dir) |
| |
| subprocess.check_call(['make', |
| '-j{}'.format(multiprocessing.cpu_count()), |
| 'install'], |
| cwd=build_dir) |
| return install_dir |
| |
| |
| def package_target(host, install_dir, dest_dir, build_id): |
| package_name = 'python3-{}-{}.tar.bz2'.format(host.value, build_id) |
| package_path = os.path.join(dest_dir, package_name) |
| |
| os.makedirs(dest_dir, exist_ok=True) |
| print('## Packaging Python ##') |
| print('## Package : {}'.format(package_path)) |
| print('## Install Dir : {}'.format(install_dir)) |
| sys.stdout.flush() |
| |
| # Libs to exclude, from PC/layout/main.py, get_lib_layout(). |
| EXCLUDES = [ |
| "lib/python*/config-*", |
| # EXCLUDE_FROM_LIB |
| "*.pyc", "__pycache__", "*.pickle", |
| # TEST_DIRS_ONLY |
| "test", "tests", |
| # TCLTK_DIRS_ONLY |
| "tkinter", "turtledemo", |
| # IDLE_DIRS_ONLY |
| "idlelib", |
| # VENV_DIRS_ONLY |
| "venv", "ensurepip", |
| # TCLTK_FILES_ONLY |
| "turtle.py", |
| # BDIST_WININST_FILES_ONLY |
| "wininst-*", "bdist_wininst.py", |
| ] |
| tar_cmd = ['tar'] |
| for pattern in EXCLUDES: |
| tar_cmd.append('--exclude') |
| tar_cmd.append(pattern) |
| tar_cmd.extend(['-cjf', package_path, '.']) |
| print(subprocess.list2cmdline(tar_cmd)) |
| subprocess.check_call(tar_cmd, cwd=install_dir) |
| |
| |
| def main(argv): |
| python_src = argv[1] |
| out_dir = argv[2] |
| dest_dir = argv[3] |
| build_id = argv[4] |
| host = get_default_host() |
| |
| install_dir = build_autoconf_target(host, python_src, out_dir) |
| package_target(host, install_dir, dest_dir, build_id) |
| |
| |
| if __name__ == '__main__': |
| main(sys.argv) |