diff sat/tools/utils.py @ 2573:18e2ca5f798e

tools (utils): better repository version handling: - removed save_dir_path and .hg_data/pickle method: it is not needed to save repository data anymore as its version is now saved on installation by setuptools_scm - added "distance" (number of commits since last tag) - use twisted's which method to find the hg executable - result is now cached on first execution - fixed dirstate method - at last resort, try to find the repository version using the package version, as it is saved there by setuptools_scm
author Goffi <goffi@goffi.org>
date Thu, 05 Apr 2018 15:23:38 +0200
parents 8e204f0d3193
children cb7bf936d8e8
line wrap: on
line diff
--- a/sat/tools/utils.py	Thu Apr 05 12:49:25 2018 +0200
+++ b/sat/tools/utils.py	Thu Apr 05 15:23:38 2018 +0200
@@ -21,10 +21,13 @@
 
 import unicodedata
 import os.path
+from sat.core.constants import Const as C
 from sat.core.log import getLogger
 log = getLogger(__name__)
 import datetime
 from dateutil import parser as dateutil_parser
+from twisted.python import procutils
+import subprocess
 import calendar
 import time
 import sys
@@ -34,6 +37,11 @@
 import functools
 
 
+NO_REPOS_DATA = u'repository data unknown'
+repos_cache_dict = None
+repos_cache = None
+
+
 def clean_ustr(ustr):
     """Clean unicode string
 
@@ -103,22 +111,19 @@
         vocabulary = [chr(i) for i in range(0x30,0x3A) + range(0x41,0x5B) + range (0x61,0x7B)]
     return u''.join([random.choice(vocabulary) for i in range(15)])
 
-def getRepositoryData(module, as_string=True, is_path=False, save_dir_path=None):
+def getRepositoryData(module, as_string=True, is_path=False):
     """Retrieve info on current mecurial repository
 
     Data is gotten by using the following methods, in order:
         - using "hg" executable
-        - looking for a ".hg_data" file in the root of the module
-            this file must contain the data dictionnary serialized with pickle
         - looking for a .hg/dirstate in parent directory of module (or in module/.hg if
             is_path is True), and parse dirstate file to get revision
+        - checking package version, which should have repository data when we are on a dev version
     @param module(unicode): module to look for (e.g. sat, libervia)
         module can be a path if is_path is True (see below)
     @param as_string(bool): if True return a string, else return a dictionary
     @param is_path(bool): if True "module" is not handled as a module name, but as an
         absolute path to the parent of a ".hg" directory
-    @param save_path(str, None): if not None, the value will be saved to given path as a pickled dict
-        /!\\ the .hg_data file in the given directory will be overwritten
     @return (unicode, dictionary): retrieved info in a nice string,
         or a dictionary with retrieved data (key is not present if data is not found),
         key can be:
@@ -126,13 +131,23 @@
             - branch: branch name
             - date: ISO 8601 format date
             - tag: latest tag used in hierarchie
+            - distance: number of commits since the last tag
     """
+    global repos_cache_dict
+    if as_string:
+        global repos_cache
+        if repos_cache is not None:
+            return repos_cache
+    else:
+        if repos_cache_dict is not None:
+            return repos_cache_dict
+
     if sys.platform == "android":
         # FIXME: workaround to avoid trouble on android, need to be fixed properly
-        return u"Cagou android build"
-    from distutils.spawn import find_executable
-    import subprocess
-    KEYS=("node", "node_short", "branch", "date", "tag")
+        repos_cache = u"Cagou android build"
+        return repos_cache
+
+    KEYS=("node", "node_short", "branch", "date", "tag", "distance")
     ori_cwd = os.getcwd()
 
     if is_path:
@@ -140,12 +155,22 @@
     else:
         repos_root = os.path.abspath(os.path.dirname(module.__file__))
 
-    hg_path = find_executable('hg')
+    try:
+        hg_path = procutils.which('hg')[0]
+    except IndexError:
+        log.warning(u"Can't find hg executable")
+        hg_path = None
+        hg_data = {}
 
     if hg_path is not None:
         os.chdir(repos_root)
         try:
-            hg_data_raw = subprocess.check_output(["hg","log", "-r", "-1", "--template","{node}\n{node|short}\n{branch}\n{date|isodate}\n{latesttag}"])
+            hg_data_raw = subprocess.check_output(["hg","log", "-r", "-1", "--template","{node}\n"
+                                                                                        "{node|short}\n"
+                                                                                        "{branch}\n"
+                                                                                        "{date|isodate}\n"
+                                                                                        "{latesttag}\n"
+                                                                                        "{latesttagdistance}"])
         except subprocess.CalledProcessError:
             hg_data = {}
         else:
@@ -158,67 +183,62 @@
         hg_data = {}
 
     if not hg_data:
-        # .hg_data pickle method
-        log.debug(u"Mercurial not available or working, trying other methods")
-        if save_dir_path is None:
-            log.debug(u"trying .hg_data method")
-
-            try:
-                with open(os.path.join(repos_root, '.hg_data')) as f:
-                    import cPickle as pickle
-                    hg_data = pickle.load(f)
-            except IOError as e:
-                log.debug(u"Can't access .hg_data file: {}".format(e))
-            except pickle.UnpicklingError:
-                log.warning(u"Error while reading {}, can't get repos data".format(f.name))
-
-    if not hg_data:
         # .hg/dirstate method
         log.debug(u"trying dirstate method")
         if is_path:
             os.chdir(repos_root)
         else:
-            os.chdir(os.path.abspath(os.path.join('..', repos_root)))
+            os.chdir(os.path.abspath(os.path.dirname(repos_root)))
         try:
             with open('.hg/dirstate') as hg_dirstate:
                 hg_data['node'] = hg_dirstate.read(20).encode('hex')
                 hg_data['node_short'] = hg_data['node'][:12]
         except IOError:
-            log.warning(u"Can't access repository data")
+            log.debug(u"Can't access repository data")
 
     # we restore original working dir
     os.chdir(ori_cwd)
 
-    # data saving
-    if save_dir_path is not None and hg_data:
-        if not os.path.isdir(save_dir_path):
-            log.warning(u"Given path is not a directory, can't save data")
+    if not hg_data:
+        log.debug(u"Mercurial not available or working, trying package version")
+        try:
+            import pkg_resources
+            pkg_version = pkg_resources.get_distribution(C.APP_NAME_FILE).version
+            version, hg_node, hg_distance = pkg_version.split('-')
+        except ImportError:
+            log.warning("pkg_resources not available, can't get package data")
+        except pkg_resources.DistributionNotFound:
+            log.warning("can't retrieve package data")
+        except ValueError:
+            log.warning(u"package version doesn't fit: {pkg_version}".format(pkg_version=pkg_version))
         else:
-            import cPickle as pickle
-            dest_path = os.path.join(save_dir_path, ".hg_data")
-            try:
-                with open(dest_path, 'w') as f:
-                    pickle.dump(hg_data, f, 2)
-            except IOError as e:
-                log.warning(u"Can't save file to {path}: {reason}".format(
-                    path=dest_path, reason=e))
+            if version != C.APP_VERSION:
+                log.error("Incompatible version ({version}) and pkg_version ({pkg_version})".format(
+                    version=C.APP_VERSION, pkg_version=pkg_version))
             else:
-                log.debug(u"repository data saved to {}".format(dest_path))
+                hg_data = {'node_short': hg_node, 'distance': hg_distance}
+
+    repos_cache_dict = hg_data
 
     if as_string:
         if not hg_data:
-            return u'repository data unknown'
-        strings = [u'rev', hg_data['node_short']]
-        try:
-            if hg_data['modified']:
-                strings.append(u"[M]")
-        except KeyError:
-            pass
-        try:
-            strings.extend([u'({branch} {date})'.format(**hg_data)])
-        except KeyError:
-            pass
-
-        return u' '.join(strings)
+            repos_cache = NO_REPOS_DATA
+        else:
+            strings = [u'rev', hg_data['node_short']]
+            try:
+                if hg_data['modified']:
+                    strings.append(u"[M]")
+            except KeyError:
+                pass
+            try:
+                strings.extend([u'({branch} {date})'.format(**hg_data)])
+            except KeyError:
+                pass
+            try:
+                strings.extend([u'+{distance}'.format(**hg_data)])
+            except KeyError:
+                pass
+            repos_cache = u' '.join(strings)
+        return repos_cache
     else:
         return hg_data