--- Begin Message ---
Source: khard
Version: 0.15.0-2
Severity: wishlist
Tags: patch
User: [email protected]
Usertags: buildpath
X-Debbugs-Cc: [email protected]
Hi,
Whilst working on the Reproducible Builds effort [0] we noticed
that khard could not be built reproducibly.
This is because it includes the absolute build directory in the
documentation via the SPEC_FILE attribute. Patch attached that
calculates this dynamically instead.
[0] https://reproducible-builds.org/
Regards,
--
,''`.
: :' : Chris Lamb
`. `'` [email protected] / chris-lamb.co.uk
`-
--- a/debian/patches/0003-add-khard-data-dir-to-MANIFEST.in.patch
2019-10-25 09:18:54.208690284 +0100
--- b/debian/patches/0003-add-khard-data-dir-to-MANIFEST.in.patch
2019-10-25 09:46:27.257501095 +0100
@@ -6,10 +6,8 @@
MANIFEST.in | 1 +
1 file changed, 1 insertion(+)
-diff --git a/MANIFEST.in b/MANIFEST.in
-index 9b1fe7d..20fab9d 100644
---- a/MANIFEST.in
-+++ b/MANIFEST.in
+--- khard-0.15.0.orig/MANIFEST.in
++++ khard-0.15.0/MANIFEST.in
@@ -4,3 +4,4 @@ include LICENSE
include README.md
recursive-include misc *
--- a/debian/patches/0004-reproducible-build.patch 1970-01-01
01:00:00.000000000 +0100
--- b/debian/patches/0004-reproducible-build.patch 2019-10-25
09:49:09.070068812 +0100
@@ -0,0 +1,42 @@
+Description: Make the build reproducible
+Author: Chris Lamb <[email protected]>
+Last-Update: 2019-10-25
+
+--- khard-0.15.0.orig/khard/config.py
++++ khard-0.15.0/khard/config.py
+@@ -92,7 +92,7 @@ def validate_private_objects(value):
+ class Config:
+
+ supported_vcard_versions = ("3.0", "4.0")
+- SPEC_FILE = os.path.join(os.path.dirname(__file__), 'data', 'config.spec')
++ SPEC_FILE = None
+
+ def __init__(self, config_file=None):
+ self.config = None
+@@ -116,8 +116,12 @@ class Config:
+ config_file = os.getenv("KHARD_CONFIG", os.path.join(
+ xdg_config_home, "khard", "khard.conf"))
+ try:
++ configspec = cls.SPEC_FILE
++ if configspec is None:
++ configspec = os.path.join(os.path.dirname(__file__),
++ 'data', 'config.spec')
+ return configobj.ConfigObj(
+- infile=config_file, configspec=cls.SPEC_FILE,
++ infile=config_file, configspec=configspec,
+ interpolation=False, file_error=True)
+ except configobj.ConfigObjError as err:
+ exit(str(err))
+--- khard-0.15.0.orig/test/test_config.py
++++ khard-0.15.0/test/test_config.py
+@@ -148,7 +148,9 @@ class Validation(unittest.TestCase):
+
+ @staticmethod
+ def _template(section, key, value):
+- c = configobj.ConfigObj(configspec=config.Config.SPEC_FILE)
++ configspec = os.path.join(os.path.dirname(os.path.dirname(__file__)),
++ 'khard', 'data', 'config.spec')
++ c = configobj.ConfigObj(configspec=configspec)
+ c['general'] = {}
+ c['vcard'] = {}
+ c['contact table'] = {}
--- a/debian/patches/0005-reproducible-build.patch 1970-01-01
01:00:00.000000000 +0100
--- b/debian/patches/0005-reproducible-build.patch 2019-10-25
09:46:49.261410668 +0100
@@ -0,0 +1,17 @@
+Description: Make the build reproducible
+Author: Chris Lamb <[email protected]>
+Last-Update: 2019-10-25
+
+--- khard-0.15.0.orig/test/test_config.py
++++ khard-0.15.0/test/test_config.py
+@@ -148,7 +148,9 @@ class Validation(unittest.TestCase):
+
+ @staticmethod
+ def _template(section, key, value):
+- c = configobj.ConfigObj(configspec=config.Config.SPEC_FILE)
++ configspec = os.path.join(os.path.dirname(__file__),
++ 'data', 'config.spec')
++ c = configobj.ConfigObj(configspec=configspec)
+ c['general'] = {}
+ c['vcard'] = {}
+ c['contact table'] = {}
--- a/debian/patches/series 2019-10-25 09:18:54.208690284 +0100
--- b/debian/patches/series 2019-10-25 09:47:07.465377164 +0100
@@ -1,3 +1,4 @@
0001-remove-travis-repology-buttons.patch
0002-use-debian-paths-in-doc.patch
0003-add-khard-data-dir-to-MANIFEST.in.patch
+0004-reproducible-build.patch
--- a/debian/rules 2019-10-25 09:18:54.208690284 +0100
--- b/debian/rules 2019-10-25 09:33:48.499676643 +0100
@@ -8,5 +8,9 @@
make man
dh_auto_build
+override_dh_auto_clean:
+ dh_auto_clean
+ rm -f khard/version.py
+
%:
dh $@ --with python3 --buildsystem=pybuild
--- a/debian/source/options 1970-01-01 01:00:00.000000000 +0100
--- b/debian/source/options 2019-10-25 09:25:24.627743707 +0100
@@ -0,0 +1 @@
+tar-ignore = "*/version.py"
--- a/khard/config.py 2019-10-25 09:18:54.208690284 +0100
--- b/khard/config.py 2019-10-25 09:46:41.000000000 +0100
@@ -92,7 +92,7 @@
class Config:
supported_vcard_versions = ("3.0", "4.0")
- SPEC_FILE = os.path.join(os.path.dirname(__file__), 'data', 'config.spec')
+ SPEC_FILE = None
def __init__(self, config_file=None):
self.config = None
@@ -116,8 +116,12 @@
config_file = os.getenv("KHARD_CONFIG", os.path.join(
xdg_config_home, "khard", "khard.conf"))
try:
+ configspec = cls.SPEC_FILE
+ if configspec is None:
+ configspec = os.path.join(os.path.dirname(__file__),
+ 'data', 'config.spec')
return configobj.ConfigObj(
- infile=config_file, configspec=cls.SPEC_FILE,
+ infile=config_file, configspec=configspec,
interpolation=False, file_error=True)
except configobj.ConfigObjError as err:
exit(str(err))
--- a/test/test_config.py 2019-10-25 09:18:54.212690314 +0100
--- b/test/test_config.py 2019-10-25 09:50:16.610281543 +0100
@@ -148,7 +148,9 @@
@staticmethod
def _template(section, key, value):
- c = configobj.ConfigObj(configspec=config.Config.SPEC_FILE)
+ configspec = os.path.join(os.path.dirname(os.path.dirname(__file__)),
+ 'khard', 'data', 'config.spec')
+ c = configobj.ConfigObj(configspec=configspec)
c['general'] = {}
c['vcard'] = {}
c['contact table'] = {}
--- a/test/test_config.py.orig 1970-01-01 01:00:00.000000000 +0100
--- b/test/test_config.py.orig 2019-10-25 09:46:49.000000000 +0100
@@ -0,0 +1,189 @@
+"""Tests for the config module."""
+# pylint: disable=missing-docstring
+
+import io
+import logging
+import os.path
+import tempfile
+import unittest
+import unittest.mock as mock
+
+from khard import config
+
+import configobj
+
+
+class LoadingConfigFile(unittest.TestCase):
+
+ def test_load_non_existing_file_fails(self):
+ filename = "I hope this file never exists"
+ stdout = io.StringIO()
+ with self.assertRaises(IOError) as cm:
+ config.Config._load_config_file(filename)
+ self.assertTrue(str(cm.exception).startswith('Config file not found:'))
+
+ def test_uses_khard_config_environment_variable(self):
+ filename = "this is some very random string"
+ with mock.patch.dict("os.environ", clear=True, KHARD_CONFIG=filename):
+ with mock.patch("configobj.ConfigObj", dict):
+ ret = config.Config._load_config_file("")
+ self.assertEqual(ret['infile'], filename)
+
+ def test_uses_xdg_config_home_environment_variable(self):
+ prefix = "this is some very random string"
+ with mock.patch.dict("os.environ", clear=True, XDG_CONFIG_HOME=prefix):
+ with mock.patch("configobj.ConfigObj", dict):
+ ret = config.Config._load_config_file("")
+ expected = os.path.join(prefix, 'khard', 'khard.conf')
+ self.assertEqual(ret['infile'], expected)
+
+ def test_uses_config_dir_if_environment_unset(self):
+ prefix = "this is some very random string"
+ with mock.patch.dict("os.environ", clear=True, HOME=prefix):
+ with mock.patch("configobj.ConfigObj", dict):
+ ret = config.Config._load_config_file("")
+ expected = os.path.join(prefix, '.config', 'khard', 'khard.conf')
+ self.assertEqual(ret['infile'], expected)
+
+ def test_load_empty_file_fails(self):
+ stdout = io.StringIO()
+ with tempfile.NamedTemporaryFile() as name:
+ with self.assertLogs(level=logging.ERROR) as cm:
+ with self.assertRaises(SystemExit):
+ config.Config(name)
+
+ @mock.patch.dict('os.environ', EDITOR='editor', MERGE_EDITOR='meditor')
+ def test_load_minimal_file_by_name(self):
+ cfg = config.Config("test/fixture/minimal.conf")
+ self.assertEqual(cfg.editor, "editor")
+ self.assertEqual(cfg.merge_editor, "meditor")
+
+
+class ConfigPreferredVcardVersion(unittest.TestCase):
+
+ def test_default_value_is_3(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertEqual(c.preferred_vcard_version, "3.0")
+
+ def test_set_preferred_version(self):
+ c = config.Config("test/fixture/minimal.conf")
+ c.preferred_vcard_version = "11"
+ self.assertEqual(c.preferred_vcard_version, "11")
+
+
+class Defaults(unittest.TestCase):
+
+ def test_debug_defaults_to_false(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertFalse(c.debug)
+
+ def test_default_action_defaults_to_list(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertEqual(c.default_action, 'list')
+
+ def test_reverse_defaults_to_false(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertFalse(c.reverse)
+
+ def test_group_by_addressbook_defaults_to_false(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertFalse(c.group_by_addressbook)
+
+ def test_show_nicknames_defaults_to_false(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertFalse(c.show_nicknames)
+
+ def test_show_uids_defaults_to_true(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertTrue(c.show_uids)
+
+ def test_sort_defaults_to_first_name(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertEqual(c.sort, 'first_name')
+
+ def test_display_defaults_to_first_name(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertEqual(c.display, 'first_name')
+
+ def test_localize_dates_defaults_to_true(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertTrue(c.localize_dates)
+
+ def test_preferred_phone_number_type_defaults_to_pref(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertListEqual(c.preferred_phone_number_type, ['pref'])
+
+ def test_preferred_email_address_type_defaults_to_pref(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertListEqual(c.preferred_email_address_type, ['pref'])
+
+ def test_private_objects_defaults_to_empty(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertListEqual(c.private_objects, [])
+
+ def test_search_in_source_files_defaults_to_false(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertFalse(c.search_in_source_files)
+
+ def test_skip_unparsable_defaults_to_false(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertFalse(c.skip_unparsable)
+
+ def test_preferred_version_defaults_to_3(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertEqual(c.preferred_vcard_version, '3.0')
+
+ @mock.patch.dict('os.environ', clear=True)
+ def test_editor_defaults_to_vim(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertEqual(c.editor, 'vim')
+
+ @mock.patch.dict('os.environ', clear=True)
+ def test_merge_editor_defaults_to_vimdiff(self):
+ c = config.Config("test/fixture/minimal.conf")
+ self.assertEqual(c.merge_editor, 'vimdiff')
+
+
+class Validation(unittest.TestCase):
+
+ @staticmethod
+ def _template(section, key, value):
+ c = configobj.ConfigObj(configspec=config.Config.SPEC_FILE)
+ c['general'] = {}
+ c['vcard'] = {}
+ c['contact table'] = {}
+ c['addressbooks'] = {'test': {'path': '/tmp'}}
+ c[section][key] = value
+ return c
+
+ def test_rejects_invalid_default_actions(self):
+ action = 'this is not a valid action'
+ conf = self._template('general', 'default_action', action)
+ with self.assertLogs(level=logging.ERROR):
+ with self.assertRaises(SystemExit):
+ config.Config._validate(conf)
+
+ def test_rejects_unparsable_editor_commands(self):
+ editor = 'editor --option "unparsable because quotes are missing'
+ conf = self._template('general', 'editor', editor)
+ with self.assertLogs(level=logging.ERROR):
+ with self.assertRaises(SystemExit):
+ config.Config._validate(conf)
+
+ def test_rejects_private_objects_with_strange_chars(self):
+ obj = 'X-VCÃRD-EXTENSIÃN'
+ conf = self._template('vcard', 'private_objects', obj)
+ with self.assertLogs(level=logging.ERROR):
+ with self.assertRaises(SystemExit):
+ config.Config._validate(conf)
+
+ def test_rejects_private_objects_starting_with_minus(self):
+ obj = '-INVALID-'
+ conf = self._template('vcard', 'private_objects', obj)
+ with self.assertLogs(level=logging.ERROR):
+ with self.assertRaises(SystemExit):
+ config.Config._validate(conf)
+
+
+if __name__ == "__main__":
+ unittest.main()
--- End Message ---