Try to get rid from hardcoded names when building binary

This commit is contained in:
Alexander Kukushkin
2016-09-05 14:11:53 +02:00
parent 5c7efa3a65
commit 2086c90a4a
5 changed files with 31 additions and 13 deletions

View File

@@ -1,5 +1,5 @@
#!/bin/sh
set -e
pip install --ignore-installed -r requirements-bin.txt
pip install --ignore-installed setuptools==19.2 pyinstaller
pyinstaller --clean --onefile patroni.spec

View File

@@ -3,11 +3,21 @@
block_cipher = None
a = Analysis(['patroni/__main__.py', 'patroni/dcs/consul.py', 'patroni/dcs/etcd.py', 'patroni/dcs/exhibitor.py', 'patroni/dcs/zookeeper.py'],
def hiddenimports():
import sys
sys.path.insert(0, '.')
try:
import patroni.dcs
return patroni.dcs.dcs_modules()
finally:
sys.path.pop(0)
a = Analysis(['patroni/__main__.py'],
pathex=[],
binaries=None,
datas=None,
hiddenimports=['patroni.dcs.consul', 'patroni.dcs.etcd', 'patroni.dcs.exhibitor', 'patroni.dcs.zookeeper'],
hiddenimports=hiddenimports(),
hookspath=[],
runtime_hooks=[],
excludes=[],

View File

@@ -34,26 +34,29 @@ def parse_connection_string(value):
def dcs_modules():
"""Get names of DCS modules, depending on execution environment. If being packaged with PyInstaller,
modules aren't discoverable dynamically by scanning source directory. Thus, when running in bundle,
a predefined list of dcs modules is returned. See:
https://pyinstaller.readthedocs.io/en/stable/runtime-information.html#run-time-information"""
modules aren't discoverable dynamically by scanning source directory because `FrozenImporter` doesn't
implement `iter_modules` method. But it is still possible to find all potential DCS modules by
iterating through `toc`, which contains list of all "frozen" resources."""
dcs_dirname = os.path.dirname(__file__)
module_prefix = __package__ + '.'
if getattr(sys, 'frozen', False):
return ['consul', 'etcd', 'zookeeper', 'exhibitor']
importer = pkgutil.get_importer(dcs_dirname)
return [module for module in list(importer.toc) if module.startswith(module_prefix) and module.count('.') == 2]
else:
module_names = (name for _, name, is_pkg in pkgutil.iter_modules([os.path.dirname(__file__)]) if not is_pkg)
return module_names
return [module_prefix + name for _, name, is_pkg in pkgutil.iter_modules([dcs_dirname]) if not is_pkg]
def get_dcs(config):
available_implementations = set()
for module_name in dcs_modules():
module = importlib.import_module(__package__ + '.' + module_name)
module = importlib.import_module(module_name)
for name in filter(lambda name: not name.startswith('__'), dir(module)): # iterate through module content
value = getattr(module, name)
name = name.lower()
# try to find implementation of AbstractDCS interface, class name must match with module_name
if inspect.isclass(value) and issubclass(value, AbstractDCS) and name == module_name:
if inspect.isclass(value) and issubclass(value, AbstractDCS) and __package__ + '.' + name == module_name:
available_implementations.add(name)
if name in config: # which has configuration section in the config file
# propagate some parameters

View File

@@ -1,2 +0,0 @@
setuptools==19.2
pyinstaller

View File

@@ -14,6 +14,11 @@ from test_etcd import SleepException, etcd_read, etcd_write
from test_postgresql import Postgresql, psycopg2_connect
class MockFrozenImporter(object):
toc = set(['patroni.dcs.etcd'])
@patch('time.sleep', Mock())
@patch('subprocess.call', Mock(return_value=0))
@patch('psycopg2.connect', psycopg2_connect)
@@ -27,6 +32,8 @@ from test_postgresql import Postgresql, psycopg2_connect
@patch.object(etcd.Client, 'read', etcd_read)
class TestPatroni(unittest.TestCase):
@patch('pkgutil.get_importer', Mock(return_value=MockFrozenImporter()))
@patch('sys.frozen', Mock(return_value=True), create=True)
@patch.object(etcd.Client, 'read', etcd_read)
def setUp(self):
RestApiServer._BaseServer__is_shut_down = Mock()