1# Copyright (c) 2020, Oracle and/or its affiliates. 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License, version 2.0, as 5# published by the Free Software Foundation. 6# 7# This program is also distributed with certain software (including 8# but not limited to OpenSSL) that is licensed under separate terms, 9# as designated in a particular file or component or in included license 10# documentation. The authors of MySQL hereby grant you an 11# additional permission to link the program and your derivative works 12# with the separately licensed software that they have included with 13# MySQL. 14# 15# Without limiting anything contained in the foregoing, this file, 16# which is part of MySQL Connector/Python, is also subject to the 17# Universal FOSS Exception, version 1.0, a copy of which can be found at 18# http://oss.oracle.com/licenses/universal-foss-exception. 19# 20# This program is distributed in the hope that it will be useful, but 21# WITHOUT ANY WARRANTY; without even the implied warranty of 22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 23# See the GNU General Public License, version 2.0, for more details. 24# 25# You should have received a copy of the GNU General Public License 26# along with this program; if not, write to the Free Software Foundation, Inc., 27# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 28 29"""Implements the Distutils command 'bdist'. 30 31Creates a binary distribution. 32""" 33 34import os 35import logging 36 37from distutils import log 38from distutils.util import byte_compile 39from distutils.dir_util import remove_tree, mkpath, copy_tree 40from distutils.file_util import copy_file 41from distutils.sysconfig import get_python_version 42from distutils.command.bdist import bdist 43 44from . import COMMON_USER_OPTIONS, VERSION_TEXT, EDITION, LOGGER 45from .utils import add_docs, write_info_src, write_info_bin 46 47 48class DistBinary(bdist): 49 """Create a generic binary distribution. 50 51 DistBinary is meant to replace distutils.bdist. 52 """ 53 54 description = "create a built (binary) distribution" 55 user_options = COMMON_USER_OPTIONS + [ 56 ("bdist-dir=", "d", 57 "temporary directory for creating the distribution"), 58 ("dist-dir=", "d", 59 "directory to put final built distributions in"), 60 ] 61 boolean_options = ["debug", "byte-code-only", "keep-temp"] 62 log = LOGGER 63 64 def initialize_options(self): 65 """Initialize the options.""" 66 bdist.initialize_options(self) 67 self.bdist_dir = None 68 self.byte_code_only = False 69 self.label = None 70 self.edition = EDITION 71 self.debug = False 72 self.keep_temp = False 73 74 def finalize_options(self): 75 """Finalize the options.""" 76 bdist.finalize_options(self) 77 78 def _get_fullname(): 79 label = "-{}".format(self.label) if self.label else "" 80 python_version = "-py{}".format(get_python_version()) \ 81 if self.byte_code_only else "" 82 return "{name}{label}-{version}{edition}{pyver}".format( 83 name=self.distribution.get_name(), 84 label=label, 85 version=self.distribution.get_version(), 86 edition=self.edition or "", 87 pyver=python_version) 88 89 self.distribution.get_fullname = _get_fullname 90 91 if self.bdist_dir is None: 92 self.bdist_dir = os.path.join(self.dist_dir, 93 "bdist.{}".format(self.plat_name)) 94 if self.debug: 95 self.log.setLevel(logging.DEBUG) 96 log.set_threshold(1) # Set Distutils logging level to DEBUG 97 98 def _remove_sources(self): 99 """Remove Python source files from the build directory.""" 100 for base, dirs, files in os.walk(self.bdist_dir): 101 for filename in files: 102 if filename.endswith(".py"): 103 filepath = os.path.join(base, filename) 104 self.log.info("Removing source '%s'", filepath) 105 os.unlink(filepath) 106 107 def _copy_from_pycache(self, start_dir): 108 """Copy .py files from __pycache__.""" 109 for base, dirs, files in os.walk(start_dir): 110 for filename in files: 111 if filename.endswith(".pyc"): 112 filepath = os.path.join(base, filename) 113 new_name = "{}.pyc".format(filename.split(".")[0]) 114 os.rename(filepath, os.path.join(base, "..", new_name)) 115 for base, dirs, files in os.walk(start_dir): 116 if base.endswith("__pycache__"): 117 os.rmdir(base) 118 119 def run(self): 120 """Run the command.""" 121 self.log.info("Installing library code to %s", self.bdist_dir) 122 self.log.info("Generating INFO_SRC and INFO_BIN files") 123 write_info_src(VERSION_TEXT) 124 write_info_bin() 125 126 dist_name = self.distribution.get_fullname() 127 self.dist_target = os.path.join(self.dist_dir, dist_name) 128 self.log.info("Distribution will be available as '%s'", 129 self.dist_target) 130 131 # build command: just to get the build_base 132 cmdbuild = self.get_finalized_command("build") 133 self.build_base = cmdbuild.build_base 134 135 # install command 136 install = self.reinitialize_command("install_lib", 137 reinit_subcommands=1) 138 install.compile = False 139 install.warn_dir = 0 140 install.install_dir = self.bdist_dir 141 142 self.log.info("Installing to %s", self.bdist_dir) 143 self.run_command("install_lib") 144 145 # install_egg_info command 146 cmd_egginfo = self.get_finalized_command("install_egg_info") 147 cmd_egginfo.install_dir = self.bdist_dir 148 self.run_command("install_egg_info") 149 150 installed_files = install.get_outputs() 151 152 # compile and remove sources 153 if self.byte_code_only: 154 byte_compile(installed_files, optimize=0, force=True, 155 prefix=install.install_dir) 156 self._remove_sources() 157 if get_python_version().startswith('3'): 158 self.log.info("Copying byte code from __pycache__") 159 self._copy_from_pycache(os.path.join(self.bdist_dir, "mysql")) 160 self._copy_from_pycache(os.path.join(self.bdist_dir, "mysqlx")) 161 162 # create distribution 163 info_files = [ 164 ("README.txt", "README.txt"), 165 ("LICENSE.txt", "LICENSE.txt"), 166 ("README.rst", "README.rst"), 167 ("CONTRIBUTING.rst", "CONTRIBUTING.rst"), 168 ("docs/INFO_SRC", "INFO_SRC"), 169 ("docs/INFO_BIN", "INFO_BIN"), 170 ] 171 172 copy_tree(self.bdist_dir, self.dist_target) 173 mkpath(os.path.join(self.dist_target)) 174 for src, dst in info_files: 175 if dst is None: 176 dest_name, _ = copy_file(src, self.dist_target) 177 else: 178 dest_name, _ = copy_file(src, 179 os.path.join(self.dist_target, dst)) 180 181 add_docs(os.path.join(self.dist_target, "docs")) 182 183 if not self.keep_temp: 184 remove_tree(self.build_base, dry_run=self.dry_run) 185