#!/usr/bin/env python # -*- coding: utf-8 -*- # # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2012,2020 Nick Hall # Copyright (C) 2012 Rob G. Healey # Copyright (C) 2012 Benny Malengier # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # """ Gramps setuptools module. """ # check python version first import sys if sys.version_info < (3, 8): raise SystemExit("Gramps requires Python 3.8 or later.") from setuptools import setup, Command try: from setuptools.command.build import build as _build except ImportError: from distutils.command.build import build as _build import os import glob import codecs import subprocess from stat import ST_MODE from gramps.version import VERSION import unittest import argparse import logging _LOG = logging.getLogger(".setup") svem_flag = "--single-version-externally-managed" if svem_flag in sys.argv: # Die, setuptools, die. sys.argv.remove(svem_flag) argparser = argparse.ArgumentParser(add_help=False) argparser.add_argument( "--no-compress-manpages", dest="no_compress_manpages", action="store_true" ) args, passthrough = argparser.parse_known_args() sys.argv = [sys.argv[0]] + passthrough def newer(source, target): """ Determines if a target file needs to be rebuilt. Returns True if the target file doesn't exist or if the source file is newer than the target file. """ if not os.path.exists(target): return True return os.path.getmtime(source) > os.path.getmtime(target) def substitute_variables(filename_in, filename_out, subst_vars): """ Substitute variables in a file. """ f_in = codecs.open(filename_in, encoding="utf-8") f_out = codecs.open(filename_out, encoding="utf-8", mode="w") for line in f_in: for variable, substitution in subst_vars: line = line.replace(variable, substitution) f_out.write(line) f_in.close() f_out.close() def get_linguas(): """ Read the po/LINGUAS file to get a list of all linguas. """ all_linguas = [] with open(os.path.join("po", "LINGUAS"), "r", encoding="utf-8") as linguas: for line in linguas: if "#" in line: line = line[: line.find("#")] all_linguas.extend(line.split()) return all_linguas def build_trans(build_cmd): """ Translate the language files into gramps.mo """ data_files = build_cmd.distribution.data_files for lang in get_linguas(): po_file = os.path.join("po", lang + ".po") mo_file = os.path.join( build_cmd.build_base, "mo", lang, "LC_MESSAGES", "gramps.mo" ) mo_file_unix = build_cmd.build_base + "/mo/" + lang + "/LC_MESSAGES/gramps.mo" mo_dir = os.path.dirname(mo_file) if not (os.path.isdir(mo_dir) or os.path.islink(mo_dir)): os.makedirs(mo_dir) if newer(po_file, mo_file): cmd = ["msgfmt", po_file, "-o", mo_file] if subprocess.call(cmd) != 0: os.remove(mo_file) msg = "ERROR: Building language translation files failed." ask = msg + "\n Continue building y/n [n] " reply = input(ask) if reply in ["n", "N"]: raise SystemExit(msg) _LOG.info("Compiling %s >> %s", po_file, mo_file) # linux specific piece: target = "share/locale/" + lang + "/LC_MESSAGES" data_files.append((target, [mo_file_unix])) def build_man(build_cmd): """ Compresses Gramps manual files """ data_files = build_cmd.distribution.data_files for man_dir, dirs, files in os.walk(os.path.join("data", "man")): if "gramps.1.in" in files: filename = os.path.join(man_dir, "gramps.1.in") newdir = os.path.join(build_cmd.build_base, man_dir) if not (os.path.isdir(newdir) or os.path.islink(newdir)): os.makedirs(newdir) newfile = os.path.join(newdir, "gramps.1") subst_vars = (("@VERSION@", VERSION),) substitute_variables(filename, newfile, subst_vars) src = "gramps.1" if not args.no_compress_manpages: import gzip src += ".gz" man_file_gz = os.path.join(newdir, src) if os.path.exists(man_file_gz): if newer(filename, man_file_gz): os.remove(man_file_gz) else: filename = False os.remove(newfile) if filename: # Binary io, so open is OK with open(newfile, "rb") as f_in, gzip.open( man_file_gz, "wb" ) as f_out: f_out.writelines(f_in) _LOG.info("Compiling %s >> %s", filename, man_file_gz) os.remove(newfile) filename = False lang = man_dir[8:] src = build_cmd.build_base + "/data/man" + lang + "/" + src target = "share/man" + lang + "/man1" data_files.append((target, [src])) def build_intl(build_cmd): """ Merge translation files into desktop and mime files """ data_files = build_cmd.distribution.data_files base = build_cmd.build_base merge_files = ( ("org.gramps_project.Gramps.desktop", "share/applications", "--desktop"), ("org.gramps_project.Gramps.xml", "share/mime/packages", "--xml"), ("org.gramps_project.Gramps.appdata.xml", "share/metainfo", "--xml"), ) for filename, target, option in merge_files: filenamelocal = os.path.join("data", filename) newfile = os.path.join(base, filenamelocal) newdir = os.path.dirname(newfile) if not (os.path.isdir(newdir) or os.path.islink(newdir)): os.makedirs(newdir) merge(filenamelocal + ".in", newfile, option) data_files.append((target, [base + "/data/" + filename])) def merge(in_file, out_file, option, po_dir="po"): """ Run the msgfmt command. """ if not os.path.exists(out_file) and os.path.exists(in_file): cmd = ["msgfmt", option, "--template", in_file, "-d", po_dir, "-o", out_file] if subprocess.call(cmd, env={"GETTEXTDATADIR": po_dir}) != 0: msg = "ERROR: %s was not merged into the translation files!\n" % out_file raise SystemExit(msg) _LOG.info("Compiling %s >> %s", in_file, out_file) class build(_build): """Custom build command.""" def run(self): build_trans(self) if not sys.platform == "win32": build_man(self) build_intl(self) _build.run(self) class test(Command): """Command to run Gramps unit tests""" description = "run all unit tests" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): if not os.path.exists("build"): raise RuntimeError( "No build directory. Run `python setup.py build` before trying to run tests." ) os.environ["GRAMPS_RESOURCES"] = "." all_tests = unittest.TestLoader().discover(".", pattern="*_test.py") result = unittest.TextTestRunner(verbosity=self.verbose).run(all_tests) if not result.wasSuccessful(): raise SystemExit("ERROR: Unit test failure.") # ------------------------------------------------------------------------- # # Packages # # ------------------------------------------------------------------------- package_core = [ "gramps", "gramps.cli", "gramps.cli.plug", "gramps.gen.utils.docgen", "gramps.gen", "gramps.gen.datehandler", "gramps.gen.db", "gramps.gen.display", "gramps.gen.filters", "gramps.gen.filters.rules", "gramps.gen.filters.rules.citation", "gramps.gen.filters.rules.event", "gramps.gen.filters.rules.family", "gramps.gen.filters.rules.media", "gramps.gen.filters.rules.note", "gramps.gen.filters.rules.person", "gramps.gen.filters.rules.place", "gramps.gen.filters.rules.repository", "gramps.gen.filters.rules.source", "gramps.gen.lib", "gramps.gen.merge", "gramps.gen.mime", "gramps.gen.plug", "gramps.gen.plug.docbackend", "gramps.gen.plug.docgen", "gramps.gen.plug.menu", "gramps.gen.plug.report", "gramps.gen.proxy", "gramps.gen.simple", "gramps.gen.utils", "gramps.gen.utils.docgen", "gramps.test", "gramps.plugins", "gramps.plugins.db", "gramps.plugins.db.bsddb", "gramps.plugins.db.dbapi", "gramps.plugins.docgen", "gramps.plugins.drawreport", "gramps.plugins.export", "gramps.plugins.gramplet", "gramps.plugins.graph", "gramps.plugins.importer", "gramps.plugins.lib", "gramps.plugins.lib.maps", "gramps.plugins.mapservices", "gramps.plugins.quickview", "gramps.plugins.rel", "gramps.plugins.sidebar", "gramps.plugins.textreport", "gramps.plugins.tool", "gramps.plugins.view", "gramps.plugins.webreport", "gramps.plugins.webstuff", ] package_gui = [ "gramps.gui", "gramps.gui.editors", "gramps.gui.editors.displaytabs", "gramps.gui.filters", "gramps.gui.filters.sidebar", "gramps.gui.logger", "gramps.gui.merge", "gramps.gui.plug", "gramps.gui.plug.export", "gramps.gui.plug.quick", "gramps.gui.plug.report", "gramps.gui.selectors", "gramps.gui.views", "gramps.gui.views.treemodels", "gramps.gui.widgets", ] packages = package_core + package_gui # ------------------------------------------------------------------------- # # Package data # # ------------------------------------------------------------------------- # add all subdirs of plugin with glade: package_data_core = [] basedir = os.path.join("gramps", "plugins") for dirpath, dirnames, filenames in os.walk(basedir): root, subdir = os.path.split(dirpath) if subdir.startswith("."): dirnames[:] = [] continue for dirname in dirnames: # Skip hidden and system directories: if dirname.startswith("."): dirnames.remove(dirname) # we add to data_list so glade , xml, files are found, we don't need the gramps/ part package_data_core.append(dirpath[7:] + "/" + dirname + "/*.glade") package_data_core.append(dirpath[7:] + "/" + dirname + "/*.xml") package_data_core.append(dirpath[7:] + "/" + dirname + "/*.ini") package_data_gui = ["gui/glade/*.glade"] package_data = package_data_core + package_data_gui # ------------------------------------------------------------------------- # # Resources # # ------------------------------------------------------------------------- data_files_core = [] DOC_FILES = ["AUTHORS", "COPYING", "FAQ", "INSTALL", "NEWS", "README.md", "TODO"] GEDCOM_FILES = glob.glob(os.path.join("example", "gedcom", "*.*")) GRAMPS_FILES = glob.glob(os.path.join("example", "gramps", "*.*")) IMAGE_WEB = glob.glob(os.path.join("images", "webstuff", "*.png")) IMAGE_WEB.extend(glob.glob(os.path.join("images", "webstuff", "*.ico"))) IMAGE_WEB.extend(glob.glob(os.path.join("images", "webstuff", "*.gif"))) GRAMPS_CSS = glob.glob(os.path.join("data", "*.css")) CSS_FILES = glob.glob(os.path.join("data", "css", "*.css")) SWANKY_PURSE = glob.glob(os.path.join("data", "css", "swanky-purse", "*.css")) SWANKY_IMG = glob.glob(os.path.join("data", "css", "swanky-purse", "images", "*.png")) data_files_core.append(("share/doc/gramps", DOC_FILES)) data_files_core.append(("share/doc/gramps/example/gedcom", GEDCOM_FILES)) data_files_core.append(("share/doc/gramps/example/gramps", GRAMPS_FILES)) data_files_core.append(("share/gramps/images/webstuff", IMAGE_WEB)) data_files_core.append(("share/gramps", GRAMPS_CSS)) data_files_core.append(("share/gramps/css", CSS_FILES)) data_files_core.append(("share/gramps/css/swanky-purse", SWANKY_PURSE)) data_files_core.append(("share/gramps/css/swanky-purse/images", SWANKY_IMG)) DTD_FILES = glob.glob(os.path.join("data", "*.dtd")) RNG_FILES = glob.glob(os.path.join("data", "*.rng")) XML_FILES = glob.glob(os.path.join("data", "*.xml")) data_files_core.append(("share/gramps", XML_FILES)) data_files_core.append(("share/gramps", DTD_FILES)) data_files_core.append(("share/gramps", RNG_FILES)) data_files_gui = [] IMAGE_FILES = glob.glob(os.path.join("images", "*.*")) THEME = os.path.join("images", "hicolor") ICON_16 = glob.glob(os.path.join(THEME, "16x16", "actions", "*.png")) ICON_22 = glob.glob(os.path.join(THEME, "22x22", "actions", "*.png")) ICON_24 = glob.glob(os.path.join(THEME, "24x24", "actions", "*.png")) ICON_48 = glob.glob(os.path.join(THEME, "48x48", "actions", "*.png")) ICON_SC = glob.glob(os.path.join(THEME, "scalable", "actions", "*.svg")) data_files_gui.append(("share/gramps/images", IMAGE_FILES)) data_files_gui.append(("share/gramps/images/hicolor/16x16/actions", ICON_16)) data_files_gui.append(("share/gramps/images/hicolor/22x22/actions", ICON_22)) data_files_gui.append(("share/gramps/images/hicolor/24x24/actions", ICON_24)) data_files_gui.append(("share/gramps/images/hicolor/48x48/actions", ICON_48)) data_files_gui.append(("share/gramps/images/hicolor/scalable/actions", ICON_SC)) APP_16 = os.path.join(THEME, "16x16", "apps", "org.gramps_project.Gramps.png") APP_22 = os.path.join(THEME, "22x22", "apps", "org.gramps_project.Gramps.png") APP_24 = os.path.join(THEME, "24x24", "apps", "org.gramps_project.Gramps.png") APP_48 = os.path.join(THEME, "48x48", "apps", "org.gramps_project.Gramps.png") APP_128 = os.path.join(THEME, "128x128", "apps", "org.gramps_project.Gramps.png") APP_256 = os.path.join(THEME, "256x256", "apps", "org.gramps_project.Gramps.png") APP_SC = os.path.join(THEME, "scalable", "apps", "org.gramps_project.Gramps.svg") data_files_gui.append(("share/icons/hicolor/16x16/apps", [APP_16])) data_files_gui.append(("share/icons/hicolor/22x22/apps", [APP_22])) data_files_gui.append(("share/icons/hicolor/24x24/apps", [APP_24])) data_files_gui.append(("share/icons/hicolor/48x48/apps", [APP_48])) data_files_gui.append(("share/icons/hicolor/128x128/apps", [APP_128])) data_files_gui.append(("share/icons/hicolor/256x256/apps", [APP_256])) data_files_gui.append(("share/icons/hicolor/scalable/apps", [APP_SC])) MIME_48 = glob.glob(os.path.join(THEME, "48x48", "mimetypes", "*.png")) MIME_SC = glob.glob(os.path.join(THEME, "scalable", "mimetypes", "*.svg")) data_files_gui.append(("share/icons/hicolor/48x48/mimetypes", MIME_48)) data_files_gui.append(("share/icons/hicolor/scalable/mimetypes", MIME_SC)) data_files = data_files_core + data_files_gui # ------------------------------------------------------------------------- # # Setup # # ------------------------------------------------------------------------- setup( name="gramps", description=( "Gramps (Genealogical Research and Analysis Management " "Programming System)" ), long_description=( "Gramps (Genealogical Research and Analysis " "Management Programming System) is a full featured " "genealogy program supporting a Python based plugin " "system." ), version=VERSION, author="Donald N. Allingham", author_email="don@gramps-project.org", maintainer="Gramps Development Team", maintainer_email="benny.malengier@gmail.com", url="http://gramps-project.org", license="GPL v2 or greater", platforms=["FreeBSD", "Linux", "MacOS", "Windows"], cmdclass={"build": build, "test": test}, packages=packages, package_data={"gramps": package_data}, extras_require={ "bsddb": ["bsddb3"], "image": ["Pillow"], "i18n": ["PyICU"], "GUI": ["PyGObject", "pycairo"], "testing": ["jsonschema", "mock", "lxml"], }, data_files=data_files, scripts=["scripts/gramps"], classifiers=[ "Development Status :: 4 - Beta", "Environment :: Console", "Environment :: MacOS X", "Environment :: Plugins", "Environment :: Web Environment", "Environment :: Win32 (MS Windows)", "Environment :: X11 Applications :: GTK", "Intended Audience :: Education", "Intended Audience :: End Users/Desktop", "Intended Audience :: Other Audience", "Intended Audience :: Science/Research", "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", "Natural Language :: Arabic", "Natural Language :: Bulgarian", "Natural Language :: Catalan", "Natural Language :: Chinese (Simplified)", "Natural Language :: Croatian", "Natural Language :: Czech", "Natural Language :: Danish", "Natural Language :: Dutch", "Natural Language :: English", "Natural Language :: Esperanto", "Natural Language :: Finnish", "Natural Language :: French", "Natural Language :: German", "Natural Language :: German (Austrian)", "Natural Language :: Greek", "Natural Language :: Hebrew", "Natural Language :: Hungarian", "Natural Language :: Icelandic", "Natural Language :: Italian", "Natural Language :: Japanese", "Natural Language :: Macedonian", "Natural Language :: Norwegian", "Natural Language :: Polish", "Natural Language :: Portuguese", "Natural Language :: Portuguese (Brazilian)", "Natural Language :: Romanian", "Natural Language :: Russian", "Natural Language :: Serbian", "Natural Language :: Slovak", "Natural Language :: Slovenian", "Natural Language :: Spanish", "Natural Language :: Swedish", "Natural Language :: Turkish", "Natural Language :: Vietnamese", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: Other OS", "Operating System :: POSIX :: BSD", "Operating System :: POSIX :: Linux", "Operating System :: POSIX :: SunOS/Solaris", "Operating System :: Unix", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Database", "Topic :: Desktop Environment :: Gnome", "Topic :: Education", "Topic :: Scientific/Engineering :: Visualization", "Topic :: Sociology :: Genealogy", ], )