Simplify launching of console utilities on macOS

This commit is contained in:
Kovid Goyal 2019-08-31 15:05:14 +05:30
parent b8968a82f8
commit a7453e877a
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
9 changed files with 27 additions and 31 deletions

View File

@ -203,7 +203,6 @@ class Freeze(object):
if not test_launchers and not self.dont_strip: if not test_launchers and not self.dont_strip:
self.strip_files() self.strip_files()
if not test_launchers: if not test_launchers:
self.create_console_app()
self.create_gui_apps() self.create_gui_apps()
self.run_tests() self.run_tests()
@ -213,8 +212,7 @@ class Freeze(object):
@flush @flush
def run_tests(self): def run_tests(self):
cc_dir = join(self.contents_dir, 'calibre-debug.app', 'Contents') self.test_runner(join(self.contents_dir, 'MacOS', 'calibre-debug'), self.contents_dir)
self.test_runner(join(cc_dir, 'MacOS', 'calibre-debug'), self.contents_dir)
@flush @flush
def add_resources(self): def add_resources(self):
@ -677,14 +675,6 @@ class Freeze(object):
else: else:
os.symlink(join('../..', x), join(cc_dir, x)) os.symlink(join('../..', x), join(cc_dir, x))
@flush
def create_console_app(self):
def specialise_plist(plist):
plist['LSBackgroundOnly'] = '1'
plist['CFBundleIdentifier'] = 'com.calibre-ebook.console'
plist['CFBundleExecutable'] = 'calibre-parallel'
self.create_app_clone('console.app', specialise_plist)
@flush @flush
def create_gui_apps(self): def create_gui_apps(self):
input_formats = sorted(set(json.loads( input_formats = sorted(set(json.loads(
@ -696,19 +686,17 @@ class Freeze(object):
def specialise_plist(launcher, remove_types, plist): def specialise_plist(launcher, remove_types, plist):
plist['CFBundleDisplayName'] = plist['CFBundleName'] = { plist['CFBundleDisplayName'] = plist['CFBundleName'] = {
'ebook-viewer': 'E-book Viewer', 'ebook-edit': 'Edit Book', 'calibre-debug': 'calibre (debug)', 'ebook-viewer': 'E-book Viewer', 'ebook-edit': 'Edit Book',
}[launcher] }[launcher]
plist['CFBundleExecutable'] = launcher plist['CFBundleExecutable'] = launcher
if launcher != 'calibre-debug':
plist['CFBundleIconFile'] = launcher + '.icns'
plist['CFBundleIdentifier'] = 'com.calibre-ebook.' + launcher plist['CFBundleIdentifier'] = 'com.calibre-ebook.' + launcher
plist['CFBundleIconFile'] = launcher + '.icns'
if not remove_types: if not remove_types:
e = plist['CFBundleDocumentTypes'][0] e = plist['CFBundleDocumentTypes'][0]
exts = 'epub azw3'.split() if launcher == 'ebook-edit' else input_formats exts = 'epub azw3'.split() if launcher == 'ebook-edit' else input_formats
e['CFBundleTypeExtensions'] = exts e['CFBundleTypeExtensions'] = exts
for launcher in ('ebook-viewer', 'ebook-edit', 'calibre-debug'): for launcher in ('ebook-viewer', 'ebook-edit'):
remove_types = launcher == 'calibre-debug' self.create_app_clone(launcher + '.app', partial(specialise_plist, launcher, False), remove_doc_types=False)
self.create_app_clone(launcher + '.app', partial(specialise_plist, launcher, remove_types), remove_doc_types=remove_types)
@flush @flush
def copy_site(self): def copy_site(self):

View File

@ -7,20 +7,23 @@ This is stripped down and customized for use in py2app applications
import sys import sys
import os import os
def makepath(*paths): def makepath(*paths):
dir = os.path.abspath(os.path.join(*paths)) dir = os.path.abspath(os.path.join(*paths))
return dir, os.path.normcase(dir) return dir, os.path.normcase(dir)
def abs__file__(): def abs__file__():
"""Set all module __file__ attribute to an absolute path""" """Set all module __file__ attribute to an absolute path"""
for m in sys.modules.values(): for m in sys.modules.values():
if hasattr(m, '__loader__'): if hasattr(m, '__loader__'):
continue # don't mess with a PEP 302-supplied __file__ continue # don't mess with a PEP 302-supplied __file__
try: try:
m.__file__ = os.path.abspath(m.__file__) m.__file__ = os.path.abspath(m.__file__)
except AttributeError: except AttributeError:
continue continue
# This ensures that the initial path provided by the interpreter contains # This ensures that the initial path provided by the interpreter contains
# only absolute pathnames, even if we're running from the build directory. # only absolute pathnames, even if we're running from the build directory.
L = [] L = []
@ -38,6 +41,7 @@ sys.path[:] = L
del dir, dircase, L del dir, dircase, L
_dirs_in_sys_path = None _dirs_in_sys_path = None
def _init_pathinfo(): def _init_pathinfo():
global _dirs_in_sys_path global _dirs_in_sys_path
_dirs_in_sys_path = d = {} _dirs_in_sys_path = d = {}
@ -47,6 +51,7 @@ def _init_pathinfo():
dir, dircase = makepath(dir) dir, dircase = makepath(dir)
d[dircase] = 1 d[dircase] = 1
def addsitedir(sitedir): def addsitedir(sitedir):
global _dirs_in_sys_path global _dirs_in_sys_path
if _dirs_in_sys_path is None: if _dirs_in_sys_path is None:
@ -56,7 +61,7 @@ def addsitedir(sitedir):
reset = 0 reset = 0
sitedir, sitedircase = makepath(sitedir) sitedir, sitedircase = makepath(sitedir)
if sitedircase not in _dirs_in_sys_path: if sitedircase not in _dirs_in_sys_path:
sys.path.append(sitedir) # Add path component sys.path.append(sitedir) # Add path component
try: try:
names = os.listdir(sitedir) names = os.listdir(sitedir)
except os.error: except os.error:
@ -68,6 +73,7 @@ def addsitedir(sitedir):
if reset: if reset:
_dirs_in_sys_path = None _dirs_in_sys_path = None
def addpackage(sitedir, name): def addpackage(sitedir, name):
global _dirs_in_sys_path global _dirs_in_sys_path
if _dirs_in_sys_path is None: if _dirs_in_sys_path is None:
@ -107,29 +113,31 @@ if hasattr(sys, "setdefaultencoding"):
sys.setdefaultencoding('utf-8') sys.setdefaultencoding('utf-8')
del sys.setdefaultencoding del sys.setdefaultencoding
def run_entry_point(): def run_entry_point():
bname, mod, func = sys.calibre_basename, sys.calibre_module, sys.calibre_function bname, mod, func = sys.calibre_basename, sys.calibre_module, sys.calibre_function
sys.argv[0] = bname sys.argv[0] = bname
pmod = __import__(mod, fromlist=[1], level=0) pmod = __import__(mod, fromlist=[1], level=0)
return getattr(pmod, func)() return getattr(pmod, func)()
def add_calibre_vars(base): def add_calibre_vars(base):
sys.frameworks_dir = os.path.join(os.path.dirname(base), 'Frameworks') sys.frameworks_dir = os.path.join(os.path.dirname(base), 'Frameworks')
sys.resources_location = os.path.abspath(os.path.join(base, 'resources')) sys.resources_location = os.path.abspath(os.path.join(base, 'resources'))
sys.extensions_location = os.path.join(sys.frameworks_dir, 'plugins') sys.extensions_location = os.path.join(sys.frameworks_dir, 'plugins')
sys.binaries_path = os.path.join(os.path.dirname(base), 'MacOS') sys.binaries_path = os.path.join(os.path.dirname(base), 'MacOS')
sys.console_binaries_path = os.path.join(os.path.dirname(base),
'console.app', 'Contents', 'MacOS')
dv = os.environ.get('CALIBRE_DEVELOP_FROM', None) dv = os.environ.get('CALIBRE_DEVELOP_FROM', None)
if dv and os.path.exists(dv): if dv and os.path.exists(dv):
sys.path.insert(0, os.path.abspath(dv)) sys.path.insert(0, os.path.abspath(dv))
def nuke_stdout(): def nuke_stdout():
# Redirect stdout, stdin and stderr to /dev/null # Redirect stdout, stdin and stderr to /dev/null
from calibre.constants import plugins from calibre.constants import plugins
plugins['speedup'][0].detach(os.devnull) plugins['speedup'][0].detach(os.devnull)
def main(): def main():
global __file__ global __file__
@ -148,7 +156,8 @@ def main():
addsitedir(sys.site_packages) addsitedir(sys.site_packages)
if sys.calibre_is_gui_app and not ( if sys.calibre_is_gui_app and not (
sys.stdout.isatty() or sys.stderr.isatty() or sys.stdin.isatty()): sys.stdout.isatty() or sys.stderr.isatty() or sys.stdin.isatty()
):
nuke_stdout() nuke_stdout()
return run_entry_point() return run_entry_point()

View File

@ -37,7 +37,7 @@ the directory in which you created :file:`__init__.py`::
.. note:: .. note::
On macOS, the command line tools are inside the calibre bundle, for example, On macOS, the command line tools are inside the calibre bundle, for example,
if you installed calibre in :file:`/Applications` the command line tools if you installed calibre in :file:`/Applications` the command line tools
are in :file:`/Applications/calibre.app/Contents/console.app/Contents/MacOS/`. are in :file:`/Applications/calibre.app/Contents/MacOS/`.
You can download the Hello World plugin from You can download the Hello World plugin from
`helloworld_plugin.zip <https://calibre-ebook.com/downloads/helloworld_plugin.zip>`_. `helloworld_plugin.zip <https://calibre-ebook.com/downloads/helloworld_plugin.zip>`_.
@ -325,4 +325,3 @@ Sharing your plugins with others
If you would like to share the plugins you have created with other users of calibre, post your plugin in a new thread in the If you would like to share the plugins you have created with other users of calibre, post your plugin in a new thread in the
`calibre plugins forum <https://www.mobileread.com/forums/forumdisplay.php?f=237>`_. `calibre plugins forum <https://www.mobileread.com/forums/forumdisplay.php?f=237>`_.

View File

@ -198,7 +198,7 @@ the previously checked out calibre code directory, for example::
calibre is the directory that contains the src and resources sub-directories. calibre is the directory that contains the src and resources sub-directories.
The calibre command line tools are found inside the calibre app bundle, in The calibre command line tools are found inside the calibre app bundle, in
:file:`/Applications/calibre.app/Contents/console.app/Contents/MacOS` :file:`/Applications/calibre.app/Contents/MacOS`
you should add this directory to your PATH environment variable, if you want to you should add this directory to your PATH environment variable, if you want to
run the command line tools easily. run the command line tools easily.

View File

@ -311,7 +311,7 @@ If you're satisfied with your recipe, and you feel there is enough demand to jus
.. note:: .. note::
On macOS, the command line tools are inside the calibre bundle, for example, On macOS, the command line tools are inside the calibre bundle, for example,
if you installed calibre in :file:`/Applications` the command line tools if you installed calibre in :file:`/Applications` the command line tools
are in :file:`/Applications/calibre.app/Contents/console.app/Contents/MacOS/`. are in :file:`/Applications/calibre.app/Contents/MacOS/`.
.. seealso:: .. seealso::

View File

@ -17,8 +17,6 @@ from polyglot.builtins import exec_path, raw_input, unicode_type, getcwd
def get_debug_executable(): def get_debug_executable():
if hasattr(sys, 'frameworks_dir'): if hasattr(sys, 'frameworks_dir'):
base = os.path.dirname(sys.frameworks_dir) base = os.path.dirname(sys.frameworks_dir)
if 'calibre-debug.app' not in base:
base = os.path.join(base, 'calibre-debug.app', 'Contents')
return os.path.join(base, 'MacOS', 'calibre-debug') return os.path.join(base, 'MacOS', 'calibre-debug')
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
return os.path.join(os.path.dirname(os.path.abspath(sys.executable)), 'calibre-debug' + ('.exe' if iswindows else '')) return os.path.join(os.path.dirname(os.path.abspath(sys.executable)), 'calibre-debug' + ('.exe' if iswindows else ''))

View File

@ -1201,6 +1201,8 @@ def ensure_app(headless=True):
has_headless = isosx or islinux or isbsd has_headless = isosx or islinux or isbsd
if headless and has_headless: if headless and has_headless:
args += ['-platformpluginpath', plugins_loc, '-platform', 'headless'] args += ['-platformpluginpath', plugins_loc, '-platform', 'headless']
if isosx:
os.environ['QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM'] = '1'
_store_app = QApplication(args) _store_app = QApplication(args)
if headless and has_headless: if headless and has_headless:
_store_app.headless = True _store_app.headless = True

View File

@ -1183,7 +1183,7 @@ def cli_index_strings():
return _('Command Line Interface'), _( return _('Command Line Interface'), _(
'On macOS, the command line tools are inside the calibre bundle, for example,' 'On macOS, the command line tools are inside the calibre bundle, for example,'
' if you installed calibre in :file:`/Applications` the command line tools' ' if you installed calibre in :file:`/Applications` the command line tools'
' are in :file:`/Applications/calibre.app/Contents/console.app/Contents/MacOS/`.'), _( ' are in :file:`/Applications/calibre.app/Contents/MacOS/`.'), _(
'Documented commands'), _('Undocumented commands'), _( 'Documented commands'), _('Undocumented commands'), _(
'You can see usage for undocumented commands by executing them without arguments in a terminal.'), _( 'You can see usage for undocumented commands by executing them without arguments in a terminal.'), _(
'Change language'), _('Search') 'Change language'), _('Search')

View File

@ -59,7 +59,7 @@ class Worker(object):
return os.path.join(os.path.dirname(sys.executable), return os.path.join(os.path.dirname(sys.executable),
e+'.exe' if isfrozen else 'Scripts\\%s.exe'%e) e+'.exe' if isfrozen else 'Scripts\\%s.exe'%e)
if isosx: if isosx:
return os.path.join(sys.console_binaries_path, e) return os.path.join(sys.binaries_path, e)
if isfrozen: if isfrozen:
return os.path.join(sys.executables_location, e) return os.path.join(sys.executables_location, e)
@ -74,7 +74,7 @@ class Worker(object):
def gui_executable(self): def gui_executable(self):
if isosx and not hasattr(sys, 'running_from_setup'): if isosx and not hasattr(sys, 'running_from_setup'):
if self.job_name in {'ebook-viewer', 'ebook-edit'}: if self.job_name in {'ebook-viewer', 'ebook-edit'}:
return self.executable.replace('/console.app/', '/%s.app/' % self.job_name) return self.executable.replace('/Contents/', '/Contents/%s.app/Contents/' % self.job_name)
return os.path.join(sys.binaries_path, self.exe_name) return os.path.join(sys.binaries_path, self.exe_name)
return self.executable return self.executable