dupeguru/build.py

381 lines
15 KiB
Python

# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html
import sys
import os
import os.path as op
from optparse import OptionParser
import shutil
import importlib
import compileall
from setuptools import setup, Extension
from hscommon import sphinxgen
from hscommon.build import (
add_to_pythonpath, print_and_do, copy_packages, filereplace,
get_module_version, move_all, copy_all, OSXAppStructure,
build_cocoalib_xibless, fix_qt_resource_file, build_cocoa_ext, copy_embeddable_python_dylib,
collect_stdlib_dependencies
)
from hscommon import loc
from hscommon.plat import ISOSX
from hscommon.util import ensure_folder, delete_files_with_pattern
def parse_args():
usage = "usage: %prog [options]"
parser = OptionParser(usage=usage)
parser.add_option(
'--clean', action='store_true', dest='clean',
help="Clean build folder before building"
)
parser.add_option(
'--doc', action='store_true', dest='doc',
help="Build only the help file"
)
parser.add_option(
'--ui', dest='ui',
help="Type of UI to build. 'qt' or 'cocoa'. Default is determined by your system."
)
parser.add_option(
'--dev', action='store_true', dest='dev', default=False,
help="If this flag is set, will configure for dev builds."
)
parser.add_option(
'--loc', action='store_true', dest='loc',
help="Build only localization"
)
parser.add_option(
'--cocoa-ext', action='store_true', dest='cocoa_ext',
help="Build only Cocoa extensions"
)
parser.add_option(
'--cocoa-compile', action='store_true', dest='cocoa_compile',
help="Build only Cocoa executable"
)
parser.add_option(
'--xibless', action='store_true', dest='xibless',
help="Build only xibless UIs"
)
parser.add_option(
'--updatepot', action='store_true', dest='updatepot',
help="Generate .pot files from source code."
)
parser.add_option(
'--mergepot', action='store_true', dest='mergepot',
help="Update all .po files based on .pot files."
)
parser.add_option(
'--normpo', action='store_true', dest='normpo',
help="Normalize all PO files (do this before commit)."
)
(options, args) = parser.parse_args()
return options
def cocoa_app():
app_path = 'build/dupeGuru.app'
return OSXAppStructure(app_path)
def build_xibless(dest='cocoa/autogen'):
import xibless
ensure_folder(dest)
FNPAIRS = [
('ignore_list_dialog.py', 'IgnoreListDialog_UI'),
('deletion_options.py', 'DeletionOptions_UI'),
('problem_dialog.py', 'ProblemDialog_UI'),
('directory_panel.py', 'DirectoryPanel_UI'),
('prioritize_dialog.py', 'PrioritizeDialog_UI'),
('result_window.py', 'ResultWindow_UI'),
('main_menu.py', 'MainMenu_UI'),
('preferences_panel.py', 'PreferencesPanel_UI'),
]
for srcname, dstname in FNPAIRS:
xibless.generate(
op.join('cocoa', 'base', 'ui', srcname), op.join(dest, dstname),
localizationTable='Localizable'
)
# XXX This is broken
assert False
# if edition == 'pe':
# xibless.generate(
# 'cocoa/pe/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'),
# localizationTable='Localizable'
# )
# else:
# xibless.generate(
# 'cocoa/base/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'),
# localizationTable='Localizable'
# )
def build_cocoa(dev):
print("Creating OS X app structure")
app = cocoa_app()
app_version = get_module_version('core_se')
cocoa_project_path = 'cocoa/se'
filereplace(op.join(cocoa_project_path, 'InfoTemplate.plist'), op.join('build', 'Info.plist'), version=app_version)
app.create(op.join('build', 'Info.plist'))
print("Building localizations")
build_localizations('cocoa')
print("Building xibless UIs")
build_cocoalib_xibless()
build_xibless()
print("Building Python extensions")
build_cocoa_proxy_module()
build_cocoa_bridging_interfaces()
print("Building the cocoa layer")
copy_embeddable_python_dylib('build')
pydep_folder = op.join(app.resources, 'py')
if not op.exists(pydep_folder):
os.mkdir(pydep_folder)
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
appscript_pkgs = ['appscript', 'aem', 'mactypes', 'osax']
tocopy = [
'core', 'core_se', 'core_me', 'core_pe', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa',
'objp', 'send2trash', 'hsaudiotag',
] + appscript_pkgs
copy_packages(tocopy, pydep_folder, create_links=dev)
sys.path.insert(0, 'build')
# ModuleFinder can't seem to correctly detect the multiprocessing dependency, so we have
# to manually specify it.
extra_deps = ['multiprocessing']
collect_stdlib_dependencies('build/dg_cocoa.py', pydep_folder, extra_deps=extra_deps)
del sys.path[0]
# Views are not referenced by python code, so they're not found by the collector.
copy_all('build/inter/*.so', op.join(pydep_folder, 'inter'))
if not dev:
# Important: Don't ever run delete_files_with_pattern('*.py') on dev builds because you'll
# be deleting all py files in symlinked folders.
compileall.compile_dir(pydep_folder, force=True, legacy=True)
delete_files_with_pattern(pydep_folder, '*.py')
delete_files_with_pattern(pydep_folder, '__pycache__')
print("Compiling with WAF")
os.chdir('cocoa')
print_and_do('{0} waf configure && {0} waf'.format(sys.executable))
os.chdir('..')
app.copy_executable('cocoa/build/dupeGuru')
build_help()
print("Copying resources and frameworks")
image_path = 'cocoa/se/dupeguru.icns'
resources = [image_path, 'cocoa/base/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help']
app.copy_resources(*resources, use_symlinks=dev)
app.copy_frameworks('build/Python', 'cocoalib/Sparkle.framework')
print("Creating the run.py file")
tmpl = open('cocoa/run_template.py', 'rt').read()
run_contents = tmpl.replace('{{app_path}}', app.dest)
open('run.py', 'wt').write(run_contents)
def build_qt(dev):
print("Building localizations")
build_localizations('qt')
print("Building Qt stuff")
print_and_do("pyrcc5 {0} > {1}".format(op.join('qt', 'base', 'dg.qrc'), op.join('qt', 'base', 'dg_rc.py')))
fix_qt_resource_file(op.join('qt', 'base', 'dg_rc.py'))
build_help()
print("Creating the run.py file")
shutil.copy(op.join('qt', 'run_template.py'), 'run.py')
def build_help():
print("Generating Help")
current_path = op.abspath('.')
help_basepath = op.join(current_path, 'help', 'en')
help_destpath = op.join(current_path, 'build', 'help')
changelog_path = op.join(current_path, 'help', 'changelog_se')
tixurl = "https://github.com/hsoft/dupeguru/issues/{}"
appname = 'dupeGuru'
homepage = 'https://www.hardcoded.net/dupeguru/'
confrepl = {'appname': appname, 'homepage': homepage, 'language': 'en'}
changelogtmpl = op.join(current_path, 'help', 'changelog.tmpl')
conftmpl = op.join(current_path, 'help', 'conf.tmpl')
sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl, conftmpl, changelogtmpl)
def build_qt_localizations():
loc.compile_all_po(op.join('qtlib', 'locale'))
loc.merge_locale_dir(op.join('qtlib', 'locale'), 'locale')
def build_localizations(ui):
loc.compile_all_po('locale')
if ui == 'cocoa':
app = cocoa_app()
loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'))
locale_dest = op.join(app.resources, 'locale')
elif ui == 'qt':
build_qt_localizations()
locale_dest = op.join('build', 'locale')
if op.exists(locale_dest):
shutil.rmtree(locale_dest)
shutil.copytree('locale', locale_dest, ignore=shutil.ignore_patterns('*.po', '*.pot'))
def build_updatepot():
if ISOSX:
print("Updating Cocoa strings file.")
build_cocoalib_xibless('cocoalib/autogen')
loc.generate_cocoa_strings_from_code('cocoalib', 'cocoalib/en.lproj')
build_xibless('se', op.join('cocoa', 'autogen', 'se'))
loc.generate_cocoa_strings_from_code('cocoa', 'cocoa/base/en.lproj')
print("Building .pot files from source files")
print("Building core.pot")
all_cores = ['core', 'core_se', 'core_me', 'core_pe']
loc.generate_pot(all_cores, op.join('locale', 'core.pot'), ['tr'])
print("Building columns.pot")
loc.generate_pot(all_cores, op.join('locale', 'columns.pot'), ['coltr'])
print("Building ui.pot")
# When we're not under OS X, we don't want to overwrite ui.pot because it contains Cocoa locs
# We want to merge the generated pot with the old pot in the most preserving way possible.
ui_packages = ['qt', op.join('cocoa', 'inter')]
loc.generate_pot(ui_packages, op.join('locale', 'ui.pot'), ['tr'], merge=(not ISOSX))
print("Building qtlib.pot")
loc.generate_pot(['qtlib'], op.join('qtlib', 'locale', 'qtlib.pot'), ['tr'])
if ISOSX:
print("Building cocoalib.pot")
cocoalib_pot = op.join('cocoalib', 'locale', 'cocoalib.pot')
os.remove(cocoalib_pot)
loc.strings2pot(op.join('cocoalib', 'en.lproj', 'cocoalib.strings'), cocoalib_pot)
print("Enhancing ui.pot with Cocoa's strings files")
loc.strings2pot(
op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'),
op.join('locale', 'ui.pot')
)
def build_mergepot():
print("Updating .po files using .pot files")
loc.merge_pots_into_pos('locale')
loc.merge_pots_into_pos(op.join('qtlib', 'locale'))
loc.merge_pots_into_pos(op.join('cocoalib', 'locale'))
def build_normpo():
loc.normalize_all_pos('locale')
loc.normalize_all_pos(op.join('qtlib', 'locale'))
loc.normalize_all_pos(op.join('cocoalib', 'locale'))
def build_cocoa_proxy_module():
print("Building Cocoa Proxy")
import objp.p2o
objp.p2o.generate_python_proxy_code('cocoalib/cocoa/CocoaProxy.h', 'build/CocoaProxy.m')
build_cocoa_ext(
"CocoaProxy", 'cocoalib/cocoa',
[
'cocoalib/cocoa/CocoaProxy.m', 'build/CocoaProxy.m', 'build/ObjP.m',
'cocoalib/HSErrorReportWindow.m', 'cocoa/autogen/HSErrorReportWindow_UI.m'
],
['AppKit', 'CoreServices'],
['cocoalib', 'cocoa/autogen']
)
def build_cocoa_bridging_interfaces():
print("Building Cocoa Bridging Interfaces")
import objp.o2p
import objp.p2o
add_to_pythonpath('cocoa')
add_to_pythonpath('cocoalib')
from cocoa.inter import (
PyGUIObject, GUIObjectView, PyColumns, ColumnsView, PyOutline,
OutlineView, PySelectableList, SelectableListView, PyTable, TableView, PyBaseApp,
PyTextField, ProgressWindowView, PyProgressWindow
)
from inter.deletion_options import PyDeletionOptions, DeletionOptionsView
from inter.details_panel import PyDetailsPanel, DetailsPanelView
from inter.directory_outline import PyDirectoryOutline, DirectoryOutlineView
from inter.prioritize_dialog import PyPrioritizeDialog, PrioritizeDialogView
from inter.prioritize_list import PyPrioritizeList, PrioritizeListView
from inter.problem_dialog import PyProblemDialog
from inter.ignore_list_dialog import PyIgnoreListDialog, IgnoreListDialogView
from inter.result_table import PyResultTable, ResultTableView
from inter.stats_label import PyStatsLabel, StatsLabelView
from inter.app import PyDupeGuruBase, DupeGuruView
appmod = importlib.import_module('inter.app_se')
allclasses = [
PyGUIObject, PyColumns, PyOutline, PySelectableList, PyTable, PyBaseApp,
PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog,
PyIgnoreListDialog, PyDeletionOptions, PyResultTable, PyStatsLabel, PyDupeGuruBase,
PyTextField, PyProgressWindow, appmod.PyDupeGuru
]
for class_ in allclasses:
objp.o2p.generate_objc_code(class_, 'cocoa/autogen', inherit=True)
allclasses = [
GUIObjectView, ColumnsView, OutlineView, SelectableListView, TableView,
DetailsPanelView, DirectoryOutlineView, PrioritizeDialogView, PrioritizeListView,
IgnoreListDialogView, DeletionOptionsView, ResultTableView, StatsLabelView,
ProgressWindowView, DupeGuruView
]
clsspecs = [objp.o2p.spec_from_python_class(class_) for class_ in allclasses]
objp.p2o.generate_python_proxy_code_from_clsspec(clsspecs, 'build/CocoaViews.m')
build_cocoa_ext('CocoaViews', 'cocoa/inter', ['build/CocoaViews.m', 'build/ObjP.m'])
def build_pe_modules(ui):
print("Building PE Modules")
exts = [
Extension("_block", [op.join('core_pe', 'modules', 'block.c'), op.join('core_pe', 'modules', 'common.c')]),
Extension("_cache", [op.join('core_pe', 'modules', 'cache.c'), op.join('core_pe', 'modules', 'common.c')]),
]
if ui == 'qt':
exts.append(Extension("_block_qt", [op.join('qt', 'pe', 'modules', 'block.c')]))
elif ui == 'cocoa':
exts.append(Extension(
"_block_osx", [op.join('core_pe', 'modules', 'block_osx.m'), op.join('core_pe', 'modules', 'common.c')],
extra_link_args=[
"-framework", "CoreFoundation",
"-framework", "Foundation",
"-framework", "ApplicationServices",
]
))
setup(
script_args=['build_ext', '--inplace'],
ext_modules=exts,
)
move_all('_block_qt*', op.join('qt', 'pe'))
move_all('_block*', 'core_pe')
move_all('_cache*', 'core_pe')
def build_normal(ui, dev):
print("Building dupeGuru with UI {}".format(ui))
add_to_pythonpath('.')
print("Building dupeGuru")
build_pe_modules(ui)
if ui == 'cocoa':
build_cocoa(dev)
elif ui == 'qt':
build_qt(dev)
def main():
options = parse_args()
ui = options.ui
if ui not in ('cocoa', 'qt'):
ui = 'cocoa' if ISOSX else 'qt'
if options.dev:
print("Building in Dev mode")
if options.clean:
for path in ['build', op.join('cocoa', 'build'), op.join('cocoa', 'autogen')]:
if op.exists(path):
shutil.rmtree(path)
if not op.exists('build'):
os.mkdir('build')
if options.doc:
build_help()
elif options.loc:
build_localizations(ui)
elif options.updatepot:
build_updatepot()
elif options.mergepot:
build_mergepot()
elif options.normpo:
build_normpo()
elif options.cocoa_ext:
build_cocoa_proxy_module()
build_cocoa_bridging_interfaces()
elif options.cocoa_compile:
os.chdir('cocoa')
print_and_do('{0} waf configure && {0} waf'.format(sys.executable))
os.chdir('..')
cocoa_app().copy_executable('cocoa/build/dupeGuru')
elif options.xibless:
build_cocoalib_xibless()
build_xibless()
else:
build_normal(ui, options.dev)
if __name__ == '__main__':
main()