546 lines
18 KiB
Python
Executable File
546 lines
18 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# This work is licensed under the GNU GPLv2 or later.
|
|
# See the COPYING file in the top-level directory.
|
|
|
|
|
|
import sys
|
|
if sys.version_info.major < 3:
|
|
print("virt-manager is python3 only. Run this as ./setup.py")
|
|
sys.exit(1)
|
|
|
|
import glob
|
|
import importlib.util
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import sysconfig
|
|
import subprocess
|
|
|
|
# distutils will be deprecated in python 3.12 in favor of setuptools,
|
|
# but as of this writing there's standard no setuptools way to extend the
|
|
# 'build' or 'build_scripts' commands which are the only standard
|
|
# commands we trigger.
|
|
import distutils.command.build
|
|
|
|
import setuptools
|
|
import setuptools.command.install
|
|
import setuptools.command.install_egg_info
|
|
|
|
SYSPREFIX = sysconfig.get_config_var("prefix")
|
|
|
|
|
|
def _import_buildconfig():
|
|
# A bit of crazyness to import the buildconfig file without importing
|
|
# the rest of virtinst, so the build process doesn't require all the
|
|
# runtime deps to be installed
|
|
spec = importlib.util.spec_from_file_location(
|
|
'buildconfig', 'virtinst/buildconfig.py')
|
|
buildconfig = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(buildconfig)
|
|
if "libvirt" in sys.modules:
|
|
raise RuntimeError("Found libvirt in sys.modules. setup.py should "
|
|
"not import virtinst.")
|
|
return buildconfig.BuildConfig
|
|
|
|
|
|
BuildConfig = _import_buildconfig()
|
|
|
|
|
|
# pylint: disable=attribute-defined-outside-init
|
|
|
|
_desktop_files = [
|
|
("share/applications", ["data/virt-manager.desktop.in"]),
|
|
]
|
|
_appdata_files = [
|
|
("share/metainfo", ["data/virt-manager.appdata.xml.in"]),
|
|
]
|
|
|
|
|
|
class my_build_i18n(setuptools.Command):
|
|
"""
|
|
Add our desktop files to the list, saves us having to track setup.cfg
|
|
"""
|
|
user_options = [
|
|
('merge-po', 'm', 'merge po files against template'),
|
|
]
|
|
|
|
def initialize_options(self):
|
|
self.merge_po = False
|
|
def finalize_options(self):
|
|
pass
|
|
|
|
def run(self):
|
|
po_dir = "po"
|
|
if self.merge_po:
|
|
pot_file = os.path.join("po", "virt-manager.pot")
|
|
for po_file in glob.glob("%s/*.po" % po_dir):
|
|
cmd = ["msgmerge", "--previous", "-o", po_file, po_file, pot_file]
|
|
self.spawn(cmd)
|
|
|
|
max_po_mtime = 0
|
|
for po_file in glob.glob("%s/*.po" % po_dir):
|
|
lang = os.path.basename(po_file[:-3])
|
|
mo_dir = os.path.join("build", "mo", lang, "LC_MESSAGES")
|
|
mo_file = os.path.join(mo_dir, "virt-manager.mo")
|
|
if not os.path.exists(mo_dir):
|
|
os.makedirs(mo_dir)
|
|
|
|
cmd = ["msgfmt", po_file, "-o", mo_file]
|
|
po_mtime = os.path.getmtime(po_file)
|
|
mo_mtime = (os.path.exists(mo_file) and
|
|
os.path.getmtime(mo_file)) or 0
|
|
if po_mtime > max_po_mtime:
|
|
max_po_mtime = po_mtime
|
|
if po_mtime > mo_mtime:
|
|
self.spawn(cmd)
|
|
|
|
targetpath = os.path.join("share/locale", lang, "LC_MESSAGES")
|
|
self.distribution.data_files.append((targetpath, (mo_file,)))
|
|
|
|
# Merge .in with translations using gettext
|
|
for (file_set, switch) in [(_appdata_files, "--xml"),
|
|
(_desktop_files, "--desktop")]:
|
|
for (target, files) in file_set:
|
|
build_target = os.path.join("build", target)
|
|
if not os.path.exists(build_target):
|
|
os.makedirs(build_target)
|
|
|
|
files_merged = []
|
|
for f in files:
|
|
if f.endswith(".in"):
|
|
file_merged = os.path.basename(f[:-3])
|
|
else:
|
|
file_merged = os.path.basename(f)
|
|
|
|
file_merged = os.path.join(build_target, file_merged)
|
|
cmd = ["msgfmt", switch, "--template", f, "-d", po_dir,
|
|
"-o", file_merged]
|
|
mtime_merged = (os.path.exists(file_merged) and
|
|
os.path.getmtime(file_merged)) or 0
|
|
mtime_file = os.path.getmtime(f)
|
|
if (mtime_merged < max_po_mtime or
|
|
mtime_merged < mtime_file):
|
|
# Only build if output is older than input (.po,.in)
|
|
self.spawn(cmd)
|
|
files_merged.append(file_merged)
|
|
self.distribution.data_files.append((target, files_merged))
|
|
|
|
|
|
class my_build(distutils.command.build.build):
|
|
def _make_bin_wrappers(self):
|
|
template = """#!/usr/bin/env python3
|
|
|
|
import os
|
|
import sys
|
|
sys.path.insert(0, "%(sharepath)s")
|
|
from %(pkgname)s import %(filename)s
|
|
|
|
%(filename)s.runcli()
|
|
"""
|
|
if not os.path.exists("build"):
|
|
os.mkdir("build")
|
|
sharepath = os.path.join(BuildConfig.prefix, "share", "virt-manager")
|
|
|
|
def make_script(pkgname, filename, toolname):
|
|
assert os.path.exists(pkgname + "/" + filename + ".py")
|
|
content = template % {
|
|
"sharepath": sharepath,
|
|
"pkgname": pkgname,
|
|
"filename": filename}
|
|
|
|
newpath = os.path.abspath(os.path.join("build", toolname))
|
|
print("Generating %s" % newpath)
|
|
open(newpath, "w").write(content)
|
|
|
|
make_script("virtinst", "virtinstall", "virt-install")
|
|
make_script("virtinst", "virtclone", "virt-clone")
|
|
make_script("virtinst", "virtxml", "virt-xml")
|
|
make_script("virtManager", "virtmanager", "virt-manager")
|
|
|
|
|
|
def _make_man_pages(self):
|
|
rstbin = shutil.which("rst2man")
|
|
if not rstbin:
|
|
rstbin = shutil.which("rst2man.py")
|
|
if not rstbin:
|
|
sys.exit("Didn't find rst2man or rst2man.py")
|
|
|
|
for path in glob.glob("man/*.rst"):
|
|
base = os.path.basename(path)
|
|
appname = os.path.splitext(base)[0]
|
|
newpath = os.path.join(os.path.dirname(path),
|
|
appname + ".1")
|
|
|
|
print("Generating %s" % newpath)
|
|
out = subprocess.check_output([rstbin, "--strict", path])
|
|
open(newpath, "wb").write(out)
|
|
|
|
self.distribution.data_files.append(
|
|
('share/man/man1', (newpath,)))
|
|
|
|
def _build_icons(self):
|
|
for size in glob.glob(os.path.join("data/icons", "*")):
|
|
for category in glob.glob(os.path.join(size, "*")):
|
|
icons = []
|
|
for icon in glob.glob(os.path.join(category, "*")):
|
|
icons.append(icon)
|
|
if not icons:
|
|
continue
|
|
|
|
category = os.path.basename(category)
|
|
dest = ("share/icons/hicolor/%s/%s" %
|
|
(os.path.basename(size), category))
|
|
if category != "apps":
|
|
dest = dest.replace("share/", "share/virt-manager/")
|
|
|
|
self.distribution.data_files.append((dest, icons))
|
|
|
|
|
|
def _make_bash_completion_files(self):
|
|
scripts = ["virt-install", "virt-clone", "virt-xml"]
|
|
srcfile = "data/bash-completion.sh.in"
|
|
builddir = "build/bash-completion/"
|
|
if not os.path.exists(builddir):
|
|
os.makedirs(builddir)
|
|
|
|
instpaths = []
|
|
for script in scripts:
|
|
genfile = os.path.join(builddir, script)
|
|
print("Generating %s" % genfile)
|
|
src = open(srcfile, "r")
|
|
dst = open(genfile, "w")
|
|
dst.write(src.read().replace("::SCRIPTNAME::", script))
|
|
dst.close()
|
|
instpaths.append(genfile)
|
|
|
|
bashdir = "share/bash-completion/completions/"
|
|
self.distribution.data_files.append((bashdir, instpaths))
|
|
|
|
|
|
def run(self):
|
|
self._make_bin_wrappers()
|
|
self._make_man_pages()
|
|
self._build_icons()
|
|
self._make_bash_completion_files()
|
|
|
|
self.run_command("build_i18n")
|
|
distutils.command.build.build.run(self)
|
|
|
|
|
|
class my_egg_info(setuptools.command.install_egg_info.install_egg_info):
|
|
"""
|
|
Disable egg_info installation, seems pointless for a non-library
|
|
"""
|
|
def run(self):
|
|
pass
|
|
|
|
|
|
class my_install(setuptools.command.install.install):
|
|
"""
|
|
Error if we weren't 'configure'd with the correct install prefix
|
|
"""
|
|
def finalize_options(self):
|
|
if self.prefix is None:
|
|
if BuildConfig.prefix != SYSPREFIX:
|
|
print("Using configured prefix=%s instead of SYSPREFIX=%s" % (
|
|
BuildConfig.prefix, SYSPREFIX))
|
|
self.prefix = BuildConfig.prefix
|
|
else:
|
|
print("Using SYSPREFIX=%s" % SYSPREFIX)
|
|
self.prefix = SYSPREFIX
|
|
|
|
elif self.prefix != BuildConfig.prefix:
|
|
print("Install prefix=%s doesn't match configure prefix=%s\n"
|
|
"Pass matching --prefix to 'setup.py configure'" %
|
|
(self.prefix, BuildConfig.prefix))
|
|
sys.exit(1)
|
|
|
|
setuptools.command.install.install.finalize_options(self)
|
|
|
|
def run(self):
|
|
if not self.distribution.no_update_icon_cache:
|
|
print("running gtk-update-icon-cache")
|
|
icon_path = os.path.join(self.install_data, "share/icons/hicolor")
|
|
self.spawn(["gtk-update-icon-cache", "-q", "-t", icon_path])
|
|
|
|
if not self.distribution.no_compile_schemas:
|
|
print("compiling gsettings schemas")
|
|
gschema_install = os.path.join(self.install_data,
|
|
"share/glib-2.0/schemas")
|
|
self.spawn(["glib-compile-schemas", gschema_install])
|
|
|
|
setuptools.command.install.install.run(self)
|
|
|
|
|
|
###################
|
|
# Custom commands #
|
|
###################
|
|
|
|
class my_rpm(setuptools.Command):
|
|
user_options = []
|
|
description = "Build RPMs and output to the source directory."
|
|
|
|
def initialize_options(self):
|
|
pass
|
|
def finalize_options(self):
|
|
pass
|
|
|
|
def run(self):
|
|
self.run_command('sdist')
|
|
srcdir = os.path.dirname(__file__)
|
|
cmd = [
|
|
"rpmbuild", "-ta",
|
|
"--define", "_rpmdir %s" % srcdir,
|
|
"--define", "_srcrpmdir %s" % srcdir,
|
|
"--define", "_specdir /tmp",
|
|
"dist/virt-manager-%s.tar.gz" % BuildConfig.version,
|
|
]
|
|
subprocess.check_call(cmd)
|
|
|
|
|
|
class configure(setuptools.Command):
|
|
user_options = [
|
|
("prefix=", None, "installation prefix"),
|
|
("default-graphics=", None,
|
|
"Default graphics type (spice or vnc) (default=spice)"),
|
|
("default-hvs=", None,
|
|
"Comma separated list of hypervisors shown in 'Open Connection' "
|
|
"wizard. (default=all hvs)"),
|
|
|
|
]
|
|
description = "Configure the build, similar to ./configure"
|
|
|
|
def finalize_options(self):
|
|
pass
|
|
|
|
def initialize_options(self):
|
|
self.prefix = SYSPREFIX
|
|
self.default_graphics = None
|
|
self.default_hvs = None
|
|
|
|
|
|
def run(self):
|
|
template = ""
|
|
template += "[config]\n"
|
|
template += "prefix = %s\n" % self.prefix
|
|
if self.default_graphics is not None:
|
|
template += "default_graphics = %s\n" % self.default_graphics
|
|
if self.default_hvs is not None:
|
|
template += "default_hvs = %s\n" % self.default_hvs
|
|
|
|
open(BuildConfig.cfgpath, "w").write(template)
|
|
print("Generated %s" % BuildConfig.cfgpath)
|
|
|
|
|
|
class TestCommand(setuptools.Command):
|
|
user_options = []
|
|
description = "DEPRECATED: Use `pytest`. See CONTRIBUTING.md"
|
|
def finalize_options(self):
|
|
pass
|
|
def initialize_options(self):
|
|
pass
|
|
def run(self):
|
|
sys.exit("ERROR: `test` is deprecated. Call `pytest` instead. "
|
|
"See CONTRIBUTING.md for more info.")
|
|
|
|
|
|
class CheckPylint(setuptools.Command):
|
|
user_options = [
|
|
("jobs=", "j", "use multiple processes to speed up Pylint"),
|
|
]
|
|
description = "Check code using pylint and pycodestyle"
|
|
|
|
def initialize_options(self):
|
|
self.jobs = None
|
|
|
|
def finalize_options(self):
|
|
if self.jobs:
|
|
self.jobs = int(self.jobs)
|
|
|
|
def run(self):
|
|
import pylint.lint
|
|
import pycodestyle
|
|
|
|
lintfiles = [
|
|
# Put this first so pylint learns what Gtk version we
|
|
# want to lint against
|
|
"virtManager/virtmanager.py",
|
|
"setup.py",
|
|
"tests",
|
|
"virtinst",
|
|
"virtManager"]
|
|
|
|
spellfiles = lintfiles[:]
|
|
spellfiles += list(glob.glob("*.md"))
|
|
spellfiles += list(glob.glob("man/*.rst"))
|
|
spellfiles += ["data/virt-manager.appdata.xml.in",
|
|
"data/virt-manager.desktop.in",
|
|
"data/org.virt-manager.virt-manager.gschema.xml",
|
|
"virt-manager.spec"]
|
|
spellfiles.remove("NEWS.md")
|
|
|
|
try:
|
|
import codespell_lib
|
|
# pylint: disable=protected-access
|
|
print("running codespell")
|
|
codespell_lib._codespell.main(
|
|
'-I', 'tests/data/codespell_dict.txt',
|
|
'--skip', '*.pyc,*.iso,*.xml', *spellfiles)
|
|
except ImportError:
|
|
print("codespell is not installed. skipping...")
|
|
except Exception as e:
|
|
print("Error running codespell: %s" % e)
|
|
|
|
output_format = sys.stdout.isatty() and "colorized" or "text"
|
|
|
|
print("running pycodestyle")
|
|
style_guide = pycodestyle.StyleGuide(
|
|
config_file='setup.cfg',
|
|
format="pylint",
|
|
paths=lintfiles,
|
|
)
|
|
report = style_guide.check_files()
|
|
if style_guide.options.count:
|
|
sys.stderr.write(str(report.total_errors) + '\n')
|
|
|
|
print("running pylint")
|
|
pylint_opts = [
|
|
"--rcfile", ".pylintrc",
|
|
"--output-format=%s" % output_format,
|
|
]
|
|
if self.jobs:
|
|
pylint_opts += ["--jobs=%d" % self.jobs]
|
|
|
|
pylint.lint.Run(lintfiles + pylint_opts)
|
|
|
|
|
|
class VMMDistribution(setuptools.dist.Distribution):
|
|
global_options = setuptools.dist.Distribution.global_options + [
|
|
("no-update-icon-cache", None, "Don't run gtk-update-icon-cache"),
|
|
("no-compile-schemas", None, "Don't compile gsettings schemas"),
|
|
]
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.no_update_icon_cache = False
|
|
self.no_compile_schemas = False
|
|
setuptools.dist.Distribution.__init__(self, *args, **kwargs)
|
|
|
|
|
|
class ExtractMessages(setuptools.Command):
|
|
user_options = [
|
|
]
|
|
description = "Extract the translation messages"
|
|
|
|
def initialize_options(self):
|
|
pass
|
|
|
|
def finalize_options(self):
|
|
pass
|
|
|
|
def run(self):
|
|
bug_address = "https://github.com/virt-manager/virt-manager/issues"
|
|
potfile = "po/virt-manager.pot"
|
|
xgettext_args = [
|
|
"xgettext",
|
|
"--add-comments=translators",
|
|
"--msgid-bugs-address=" + bug_address,
|
|
"--package-name=virt-manager",
|
|
"--output=" + potfile,
|
|
"--sort-by-file",
|
|
"--join-existing",
|
|
]
|
|
|
|
# Truncate .pot file to ensure it exists
|
|
open(potfile, "w").write("")
|
|
|
|
# First extract the messages from the AppStream sources,
|
|
# creating the template
|
|
appdata_files = [f for sublist in _appdata_files for f in sublist[1]]
|
|
cmd = xgettext_args + appdata_files
|
|
self.spawn(cmd)
|
|
|
|
# Extract the messages from the desktop files
|
|
desktop_files = [f for sublist in _desktop_files for f in sublist[1]]
|
|
cmd = xgettext_args + ["--language=Desktop"] + desktop_files
|
|
self.spawn(cmd)
|
|
|
|
# Extract the messages from the Python sources
|
|
py_sources = list(Path("virtManager").rglob("*.py"))
|
|
py_sources += list(Path("virtinst").rglob("*.py"))
|
|
py_sources = [str(src) for src in py_sources]
|
|
cmd = xgettext_args + ["--language=Python"] + py_sources
|
|
self.spawn(cmd)
|
|
|
|
# Extract the messages from the Glade UI files
|
|
ui_files = list(Path(".").rglob("*.ui"))
|
|
ui_files = [str(src) for src in ui_files]
|
|
cmd = xgettext_args + ["--language=Glade"] + ui_files
|
|
self.spawn(cmd)
|
|
|
|
|
|
setuptools.setup(
|
|
name="virt-manager",
|
|
version=BuildConfig.version,
|
|
author="Cole Robinson",
|
|
author_email="virt-tools-list@redhat.com",
|
|
url="http://virt-manager.org",
|
|
license="GPLv2+",
|
|
|
|
# These wrappers are generated in our custom build command
|
|
scripts=([
|
|
"build/virt-manager",
|
|
"build/virt-clone",
|
|
"build/virt-install",
|
|
"build/virt-xml"]),
|
|
|
|
data_files=[
|
|
("share/glib-2.0/schemas",
|
|
["data/org.virt-manager.virt-manager.gschema.xml"]),
|
|
("share/virt-manager/ui", glob.glob("ui/*.ui")),
|
|
|
|
("share/man/man1", [
|
|
"man/virt-manager.1",
|
|
"man/virt-install.1",
|
|
"man/virt-clone.1",
|
|
"man/virt-xml.1"
|
|
]),
|
|
|
|
("share/virt-manager/virtManager", glob.glob("virtManager/*.py")),
|
|
("share/virt-manager/virtManager/details",
|
|
glob.glob("virtManager/details/*.py")),
|
|
("share/virt-manager/virtManager/device",
|
|
glob.glob("virtManager/device/*.py")),
|
|
("share/virt-manager/virtManager/lib",
|
|
glob.glob("virtManager/lib/*.py")),
|
|
("share/virt-manager/virtManager/object",
|
|
glob.glob("virtManager/object/*.py")),
|
|
("share/virt-manager/virtinst",
|
|
glob.glob("virtinst/*.py") + glob.glob("virtinst/build.cfg")),
|
|
("share/virt-manager/virtinst/devices",
|
|
glob.glob("virtinst/devices/*.py")),
|
|
("share/virt-manager/virtinst/domain",
|
|
glob.glob("virtinst/domain/*.py")),
|
|
("share/virt-manager/virtinst/install",
|
|
glob.glob("virtinst/install/*.py")),
|
|
],
|
|
|
|
cmdclass={
|
|
'build': my_build,
|
|
'build_i18n': my_build_i18n,
|
|
|
|
'install': my_install,
|
|
'install_egg_info': my_egg_info,
|
|
|
|
'configure': configure,
|
|
|
|
'pylint': CheckPylint,
|
|
'rpm': my_rpm,
|
|
'test': TestCommand,
|
|
|
|
'extract_messages': ExtractMessages,
|
|
},
|
|
|
|
distclass=VMMDistribution,
|
|
)
|