mirror of
https://github.com/arsenetar/dupeguru.git
synced 2025-05-07 09:19:50 +00:00
Adapt build/package scripts to single-edition
This commit is contained in:
parent
c865f84c16
commit
ad45a6e16e
@ -5,9 +5,6 @@ a system. It's written mostly in Python 3 and has the peculiarity of using
|
|||||||
[multiple GUI toolkits][cross-toolkit], all using the same core Python code. On OS X, the UI layer
|
[multiple GUI toolkits][cross-toolkit], all using the same core Python code. On OS X, the UI layer
|
||||||
is written in Objective-C and uses Cocoa. On Linux, it's written in Python and uses Qt5.
|
is written in Objective-C and uses Cocoa. On Linux, it's written in Python and uses Qt5.
|
||||||
|
|
||||||
dupeGuru comes in 3 editions (standard, music and picture) which are all buildable from this same
|
|
||||||
source tree. You choose the edition you want to build in a `configure.py` flag.
|
|
||||||
|
|
||||||
# Current status: People wanted
|
# Current status: People wanted
|
||||||
|
|
||||||
dupeGuru has currently only one maintainer, me. This is a dangerous situation that needs to be
|
dupeGuru has currently only one maintainer, me. This is a dangerous situation that needs to be
|
||||||
@ -121,7 +118,6 @@ For OS X:
|
|||||||
|
|
||||||
With your virtualenv activated, you can build and run dupeGuru with these commands:
|
With your virtualenv activated, you can build and run dupeGuru with these commands:
|
||||||
|
|
||||||
$ python configure.py
|
|
||||||
$ python build.py
|
$ python build.py
|
||||||
$ python run.py
|
$ python run.py
|
||||||
|
|
||||||
|
@ -44,4 +44,4 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Bootstrapping complete! You can now configure, build and run dupeGuru with:"
|
echo "Bootstrapping complete! You can now configure, build and run dupeGuru with:"
|
||||||
echo ". env/bin/activate && python configure.py && python build.py && python run.py"
|
echo ". env/bin/activate && python build.py && python run.py"
|
||||||
|
164
build.py
164
build.py
@ -1,6 +1,4 @@
|
|||||||
# Created By: Virgil Dupras
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Created On: 2009-12-30
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# 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
|
# which should be included with this package. The terms are also available at
|
||||||
@ -11,7 +9,6 @@ import os
|
|||||||
import os.path as op
|
import os.path as op
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
import shutil
|
import shutil
|
||||||
import json
|
|
||||||
import importlib
|
import importlib
|
||||||
import compileall
|
import compileall
|
||||||
|
|
||||||
@ -22,10 +19,10 @@ from hscommon.build import (
|
|||||||
add_to_pythonpath, print_and_do, copy_packages, filereplace,
|
add_to_pythonpath, print_and_do, copy_packages, filereplace,
|
||||||
get_module_version, move_all, copy_all, OSXAppStructure,
|
get_module_version, move_all, copy_all, OSXAppStructure,
|
||||||
build_cocoalib_xibless, fix_qt_resource_file, build_cocoa_ext, copy_embeddable_python_dylib,
|
build_cocoalib_xibless, fix_qt_resource_file, build_cocoa_ext, copy_embeddable_python_dylib,
|
||||||
collect_stdlib_dependencies, copy
|
collect_stdlib_dependencies
|
||||||
)
|
)
|
||||||
from hscommon import loc
|
from hscommon import loc
|
||||||
from hscommon.plat import ISOSX, ISLINUX
|
from hscommon.plat import ISOSX
|
||||||
from hscommon.util import ensure_folder, delete_files_with_pattern
|
from hscommon.util import ensure_folder, delete_files_with_pattern
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
@ -39,6 +36,14 @@ def parse_args():
|
|||||||
'--doc', action='store_true', dest='doc',
|
'--doc', action='store_true', dest='doc',
|
||||||
help="Build only the help file"
|
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(
|
parser.add_option(
|
||||||
'--loc', action='store_true', dest='loc',
|
'--loc', action='store_true', dest='loc',
|
||||||
help="Build only localization"
|
help="Build only localization"
|
||||||
@ -70,15 +75,11 @@ def parse_args():
|
|||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def cocoa_app(edition):
|
def cocoa_app():
|
||||||
app_path = {
|
app_path = 'build/dupeGuru.app'
|
||||||
'se': 'build/dupeGuru.app',
|
|
||||||
'me': 'build/dupeGuru ME.app',
|
|
||||||
'pe': 'build/dupeGuru PE.app',
|
|
||||||
}[edition]
|
|
||||||
return OSXAppStructure(app_path)
|
return OSXAppStructure(app_path)
|
||||||
|
|
||||||
def build_xibless(edition, dest='cocoa/autogen'):
|
def build_xibless(dest='cocoa/autogen'):
|
||||||
import xibless
|
import xibless
|
||||||
ensure_folder(dest)
|
ensure_folder(dest)
|
||||||
FNPAIRS = [
|
FNPAIRS = [
|
||||||
@ -94,35 +95,36 @@ def build_xibless(edition, dest='cocoa/autogen'):
|
|||||||
for srcname, dstname in FNPAIRS:
|
for srcname, dstname in FNPAIRS:
|
||||||
xibless.generate(
|
xibless.generate(
|
||||||
op.join('cocoa', 'base', 'ui', srcname), op.join(dest, dstname),
|
op.join('cocoa', 'base', 'ui', srcname), op.join(dest, dstname),
|
||||||
localizationTable='Localizable', args={'edition': edition}
|
|
||||||
)
|
|
||||||
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'
|
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(edition, dev):
|
def build_cocoa(dev):
|
||||||
print("Creating OS X app structure")
|
print("Creating OS X app structure")
|
||||||
ed = lambda s: s.format(edition)
|
app = cocoa_app()
|
||||||
app = cocoa_app(edition)
|
app_version = get_module_version('core_se')
|
||||||
app_version = get_module_version(ed('core_{}'))
|
cocoa_project_path = 'cocoa/se'
|
||||||
cocoa_project_path = ed('cocoa/{}')
|
|
||||||
filereplace(op.join(cocoa_project_path, 'InfoTemplate.plist'), op.join('build', 'Info.plist'), version=app_version)
|
filereplace(op.join(cocoa_project_path, 'InfoTemplate.plist'), op.join('build', 'Info.plist'), version=app_version)
|
||||||
app.create(op.join('build', 'Info.plist'))
|
app.create(op.join('build', 'Info.plist'))
|
||||||
print("Building localizations")
|
print("Building localizations")
|
||||||
build_localizations('cocoa', edition)
|
build_localizations('cocoa')
|
||||||
print("Building xibless UIs")
|
print("Building xibless UIs")
|
||||||
build_cocoalib_xibless()
|
build_cocoalib_xibless()
|
||||||
build_xibless(edition)
|
build_xibless()
|
||||||
print("Building Python extensions")
|
print("Building Python extensions")
|
||||||
build_cocoa_proxy_module()
|
build_cocoa_proxy_module()
|
||||||
build_cocoa_bridging_interfaces(edition)
|
build_cocoa_bridging_interfaces()
|
||||||
print("Building the cocoa layer")
|
print("Building the cocoa layer")
|
||||||
copy_embeddable_python_dylib('build')
|
copy_embeddable_python_dylib('build')
|
||||||
pydep_folder = op.join(app.resources, 'py')
|
pydep_folder = op.join(app.resources, 'py')
|
||||||
@ -130,21 +132,15 @@ def build_cocoa(edition, dev):
|
|||||||
os.mkdir(pydep_folder)
|
os.mkdir(pydep_folder)
|
||||||
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
|
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
|
||||||
appscript_pkgs = ['appscript', 'aem', 'mactypes', 'osax']
|
appscript_pkgs = ['appscript', 'aem', 'mactypes', 'osax']
|
||||||
specific_packages = {
|
|
||||||
'se': ['core_se'],
|
|
||||||
'me': ['core_me'] + appscript_pkgs + ['hsaudiotag'],
|
|
||||||
'pe': ['core_pe'] + appscript_pkgs,
|
|
||||||
}[edition]
|
|
||||||
tocopy = [
|
tocopy = [
|
||||||
'core', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa', 'objp', 'send2trash'
|
'core', 'core_se', 'core_me', 'core_pe', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa',
|
||||||
] + specific_packages
|
'objp', 'send2trash', 'hsaudiotag',
|
||||||
|
] + appscript_pkgs
|
||||||
copy_packages(tocopy, pydep_folder, create_links=dev)
|
copy_packages(tocopy, pydep_folder, create_links=dev)
|
||||||
sys.path.insert(0, 'build')
|
sys.path.insert(0, 'build')
|
||||||
extra_deps = None
|
# ModuleFinder can't seem to correctly detect the multiprocessing dependency, so we have
|
||||||
if edition == 'pe':
|
# to manually specify it.
|
||||||
# ModuleFinder can't seem to correctly detect the multiprocessing dependency, so we have
|
extra_deps = ['multiprocessing']
|
||||||
# to manually specify it.
|
|
||||||
extra_deps = ['multiprocessing']
|
|
||||||
collect_stdlib_dependencies('build/dg_cocoa.py', pydep_folder, extra_deps=extra_deps)
|
collect_stdlib_dependencies('build/dg_cocoa.py', pydep_folder, extra_deps=extra_deps)
|
||||||
del sys.path[0]
|
del sys.path[0]
|
||||||
# Views are not referenced by python code, so they're not found by the collector.
|
# Views are not referenced by python code, so they're not found by the collector.
|
||||||
@ -157,12 +153,12 @@ def build_cocoa(edition, dev):
|
|||||||
delete_files_with_pattern(pydep_folder, '__pycache__')
|
delete_files_with_pattern(pydep_folder, '__pycache__')
|
||||||
print("Compiling with WAF")
|
print("Compiling with WAF")
|
||||||
os.chdir('cocoa')
|
os.chdir('cocoa')
|
||||||
print_and_do('{0} waf configure --edition {1} && {0} waf'.format(sys.executable, edition))
|
print_and_do('{0} waf configure && {0} waf'.format(sys.executable))
|
||||||
os.chdir('..')
|
os.chdir('..')
|
||||||
app.copy_executable('cocoa/build/dupeGuru')
|
app.copy_executable('cocoa/build/dupeGuru')
|
||||||
build_help(edition)
|
build_help()
|
||||||
print("Copying resources and frameworks")
|
print("Copying resources and frameworks")
|
||||||
image_path = ed('cocoa/{}/dupeguru.icns')
|
image_path = 'cocoa/se/dupeguru.icns'
|
||||||
resources = [image_path, 'cocoa/base/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help']
|
resources = [image_path, 'cocoa/base/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help']
|
||||||
app.copy_resources(*resources, use_symlinks=dev)
|
app.copy_resources(*resources, use_symlinks=dev)
|
||||||
app.copy_frameworks('build/Python', 'cocoalib/Sparkle.framework')
|
app.copy_frameworks('build/Python', 'cocoalib/Sparkle.framework')
|
||||||
@ -171,26 +167,26 @@ def build_cocoa(edition, dev):
|
|||||||
run_contents = tmpl.replace('{{app_path}}', app.dest)
|
run_contents = tmpl.replace('{{app_path}}', app.dest)
|
||||||
open('run.py', 'wt').write(run_contents)
|
open('run.py', 'wt').write(run_contents)
|
||||||
|
|
||||||
def build_qt(edition, dev, conf):
|
def build_qt(dev):
|
||||||
print("Building localizations")
|
print("Building localizations")
|
||||||
build_localizations('qt', edition)
|
build_localizations('qt')
|
||||||
print("Building Qt stuff")
|
print("Building Qt stuff")
|
||||||
print_and_do("pyrcc5 {0} > {1}".format(op.join('qt', 'base', 'dg.qrc'), op.join('qt', 'base', 'dg_rc.py')))
|
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'))
|
fix_qt_resource_file(op.join('qt', 'base', 'dg_rc.py'))
|
||||||
build_help(edition)
|
build_help()
|
||||||
print("Creating the run.py file")
|
print("Creating the run.py file")
|
||||||
filereplace(op.join('qt', 'run_template.py'), 'run.py', edition=edition)
|
shutil.copy(op.join('qt', 'run_template.py'), 'run.py')
|
||||||
|
|
||||||
def build_help(edition):
|
def build_help():
|
||||||
print("Generating Help")
|
print("Generating Help")
|
||||||
current_path = op.abspath('.')
|
current_path = op.abspath('.')
|
||||||
help_basepath = op.join(current_path, 'help', 'en')
|
help_basepath = op.join(current_path, 'help', 'en')
|
||||||
help_destpath = op.join(current_path, 'build', 'help'.format(edition))
|
help_destpath = op.join(current_path, 'build', 'help')
|
||||||
changelog_path = op.join(current_path, 'help', 'changelog_{}'.format(edition))
|
changelog_path = op.join(current_path, 'help', 'changelog_se')
|
||||||
tixurl = "https://github.com/hsoft/dupeguru/issues/{}"
|
tixurl = "https://github.com/hsoft/dupeguru/issues/{}"
|
||||||
appname = {'se': 'dupeGuru', 'me': 'dupeGuru Music Edition', 'pe': 'dupeGuru Picture Edition'}[edition]
|
appname = 'dupeGuru'
|
||||||
homepage = 'http://www.hardcoded.net/dupeguru{}/'.format('_' + edition if edition != 'se' else '')
|
homepage = 'https://www.hardcoded.net/dupeguru/'
|
||||||
confrepl = {'edition': edition, 'appname': appname, 'homepage': homepage, 'language': 'en'}
|
confrepl = {'appname': appname, 'homepage': homepage, 'language': 'en'}
|
||||||
changelogtmpl = op.join(current_path, 'help', 'changelog.tmpl')
|
changelogtmpl = op.join(current_path, 'help', 'changelog.tmpl')
|
||||||
conftmpl = op.join(current_path, 'help', 'conf.tmpl')
|
conftmpl = op.join(current_path, 'help', 'conf.tmpl')
|
||||||
sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl, conftmpl, changelogtmpl)
|
sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl, conftmpl, changelogtmpl)
|
||||||
@ -199,10 +195,10 @@ def build_qt_localizations():
|
|||||||
loc.compile_all_po(op.join('qtlib', 'locale'))
|
loc.compile_all_po(op.join('qtlib', 'locale'))
|
||||||
loc.merge_locale_dir(op.join('qtlib', 'locale'), 'locale')
|
loc.merge_locale_dir(op.join('qtlib', 'locale'), 'locale')
|
||||||
|
|
||||||
def build_localizations(ui, edition):
|
def build_localizations(ui):
|
||||||
loc.compile_all_po('locale')
|
loc.compile_all_po('locale')
|
||||||
if ui == 'cocoa':
|
if ui == 'cocoa':
|
||||||
app = cocoa_app(edition)
|
app = cocoa_app()
|
||||||
loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'))
|
loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'))
|
||||||
locale_dest = op.join(app.resources, 'locale')
|
locale_dest = op.join(app.resources, 'locale')
|
||||||
elif ui == 'qt':
|
elif ui == 'qt':
|
||||||
@ -211,25 +207,13 @@ def build_localizations(ui, edition):
|
|||||||
if op.exists(locale_dest):
|
if op.exists(locale_dest):
|
||||||
shutil.rmtree(locale_dest)
|
shutil.rmtree(locale_dest)
|
||||||
shutil.copytree('locale', locale_dest, ignore=shutil.ignore_patterns('*.po', '*.pot'))
|
shutil.copytree('locale', locale_dest, ignore=shutil.ignore_patterns('*.po', '*.pot'))
|
||||||
if ui == 'qt' and not ISLINUX:
|
|
||||||
print("Copying qt_*.qm files into the 'locale' folder")
|
|
||||||
from PyQt5.QtCore import QLibraryInfo
|
|
||||||
trfolder = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
|
|
||||||
for lang in loc.get_langs('locale'):
|
|
||||||
qmname = 'qt_%s.qm' % lang
|
|
||||||
src = op.join(trfolder, qmname)
|
|
||||||
if op.exists(src):
|
|
||||||
copy(src, op.join('build', 'locale', qmname))
|
|
||||||
|
|
||||||
def build_updatepot():
|
def build_updatepot():
|
||||||
if ISOSX:
|
if ISOSX:
|
||||||
print("Updating Cocoa strings file.")
|
print("Updating Cocoa strings file.")
|
||||||
# We need to have strings from *all* editions in here, so we'll call xibless for all editions
|
|
||||||
# in dummy subfolders.
|
|
||||||
build_cocoalib_xibless('cocoalib/autogen')
|
build_cocoalib_xibless('cocoalib/autogen')
|
||||||
loc.generate_cocoa_strings_from_code('cocoalib', 'cocoalib/en.lproj')
|
loc.generate_cocoa_strings_from_code('cocoalib', 'cocoalib/en.lproj')
|
||||||
for edition in ('se', 'me', 'pe'):
|
build_xibless('se', op.join('cocoa', 'autogen', 'se'))
|
||||||
build_xibless(edition, op.join('cocoa', 'autogen', edition))
|
|
||||||
loc.generate_cocoa_strings_from_code('cocoa', 'cocoa/base/en.lproj')
|
loc.generate_cocoa_strings_from_code('cocoa', 'cocoa/base/en.lproj')
|
||||||
print("Building .pot files from source files")
|
print("Building .pot files from source files")
|
||||||
print("Building core.pot")
|
print("Building core.pot")
|
||||||
@ -280,7 +264,7 @@ def build_cocoa_proxy_module():
|
|||||||
['cocoalib', 'cocoa/autogen']
|
['cocoalib', 'cocoa/autogen']
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_cocoa_bridging_interfaces(edition):
|
def build_cocoa_bridging_interfaces():
|
||||||
print("Building Cocoa Bridging Interfaces")
|
print("Building Cocoa Bridging Interfaces")
|
||||||
import objp.o2p
|
import objp.o2p
|
||||||
import objp.p2o
|
import objp.p2o
|
||||||
@ -301,7 +285,7 @@ def build_cocoa_bridging_interfaces(edition):
|
|||||||
from inter.result_table import PyResultTable, ResultTableView
|
from inter.result_table import PyResultTable, ResultTableView
|
||||||
from inter.stats_label import PyStatsLabel, StatsLabelView
|
from inter.stats_label import PyStatsLabel, StatsLabelView
|
||||||
from inter.app import PyDupeGuruBase, DupeGuruView
|
from inter.app import PyDupeGuruBase, DupeGuruView
|
||||||
appmod = importlib.import_module('inter.app_{}'.format(edition))
|
appmod = importlib.import_module('inter.app_se')
|
||||||
allclasses = [
|
allclasses = [
|
||||||
PyGUIObject, PyColumns, PyOutline, PySelectableList, PyTable, PyBaseApp,
|
PyGUIObject, PyColumns, PyOutline, PySelectableList, PyTable, PyBaseApp,
|
||||||
PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog,
|
PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog,
|
||||||
@ -345,24 +329,22 @@ def build_pe_modules(ui):
|
|||||||
move_all('_block*', 'core_pe')
|
move_all('_block*', 'core_pe')
|
||||||
move_all('_cache*', 'core_pe')
|
move_all('_cache*', 'core_pe')
|
||||||
|
|
||||||
def build_normal(edition, ui, dev, conf):
|
def build_normal(ui, dev):
|
||||||
print("Building dupeGuru {0} with UI {1}".format(edition.upper(), ui))
|
print("Building dupeGuru with UI {}".format(ui))
|
||||||
add_to_pythonpath('.')
|
add_to_pythonpath('.')
|
||||||
print("Building dupeGuru")
|
print("Building dupeGuru")
|
||||||
if edition == 'pe':
|
build_pe_modules(ui)
|
||||||
build_pe_modules(ui)
|
|
||||||
if ui == 'cocoa':
|
if ui == 'cocoa':
|
||||||
build_cocoa(edition, dev)
|
build_cocoa(dev)
|
||||||
elif ui == 'qt':
|
elif ui == 'qt':
|
||||||
build_qt(edition, dev, conf)
|
build_qt(dev)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
options = parse_args()
|
options = parse_args()
|
||||||
conf = json.load(open('conf.json'))
|
ui = options.ui
|
||||||
edition = conf['edition']
|
if ui not in ('cocoa', 'qt'):
|
||||||
ui = conf['ui']
|
ui = 'cocoa' if ISOSX else 'qt'
|
||||||
dev = conf['dev']
|
if options.dev:
|
||||||
if dev:
|
|
||||||
print("Building in Dev mode")
|
print("Building in Dev mode")
|
||||||
if options.clean:
|
if options.clean:
|
||||||
for path in ['build', op.join('cocoa', 'build'), op.join('cocoa', 'autogen')]:
|
for path in ['build', op.join('cocoa', 'build'), op.join('cocoa', 'autogen')]:
|
||||||
@ -371,9 +353,9 @@ def main():
|
|||||||
if not op.exists('build'):
|
if not op.exists('build'):
|
||||||
os.mkdir('build')
|
os.mkdir('build')
|
||||||
if options.doc:
|
if options.doc:
|
||||||
build_help(edition)
|
build_help()
|
||||||
elif options.loc:
|
elif options.loc:
|
||||||
build_localizations(ui, edition)
|
build_localizations(ui)
|
||||||
elif options.updatepot:
|
elif options.updatepot:
|
||||||
build_updatepot()
|
build_updatepot()
|
||||||
elif options.mergepot:
|
elif options.mergepot:
|
||||||
@ -382,17 +364,17 @@ def main():
|
|||||||
build_normpo()
|
build_normpo()
|
||||||
elif options.cocoa_ext:
|
elif options.cocoa_ext:
|
||||||
build_cocoa_proxy_module()
|
build_cocoa_proxy_module()
|
||||||
build_cocoa_bridging_interfaces(edition)
|
build_cocoa_bridging_interfaces()
|
||||||
elif options.cocoa_compile:
|
elif options.cocoa_compile:
|
||||||
os.chdir('cocoa')
|
os.chdir('cocoa')
|
||||||
print_and_do('{0} waf configure --edition {1} && {0} waf'.format(sys.executable, edition))
|
print_and_do('{0} waf configure && {0} waf'.format(sys.executable))
|
||||||
os.chdir('..')
|
os.chdir('..')
|
||||||
cocoa_app(edition).copy_executable('cocoa/build/dupeGuru')
|
cocoa_app().copy_executable('cocoa/build/dupeGuru')
|
||||||
elif options.xibless:
|
elif options.xibless:
|
||||||
build_cocoalib_xibless()
|
build_cocoalib_xibless()
|
||||||
build_xibless(edition)
|
build_xibless()
|
||||||
else:
|
else:
|
||||||
build_normal(edition, ui, dev, conf)
|
build_normal(ui, options.dev)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
45
configure.py
45
configure.py
@ -1,45 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-12-30
|
|
||||||
# Copyright 2015 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
|
|
||||||
|
|
||||||
from optparse import OptionParser
|
|
||||||
import json
|
|
||||||
|
|
||||||
from hscommon.plat import ISOSX
|
|
||||||
|
|
||||||
def main(options):
|
|
||||||
if options.edition not in {'se', 'me', 'pe'}:
|
|
||||||
options.edition = 'se'
|
|
||||||
if options.ui not in {'cocoa', 'qt'}:
|
|
||||||
options.ui = 'cocoa' if ISOSX else 'qt'
|
|
||||||
build_type = 'Dev' if options.dev else 'Release'
|
|
||||||
print("Configuring dupeGuru {0} for UI {1} ({2})".format(options.edition.upper(), options.ui, build_type))
|
|
||||||
conf = {
|
|
||||||
'edition': options.edition,
|
|
||||||
'ui': options.ui,
|
|
||||||
'dev': options.dev,
|
|
||||||
}
|
|
||||||
json.dump(conf, open('conf.json', 'w'))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
usage = "usage: %prog [options]"
|
|
||||||
parser = OptionParser(usage=usage)
|
|
||||||
parser.add_option(
|
|
||||||
'--edition', dest='edition',
|
|
||||||
help="dupeGuru edition to build (se, me or pe). Default is se."
|
|
||||||
)
|
|
||||||
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."
|
|
||||||
)
|
|
||||||
(options, args) = parser.parse_args()
|
|
||||||
main(options)
|
|
||||||
|
|
190
package.py
190
package.py
@ -1,6 +1,4 @@
|
|||||||
# Created By: Virgil Dupras
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Created On: 2009-12-30
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# 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
|
# which should be included with this package. The terms are also available at
|
||||||
@ -13,13 +11,12 @@ import shutil
|
|||||||
import json
|
import json
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
import platform
|
import platform
|
||||||
import glob
|
|
||||||
|
|
||||||
from hscommon.plat import ISWINDOWS, ISLINUX
|
from hscommon.plat import ISOSX
|
||||||
from hscommon.build import (
|
from hscommon.build import (
|
||||||
add_to_pythonpath, print_and_do, copy_packages, build_debian_changelog,
|
print_and_do, copy_packages, build_debian_changelog,
|
||||||
copy_qt_plugins, get_module_version, filereplace, copy, setup_package_argparser,
|
get_module_version, filereplace, copy, setup_package_argparser,
|
||||||
package_cocoa_app_in_dmg, copy_all, find_in_path
|
package_cocoa_app_in_dmg, copy_all
|
||||||
)
|
)
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
@ -27,90 +24,10 @@ def parse_args():
|
|||||||
setup_package_argparser(parser)
|
setup_package_argparser(parser)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
def package_cocoa(edition, args):
|
def package_cocoa(args):
|
||||||
app_path = {
|
app_path = 'build/dupeGuru.app'
|
||||||
'se': 'build/dupeGuru.app',
|
|
||||||
'me': 'build/dupeGuru ME.app',
|
|
||||||
'pe': 'build/dupeGuru PE.app',
|
|
||||||
}[edition]
|
|
||||||
package_cocoa_app_in_dmg(app_path, '.', args)
|
package_cocoa_app_in_dmg(app_path, '.', args)
|
||||||
|
|
||||||
def package_windows(edition, dev):
|
|
||||||
if not ISWINDOWS:
|
|
||||||
print("Qt packaging only works under Windows.")
|
|
||||||
return
|
|
||||||
from cx_Freeze import setup, Executable
|
|
||||||
from PyQt5.QtCore import QLibraryInfo
|
|
||||||
add_to_pythonpath('.')
|
|
||||||
app_version = get_module_version('core_{}'.format(edition))
|
|
||||||
distdir = 'dist'
|
|
||||||
|
|
||||||
if op.exists(distdir):
|
|
||||||
shutil.rmtree(distdir)
|
|
||||||
|
|
||||||
if not dev:
|
|
||||||
# Copy qt plugins
|
|
||||||
plugin_dest = distdir
|
|
||||||
plugin_names = ['accessible', 'codecs', 'iconengines', 'imageformats']
|
|
||||||
copy_qt_plugins(plugin_names, plugin_dest)
|
|
||||||
|
|
||||||
# Since v4.2.3, cx_freeze started to falsely include tkinter in the package. We exclude it
|
|
||||||
# explicitly because of that.
|
|
||||||
options = {
|
|
||||||
'build_exe': {
|
|
||||||
'includes': 'atexit',
|
|
||||||
'excludes': ['tkinter'],
|
|
||||||
'bin_excludes': ['icudt51', 'icuin51.dll', 'icuuc51.dll'],
|
|
||||||
'icon': 'images\\dg{0}_logo.ico'.format(edition),
|
|
||||||
'include_msvcr': True,
|
|
||||||
},
|
|
||||||
'install_exe': {
|
|
||||||
'install_dir': 'dist',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executables = [
|
|
||||||
Executable(
|
|
||||||
'run.py',
|
|
||||||
base='Win32GUI',
|
|
||||||
targetDir=distdir,
|
|
||||||
targetName={'se': 'dupeGuru', 'me': 'dupeGuru ME', 'pe': 'dupeGuru PE'}[edition] + '.exe',
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
setup(
|
|
||||||
script_args=['install'],
|
|
||||||
options=options,
|
|
||||||
executables=executables
|
|
||||||
)
|
|
||||||
|
|
||||||
print("Removing useless files")
|
|
||||||
# Debug info that cx_freeze brings in.
|
|
||||||
for fn in glob.glob(op.join(distdir, '*', '*.pdb')):
|
|
||||||
os.remove(fn)
|
|
||||||
print("Copying forgotten DLLs")
|
|
||||||
qtlibpath = QLibraryInfo.location(QLibraryInfo.LibrariesPath)
|
|
||||||
shutil.copy(op.join(qtlibpath, 'libEGL.dll'), distdir)
|
|
||||||
shutil.copy(find_in_path('msvcp110.dll'), distdir)
|
|
||||||
print("Copying the rest")
|
|
||||||
help_path = op.join('build', 'help')
|
|
||||||
print("Copying {} to dist\\help".format(help_path))
|
|
||||||
shutil.copytree(help_path, op.join(distdir, 'help'))
|
|
||||||
locale_path = op.join('build', 'locale')
|
|
||||||
print("Copying {} to dist\\locale".format(locale_path))
|
|
||||||
shutil.copytree(locale_path, op.join(distdir, 'locale'))
|
|
||||||
|
|
||||||
# AdvancedInstaller.com has to be in your PATH
|
|
||||||
# this is so we don'a have to re-commit installer.aip at every version change
|
|
||||||
installer_file = 'installer.aip'
|
|
||||||
installer_path = op.join('qt', edition, installer_file)
|
|
||||||
shutil.copy(installer_path, 'installer_tmp.aip')
|
|
||||||
print_and_do('AdvancedInstaller.com /edit installer_tmp.aip /SetVersion %s' % app_version)
|
|
||||||
print_and_do('AdvancedInstaller.com /build installer_tmp.aip -force')
|
|
||||||
os.remove('installer_tmp.aip')
|
|
||||||
if op.exists('installer_tmp.back.aip'):
|
|
||||||
os.remove('installer_tmp.back.aip')
|
|
||||||
|
|
||||||
def copy_files_to_package(destpath, packages, with_so):
|
def copy_files_to_package(destpath, packages, with_so):
|
||||||
# when with_so is true, we keep .so files in the package, and otherwise, we don't. We need this
|
# when with_so is true, we keep .so files in the package, and otherwise, we don't. We need this
|
||||||
# flag because when building debian src pkg, we *don't* want .so files (they're compiled later)
|
# flag because when building debian src pkg, we *don't* want .so files (they're compiled later)
|
||||||
@ -126,71 +43,70 @@ def copy_files_to_package(destpath, packages, with_so):
|
|||||||
shutil.copytree(op.join('build', 'locale'), op.join(destpath, 'locale'))
|
shutil.copytree(op.join('build', 'locale'), op.join(destpath, 'locale'))
|
||||||
compileall.compile_dir(destpath)
|
compileall.compile_dir(destpath)
|
||||||
|
|
||||||
def package_debian_distribution(edition, distribution):
|
def package_debian_distribution(distribution):
|
||||||
app_version = get_module_version('core_{}'.format(edition))
|
app_version = get_module_version('core_se')
|
||||||
version = '{}~{}'.format(app_version, distribution)
|
version = '{}~{}'.format(app_version, distribution)
|
||||||
ed = lambda s: s.format(edition)
|
destpath = op.join('build', 'dupeguru-{}'.format(version))
|
||||||
destpath = op.join('build', 'dupeguru-{0}-{1}'.format(edition, version))
|
|
||||||
srcpath = op.join(destpath, 'src')
|
srcpath = op.join(destpath, 'src')
|
||||||
packages = ['hscommon', 'core', ed('core_{0}'), 'qtlib', 'qt', 'send2trash']
|
packages = [
|
||||||
if edition == 'me':
|
'hscommon', 'core', 'core_se', 'core_me', 'core_pe', 'qtlib', 'qt', 'send2trash',
|
||||||
packages.append('hsaudiotag')
|
'hsaudiotag'
|
||||||
|
]
|
||||||
copy_files_to_package(srcpath, packages, with_so=False)
|
copy_files_to_package(srcpath, packages, with_so=False)
|
||||||
if edition == 'pe':
|
os.mkdir(op.join(destpath, 'modules'))
|
||||||
os.mkdir(op.join(destpath, 'modules'))
|
copy_all(op.join('core_pe', 'modules', '*.*'), op.join(destpath, 'modules'))
|
||||||
copy_all(op.join('core_pe', 'modules', '*.*'), op.join(destpath, 'modules'))
|
copy(op.join('qt', 'pe', 'modules', 'block.c'), op.join(destpath, 'modules', 'block_qt.c'))
|
||||||
copy(op.join('qt', 'pe', 'modules', 'block.c'), op.join(destpath, 'modules', 'block_qt.c'))
|
copy(op.join('pkg', 'debian', 'build_pe_modules.py'), op.join(destpath, 'build_pe_modules.py'))
|
||||||
copy(op.join('pkg', 'debian', 'build_pe_modules.py'), op.join(destpath, 'build_pe_modules.py'))
|
|
||||||
debdest = op.join(destpath, 'debian')
|
debdest = op.join(destpath, 'debian')
|
||||||
debskel = op.join('pkg', 'debian')
|
debskel = op.join('pkg', 'debian')
|
||||||
os.makedirs(debdest)
|
os.makedirs(debdest)
|
||||||
debopts = json.load(open(op.join(debskel, ed('{}.json'))))
|
debopts = json.load(open(op.join(debskel, 'se.json')))
|
||||||
for fn in ['compat', 'copyright', 'dirs', 'rules']:
|
for fn in ['compat', 'copyright', 'dirs', 'rules']:
|
||||||
copy(op.join(debskel, fn), op.join(debdest, fn))
|
copy(op.join(debskel, fn), op.join(debdest, fn))
|
||||||
filereplace(op.join(debskel, 'control'), op.join(debdest, 'control'), **debopts)
|
filereplace(op.join(debskel, 'control'), op.join(debdest, 'control'), **debopts)
|
||||||
filereplace(op.join(debskel, 'Makefile'), op.join(destpath, 'Makefile'), **debopts)
|
filereplace(op.join(debskel, 'Makefile'), op.join(destpath, 'Makefile'), **debopts)
|
||||||
filereplace(op.join(debskel, 'dupeguru.desktop'), op.join(debdest, ed('dupeguru_{}.desktop')), **debopts)
|
filereplace(op.join(debskel, 'dupeguru.desktop'), op.join(debdest, 'dupeguru.desktop'), **debopts)
|
||||||
changelogpath = op.join('help', ed('changelog_{}'))
|
changelogpath = op.join('help', 'changelog_se')
|
||||||
changelog_dest = op.join(debdest, 'changelog')
|
changelog_dest = op.join(debdest, 'changelog')
|
||||||
project_name = debopts['pkgname']
|
project_name = debopts['pkgname']
|
||||||
from_version = {'se': '2.9.2', 'me': '5.7.2', 'pe': '1.8.5'}[edition]
|
from_version = '2.9.2'
|
||||||
build_debian_changelog(
|
build_debian_changelog(
|
||||||
changelogpath, changelog_dest, project_name, from_version=from_version,
|
changelogpath, changelog_dest, project_name, from_version=from_version,
|
||||||
distribution=distribution
|
distribution=distribution
|
||||||
)
|
)
|
||||||
shutil.copy(op.join('images', ed('dg{0}_logo_128.png')), srcpath)
|
shutil.copy(op.join('images', 'dgse_logo_128.png'), srcpath)
|
||||||
os.chdir(destpath)
|
os.chdir(destpath)
|
||||||
cmd = "dpkg-buildpackage -S"
|
cmd = "dpkg-buildpackage -S"
|
||||||
os.system(cmd)
|
os.system(cmd)
|
||||||
os.chdir('../..')
|
os.chdir('../..')
|
||||||
|
|
||||||
def package_debian(edition):
|
def package_debian():
|
||||||
print("Packaging for Ubuntu")
|
print("Packaging for Ubuntu")
|
||||||
for distribution in ['trusty', 'utopic']:
|
for distribution in ['trusty', 'utopic']:
|
||||||
package_debian_distribution(edition, distribution)
|
package_debian_distribution(distribution)
|
||||||
|
|
||||||
def package_arch(edition):
|
def package_arch():
|
||||||
# For now, package_arch() will only copy the source files into build/. It copies less packages
|
# For now, package_arch() will only copy the source files into build/. It copies less packages
|
||||||
# than package_debian because there are more python packages available in Arch (so we don't
|
# than package_debian because there are more python packages available in Arch (so we don't
|
||||||
# need to include them).
|
# need to include them).
|
||||||
print("Packaging for Arch")
|
print("Packaging for Arch")
|
||||||
ed = lambda s: s.format(edition)
|
srcpath = op.join('build', 'dupeguru-arch')
|
||||||
srcpath = op.join('build', ed('dupeguru-{}-arch'))
|
packages = [
|
||||||
packages = ['hscommon', 'core', ed('core_{0}'), 'qtlib', 'qt', 'send2trash']
|
'hscommon', 'core', 'core_se', 'core_me', 'core_pe', 'qtlib', 'qt', 'send2trash',
|
||||||
if edition == 'me':
|
'hsaudiotag',
|
||||||
packages.append('hsaudiotag')
|
]
|
||||||
copy_files_to_package(srcpath, packages, with_so=True)
|
copy_files_to_package(srcpath, packages, with_so=True)
|
||||||
shutil.copy(op.join('images', ed('dg{}_logo_128.png')), srcpath)
|
shutil.copy(op.join('images', 'dgse_logo_128.png'), srcpath)
|
||||||
debopts = json.load(open(op.join('pkg', 'arch', ed('{}.json'))))
|
debopts = json.load(open(op.join('pkg', 'arch', 'se.json')))
|
||||||
filereplace(op.join('pkg', 'arch', 'dupeguru.desktop'), op.join(srcpath, ed('dupeguru-{}.desktop')), **debopts)
|
filereplace(op.join('pkg', 'arch', 'dupeguru.desktop'), op.join(srcpath, 'dupeguru.desktop'), **debopts)
|
||||||
|
|
||||||
def package_source_tgz(edition):
|
def package_source_tgz():
|
||||||
if not op.exists('deps'):
|
if not op.exists('deps'):
|
||||||
print("Downloading PyPI dependencies")
|
print("Downloading PyPI dependencies")
|
||||||
print_and_do('./download_deps.sh')
|
print_and_do('./download_deps.sh')
|
||||||
print("Creating git archive")
|
print("Creating git archive")
|
||||||
app_version = get_module_version('core_{}'.format(edition))
|
app_version = get_module_version('core_se')
|
||||||
name = 'dupeguru-{}-src-{}.tar'.format(edition, app_version)
|
name = 'dupeguru-src-{}.tar'.format(app_version)
|
||||||
dest = op.join('build', name)
|
dest = op.join('build', name)
|
||||||
print_and_do('git archive -o {} HEAD'.format(dest))
|
print_and_do('git archive -o {} HEAD'.format(dest))
|
||||||
print("Adding dependencies and wrapping up")
|
print("Adding dependencies and wrapping up")
|
||||||
@ -199,31 +115,23 @@ def package_source_tgz(edition):
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
conf = json.load(open('conf.json'))
|
ui = 'cocoa' if ISOSX else 'qt'
|
||||||
edition = conf['edition']
|
|
||||||
ui = conf['ui']
|
|
||||||
dev = conf['dev']
|
|
||||||
if args.src_pkg:
|
if args.src_pkg:
|
||||||
print("Creating source package for dupeGuru {}".format(edition.upper()))
|
print("Creating source package for dupeGuru")
|
||||||
package_source_tgz(edition)
|
package_source_tgz()
|
||||||
return
|
return
|
||||||
print("Packaging dupeGuru {0} with UI {1}".format(edition.upper(), ui))
|
print("Packaging dupeGuru with UI {}".format(ui))
|
||||||
if ui == 'cocoa':
|
if ui == 'cocoa':
|
||||||
package_cocoa(edition, args)
|
package_cocoa(args)
|
||||||
elif ui == 'qt':
|
elif ui == 'qt':
|
||||||
if ISWINDOWS:
|
if not args.arch_pkg:
|
||||||
package_windows(edition, dev)
|
distname, _, _ = platform.dist()
|
||||||
elif ISLINUX:
|
|
||||||
if not args.arch_pkg:
|
|
||||||
distname, _, _ = platform.dist()
|
|
||||||
else:
|
|
||||||
distname = 'arch'
|
|
||||||
if distname == 'arch':
|
|
||||||
package_arch(edition)
|
|
||||||
else:
|
|
||||||
package_debian(edition)
|
|
||||||
else:
|
else:
|
||||||
print("Qt packaging only works under Windows or Linux.")
|
distname = 'arch'
|
||||||
|
if distname == 'arch':
|
||||||
|
package_arch()
|
||||||
|
else:
|
||||||
|
package_debian()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# 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
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@ -13,16 +13,12 @@ from PyQt5.QtCore import QCoreApplication, QSettings
|
|||||||
from PyQt5.QtGui import QIcon, QPixmap
|
from PyQt5.QtGui import QIcon, QPixmap
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
from hscommon.plat import ISWINDOWS
|
|
||||||
from hscommon.trans import install_gettext_trans_under_qt
|
from hscommon.trans import install_gettext_trans_under_qt
|
||||||
from qtlib.error_report_dialog import install_excepthook
|
from qtlib.error_report_dialog import install_excepthook
|
||||||
from qtlib.util import setupQtLogging
|
from qtlib.util import setupQtLogging
|
||||||
from qt.base import dg_rc
|
from qt.base import dg_rc
|
||||||
from qt.base.platform import BASE_PATH
|
from qt.base.platform import BASE_PATH
|
||||||
from core_{edition} import __version__, __appname__
|
from core_se import __version__, __appname__
|
||||||
|
|
||||||
if ISWINDOWS:
|
|
||||||
import qt.base.cxfreeze_fix
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
@ -36,7 +32,7 @@ def main():
|
|||||||
install_gettext_trans_under_qt(locale_folder, lang)
|
install_gettext_trans_under_qt(locale_folder, lang)
|
||||||
# Many strings are translated at import time, so this is why we only import after the translator
|
# Many strings are translated at import time, so this is why we only import after the translator
|
||||||
# has been installed
|
# has been installed
|
||||||
from qt.{edition}.app import DupeGuru
|
from qt.se.app import DupeGuru
|
||||||
app.setWindowIcon(QIcon(QPixmap(":/{0}".format(DupeGuru.LOGO_NAME))))
|
app.setWindowIcon(QIcon(QPixmap(":/{0}".format(DupeGuru.LOGO_NAME))))
|
||||||
dgapp = DupeGuru()
|
dgapp = DupeGuru()
|
||||||
install_excepthook('https://github.com/hsoft/dupeguru/issues')
|
install_excepthook('https://github.com/hsoft/dupeguru/issues')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user