# 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 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'), ('details_panel.py', 'DetailsPanel_UI'), ('details_panel_picture.py', 'DetailsPanelPicture_UI'), ] for srcname, dstname in FNPAIRS: xibless.generate( op.join('cocoa', 'ui', srcname), op.join(dest, dstname), localizationTable='Localizable' ) for appmode in ('standard', 'music', 'picture'): xibless.generate( op.join('cocoa', 'ui', 'preferences_panel.py'), op.join(dest, 'PreferencesPanel%s_UI' % appmode.capitalize()), localizationTable='Localizable', args={'appmode': appmode}, ) def build_cocoa(dev): sparkle_framework_path = op.join('cocoa', 'Sparkle', 'build', 'Release', 'Sparkle.framework') if not op.exists(sparkle_framework_path): print("Building Sparkle") os.chdir(op.join('cocoa', 'Sparkle')) print_and_do('make build') os.chdir(op.join('..', '..')) print("Creating OS X app structure") app = cocoa_app() app_version = get_module_version('core') cocoa_project_path = 'cocoa' 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') tocopy = [ 'core', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa', 'objp', 'send2trash', 'hsaudiotag', ] 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/dupeguru.icns' resources = [image_path, 'cocoa/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help'] app.copy_resources(*resources, use_symlinks=dev) app.copy_frameworks('build/Python', sparkle_framework_path) 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', 'dg.qrc'), op.join('qt', 'dg_rc.py'))) fix_qt_resource_file(op.join('qt', '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') tixurl = "https://github.com/hsoft/dupeguru/issues/{}" confrepl = {'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', '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/en.lproj') print("Building .pot files from source files") print("Building core.pot") loc.generate_pot(['core'], op.join('locale', 'core.pot'), ['tr']) print("Building columns.pot") loc.generate_pot(['core'], 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', '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 PyDupeGuru, DupeGuruView allclasses = [ PyGUIObject, PyColumns, PyOutline, PySelectableList, PyTable, PyBaseApp, PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog, PyIgnoreListDialog, PyDeletionOptions, PyResultTable, PyStatsLabel, PyDupeGuru, PyTextField, PyProgressWindow ] 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*', op.join('core', 'pe')) move_all('_cache*', op.join('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()