Skip to content

Commit 6320e07

Browse files
committed
WL11665: Allow OpenSSL linkage in the C extension
1 parent 12b4d05 commit 6320e07

File tree

8 files changed

+159
-58
lines changed

8 files changed

+159
-58
lines changed

cpyint

lib/cpy_distutils.py

Lines changed: 108 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
from distutils.command.install_lib import install_lib
3030
from distutils.errors import DistutilsExecError
3131
from distutils.util import get_platform
32-
from distutils.dir_util import copy_tree
32+
from distutils.version import LooseVersion
33+
from distutils.dir_util import copy_tree, mkpath
34+
from distutils.sysconfig import get_python_lib
3335
from distutils import log
3436
from glob import glob
3537
import os
@@ -160,6 +162,8 @@ def parse_mysql_config_info(options, stdout):
160162

161163
info['version'] = tuple([int(v) for v in ver.split('.')[0:3]])
162164
libs = shlex.split(info['libs'])
165+
if ',' in libs[1]:
166+
libs.pop(1)
163167
info['lib_dir'] = libs[0].replace('-L', '')
164168
info['libs'] = [ lib.replace('-l', '') for lib in libs[1:] ]
165169
if platform.uname()[0] == 'SunOS':
@@ -169,9 +173,10 @@ def parse_mysql_config_info(options, stdout):
169173
for lib in info['libs']:
170174
log.debug("# {0}".format(lib))
171175
libs = shlex.split(info['libs_r'])
176+
if ',' in libs[1]:
177+
libs.pop(1)
172178
info['lib_r_dir'] = libs[0].replace('-L', '')
173179
info['libs_r'] = [ lib.replace('-l', '') for lib in libs[1:] ]
174-
175180
info['include'] = [x.strip() for x in info['include'].split('-I')[1:]]
176181

177182
return info
@@ -290,6 +295,58 @@ def initialize_options(self):
290295
self.extra_link_args = None
291296
self.with_mysql_capi = None
292297

298+
def _get_posix_openssl_libs(self):
299+
openssl_libs = []
300+
try:
301+
openssl_libs_path = os.path.join(self.with_mysql_capi, "lib")
302+
openssl_libs.extend([
303+
os.path.basename(glob(
304+
os.path.join(openssl_libs_path, "libssl.*.*.*"))[0]),
305+
os.path.basename(glob(
306+
os.path.join(openssl_libs_path, "libcrypto.*.*.*"))[0])
307+
])
308+
except IndexError:
309+
log.error("Couldn't find OpenSSL libraries in libmysqlclient")
310+
return openssl_libs
311+
312+
def _copy_vendor_libraries(self):
313+
if not self.with_mysql_capi:
314+
return
315+
316+
data_files = []
317+
318+
if os.name == "nt":
319+
openssl_libs = ["ssleay32.dll", "libeay32.dll"]
320+
vendor_folder = ""
321+
mysql_capi = os.path.join(self.with_mysql_capi, "bin")
322+
# Bundle libmysql.dll
323+
src = os.path.join(self.with_mysql_capi, "lib", "libmysql.dll")
324+
dst = os.getcwd()
325+
log.info("copying {0} -> {1}".format(src, dst))
326+
shutil.copy(src, dst)
327+
data_files.append("libmysql.dll")
328+
else:
329+
openssl_libs = self._get_posix_openssl_libs()
330+
vendor_folder = "mysql-vendor"
331+
mysql_capi = os.path.join(self.with_mysql_capi, "lib")
332+
333+
if vendor_folder:
334+
mkpath(os.path.join(os.getcwd(), vendor_folder))
335+
336+
# Copy OpenSSL libraries to 'mysql-vendor' folder
337+
log.info("Copying OpenSSL libraries")
338+
for filename in openssl_libs:
339+
data_files.append(os.path.join(vendor_folder, filename))
340+
src = os.path.join(mysql_capi, filename)
341+
dst = os.path.join(os.getcwd(), vendor_folder)
342+
log.info("copying {0} -> {1}".format(src, dst))
343+
shutil.copy(src, dst)
344+
# Add data_files to distribution
345+
self.distribution.data_files = [(
346+
os.path.join(get_python_lib(), vendor_folder),
347+
data_files
348+
)]
349+
293350
def _finalize_connector_c(self, connc_loc):
294351
"""Finalize the --with-connector-c command line argument
295352
"""
@@ -317,22 +374,21 @@ def _finalize_connector_c(self, connc_loc):
317374
log.debug("# connc_loc: {0}".format(connc_loc))
318375
else:
319376
# Probably using MS Windows
320-
myconfigh = os.path.join(connc_loc, 'include', 'my_config.h')
377+
myversionh = os.path.join(connc_loc, 'include',
378+
'mysql_version.h')
321379

322-
if not os.path.exists(myconfigh):
380+
if not os.path.exists(myversionh):
323381
log.error("MySQL C API installation invalid "
324-
"(my_config.h not found)")
382+
"(mysql_version.h not found)")
325383
sys.exit(1)
326384
else:
327-
with open(myconfigh, 'rb') as fp:
385+
with open(myversionh, 'rb') as fp:
328386
for line in fp.readlines():
329-
if b'#define VERSION' in line:
330-
version = tuple([
331-
int(v) for v in
332-
line.split()[2].replace(
333-
b'"', b'').split(b'.')
334-
])
335-
if version < min_version:
387+
if b'#define LIBMYSQL_VERSION' in line:
388+
version = LooseVersion(
389+
line.split()[2].replace(b'"', b'').decode()
390+
).version
391+
if tuple(version) < min_version:
336392
log.error(err_version);
337393
sys.exit(1)
338394
break
@@ -414,6 +470,8 @@ def finalize_options(self):
414470
('extra_link_args', 'extra_link_args'),
415471
('with_mysql_capi', 'with_mysql_capi'))
416472

473+
self._copy_vendor_libraries()
474+
417475
build_ext.finalize_options(self)
418476

419477
print("# Python architecture: {0}".format(py_arch))
@@ -423,13 +481,11 @@ def finalize_options(self):
423481
self._finalize_connector_c(self.with_mysql_capi)
424482

425483
def fix_compiler(self):
426-
platform = get_platform()
427-
428484
cc = self.compiler
429485
if not cc:
430486
return
431487

432-
if 'macosx-10.9' in platform:
488+
if 'macosx-10.9' in get_platform():
433489
for needle in ['-mno-fused-madd']:
434490
try:
435491
cc.compiler.remove(needle)
@@ -467,8 +523,11 @@ def fix_compiler(self):
467523
if self.extra_compile_args:
468524
ext.extra_compile_args.extend(self.extra_compile_args.split())
469525
# Add extra link args
470-
if self.extra_link_args:
471-
ext.extra_link_args.extend(self.extra_link_args.split())
526+
if self.extra_link_args and ext.name == "_mysql_connector":
527+
extra_link_args = self.extra_link_args.split()
528+
if platform.system() == "Linux":
529+
extra_link_args += ["-Wl,-rpath,$ORIGIN/mysql-vendor"]
530+
ext.extra_link_args.extend(extra_link_args)
472531
# Add system headers
473532
for sysheader in sysheaders:
474533
if sysheader not in ext.extra_compile_args:
@@ -480,6 +539,8 @@ def fix_compiler(self):
480539

481540
def run(self):
482541
"""Run the command"""
542+
if not self.with_mysql_capi:
543+
return
483544
if os.name == 'nt':
484545
for ext in self.extensions:
485546
# Use the multithread, static version of the run-time library
@@ -498,6 +559,26 @@ def run(self):
498559
self.fix_compiler()
499560
self.real_build_extensions()
500561

562+
if platform.system() == "Darwin":
563+
libssl, libcrypto = self._get_posix_openssl_libs()
564+
cmd_libssl = [
565+
"install_name_tool", "-change", libssl,
566+
"@loader_path/mysql-vendor/{0}".format(libssl),
567+
build_ext.get_ext_fullpath(self, "_mysql_connector")
568+
]
569+
log.info("Executing: {0}".format(" ".join(cmd_libssl)))
570+
proc = Popen(cmd_libssl, stdout=PIPE, universal_newlines=True)
571+
stdout, _ = proc.communicate()
572+
573+
cmd_libcrypto = [
574+
"install_name_tool", "-change", libcrypto,
575+
"@loader_path/mysql-vendor/{0}".format(libcrypto),
576+
build_ext.get_ext_fullpath(self, "_mysql_connector")
577+
]
578+
log.info("Executing: {0}".format(" ".join(cmd_libcrypto)))
579+
proc = Popen(cmd_libcrypto, stdout=PIPE, universal_newlines=True)
580+
stdout, _ = proc.communicate()
581+
501582

502583
class BuildExtStatic(BuildExtDynamic):
503584

@@ -506,6 +587,8 @@ class BuildExtStatic(BuildExtDynamic):
506587
user_options = build_ext.user_options + CEXT_OPTIONS
507588

508589
def finalize_options(self):
590+
self._copy_vendor_libraries()
591+
509592
install_obj = self.distribution.get_command_obj('install')
510593
install_obj.with_mysql_capi = self.with_mysql_capi
511594
install_obj.extra_compile_args = self.extra_compile_args
@@ -550,7 +633,10 @@ def _finalize_connector_c(self, connc_loc):
550633
lib_file_path = os.path.join(self.connc_lib, lib_file)
551634
if os.path.isfile(lib_file_path) and not lib_file.endswith('.a'):
552635
os.unlink(os.path.join(self.connc_lib, lib_file))
553-
636+
elif os.name == 'nt':
637+
self.include_dirs.extend([self.connc_include])
638+
self.libraries.extend(['libmysql'])
639+
self.library_dirs.extend([self.connc_lib])
554640

555641
def fix_compiler(self):
556642
BuildExtDynamic.fix_compiler(self)
@@ -601,16 +687,16 @@ def finalize_options(self):
601687

602688
def run(self):
603689
self.build()
604-
outfiles = self.install()
690+
outfiles = [
691+
filename for filename in self.install() if filename.endswith(".py")
692+
]
605693

606694
# (Optionally) compile .py to .pyc
607695
if outfiles is not None and self.distribution.has_pure_modules():
608696
self.byte_compile(outfiles)
609697

610698
if self.byte_code_only:
611699
for source_file in outfiles:
612-
if os.path.join('mysql', '__init__.py') in source_file:
613-
continue
614700
log.info("Removing %s", source_file)
615701
os.remove(source_file)
616702

lib/mysql/connector/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
as mysql.connector.version.
2727
"""
2828

29-
VERSION = (2, 1, 7, '', 0)
29+
VERSION = (2, 1, 8, '', 0)
3030

3131
if VERSION[3] and VERSION[4]:
3232
VERSION_TEXT = '{0}.{1}.{2}{3}{4}'.format(*VERSION)

setupinfo.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
'Programming Language :: Python :: 3.3',
110110
'Programming Language :: Python :: 3.4',
111111
'Programming Language :: Python :: 3.5',
112+
'Programming Language :: Python :: 3.6',
112113
'Topic :: Database',
113114
'Topic :: Software Development',
114115
'Topic :: Software Development :: Libraries :: Application Frameworks',

tests/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ def setup_logger(logger, debug=False, logfile=None):
752752

753753

754754
def install_connector(root_dir, install_dir, connc_location=None,
755-
extra_compile_args=None):
755+
extra_compile_args=None, extra_link_args=None):
756756
"""Install Connector/Python in working directory
757757
"""
758758
logfile = 'myconnpy_install.log'
@@ -785,6 +785,9 @@ def install_connector(root_dir, install_dir, connc_location=None,
785785
if extra_compile_args:
786786
cmd.extend(['--extra-compile-args', extra_compile_args])
787787

788+
if extra_link_args:
789+
cmd.extend(['--extra-link-args', extra_link_args])
790+
788791
prc = subprocess.Popen(cmd, stdin=subprocess.PIPE,
789792
stderr=subprocess.STDOUT, stdout=subprocess.PIPE,
790793
cwd=root_dir)

tests/mysqld.py

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,6 @@ def _get_bootstrap_cmd(self):
413413
'--no-defaults',
414414
'--basedir=%s' % self._basedir,
415415
'--datadir=%s' % self._datadir,
416-
'--log-warnings=0',
417416
'--max_allowed_packet=8M',
418417
'--default-storage-engine=myisam',
419418
'--net_buffer_length=16K',
@@ -427,6 +426,9 @@ def _get_bootstrap_cmd(self):
427426
else:
428427
cmd.append("--bootstrap")
429428

429+
if self._version < (8, 0, 3):
430+
cmd.append('--log-warnings=0')
431+
430432
if self._version[0:2] < (5, 5):
431433
cmd.append('--language={0}/english'.format(self._lc_messages_dir))
432434
else:
@@ -463,32 +465,35 @@ def bootstrap(self):
463465
extra_sql = [
464466
"CREATE DATABASE myconnpy;"
465467
]
466-
defaults = ("'root'{0}, "
467-
"'Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y',"
468-
"'Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y',"
469-
"'Y','Y','Y','Y','Y','','','','',0,0,0,0,"
470-
"@@default_authentication_plugin,'','N',"
471-
"CURRENT_TIMESTAMP,NULL{1}")
472-
473-
hosts = ["::1", "127.0.0.1"]
474-
if self._version[0:3] < (8, 0, 1):
475-
# because we use --initialize-insecure for 8.0 above
476-
# which already creates root @ localhost
477-
hosts.append("localhost")
478-
479-
insert = "INSERT INTO mysql.user VALUES {0};".format(
480-
", ".join("('{0}', {{0}})".format(host) for host in hosts))
481-
482-
if self._version[0:3] >= (8, 0, 1):
483-
# No password column, has account_locked, Create_role_priv and
484-
# Drop_role_priv columns
485-
extra_sql.append(insert.format(defaults.format("", ", 'N', 'Y', 'Y'")))
486-
elif self._version[0:3] >= (5, 7, 6):
487-
# No password column, has account_locked column
488-
extra_sql.append(insert.format(defaults.format("", ", 'N'")))
489-
elif self._version[0:3] >= (5, 7, 4):
490-
# The password column
491-
extra_sql.append(insert.format(defaults.format(", ''", "")))
468+
469+
if self._version < (8, 0, 1):
470+
defaults = ("'root'{0}, "
471+
"'Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y',"
472+
"'Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y',"
473+
"'Y','Y','Y','Y','Y','','','','',0,0,0,0,"
474+
"@@default_authentication_plugin,'','N',"
475+
"CURRENT_TIMESTAMP,NULL{1}")
476+
477+
hosts = ["::1", "127.0.0.1", "localhost"]
478+
479+
insert = "INSERT INTO mysql.user VALUES {0};".format(
480+
", ".join("('{0}', {{0}})".format(host) for host in hosts))
481+
482+
if self._version[0:3] >= (5, 7, 6):
483+
# No password column, has account_locked column
484+
defaults = defaults.format("", ", 'N'")
485+
elif self._version[0:3] >= (5, 7, 5):
486+
# The password column
487+
defaults = defaults.format(", ''", "")
488+
489+
extra_sql.append(insert.format(defaults))
490+
else:
491+
extra_sql.extend([
492+
"CREATE USER 'root'@'127.0.01';",
493+
"GRANT ALL ON *.* TO 'root'@'127.0.0.1';",
494+
"CREATE USER 'root'@'::1';",
495+
"GRANT ALL ON *.* TO 'root'@'::1';"
496+
])
492497

493498
bootstrap_log = os.path.join(self._topdir, 'bootstrap.log')
494499
try:
@@ -570,6 +575,7 @@ def update_config(self, **kwargs):
570575
'serverid': self._serverid,
571576
'lc_messages_dir': _convert_forward_slash(
572577
self._lc_messages_dir),
578+
'ssl': 1,
573579
}
574580

575581
for arg in self._extra_args:

tests/test_setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def test_classifiers(self):
133133
if 'Programming Language :: Python' in clsfr:
134134
ver = clsfr.replace('Programming Language :: Python :: ', '')
135135
if ver not in ('2.6', '2.7', '3', '3.1', '3.2', '3.3', '3.4',
136-
'3.5'):
136+
'3.5', '3.6'):
137137
self.fail('Unsupported version in classifiers')
138138
if 'Development Status ::' in clsfr:
139139
status = clsfr.replace('Development Status :: ', '')

0 commit comments

Comments
 (0)