def _fix_sparkle_delta_updates(): # Sparkle's Delta Updates mechanism does not support signed non-Mach-O files # in Contents/MacOS. base_library.zip, which is created by PyInstaller, # violates this. We therefore move base_library.zip to Contents/Resources. # Fortunately, everything still works if we then create a symlink # MacOS/base_library.zip -> ../Resources/base_library.zip. rename(path('target/App.app/Contents/MacOS/base_library.zip'), path('target/App.app/Contents/Resources/base_library.zip')) symlink('../Resources/base_library.zip', path('target/App.app/Contents/MacOS/base_library.zip'))
def _remove_unwanted_pyinstaller_files(): for unwanted in ('include', 'lib', 'lib2to3'): try: unlink(path('target/App.app/Contents/MacOS/' + unwanted)) except FileNotFoundError: pass try: rmtree(path('target/App.app/Contents/Resources/' + unwanted)) except FileNotFoundError: pass
def freeze_linux(): run_pyinstaller() generate_resources(dest_dir=path('target/App.app')) copy(path('src/main/icons/Icon.ico'), path('target/App.app')) # For some reason, PyInstaller packages libstdc++.so.6 even though it is # available on most Linux distributions. If we include it and run our app on # a different Ubuntu version, then Popen(...) calls fail with errors # "GLIBCXX_... not found" or "CXXABI_..." not found. So ensure we don't # package the file, so that the respective system's compatible version is # used: remove_shared_libraries('libstdc++.so.*', 'libtinfo.so.*', 'libreadline.so.*', 'libdrm.so.*')
def freeze_mac(): if not exists(path('target/Icon.icns')): _generate_iconset() run(['iconutil', '-c', 'icns', path('target/Icon.iconset')], check=True) pyinstaller_args = ['--windowed', '--icon', path('target/Icon.icns')] bundle_identifier = SETTINGS.get('mac_bundle_identifier', '') if bundle_identifier: pyinstaller_args.extend(['--osx-bundle-identifier', bundle_identifier]) run_pyinstaller(extra_args=pyinstaller_args) _remove_unwanted_pyinstaller_files() _fix_sparkle_delta_updates() generate_resources( dest_dir=path('target/App.app'), dest_dir_for_base=path('target/App.app/Contents/Resources'))
def test(): """ Execute your automated tests """ sys.path.append(path('src/main/python')) suite = TestSuite() test_dirs = SETTINGS['test_dirs'] for test_dir in map(path, test_dirs): sys.path.append(test_dir) try: dir_names = listdir(test_dir) except FileNotFoundError: continue for dir_name in dir_names: dir_path = join(test_dir, dir_name) if isfile(join(dir_path, '__init__.py')): suite.addTest( defaultTestLoader.discover(dir_name, top_level_dir=test_dir)) has_tests = bool(list(suite)) if has_tests: TextTestRunner().run(suite) else: print('No tests found. You can add them to:\n * ' + '\n * '.join(test_dirs))
def get_icons(): result = {} for icons_dir in ('src/main/icons/base', 'src/main/icons/' + platform.name().lower()): for icon_path in glob(path(icons_dir + '/*.png')): size = int(splitext(basename(icon_path))[0]) result[size] = icon_path return list(result.items())
def generate_resources(dest_dir=None, dest_dir_for_base=None, exclude=None): if dest_dir is None: # Set this default here instead of in the function definition # (`def generate_resources(dest_dir=path(...) ...)`) because we can't # call path(...) at the module level. dest_dir = path('target/resources') if dest_dir_for_base is None: dest_dir_for_base = dest_dir if exclude is None: exclude = [] resources_to_filter = SETTINGS['resources_to_filter'] kwargs = {'exclude': exclude, 'files_to_filter': resources_to_filter} copy_with_filtering(path('src/main/resources/base'), dest_dir_for_base, **kwargs) os_resources_dir = path('src/main/resources/' + platform.name().lower()) if exists(os_resources_dir): copy_with_filtering(os_resources_dir, dest_dir, **kwargs)
def clean(): """ Remove previous build outputs """ try: rmtree(path('target')) except FileNotFoundError: return except OSError: # In a docker container, target/ may be mounted so we can't delete it. # Delete its contents instead: for f in listdir(path('target')): fpath = join(path('target'), f) if isdir(fpath): rmtree(fpath, ignore_errors=True) elif isfile(fpath): remove(fpath) elif islink(fpath): unlink(fpath)
def run(): """ Run your app from source """ env = dict(os.environ) pythonpath = path('src/main/python') old_pythonpath = env.get('PYTHONPATH', '') if old_pythonpath: pythonpath += os.pathsep + old_pythonpath env['PYTHONPATH'] = pythonpath subprocess.run([sys.executable, SETTINGS['main_module']], env=env)
def freeze_windows(extra_pyinstaller_args=None): if extra_pyinstaller_args is None: extra_pyinstaller_args = [] args = ['--windowed', '--icon', path('src/main/icons/Icon.ico')] + extra_pyinstaller_args run_pyinstaller(extra_args=args) # PyInstaller somehow corrupts python3*.dll - see: # https://github.com/pyinstaller/pyinstaller/issues/2526 # Restore the uncorrupted original: for dll_name in ('python3.dll', 'python35.dll'): remove(path('target/App.app/' + dll_name)) copy(join(dirname(sys.executable), dll_name), path('target/App.app')) generate_resources(dest_dir=path('target/App.app')) copy(path('src/main/icons/Icon.ico'), path('target/App.app')) _add_missing_dlls()
def _generate_iconset(): makedirs(path('target/Icon.iconset'), exist_ok=True) for size, icon_path in get_icons(): dest_name = 'icon_%dx%d.png' % (size, size) copy(icon_path, path('target/Icon.iconset/' + dest_name))
def remove_shared_libraries(*filename_patterns): for pattern in filename_patterns: for file_path in glob(path('target/App.app/' + pattern)): remove(file_path)
def _add_missing_dlls(): for dll in ('msvcr100.dll', 'msvcr110.dll', 'msvcp110.dll', 'vcruntime140.dll', 'msvcp140.dll', 'concrt140.dll', 'vccorlib140.dll', 'api-ms-win-crt-multibyte-l1-1-0.dll'): copy(join(r'c:\Windows\System32', dll), path('target/App.app'))