| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961 |
- """
- Generates a wheel (.whl) file from the output of makepanda.
- """
- from __future__ import print_function, unicode_literals
- import distutils.util
- import json
- import sys
- import os
- from os.path import join
- import zipfile
- import hashlib
- import tempfile
- import subprocess
- import configparser
- from distutils.sysconfig import get_config_var
- from optparse import OptionParser
- from base64 import urlsafe_b64encode
- cfg_parser = None
- def get_metadata_value(key):
- global cfg_parser
- if not cfg_parser:
- # Parse the metadata from the setup.cfg file.
- cfg_parser = configparser.ConfigParser()
- path = os.path.join(os.path.dirname(__file__), '..', 'setup.cfg')
- assert cfg_parser.read(path), "Could not read setup.cfg file."
- value = cfg_parser.get('metadata', key)
- if key == 'classifiers':
- value = value.strip().split('\n')
- return value
- def get_host():
- """Returns the host platform, ie. the one we're compiling on."""
- if sys.platform == 'win32' or sys.platform == 'cygwin':
- # sys.platform is win32 on 64-bits Windows as well.
- return 'windows'
- elif sys.platform == 'darwin':
- return 'darwin'
- elif sys.platform.startswith('linux'):
- try:
- # Python seems to offer no built-in way to check this.
- osname = subprocess.check_output(["uname", "-o"])
- if osname.strip().lower() == b'android':
- return 'android'
- else:
- return 'linux'
- except:
- return 'linux'
- elif sys.platform.startswith('freebsd'):
- return 'freebsd'
- else:
- exit('Unrecognized sys.platform: %s' % (sys.platform))
- def locate_binary(binary):
- """
- Searches the system PATH for the binary.
- :param binary: Name of the binary to locate.
- :return: The full path to the binary, or None if not found.
- """
- if os.path.isfile(binary):
- return binary
- if "PATH" not in os.environ or os.environ["PATH"] == "":
- p = os.defpath
- else:
- p = os.environ["PATH"]
- pathList = p.split(os.pathsep)
- suffixes = ['']
- if get_host() == 'windows':
- if not binary.lower().endswith('.exe') and not binary.lower().endswith('.bat'):
- # Append .exe if necessary
- suffixes = ['.exe', '.bat']
- # On Windows the current directory is always implicitly
- # searched before anything else on PATH.
- pathList = ['.'] + pathList
- for path in pathList:
- binpath = os.path.join(os.path.expanduser(path), binary)
- for suffix in suffixes:
- if os.access(binpath + suffix, os.X_OK):
- return os.path.abspath(os.path.realpath(binpath + suffix))
- return None
- def get_abi_tag():
- soabi = get_config_var('SOABI')
- if soabi and soabi.startswith('cpython-'):
- return 'cp' + soabi.split('-')[1]
- elif soabi:
- return soabi.replace('.', '_').replace('-', '_')
- soabi = 'cp%d%d' % (sys.version_info[:2])
- if sys.version_info >= (3, 8):
- return soabi
- debug_flag = get_config_var('Py_DEBUG')
- if (debug_flag is None and hasattr(sys, 'gettotalrefcount')) or debug_flag:
- soabi += 'd'
- return soabi
- def is_exe_file(path):
- return os.path.isfile(path) and path.lower().endswith('.exe')
- def is_elf_file(path):
- base = os.path.basename(path)
- return os.path.isfile(path) and '.' not in base and \
- open(path, 'rb').read(4) == b'\x7FELF'
- def is_macho_or_fat_file(path):
- base = os.path.basename(path)
- return os.path.isfile(path) and '.' not in base and \
- open(path, 'rb').read(4) in (b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE',
- b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE',
- b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA',
- b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA')
- def is_fat_file(path):
- return os.path.isfile(path) and \
- open(path, 'rb').read(4) in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA',
- b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA')
- def get_python_ext_module_dir():
- import _ctypes
- return os.path.dirname(_ctypes.__file__)
- if sys.platform in ('win32', 'cygwin'):
- is_executable = is_exe_file
- elif sys.platform == 'darwin':
- is_executable = is_macho_or_fat_file
- else:
- is_executable = is_elf_file
- class TargetInfo:
- """
- Holds information about the system the wheel is being prepared for.
- """
- def __init__(self,
- platform_tag=None,
- soabi='',
- python_version=None,
- python_root=sys.exec_prefix,
- sys_platform=get_host(),
- static_panda=False):
- """
- With no arguments, it will be assumed that the target is the same as the
- host (which will be most cases).
- :param platform_string: The platform tag used in the wheel filename.
- :param soabi: Value of SOABI from Python's makefile. This isn't really
- a thing with Python 2.
- :param python_version: Version of Python we're bundling. Will be
- inferred from SOABI or the system's python version if not
- explicitly set.
- :param python_root: Root of the Python installation containing the
- libraries to be bundled in deploy_libs.
- :param sys_platform:
- """
- if platform_tag:
- self.platform_tag = platform_tag
- else:
- self.platform_tag = distutils.util.get_platform()
- if (self.platform_tag.startswith("linux-")
- and os.path.isfile("/lib/libc-2.5.so")
- and os.path.isdir("/opt/python")):
- self.platform_tag = self.platform_tag.replace("linux",
- "manylinux1")
- self.abi_flags = None
- self.soabi = soabi
- self.python_version = python_version
- self.python_root = python_root
- self.sys_platform = sys_platform
- self.static_panda = static_panda
- self.platform_tag = self.platform_tag.replace('-', '_').replace('.', '_')
- if not self.python_version:
- if self.soabi:
- self.python_version = tuple(
- int(i) for i in self.soabi.split('-')[1].rstrip('mdu')
- )
- print("Inferring Python version %s from provided soabi."
- % str(self.python_version))
- else:
- self.python_version = sys.version_info[:2]
- self.soabi = get_config_var('SOABI')
- print("Inferring Python version %s from system version."
- % str(self.python_version))
- elif type(self.python_version) is str:
- self.abi_flags = self.python_version.lstrip('0123456789')
- self.python_version = tuple(int(i) for i in self.python_version.rstrip('mdu'))
- @property
- def python_ext_module_dir(self):
- return os.path.join(self.python_root,
- 'lib/python{}.{}/lib-dynload'
- .format(*self.python_version))
- @property
- def python_tag(self):
- return "cp{0}{1}".format(*self.python_version)
- @property
- def abi_tag(self):
- if sys.version_info >= (3, 0) and self.soabi:
- if self.soabi.startswith('cpython-'):
- return 'cp' + self.soabi.split('-')[1]
- return self.soabi.replace('.', '_').replace('-', '_')
- abi_tag = self.python_tag
- if self.abi_flags:
- return abi_tag + self.abi_flags
- debug_flag = get_config_var('Py_DEBUG')
- if (debug_flag is None and hasattr(sys,
- 'gettotalrefcount')) or debug_flag:
- abi_tag += 'd'
- malloc_flag = get_config_var('WITH_PYMALLOC')
- if malloc_flag is None or malloc_flag:
- abi_tag += 'm'
- if sys.version_info < (3, 3):
- usize = get_config_var('Py_UNICODE_SIZE')
- if (usize is None and sys.maxunicode == 0x10ffff) or usize == 4:
- abi_tag += 'u'
- return abi_tag
- @property
- def extension_suffix(self):
- if 'ios' in self.platform_tag:
- return '.a' if self.static_panda else '.so'
- if self.soabi != '':
- ext = '.pyd' if self.sys_platform == 'win32' else '.so'
- return '.' + self.soabi + ext
- import _imp
- return _imp.extension_suffixes()[0]
- # Other global parameters
- # PY_VERSION = "cp{0}{1}".format(*sys.version_info)
- # ABI_TAG = get_abi_tag()
- EXCLUDE_EXT = [".pyc", ".pyo", ".N", ".prebuilt", ".xcf", ".plist", ".vcproj", ".sln"]
- # Plug-ins to install.
- PLUGIN_LIBS = ["pandagl", "pandagles", "pandagles2", "pandadx9", "p3tinydisplay", "p3ptloader", "p3assimp", "p3ffmpeg", "p3openal_audio", "p3fmod_audio"]
- # Libraries included in manylinux ABI that should be ignored. See PEP 513/571/599.
- MANYLINUX_LIBS = [
- "libgcc_s.so.1", "libstdc++.so.6", "libm.so.6", "libdl.so.2", "librt.so.1",
- "libcrypt.so.1", "libc.so.6", "libnsl.so.1", "libutil.so.1",
- "libpthread.so.0", "libresolv.so.2", "libX11.so.6", "libXext.so.6",
- "libXrender.so.1", "libICE.so.6", "libSM.so.6", "libGL.so.1",
- "libgobject-2.0.so.0", "libgthread-2.0.so.0", "libglib-2.0.so.0",
- # These are not mentioned in manylinux1 spec but should nonetheless always
- # be excluded.
- "linux-vdso.so.1", "linux-gate.so.1", "ld-linux.so.2", "libdrm.so.2",
- ]
- # Binaries to never scan for dependencies on non-Windows systems.
- IGNORE_UNIX_DEPS_OF = [
- "panda3d_tools/pstats",
- ]
- WHEEL_DATA = """Wheel-Version: 1.0
- Generator: makepanda
- Root-Is-Purelib: false
- Tag: {0}-{1}-{2}
- """
- PROJECT_URLS = dict([line.split('=', 1) for line in get_metadata_value('project_urls').strip().splitlines()])
- METADATA = {
- "license": get_metadata_value('license'),
- "name": get_metadata_value('name'),
- "metadata_version": "2.0",
- "generator": "makepanda",
- "summary": get_metadata_value('description'),
- "extensions": {
- "python.details": {
- "project_urls": dict(PROJECT_URLS, Home=get_metadata_value('url')),
- "document_names": {
- "license": "LICENSE.txt"
- },
- "contacts": [
- {
- "role": "author",
- "name": get_metadata_value('author'),
- "email": get_metadata_value('author_email'),
- }
- ]
- }
- },
- "classifiers": get_metadata_value('classifiers'),
- }
- DESCRIPTION = """
- The Panda3D free 3D game engine
- ===============================
- Panda3D is a powerful 3D engine written in C++, with a complete set of Python
- bindings. Unlike other engines, these bindings are automatically generated,
- meaning that they are always up-to-date and complete: all functions of the
- engine can be controlled from Python. All major Panda3D applications have been
- written in Python, this is the intended way of using the engine.
- Panda3D now supports automatic shader generation, which now means you can use
- normal maps, gloss maps, glow maps, HDR, cartoon shading, and the like without
- having to write any shaders.
- Panda3D is a modern engine supporting advanced features such as shaders,
- stencil, and render-to-texture. Panda3D is unusual in that it emphasizes a
- short learning curve, rapid development, and extreme stability and robustness.
- Panda3D is free software that runs under Windows, Linux, or macOS.
- The Panda3D team is very concerned with making the engine accessible to new
- users. We provide a detailed manual, a complete API reference, and a large
- collection of sample programs to help you get started. We have active forums,
- with many helpful users, and the developers are regularly online to answer
- questions.
- """
- PANDA3D_TOOLS_INIT = """import os, sys
- import panda3d
- dir = os.path.dirname(panda3d.__file__)
- del panda3d
- if sys.platform in ('win32', 'cygwin'):
- path_var = 'PATH'
- if hasattr(os, 'add_dll_directory'):
- os.add_dll_directory(dir)
- elif sys.platform == 'darwin':
- path_var = 'DYLD_LIBRARY_PATH'
- else:
- path_var = 'LD_LIBRARY_PATH'
- if not os.environ.get(path_var):
- os.environ[path_var] = dir
- else:
- os.environ[path_var] = dir + os.pathsep + os.environ[path_var]
- del os, sys, path_var, dir
- def _exec_tool(tool):
- import os, sys
- from subprocess import Popen
- tools_dir = os.path.dirname(__file__)
- handle = Popen(sys.argv, executable=os.path.join(tools_dir, tool))
- try:
- try:
- return handle.wait()
- except KeyboardInterrupt:
- # Give the program a chance to handle the signal gracefully.
- return handle.wait()
- except:
- handle.kill()
- handle.wait()
- raise
- # Register all the executables in this directory as global functions.
- {0}
- """
- def parse_dependencies_windows(data):
- """ Parses the given output from dumpbin /dependents to determine the list
- of dll's this executable file depends on. """
- lines = data.splitlines()
- li = 0
- while li < len(lines):
- line = lines[li]
- li += 1
- if line.find(' has the following dependencies') != -1:
- break
- if li < len(lines):
- line = lines[li]
- if line.strip() == '':
- # Skip a blank line.
- li += 1
- # Now we're finding filenames, until the next blank line.
- filenames = []
- while li < len(lines):
- line = lines[li]
- li += 1
- line = line.strip()
- if line == '':
- # We're done.
- return filenames
- filenames.append(line)
- # At least we got some data.
- return filenames
- def parse_dependencies_unix(data):
- """ Parses the given output from otool -XL or ldd to determine the list of
- libraries this executable file depends on. """
- lines = data.splitlines()
- filenames = []
- for l in lines:
- l = l.strip()
- if l != "statically linked":
- filenames.append(l.split(' ', 1)[0])
- return filenames
- def scan_dependencies(pathname, target_info):
- """ Checks the named file for DLL dependencies, and adds any appropriate
- dependencies found into pluginDependencies and dependentFiles. """
- if target_info.sys_platform in ("darwin", "ios"):
- command = ['otool', '-XL', pathname]
- elif target_info.sys_platform in ("win32", "cygwin"):
- command = ['dumpbin', '/dependents', pathname]
- else:
- command = ['ldd', pathname]
- process = subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True)
- output, unused_err = process.communicate()
- retcode = process.poll()
- if retcode:
- raise subprocess.CalledProcessError(retcode, command[0], output=output)
- filenames = None
- if target_info.sys_platform in ("win32", "cygwin"):
- filenames = parse_dependencies_windows(output)
- else:
- filenames = parse_dependencies_unix(output)
- if filenames is None:
- sys.exit("Unable to determine dependencies from %s" % (pathname))
- if target_info.sys_platform in ("darwin", "ios") and len(filenames) > 0:
- # Filter out the library ID.
- if os.path.basename(filenames[0]).split('.', 1)[0] == os.path.basename(pathname).split('.', 1)[0]:
- del filenames[0]
- return filenames
- class WheelFile(object):
- def __init__(self, name, version, target_info):
- self.name = name
- self.version = version
- self.target_info = target_info
- wheel_name = "{0}-{1}-{2}-{3}-{4}.whl".format(
- name, version, target_info.python_tag, target_info.abi_tag, target_info.platform_tag)
- print("Writing %s" % (wheel_name))
- self.zip_file = zipfile.ZipFile(wheel_name, 'w', zipfile.ZIP_DEFLATED)
- self.records = []
- # Used to locate dependency libraries.
- self.lib_path = []
- self.dep_paths = {}
- self.ignore_deps = set()
- def consider_add_dependency(self, target_path, dep, search_path=None):
- """Considers adding a dependency library.
- Returns the target_path if it was added, which may be different from
- target_path if it was already added earlier, or None if it wasn't."""
- if dep in self.dep_paths:
- # Already considered this.
- return self.dep_paths[dep]
- self.dep_paths[dep] = None
- if dep in self.ignore_deps or dep.lower().startswith("python") or os.path.basename(dep).startswith("libpython"):
- # Don't include the Python library, or any other explicit ignore.
- if verbose:
- print("Ignoring {0} (explicitly ignored)".format(dep))
- return
- if target_info.sys_platform in ("darwin", "ios") and dep.endswith(".so"):
- # Temporary hack for 1.9, which had link deps on modules.
- return
- if target_info.sys_platform in ("darwin", "ios") and dep.startswith("/System/"):
- return
- if dep.startswith('/'):
- source_path = dep
- else:
- source_path = None
- if search_path is None:
- search_path = self.lib_path
- for lib_dir in search_path:
- # Ignore static stuff.
- path = os.path.join(lib_dir, dep)
- if os.path.isfile(path):
- source_path = os.path.normpath(path)
- break
- if not source_path:
- # Couldn't find library in the panda3d lib dir.
- if verbose:
- print("Ignoring {0} (not in search path)".format(dep))
- return
- self.dep_paths[dep] = target_path
- self.write_file(target_path, source_path)
- return target_path
- def write_file(self, target_path, source_path):
- """Adds the given file to the .whl file."""
- orig_source_path = source_path
- # If this is a .so file, we should set the rpath appropriately.
- temp = None
- basename, ext = os.path.splitext(source_path)
- if ext in ('.so', '.dylib') or '.so.' in os.path.basename(source_path) or \
- (not ext and is_executable(source_path)):
- # Scan Unix dependencies.
- if target_path not in IGNORE_UNIX_DEPS_OF:
- deps = scan_dependencies(source_path, target_info)
- else:
- deps = []
- suffix = ''
- if '.so' in os.path.basename(source_path):
- suffix = '.so'
- elif ext == '.dylib':
- suffix = '.dylib'
- temp = tempfile.NamedTemporaryFile(suffix=suffix, prefix='whl', delete=False)
- # On macOS, if no fat wheel was requested, extract the right architecture.
- if target_info.sys_platform == "darwin" and is_fat_file(source_path) \
- and not target_info.platform_tag.endswith("_intel") \
- and "_fat" not in target_info.platform_tag:
- if target_info.platform_tag.endswith("_x86_64"):
- arch = 'x86_64'
- else:
- arch = target_info.platform_tag.split('_')[-1]
- subprocess.call(['lipo', source_path, '-extract', arch, '-output', temp.name])
- else:
- # Otherwise, just copy it over.
- temp.write(open(source_path, 'rb').read())
- temp.close()
- os.chmod(temp.name, os.stat(temp.name).st_mode | 0o711)
- # Now add dependencies. On macOS, fix @loader_path references.
- if target_info.sys_platform in ("darwin", "ios"):
- if source_path.endswith('deploy-stubw'):
- deps_path = '@executable_path/../Frameworks'
- else:
- deps_path = '@loader_path'
- remove_signature = False
- loader_path = [os.path.dirname(source_path)]
- for dep in deps:
- if dep.endswith('/Python'):
- # If this references the Python framework, change it
- # to reference libpython instead.
- new_dep = deps_path + '/libpython{0}.{1}.dylib'.format(*target_info.python_version)
- elif '@loader_path' in dep:
- dep_path = dep.replace('@loader_path', '.')
- target_dep = os.path.dirname(target_path) + '/' + os.path.basename(dep)
- target_dep = self.consider_add_dependency(target_dep, dep_path, loader_path)
- if not target_dep:
- # It won't be included, so no use adjusting the path.
- continue
- new_dep = os.path.join(deps_path, os.path.relpath(target_dep, os.path.dirname(target_path)))
- elif '@rpath' in dep:
- # Unlike makepanda, CMake uses @rpath instead of
- # @loader_path. This means we can just search for the
- # dependencies like normal.
- dep_path = dep.replace('@rpath', '.')
- target_dep = os.path.dirname(target_path) + '/' + os.path.basename(dep)
- self.consider_add_dependency(target_dep, dep_path)
- continue
- elif dep.startswith('/Library/Frameworks/Python.framework/'):
- # Add this dependency if it's in the Python directory.
- target_dep = os.path.dirname(target_path) + '/' + os.path.basename(dep)
- target_dep = self.consider_add_dependency(target_dep, dep, loader_path)
- if not target_dep:
- # It won't be included, so no use adjusting the path.
- continue
- new_dep = os.path.join(deps_path, os.path.relpath(target_dep, os.path.dirname(target_path)))
- else:
- if '/' in dep:
- if verbose:
- print("Ignoring dependency %s" % (dep))
- continue
- subprocess.call(["install_name_tool", "-change", dep, new_dep, temp.name])
- remove_signature = True
- # Remove the codesign signature if we modified the library.
- if remove_signature:
- subprocess.call(["codesign", "--remove-signature", temp.name])
- else:
- # On other unixes, we just add dependencies normally.
- for dep in deps:
- # Only include dependencies with relative path, for now.
- if '/' not in dep:
- target_dep = os.path.dirname(target_path) + '/' + dep
- self.consider_add_dependency(target_dep, dep)
- subprocess.call(["strip", "-s", temp.name])
- subprocess.call(["patchelf", "--set-rpath", "$ORIGIN", temp.name])
- source_path = temp.name
- ext = ext.lower()
- if ext in ('.dll', '.pyd', '.exe'):
- # Scan and add Win32 dependencies.
- for dep in scan_dependencies(source_path, target_info):
- target_dep = os.path.dirname(target_path) + '/' + dep
- self.consider_add_dependency(target_dep, dep)
- # Calculate the SHA-256 hash and size.
- sha = hashlib.sha256()
- fp = open(source_path, 'rb')
- size = 0
- data = fp.read(1024 * 1024)
- while data:
- size += len(data)
- sha.update(data)
- data = fp.read(1024 * 1024)
- fp.close()
- # Save it in PEP-0376 format for writing out later.
- digest = urlsafe_b64encode(sha.digest()).decode('ascii')
- digest = digest.rstrip('=')
- self.records.append("{0},sha256={1},{2}\n".format(target_path, digest, size))
- if verbose:
- print("Adding {0} from {1}".format(target_path, orig_source_path))
- self.zip_file.write(source_path, target_path)
- #if temp:
- # os.unlink(temp.name)
- def write_file_data(self, target_path, source_data):
- """Adds the given file from a string."""
- sha = hashlib.sha256()
- sha.update(source_data.encode())
- digest = urlsafe_b64encode(sha.digest()).decode('ascii')
- digest = digest.rstrip('=')
- self.records.append("{0},sha256={1},{2}\n".format(target_path, digest, len(source_data)))
- if verbose:
- print("Adding %s from data" % target_path)
- self.zip_file.writestr(target_path, source_data)
- def write_directory(self, target_dir, source_dir):
- """Adds the given directory recursively to the .whl file."""
- for root, dirs, files in os.walk(source_dir):
- for file in files:
- if os.path.splitext(file)[1] in EXCLUDE_EXT:
- continue
- source_path = os.path.join(root, file)
- target_path = os.path.join(target_dir, os.path.relpath(source_path, source_dir))
- target_path = target_path.replace('\\', '/')
- self.write_file(target_path, source_path)
- def close(self):
- # Write the RECORD file.
- record_file = "{0}-{1}.dist-info/RECORD".format(self.name, self.version)
- self.records.append(record_file + ",,\n")
- self.zip_file.writestr(record_file, "".join(self.records))
- self.zip_file.close()
- def makewheel(version, output_dir, target_info):
- if target_info.sys_platform not in ("win32", "darwin", "ios") and not target_info.sys_platform.startswith("cygwin"):
- if not locate_binary("patchelf"):
- raise Exception("patchelf is required when building a Linux wheel.")
- if sys.version_info < (3, 6):
- raise Exception("Python 3.6 is required to produce a wheel.")
- platform = target_info.platform_tag
- if platform is None:
- # Determine the platform from the build.
- platform_dat = os.path.join(output_dir, 'tmp', 'platform.dat')
- if os.path.isfile(platform_dat):
- platform = open(platform_dat, 'r').read().strip()
- else:
- print("Could not find platform.dat in build directory")
- platform = get_platform()
- if platform.startswith("linux-") and os.path.isdir("/opt/python"):
- # Is this manylinux?
- if os.path.isfile("/lib/libc-2.5.so") or os.path.isfile("/lib64/libc-2.5.so"):
- platform = platform.replace("linux", "manylinux1")
- elif os.path.isfile("/lib/libc-2.12.so") or os.path.isfile("/lib64/libc-2.12.so"):
- platform = platform.replace("linux", "manylinux2010")
- elif os.path.isfile("/lib/libc-2.17.so") or os.path.isfile("/lib64/libc-2.17.so"):
- platform = platform.replace("linux", "manylinux2014")
- platform = platform.replace('-', '_').replace('.', '_')
- # Global filepaths
- panda3d_dir = join(output_dir, "panda3d")
- pandac_dir = join(output_dir, "pandac")
- direct_dir = join(output_dir, "direct")
- models_dir = join(output_dir, "models")
- etc_dir = join(output_dir, "etc")
- bin_dir = join(output_dir, "bin")
- if target_info.sys_platform == "win32":
- libs_dir = join(output_dir, "bin")
- else:
- libs_dir = join(output_dir, "lib")
- ext_mod_dir = get_python_ext_module_dir()
- license_src = "LICENSE"
- readme_src = "README.md"
- # Update relevant METADATA entries
- METADATA['version'] = version
- # Build out the metadata
- details = METADATA["extensions"]["python.details"]
- homepage = details["project_urls"]["Home"]
- author = details["contacts"][0]["name"]
- email = details["contacts"][0]["email"]
- metadata = ''.join([
- "Metadata-Version: {metadata_version}\n" \
- "Name: {name}\n" \
- "Version: {version}\n" \
- "Summary: {summary}\n" \
- "License: {license}\n".format(**METADATA),
- "Home-page: {0}\n".format(homepage),
- ] + ["Project-URL: {0}, {1}\n".format(*url) for url in PROJECT_URLS.items()] + [
- "Author: {0}\n".format(author),
- "Author-email: {0}\n".format(email),
- "Platform: {0}\n".format(target_info.platform_tag),
- ] + ["Classifier: {0}\n".format(c) for c in METADATA['classifiers']])
- metadata += '\n' + DESCRIPTION.strip() + '\n'
- # Zip it up and name it the right thing
- whl = WheelFile('panda3d', version, target_info)
- whl.lib_path = [libs_dir]
- if sys.platform == "win32":
- whl.lib_path.append(ext_mod_dir)
- if target_info.platform_tag.startswith("manylinux"):
- # On manylinux1, we pick up all libraries except for the ones specified
- # by the manylinux1 ABI.
- whl.lib_path.append("/usr/local/lib")
- if target_info.platform_tag.endswith("_x86_64"):
- whl.lib_path += ["/lib64", "/usr/lib64"]
- else:
- whl.lib_path += ["/lib", "/usr/lib"]
- whl.ignore_deps.update(MANYLINUX_LIBS)
- # Add libpython for deployment.
- if sys.platform in ('win32', 'cygwin'):
- pylib_name = 'python{0}{1}.dll'.format(*sys.version_info)
- pylib_path = os.path.join(get_config_var('BINDIR'), pylib_name)
- elif sys.platform == 'darwin':
- pylib_name = 'libpython{0}.{1}.dylib'.format(*sys.version_info)
- pylib_path = os.path.join(get_config_var('LIBDIR'), pylib_name)
- else:
- pylib_name = get_config_var('LDLIBRARY')
- pylib_arch = get_config_var('MULTIARCH')
- libdir = get_config_var('LIBDIR')
- if pylib_arch and os.path.exists(os.path.join(libdir, pylib_arch, pylib_name)):
- pylib_path = os.path.join(libdir, pylib_arch, pylib_name)
- else:
- pylib_path = os.path.join(libdir, pylib_name)
- # If Python was linked statically, we don't need to include this.
- if not pylib_name.endswith('.a'):
- whl.write_file('deploy_libs/' + pylib_name, pylib_path)
- # Add the trees with Python modules.
- whl.write_directory('direct', direct_dir)
- # Write the panda3d tree. We use a custom empty __init__ since the
- # default one adds the bin directory to the PATH, which we don't have.
- p3d_init = """"Python bindings for the Panda3D libraries"
- __version__ = '{0}'
- """.format(version)
- if (2, 7) in target_info.python_version:
- p3d_init += """
- if __debug__:
- if 1 / 2 == 0:
- raise ImportError(\"Python 2 is not supported.\")
- """
- whl.write_file_data('panda3d/__init__.py', p3d_init)
- ext_suffix = target_info.extension_suffix
- module_dir = libs_dir if target_info.static_panda else panda3d_dir
- for file in os.listdir(module_dir):
- if file == '__init__.py':
- pass
- elif file.endswith('.py') or (file.endswith(ext_suffix) and '.' not in file[:-len(ext_suffix)]) or file.startswith('libpy'):
- source_path = os.path.join(module_dir, file)
- if file.endswith('.pyd') and target_info.sys_platform == 'cygwin':
- # Rename it to .dll for cygwin Python to be able to load it.
- target_path = 'panda3d/' + os.path.splitext(file)[0] + '.dll'
- else:
- target_path = 'panda3d/' + file
- whl.write_file(target_path, source_path)
- # And copy the extension modules from the Python installation into the
- # deploy_libs directory, for use by deploy-ng.
- if target_info.static_panda:
- ext_suffix = '.lib' if target_info.sys_platform in ('win32', 'cygwin') else '.a'
- else:
- ext_suffix = '.pyd' if target_info.sys_platform in ('win32', 'cygwin') else '.so'
- ext_mod_dir = target_info.python_ext_module_dir
- if not 'ios' in target_info.platform_tag:
- for file in os.listdir(ext_mod_dir):
- if file.endswith(ext_suffix):
- source_path = os.path.join(ext_mod_dir, file)
- if file.endswith('.pyd') and target_info.sys_platform == 'cygwin':
- # Rename it to .dll for cygwin Python to be able to load it.
- target_path = 'deploy_libs/' + os.path.splitext(file)[0] + '.dll'
- else:
- target_path = 'deploy_libs/' + file
- whl.write_file(target_path, source_path)
- # Add plug-ins.
- for lib in PLUGIN_LIBS:
- plugin_name = 'lib' + lib
- if target_info.sys_platform in ('win32', 'cygwin'):
- plugin_name += '.lib' if target_info.static_panda else '.dll'
- elif target_info.sys_platform in ('darwin', 'ios'):
- plugin_name += '.a' if target_info.static_panda else '.dylib'
- else:
- plugin_name += '.a' if target_info.static_panda else '.so'
- plugin_path = os.path.join(libs_dir, plugin_name)
- if os.path.isfile(plugin_path):
- whl.write_file('panda3d/' + plugin_name, plugin_path)
- # Add the .data directory, containing additional files.
- data_dir = 'panda3d-{0}.data'.format(version)
- #whl.write_directory(data_dir + '/data/etc', etc_dir)
- #whl.write_directory(data_dir + '/data/models', models_dir)
- # Actually, let's not. That seems to install the files to the strangest
- # places in the user's filesystem. Let's instead put them in panda3d.
- whl.write_directory('panda3d/etc', etc_dir)
- whl.write_directory('panda3d/models', models_dir)
- # Add the pandac tree for backward compatibility.
- for file in os.listdir(pandac_dir):
- if file.endswith('.py'):
- whl.write_file('pandac/' + file, os.path.join(pandac_dir, file))
- # Let's also add the interrogate databases.
- input_dir = os.path.join(pandac_dir, 'input')
- if os.path.isdir(input_dir):
- for file in os.listdir(input_dir):
- if file.endswith('.in'):
- whl.write_file('pandac/input/' + file, os.path.join(input_dir, file))
- # Add a panda3d-tools directory containing the executables.
- entry_points = '[console_scripts]\n'
- entry_points += 'eggcacher = direct.directscripts.eggcacher:main\n'
- entry_points += 'pfreeze = direct.dist.pfreeze:main\n'
- tools_init = ''
- for file in os.listdir(bin_dir):
- basename = os.path.splitext(file)[0]
- if basename in ('eggcacher', 'packpanda'):
- continue
- source_path = os.path.join(bin_dir, file)
- if is_executable(source_path):
- # Put the .exe files inside the panda3d-tools directory.
- whl.write_file('panda3d_tools/' + file, source_path)
- if basename.endswith('_bin'):
- # These tools won't be invoked by the user directly.
- continue
- # Tell pip to create a wrapper script.
- funcname = basename.replace('-', '_')
- entry_points += '{0} = panda3d_tools:{1}\n'.format(basename, funcname)
- tools_init += '{0} = lambda: _exec_tool({1!r})\n'.format(funcname, file)
- entry_points += '[distutils.commands]\n'
- entry_points += 'build_apps = direct.dist.commands:build_apps\n'
- entry_points += 'bdist_apps = direct.dist.commands:bdist_apps\n'
- whl.write_file_data('panda3d_tools/__init__.py', PANDA3D_TOOLS_INIT.format(tools_init))
- # Add the dist-info directory last.
- info_dir = 'panda3d-{0}.dist-info'.format(version)
- whl.write_file_data(info_dir + '/entry_points.txt', entry_points)
- whl.write_file_data(info_dir + '/metadata.json', json.dumps(METADATA, indent=4, separators=(',', ': ')))
- whl.write_file_data(info_dir + '/METADATA', metadata)
- whl.write_file_data(info_dir + '/WHEEL', WHEEL_DATA.format(target_info.python_tag, target_info.abi_tag, target_info.platform_tag))
- whl.write_file(info_dir + '/LICENSE.txt', license_src)
- whl.write_file(info_dir + '/README.md', readme_src)
- whl.write_file_data(info_dir + '/top_level.txt', 'direct\npanda3d\npandac\npanda3d_tools\n')
- whl.close()
- if __name__ == "__main__":
- version = get_metadata_value('version')
- parser = OptionParser()
- parser.add_option('', '--version', dest = 'version', help = 'Panda3D version number (default: %s)' % (version), default = version)
- parser.add_option('', '--outputdir', dest = 'outputdir', help = 'Makepanda\'s output directory (default: built)', default = 'built')
- parser.add_option('', '--verbose', dest = 'verbose', help = 'Enable verbose output', action = 'store_true', default = False)
- parser.add_option('', '--platform', dest = 'platform_tag', help = 'Override platform tag', default = None)
- parser.add_option('', '--soabi', dest = 'soabi', help = 'SOABI, used for extension suffixes.')
- parser.add_option('', '--pyver', dest = 'python_version', help = 'Custom Python version we\'re making the wheel for.')
- parser.add_option('', '--pyroot', dest = 'python_root', help = 'Custom root of Python installation.', default = sys.exec_prefix)
- parser.add_option('', '--sysplatform', dest = 'sys_platform', help = 'Output of "sys.platform" on the target', default = get_host())
- parser.add_option('', '--static', dest = 'static_panda', help = 'If the Panda libraries we\'re packaging are statically linked', action = 'store_true', default = False)
- (options, args) = parser.parse_args()
- ti_opts = {key: options.__dict__[key] for key in ('platform_tag', 'soabi', 'python_version', 'python_root', 'sys_platform', 'static_panda')}
- target_info = TargetInfo(**ti_opts)
- global verbose
- verbose = options.verbose
- makewheel(options.version, options.outputdir, target_info)
|