1# Copyright (c) 2020, 2021, 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"""Connector/Python packaging system.""" 30 31import logging 32import os 33import platform 34import shutil 35import sys 36import tempfile 37 38from glob import glob 39from setuptools.command.build_ext import build_ext 40from distutils import log 41from distutils.command.install import install 42from distutils.command.install_lib import install_lib 43from distutils.core import Command 44from distutils.dir_util import mkpath, remove_tree 45from distutils.sysconfig import get_config_vars, get_python_version 46from distutils.version import LooseVersion 47from subprocess import check_call, Popen, PIPE 48 49from .utils import (ARCH, ARCH_64BIT, mysql_c_api_info, write_info_src, 50 write_info_bin) 51 52 53# Load version information 54VERSION = [999, 0, 0, "a", 0] 55VERSION_TEXT = "999.0.0" 56VERSION_EXTRA = "" 57EDITION = "" 58version_py = os.path.join("lib", "mysql", "connector", "version.py") 59with open(version_py, "rb") as fp: 60 exec(compile(fp.read(), version_py, "exec")) 61 62if "MACOSX_DEPLOYMENT_TARGET" in get_config_vars(): 63 get_config_vars()["MACOSX_DEPLOYMENT_TARGET"] = "11.0" 64 65COMMON_USER_OPTIONS = [ 66 ("byte-code-only", None, 67 "remove Python .py files; leave byte code .pyc only"), 68 ("edition=", None, 69 "Edition added in the package name after the version"), 70 ("label=", None, 71 "label added in the package name after the name"), 72 ("debug", None, 73 "turn debugging on"), 74 ("keep-temp", "k", 75 "keep the pseudo-installation tree around after creating the " 76 "distribution archive"), 77] 78 79CEXT_OPTIONS = [ 80 ("with-mysql-capi=", None, 81 "location of MySQL C API installation or path to mysql_config"), 82 ("with-openssl-include-dir=", None, 83 "location of OpenSSL include directory"), 84 ("with-openssl-lib-dir=", None, 85 "location of OpenSSL library directory"), 86 ("with-protobuf-include-dir=", None, 87 "location of Protobuf include directory"), 88 ("with-protobuf-lib-dir=", None, 89 "location of Protobuf library directory"), 90 ("with-protoc=", None, 91 "location of Protobuf protoc binary"), 92 ("extra-compile-args=", None, 93 "extra compile args"), 94 ("extra-link-args=", None, 95 "extra link args") 96] 97 98LOGGER = logging.getLogger(__name__) 99handler = logging.StreamHandler() 100handler.setFormatter( 101 logging.Formatter("%(levelname)s[%(name)s]: %(message)s") 102) 103LOGGER.addHandler(handler) 104LOGGER.setLevel(logging.WARNING) 105 106 107class BaseCommand(Command): 108 """Base command class for Connector/Python.""" 109 110 user_options = COMMON_USER_OPTIONS + CEXT_OPTIONS 111 boolean_options = ["debug", "byte_code_only", "keep-temp"] 112 113 with_mysql_capi = None 114 with_mysqlxpb_cext = False 115 116 with_openssl_include_dir = None 117 with_openssl_lib_dir = None 118 119 with_protobuf_include_dir = None 120 with_protobuf_lib_dir = None 121 with_protoc = None 122 123 extra_compile_args = None 124 extra_link_args = None 125 126 byte_code_only = False 127 edition = None 128 label = None 129 debug = False 130 keep_temp = False 131 build_base = None 132 log = LOGGER 133 vendor_folder = os.path.join("lib", "mysql", "vendor") 134 135 _mysql_info = {} 136 _build_mysql_lib_dir = None 137 _build_protobuf_lib_dir = None 138 139 def initialize_options(self): 140 """Initialize the options.""" 141 self.with_mysql_capi = None 142 self.with_mysqlxpb_cext = False 143 self.with_openssl_include_dir = None 144 self.with_openssl_lib_dir = None 145 self.with_protobuf_include_dir = None 146 self.with_protobuf_lib_dir = None 147 self.with_protoc = None 148 self.extra_compile_args = None 149 self.extra_link_args = None 150 self.byte_code_only = False 151 self.edition = None 152 self.label = None 153 self.debug = False 154 self.keep_temp = False 155 156 def finalize_options(self): 157 """Finalize the options.""" 158 if self.debug: 159 self.log.setLevel(logging.DEBUG) 160 log.set_threshold(1) # Set Distutils logging level to DEBUG 161 162 cmd_build_ext = self.distribution.get_command_obj("build_ext") 163 cmd_build_ext.with_mysql_capi = ( 164 self.with_mysql_capi or 165 os.environ.get("MYSQL_CAPI") 166 ) 167 cmd_build_ext.with_openssl_include_dir = ( 168 self.with_openssl_include_dir or 169 os.environ.get("OPENSSL_INCLUDE_DIR") 170 ) 171 cmd_build_ext.with_openssl_lib_dir = ( 172 self.with_openssl_lib_dir or 173 os.environ.get("OPENSSL_LIB_DIR") 174 ) 175 cmd_build_ext.with_protobuf_include_dir = ( 176 self.with_protobuf_include_dir or 177 os.environ.get("PROTOBUF_INCLUDE_DIR") 178 ) 179 cmd_build_ext.with_protobuf_lib_dir = ( 180 self.with_protobuf_lib_dir or 181 os.environ.get("PROTOBUF_LIB_DIR") 182 ) 183 cmd_build_ext.with_protoc = ( 184 self.with_protoc or 185 os.environ.get("PROTOC") 186 ) 187 cmd_build_ext.extra_compile_args = ( 188 self.extra_compile_args or 189 os.environ.get("EXTRA_COMPILE_ARGS") 190 ) 191 cmd_build_ext.extra_link_args = ( 192 self.extra_link_args or 193 os.environ.get("EXTRA_LINK_ARGS") 194 ) 195 self._copy_vendor_libraries() 196 197 def remove_temp(self): 198 """Remove temporary build files.""" 199 if not self.keep_temp: 200 cmd_build = self.get_finalized_command("build") 201 remove_tree(cmd_build.build_base, dry_run=self.dry_run) 202 vendor_folder = os.path.join(os.getcwd(), self.vendor_folder) 203 if os.path.exists(vendor_folder): 204 remove_tree(vendor_folder) 205 elif os.name == "nt": 206 if ARCH == "64-bit": 207 libraries = ["libmysql.dll", "libssl-1_1-x64.dll", 208 "libcrypto-1_1-x64.dll"] 209 else: 210 libraries = ["libmysql.dll", "libssl-1_1.dll", 211 "libcrypto-1_1.dll"] 212 for filename in libraries: 213 dll_file = os.path.join(os.getcwd(), filename) 214 if os.path.exists(dll_file): 215 os.unlink(dll_file) 216 217 def _get_openssl_libs(self): 218 libssl = glob(os.path.join( 219 self.with_openssl_lib_dir, "libssl.*.*.*")) 220 libcrypto = glob(os.path.join( 221 self.with_openssl_lib_dir, "libcrypto.*.*.*")) 222 if not libssl or not libcrypto: 223 self.log.error("Unable to find OpenSSL libraries in '%s'", 224 self.with_openssl_lib_dir) 225 sys.exit(1) 226 return (os.path.basename(libssl[0]), os.path.basename(libcrypto[0])) 227 228 def _copy_vendor_libraries(self): 229 vendor_libs = [] 230 231 if os.name == "posix": 232 # Bundle OpenSSL libs 233 if self.with_openssl_lib_dir: 234 libssl, libcrypto = self._get_openssl_libs() 235 vendor_libs.append( 236 (self.with_openssl_lib_dir, [libssl, libcrypto])) 237 238 # Plugins 239 bundle_plugin_libs = False 240 if self.with_mysql_capi: 241 plugin_ext = "dll" if os.name == "nt" else "so" 242 plugin_path = os.path.join(self.with_mysql_capi, "lib", "plugin") 243 244 # authentication_ldap_sasl_client 245 plugin_name = ( 246 "authentication_ldap_sasl_client.{}".format(plugin_ext) 247 ) 248 plugin_full_path = os.path.join(plugin_path, plugin_name) 249 self.log.debug("ldap plugin_path: '%s'", plugin_full_path) 250 if os.path.exists(plugin_full_path): 251 bundle_plugin_libs = True 252 vendor_libs.append( 253 (plugin_path, [os.path.join("plugin", plugin_name)]) 254 ) 255 256 # authentication_kerberos_client 257 plugin_name = ( 258 "authentication_kerberos_client.{}".format(plugin_ext) 259 ) 260 plugin_full_path = os.path.join(plugin_path, plugin_name) 261 self.log.debug("kerberos plugin_path: '%s'", plugin_full_path) 262 if os.path.exists(plugin_full_path): 263 bundle_plugin_libs = True 264 vendor_libs.append( 265 (plugin_path, [os.path.join("plugin", plugin_name)]) 266 ) 267 268 # authentication_oci_client 269 plugin_name = ( 270 "authentication_oci_client.{}".format(plugin_ext) 271 ) 272 plugin_full_path = os.path.join(plugin_path, plugin_name) 273 self.log.debug("OCI IAM plugin_path: '%s'", plugin_full_path) 274 if os.path.exists(plugin_full_path): 275 bundle_plugin_libs = True 276 vendor_libs.append( 277 (plugin_path, [os.path.join("plugin", plugin_name)]) 278 ) 279 280 # vendor libraries 281 if bundle_plugin_libs and os.name == "nt": 282 plugin_libs = [] 283 libs_path = os.path.join(self.with_mysql_capi, "bin") 284 for lib_name in ["libsasl.dll", "saslSCRAM.dll"]: 285 if os.path.exists(os.path.join(libs_path, lib_name)): 286 plugin_libs.append(lib_name) 287 if plugin_libs: 288 vendor_libs.append((libs_path, plugin_libs)) 289 290 if ARCH_64BIT: 291 openssl_libs = ["libssl-1_1-x64.dll", 292 "libcrypto-1_1-x64.dll"] 293 else: 294 openssl_libs = ["libssl-1_1.dll", "libcrypto-1_1.dll"] 295 if self.with_openssl_lib_dir: 296 openssl_libs_path = os.path.abspath(self.with_openssl_lib_dir) 297 if os.path.basename(openssl_libs_path) == "lib": 298 openssl_libs_path = os.path.split(openssl_libs_path)[0] 299 if os.path.exists(openssl_libs_path) and \ 300 os.path.exists(os.path.join(openssl_libs_path, "bin")): 301 openssl_libs_path = os.path.join(openssl_libs_path, "bin") 302 self.log.info("# openssl_libs_path: %s", openssl_libs_path) 303 else: 304 openssl_libs_path = os.path.join( 305 self.with_mysql_capi, "bin") 306 vendor_libs.append((openssl_libs_path, openssl_libs)) 307 308 if not vendor_libs: 309 return 310 311 self.log.debug("# vendor_libs: %s", vendor_libs) 312 313 # mysql/vendor 314 if not os.path.exists(self.vendor_folder): 315 mkpath(os.path.join(os.getcwd(), self.vendor_folder)) 316 317 # mysql/vendor/plugin 318 if not os.path.exists(os.path.join(self.vendor_folder, "plugin")): 319 mkpath(os.path.join(os.getcwd(), self.vendor_folder, "plugin")) 320 321 # mysql/vendor/private 322 if not os.path.exists(os.path.join(self.vendor_folder, "private")): 323 mkpath(os.path.join(os.getcwd(), self.vendor_folder, "private")) 324 325 # Copy vendor libraries to 'mysql/vendor' folder 326 self.log.info("Copying vendor libraries") 327 for src_folder, files in vendor_libs: 328 self.log.info("Copying folder: %s", src_folder) 329 for filepath in files: 330 dst_folder, filename = os.path.split(filepath) 331 src = os.path.join(src_folder, filename) 332 dst = os.path.join(os.getcwd(), self.vendor_folder, dst_folder) 333 self.log.info("copying %s -> %s", src, dst) 334 self.log.info("shutil res: %s", shutil.copy(src, dst)) 335 336 if os.name == "nt": 337 self.distribution.package_data = {"mysql": ["vendor/plugin/*"]} 338 site_packages_files = [ 339 os.path.join(openssl_libs_path, lib_n) for lib_n in openssl_libs 340 ] 341 site_packages_files.append( 342 os.path.join(self.with_mysql_capi, "lib", "libmysql.dll")) 343 self.distribution.data_files = [( 344 'lib\\site-packages\\', site_packages_files 345 )] 346 self.log.debug("# site_packages_files: %s", 347 self.distribution.data_files) 348 elif bundle_plugin_libs: 349 # Bundle SASL libs 350 sasl_libs_path = os.path.join(self.with_mysql_capi, "lib", 351 "private") 352 if not os.path.exists(sasl_libs_path): 353 self.log.info("sasl2 llibraries not found at %s", 354 sasl_libs_path) 355 self.distribution.package_data = { 356 "mysql": ["vendor/*"]} 357 sasl_libs = [] 358 sasl_plugin_libs_w = [ 359 "libsasl2.*.*", "libgssapi_krb5.*.*", "libgssapi_krb5.*.*", 360 "libkrb5.*.*", "libk5crypto.*.*", "libkrb5support.*.*", 361 "libcrypto.*.*.*", "libssl.*.*.*", "libcom_err.*.*"] 362 sasl_plugin_libs = [] 363 for sasl_lib in sasl_plugin_libs_w: 364 lib_path_entries = glob(os.path.join( 365 sasl_libs_path, sasl_lib)) 366 for lib_path_entry in lib_path_entries: 367 sasl_plugin_libs.append(os.path.basename(lib_path_entry)) 368 sasl_libs.append((sasl_libs_path, sasl_plugin_libs)) 369 370 # Copy vendor libraries to 'mysql/vendor/private' folder 371 self.log.info("Copying vendor libraries") 372 for src_folder, files in sasl_libs: 373 self.log.info("Copying folder: %s", src_folder) 374 for filename in files: 375 src = os.path.join(src_folder, filename) 376 if not os.path.exists(src): 377 self.log.warn("Library not found: %s", src) 378 continue 379 dst = os.path.join( 380 os.getcwd(), 381 self.vendor_folder, 382 "private" 383 ) 384 self.log.info("copying %s -> %s", src, dst) 385 shutil.copy(src, dst) 386 387 # include sasl2 libs 388 sasl2_libs = [] 389 sasl2_libs_path = os.path.join(self.with_mysql_capi, "lib", 390 "private", "sasl2") 391 if not os.path.exists(sasl2_libs_path): 392 self.log.info("sasl2 llibraries not found at %s", 393 sasl2_libs_path) 394 self.distribution.package_data = {"mysql": ["vendor/*"]} 395 return 396 sasl2_libs_w = [ 397 "libanonymous.*", "libcrammd5.*.*", "libdigestmd5.*.*.*.*", 398 "libgssapiv2.*", "libplain.*.*", "libscram.*.*.*.*", 399 "libanonymous.*.*", "libcrammd5.*.*.*.*", "libgs2.*", 400 "libgssapiv2.*.*", "libplain.*.*.*.*", "libanonymous.*.*.*.*", 401 "libdigestmd5.*", "libgs2.*.*", "libgssapiv2.*.*.*.*", 402 "libscram.*", "libcrammd5.*", "libdigestmd5.*.*", 403 "libgs2.*.*.*.*", "libplain.*", "libscram.*.*"] 404 405 sasl2_scram_libs = [] 406 for sasl2_lib in sasl2_libs_w: 407 lib_path_entries = glob(os.path.join( 408 sasl2_libs_path, sasl2_lib)) 409 for lib_path_entry in lib_path_entries: 410 sasl2_scram_libs.append(os.path.basename(lib_path_entry)) 411 412 sasl2_libs.append((sasl2_libs_path, sasl2_scram_libs)) 413 414 sasl2_libs_private_path = os.path.join( 415 self.vendor_folder, "private", "sasl2" 416 ) 417 if not os.path.exists(sasl2_libs_private_path): 418 mkpath(sasl2_libs_private_path) 419 420 # Copy vendor libraries to 'mysql/vendor/private/sasl2' folder 421 self.log.info("Copying vendor libraries") 422 for src_folder, files in sasl2_libs: 423 self.log.info("Copying folder: %s", src_folder) 424 for filename in files: 425 src = os.path.join(src_folder, filename) 426 if not os.path.exists(src): 427 self.log.warning("Library not found: %s", src) 428 continue 429 dst = os.path.join(os.getcwd(), sasl2_libs_private_path) 430 self.log.info("copying %s -> %s", src, dst) 431 shutil.copy(src, dst) 432 433 self.distribution.package_data = { 434 "mysql": [ 435 "vendor/*", 436 "vendor/plugin/*", 437 "vendor/private/*", 438 "vendor/private/sasl2/*" 439 ] 440 } 441 442 443class BuildExt(build_ext, BaseCommand): 444 """Command class for building the Connector/Python C Extensions.""" 445 446 description = "build MySQL Connector/Python C extensions" 447 user_options = build_ext.user_options + CEXT_OPTIONS 448 boolean_options = build_ext.boolean_options + BaseCommand.boolean_options 449 450 def _get_mysql_version(self): 451 if os.name == "posix" and self._mysql_info: 452 mysql_version = "{}.{}.{}".format(*self._mysql_info["version"][:3]) 453 elif os.name == "nt" and os.path.isdir(self.with_mysql_capi): 454 mysql_version_h = os.path.join(self.with_mysql_capi, 455 "include", 456 "mysql_version.h") 457 with open(mysql_version_h, "rb") as fp: 458 for line in fp.readlines(): 459 if b"#define LIBMYSQL_VERSION" in line: 460 mysql_version = LooseVersion( 461 line.split()[2].replace(b'"', b"").decode() 462 ).version 463 break 464 else: 465 mysql_version = None 466 return mysql_version 467 468 def _finalize_mysql_capi(self): 469 self.log.info("Copying MySQL libraries") 470 471 if not os.path.exists(self._build_mysql_lib_dir): 472 os.makedirs(self._build_mysql_lib_dir) 473 474 libs = [] 475 476 # Add libmysqlclient libraries to be copied 477 if "link_dirs" in self._mysql_info: 478 libs += glob(os.path.join( 479 self._mysql_info["link_dirs"][0], "libmysqlclient*")) 480 481 for lib in libs: 482 self.log.info("copying %s -> %s", 483 lib, self._build_mysql_lib_dir) 484 shutil.copy(lib, self._build_mysql_lib_dir) 485 486 # Remove all but static libraries to force static linking 487 if os.name == "posix": 488 self.log.info("Removing non-static MySQL libraries from %s", 489 self._build_mysql_lib_dir) 490 for lib in os.listdir(self._build_mysql_lib_dir): 491 lib_path = os.path.join(self._build_mysql_lib_dir, lib) 492 if os.path.isfile(lib_path) and not lib.endswith(".a"): 493 os.unlink(os.path.join(self._build_mysql_lib_dir, lib)) 494 495 def _finalize_protobuf(self): 496 if not self.with_protobuf_include_dir: 497 self.with_protobuf_include_dir = \ 498 os.environ.get("MYSQLXPB_PROTOBUF_INCLUDE_DIR") 499 500 if not self.with_protobuf_lib_dir: 501 self.with_protobuf_lib_dir = \ 502 os.environ.get("MYSQLXPB_PROTOBUF_LIB_DIR") 503 504 if not self.with_protoc: 505 self.with_protoc = os.environ.get("MYSQLXPB_PROTOC") 506 507 if self.with_protobuf_include_dir: 508 self.log.info("Protobuf include directory: %s", 509 self.with_protobuf_include_dir) 510 if not os.path.isdir(self.with_protobuf_include_dir): 511 self.log.error("Protobuf include dir should be a directory") 512 sys.exit(1) 513 else: 514 self.log.error("Unable to find Protobuf include directory") 515 sys.exit(1) 516 517 if self.with_protobuf_lib_dir: 518 self.log.info("Protobuf library directory: %s", 519 self.with_protobuf_lib_dir) 520 if not os.path.isdir(self.with_protobuf_lib_dir): 521 self.log.error("Protobuf library dir should be a directory") 522 sys.exit(1) 523 else: 524 self.log.error("Unable to find Protobuf library directory") 525 sys.exit(1) 526 527 if self.with_protoc: 528 self.log.info("Protobuf protoc binary: %s", self.with_protoc) 529 if not os.path.isfile(self.with_protoc): 530 self.log.error("Protobuf protoc binary is not valid") 531 sys.exit(1) 532 else: 533 self.log.error("Unable to find Protobuf protoc binary") 534 sys.exit(1) 535 536 if not os.path.exists(self._build_protobuf_lib_dir): 537 os.makedirs(self._build_protobuf_lib_dir) 538 539 self.log.info("Copying Protobuf libraries") 540 541 libs = glob(os.path.join(self.with_protobuf_lib_dir, "libprotobuf*")) 542 for lib in libs: 543 if os.path.isfile(lib): 544 self.log.info("copying %s -> %s", 545 lib, self._build_protobuf_lib_dir) 546 shutil.copy2(lib, self._build_protobuf_lib_dir) 547 548 # Remove all but static libraries to force static linking 549 if os.name == "posix": 550 self.log.info("Removing non-static Protobuf libraries from %s", 551 self._build_protobuf_lib_dir) 552 for lib in os.listdir(self._build_protobuf_lib_dir): 553 lib_path = os.path.join(self._build_protobuf_lib_dir, lib) 554 if os.path.isfile(lib_path) and \ 555 not lib.endswith((".a", ".dylib",)): 556 os.unlink(os.path.join(self._build_protobuf_lib_dir, lib)) 557 558 def _run_protoc(self): 559 base_path = os.path.join(os.getcwd(), "src", "mysqlxpb", "mysqlx") 560 command = [self.with_protoc, "-I"] 561 command.append(os.path.join(base_path, "protocol")) 562 command.extend(glob(os.path.join(base_path, "protocol", "*.proto"))) 563 command.append("--cpp_out={0}".format(base_path)) 564 self.log.info("Running protoc command: %s", " ".join(command)) 565 check_call(command) 566 567 def initialize_options(self): 568 """Initialize the options.""" 569 build_ext.initialize_options(self) 570 BaseCommand.initialize_options(self) 571 572 def finalize_options(self): 573 """Finalize the options.""" 574 build_ext.finalize_options(self) 575 BaseCommand.finalize_options(self) 576 577 self.log.info("Python architecture: %s", ARCH) 578 579 self._build_mysql_lib_dir = os.path.join( 580 self.build_temp, "capi", "lib") 581 self._build_protobuf_lib_dir = os.path.join( 582 self.build_temp, "protobuf", "lib") 583 if self.with_mysql_capi: 584 self._mysql_info = mysql_c_api_info(self.with_mysql_capi) 585 self._finalize_mysql_capi() 586 587 self.with_mysqlxpb_cext = any((self.with_protobuf_include_dir, 588 self.with_protobuf_lib_dir, 589 self.with_protoc)) 590 if self.with_mysqlxpb_cext: 591 self._finalize_protobuf() 592 593 def run(self): 594 """Run the command.""" 595 # Generate docs/INFO_SRC 596 write_info_src(VERSION_TEXT) 597 598 disabled = [] # Extensions to be disabled 599 for ext in self.extensions: 600 # Add Protobuf include and library dirs 601 if ext.name == "_mysqlxpb": 602 if not self.with_mysqlxpb_cext: 603 self.log.warning( 604 "The '_mysqlxpb' C extension will not be built") 605 disabled.append(ext) 606 continue 607 if platform.system() == "Darwin": 608 symbol_file = tempfile.NamedTemporaryFile() 609 ext.extra_link_args.extend( 610 ["-exported_symbols_list", symbol_file.name] 611 ) 612 with open(symbol_file.name, "w") as fp: 613 fp.write("_PyInit__mysqlxpb") 614 fp.write("\n") 615 ext.include_dirs.append(self.with_protobuf_include_dir) 616 ext.library_dirs.append(self._build_protobuf_lib_dir) 617 ext.libraries.append( 618 "libprotobuf" if os.name == "nt" else "protobuf") 619 # Add -std=c++11 needed for Protobuf 3.6.1 620 ext.extra_compile_args.append("-std=c++11") 621 self._run_protoc() 622 if ext.name == "_mysql_connector": 623 if not self.with_mysql_capi: 624 self.log.warning( 625 "The '_mysql_connector' C extension will not be built") 626 disabled.append(ext) 627 continue 628 # Add extra compile args 629 if self.extra_compile_args: 630 ext.extra_compile_args.extend( 631 self.extra_compile_args.split()) 632 # Add extra link args 633 if self.extra_link_args: 634 ext.extra_link_args.extend(self.extra_link_args.split()) 635 # Add -rpath if the platform is Linux 636 if platform.system() == "Linux": 637 ext.extra_link_args.extend([ 638 "-Wl,-rpath,$ORIGIN/mysql/vendor"]) 639 # Add include dirs 640 if self.with_openssl_include_dir: 641 ext.include_dirs.append(self.with_openssl_include_dir) 642 if "include_dirs" in self._mysql_info: 643 ext.include_dirs.extend(self._mysql_info["include_dirs"]) 644 # Add library dirs 645 ext.library_dirs.append(self._build_mysql_lib_dir) 646 if "library_dirs" in self._mysql_info: 647 ext.library_dirs.extend(self._mysql_info["library_dirs"]) 648 if self.with_openssl_lib_dir: 649 ext.library_dirs.append(self.with_openssl_lib_dir) 650 # Add libraries 651 if "libraries" in self._mysql_info: 652 ext.libraries.extend(self._mysql_info["libraries"]) 653 # Suppress unknown pragmas 654 if os.name == "posix": 655 ext.extra_compile_args.append("-Wno-unknown-pragmas") 656 657 if os.name != "nt": 658 cmd_gcc_ver = ["cc", "-v"] 659 self.log.info("Executing: {0}" 660 "".format(" ".join(cmd_gcc_ver))) 661 proc = Popen(cmd_gcc_ver, stdout=PIPE, 662 universal_newlines=True) 663 self.log.info(proc.communicate()) 664 cmd_gpp_ver = ["c++", "-v"] 665 self.log.info("Executing: {0}" 666 "".format(" ".join(cmd_gcc_ver))) 667 proc = Popen(cmd_gpp_ver, stdout=PIPE, 668 universal_newlines=True) 669 self.log.info(proc.communicate()) 670 671 # Remove disabled extensions 672 for ext in disabled: 673 self.extensions.remove(ext) 674 675 build_ext.run(self) 676 677 # Change @loader_path if the platform is MacOS 678 if platform.system() == "Darwin" and self.with_openssl_lib_dir: 679 for ext in self.extensions: 680 if ext.name == "_mysql_connector": 681 libssl, libcrypto = self._get_openssl_libs() 682 cmd_libssl = [ 683 "install_name_tool", "-change", libssl, 684 "@loader_path/mysql/vendor/{0}".format(libssl), 685 build_ext.get_ext_fullpath(self, "_mysql_connector") 686 ] 687 self.log.info("Executing: {0}" 688 "".format(" ".join(cmd_libssl))) 689 proc = Popen(cmd_libssl, stdout=PIPE, 690 universal_newlines=True) 691 stdout, _ = proc.communicate() 692 cmd_libcrypto = [ 693 "install_name_tool", "-change", libcrypto, 694 "@loader_path/mysql/vendor/{0}".format(libcrypto), 695 build_ext.get_ext_fullpath(self, "_mysql_connector") 696 ] 697 self.log.info("Executing: {0}" 698 "".format(" ".join(cmd_libcrypto))) 699 proc = Popen(cmd_libcrypto, stdout=PIPE, 700 universal_newlines=True) 701 stdout, _ = proc.communicate() 702 703 # Generate docs/INFO_BIN 704 if self.with_mysql_capi: 705 mysql_version = self._get_mysql_version() 706 compiler = self.compiler.compiler_so[0] \ 707 if hasattr(self.compiler, "compiler_so") else None 708 write_info_bin(mysql_version, compiler) 709 710 711class InstallLib(install_lib): 712 """InstallLib Connector/Python implementation.""" 713 714 user_options = install_lib.user_options + [ 715 ("byte-code-only", None, 716 "remove Python .py files; leave byte code .pyc only"), 717 ] 718 boolean_options = ["byte-code-only"] 719 log = LOGGER 720 721 def initialize_options(self): 722 """Initialize the options.""" 723 install_lib.initialize_options(self) 724 self.byte_code_only = False 725 726 def finalize_options(self): 727 """Finalize the options.""" 728 install_lib.finalize_options(self) 729 self.set_undefined_options("install", 730 ("byte_code_only", "byte_code_only")) 731 self.set_undefined_options("build", ("build_base", "build_dir")) 732 733 def run(self): 734 """Run the command.""" 735 if not os.path.exists(self.build_dir): 736 mkpath(self.build_dir) 737 738 self.build() 739 740 outfiles = self.install() 741 742 # (Optionally) compile .py to .pyc 743 if outfiles is not None and self.distribution.has_pure_modules(): 744 self.byte_compile(outfiles) 745 746 if self.byte_code_only: 747 if get_python_version().startswith("3"): 748 for base, _, files in os.walk(self.install_dir): 749 for filename in files: 750 if filename.endswith(".pyc"): 751 new_name = "{0}.pyc".format(filename.split(".")[0]) 752 os.rename(os.path.join(base, filename), 753 os.path.join(base, "..", new_name)) 754 if base.endswith("__pycache__"): 755 os.rmdir(base) 756 for source_file in outfiles: 757 if source_file.endswith(".py"): 758 self.log.info("Removing %s", source_file) 759 os.remove(source_file) 760 761 762class Install(install, BaseCommand): 763 """Install Connector/Python implementation.""" 764 765 description = "install MySQL Connector/Python" 766 user_options = install.user_options + BaseCommand.user_options 767 boolean_options = install.boolean_options + BaseCommand.boolean_options 768 769 def initialize_options(self): 770 """Initialize the options.""" 771 install.initialize_options(self) 772 BaseCommand.initialize_options(self) 773 774 def finalize_options(self): 775 """Finalize the options.""" 776 BaseCommand.finalize_options(self) 777 install.finalize_options(self) 778 cmd_install_lib = self.distribution.get_command_obj("install_lib") 779 cmd_install_lib.byte_code_only = self.byte_code_only 780