1#!/usr/bin/env python
2# coding=utf-8
3
4"""
5A setup file to build Frescobaldi.app with py2app.
6
7Use the '-h' flag to see the usage notes.
8"""
9
10
11import argparse
12import os
13import sys
14from setuptools import setup
15import shutil
16from subprocess import Popen
17
18# Python 2 text strings: basestring = str (ASCII) + unicode (Unicode)
19# Python 3 text strings: str (Unicode)
20# See https://docs.python.org/3/howto/pyporting.html for details.
21try:
22    string_types = basestring
23except NameError:
24    string_types = str
25
26macosx = os.path.realpath(os.path.dirname(__file__))
27root = os.path.dirname(macosx)
28
29sys.path.insert(0, root)
30
31from frescobaldi_app import toplevel
32toplevel.install()
33import appinfo
34try:
35    from portmidi import pm_ctypes
36    dylib_name = pm_ctypes.dll_name
37except ImportError:
38    dylib_name = None
39
40icon = '{0}/icons/{1}.icns'.format(macosx, appinfo.name)
41ipstrings = '{0}/app_resources/InfoPlist.strings'.format(macosx)
42
43parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
44parser.add_argument('-f', '--force', action = 'store_true', \
45  help = 'force execution even if SCRIPT does not exist')
46parser.add_argument('-v', '--version', \
47  help = 'version string for the application bundle, \
48  visible e.g. in \'Get Info\' and in \'Open with...\'', default = appinfo.version)
49parser.add_argument('-s', '--script', \
50  help = 'path of {0}\'s main script; you should use an absolute path, \
51  so that the application bundle can be moved to another \
52  directory'.format(appinfo.appname), default = '{0}/{1}'.format(root, appinfo.name))
53parser.add_argument('-a', '--standalone', action = 'store_true', \
54  help = 'build a standalone application bundle \
55  (WARNING: some manual steps are required after the execution of this script)')
56parser.add_argument('-p', '--portmidi', \
57  help = 'full path of PortMIDI library (used only with \'-a\')', \
58  default = dylib_name)
59parser.add_argument('-r', '--arch', \
60  help = 'architecture set to include, e.g. i386, x86_64, intel; \
61  if the value is None, the architecture of the current Python binary is used \
62  (used only with \'-a\')')
63args = parser.parse_args()
64
65if not (os.path.isfile(args.script) or args.force):
66    sys.exit('Error: \'{0}\' does not exist or is not a file.\n\
67If you really want to point the application bundle to \'{0}\',\n\
68use the \'-f\' or \'--force\' flag.'.format(args.script))
69
70if args.standalone and not (isinstance(args.portmidi, string_types) and os.path.isfile(args.portmidi)):
71    sys.exit('Error: \'{0}\' does not exist or is not a file.'.format(args.portmidi))
72
73plist = dict(
74    CFBundleName                  = appinfo.appname,
75    CFBundleDisplayName           = appinfo.appname,
76    CFBundleShortVersionString    = args.version,
77    CFBundleVersion               = args.version,
78    CFBundleExecutable            = appinfo.appname,
79    CFBundleIdentifier            = 'org.{0}.{0}'.format(appinfo.name),
80    CFBundleIconFile              = '{0}.icns'.format(appinfo.name),
81    NSHumanReadableCopyright      = u'Copyright © 2008-2014 Wilbert Berendsen.',
82    CFBundleDocumentTypes         = [
83        {
84            'CFBundleTypeExtensions': ['ly', 'lyi', 'ily'],
85            'CFBundleTypeName': 'LilyPond file',
86            'CFBundleTypeRole': 'Editor',
87        },
88        {
89            'CFBundleTypeExtensions': ['tex', 'lytex', 'latex'],
90            'CFBundleTypeName': 'LaTeX file',
91            'CFBundleTypeRole': 'Editor',
92        },
93        {
94            'CFBundleTypeExtensions': ['docbook', 'lyxml'],
95            'CFBundleTypeName': 'DocBook file',
96            'CFBundleTypeRole': 'Editor',
97        },
98        {
99            'CFBundleTypeExtensions': ['html'],
100            'CFBundleTypeName': 'HTML file',
101            'CFBundleTypeRole': 'Editor',
102            'LSItemContentTypes': ['public.html']
103        },
104        {
105            'CFBundleTypeExtensions': ['xml'],
106            'CFBundleTypeName': 'XML file',
107            'CFBundleTypeRole': 'Editor',
108            'LSItemContentTypes': ['public.xml']
109        },
110        {
111            'CFBundleTypeExtensions': ['itely', 'tely', 'texi', 'texinfo'],
112            'CFBundleTypeName': 'Texinfo file',
113            'CFBundleTypeRole': 'Editor',
114        },
115        {
116            'CFBundleTypeExtensions': ['scm'],
117            'CFBundleTypeName': 'Scheme file',
118            'CFBundleTypeRole': 'Editor',
119        },
120        {
121            'CFBundleTypeExtensions': ['*'],
122            'CFBundleTypeName': 'Text file',
123            'CFBundleTypeRole': 'Editor',
124            'LSItemContentTypes': ['public.text']
125        }
126    ]
127)
128
129options = {
130    'argv_emulation': True,
131    'plist': plist
132}
133
134if args.standalone:
135    options.update({
136        'packages': ['frescobaldi_app'],
137        'frameworks': [args.portmidi],
138    })
139    if args.arch:
140        options.update({
141            'arch': args.arch
142        })
143    try:
144        patchlist = [f for f in os.listdir('patch') if f.endswith(".diff")]
145    except OSError:
146        patchlist = []
147    for patchfile in patchlist:
148        with open('patch/{0}'.format(patchfile), 'r') as input:
149            Popen(["patch", "-d..", "-p0"], stdin=input)
150else:
151    options.update({
152        'semi_standalone': True,
153        'alias': True
154    })
155
156setup(
157    app = [args.script],
158    name = appinfo.appname,
159    options = {'py2app': options},
160    setup_requires = ['py2app'],
161    script_args = ['py2app']
162)
163
164app_resources = 'dist/{0}.app/Contents/Resources'.format(appinfo.appname)
165icon_dest = '{0}/{1}.icns'.format(app_resources, appinfo.name)
166print('copying file {0} -> {1}'.format(icon, icon_dest))
167shutil.copyfile(icon, icon_dest)
168os.chmod(icon_dest, 0o0644)
169locales = ['cs', 'de', 'en', 'es', 'fr', 'gl', 'it', 'nl', 'pl', 'pt', 'ru', 'tr', 'uk', 'zh_CN', 'zh_HK', 'zh_TW']
170for l in locales:
171    app_lproj = '{0}/{1}.lproj'.format(app_resources, l)
172    os.mkdir(app_lproj, 0o0755)
173    ipstrings_dest = '{0}/InfoPlist.strings'.format(app_lproj)
174    print('copying file {0} -> {1}'.format(ipstrings, ipstrings_dest))
175    shutil.copyfile(ipstrings, ipstrings_dest)
176    os.chmod(ipstrings_dest, 0o0644)
177
178if args.standalone:
179    if patchlist:
180        print('reversing patches:')
181    for patchfile in patchlist:
182        with open('patch/{0}'.format(patchfile), 'r') as input:
183            Popen(["patch", "-R", "-d..", "-p0"], stdin=input)
184    print('removing file {0}/qt.conf'.format(app_resources))
185    os.remove('{0}/qt.conf'.format(app_resources))
186    imageformats_dest = 'dist/{0}.app/Contents/PlugIns/imageformats'.format(appinfo.appname)
187    print('creating directory {0}'.format(imageformats_dest))
188    os.makedirs(imageformats_dest, 0o0755)
189    print("""
190WARNING: To complete the creation of the standalone application bundle \
191you need to perform the following steps manually:
192
193- copy libqsvg.dylib from Qt's 'plugins/imageformats' directory to '{1}',
194- execute Qt's macdeployqt tool on dist/{0}.app \
195(you can safely ignore the error about the failed copy of libqsvg.dylib).
196""".format(appinfo.appname, imageformats_dest))
197