comparison 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
comparison
equal deleted inserted replaced
2572:7e7f4e344a96 2573:18e2ca5f798e
19 19
20 """ various useful methods """ 20 """ various useful methods """
21 21
22 import unicodedata 22 import unicodedata
23 import os.path 23 import os.path
24 from sat.core.constants import Const as C
24 from sat.core.log import getLogger 25 from sat.core.log import getLogger
25 log = getLogger(__name__) 26 log = getLogger(__name__)
26 import datetime 27 import datetime
27 from dateutil import parser as dateutil_parser 28 from dateutil import parser as dateutil_parser
29 from twisted.python import procutils
30 import subprocess
28 import calendar 31 import calendar
29 import time 32 import time
30 import sys 33 import sys
31 import random 34 import random
32 import inspect 35 import inspect
33 import textwrap 36 import textwrap
34 import functools 37 import functools
35 38
36 39
40 NO_REPOS_DATA = u'repository data unknown'
41 repos_cache_dict = None
42 repos_cache = None
43
44
37 def clean_ustr(ustr): 45 def clean_ustr(ustr):
38 """Clean unicode string 46 """Clean unicode string
39 47
40 remove special characters from unicode string 48 remove special characters from unicode string
41 """ 49 """
101 random.seed() 109 random.seed()
102 if vocabulary is None: 110 if vocabulary is None:
103 vocabulary = [chr(i) for i in range(0x30,0x3A) + range(0x41,0x5B) + range (0x61,0x7B)] 111 vocabulary = [chr(i) for i in range(0x30,0x3A) + range(0x41,0x5B) + range (0x61,0x7B)]
104 return u''.join([random.choice(vocabulary) for i in range(15)]) 112 return u''.join([random.choice(vocabulary) for i in range(15)])
105 113
106 def getRepositoryData(module, as_string=True, is_path=False, save_dir_path=None): 114 def getRepositoryData(module, as_string=True, is_path=False):
107 """Retrieve info on current mecurial repository 115 """Retrieve info on current mecurial repository
108 116
109 Data is gotten by using the following methods, in order: 117 Data is gotten by using the following methods, in order:
110 - using "hg" executable 118 - using "hg" executable
111 - looking for a ".hg_data" file in the root of the module
112 this file must contain the data dictionnary serialized with pickle
113 - looking for a .hg/dirstate in parent directory of module (or in module/.hg if 119 - looking for a .hg/dirstate in parent directory of module (or in module/.hg if
114 is_path is True), and parse dirstate file to get revision 120 is_path is True), and parse dirstate file to get revision
121 - checking package version, which should have repository data when we are on a dev version
115 @param module(unicode): module to look for (e.g. sat, libervia) 122 @param module(unicode): module to look for (e.g. sat, libervia)
116 module can be a path if is_path is True (see below) 123 module can be a path if is_path is True (see below)
117 @param as_string(bool): if True return a string, else return a dictionary 124 @param as_string(bool): if True return a string, else return a dictionary
118 @param is_path(bool): if True "module" is not handled as a module name, but as an 125 @param is_path(bool): if True "module" is not handled as a module name, but as an
119 absolute path to the parent of a ".hg" directory 126 absolute path to the parent of a ".hg" directory
120 @param save_path(str, None): if not None, the value will be saved to given path as a pickled dict
121 /!\\ the .hg_data file in the given directory will be overwritten
122 @return (unicode, dictionary): retrieved info in a nice string, 127 @return (unicode, dictionary): retrieved info in a nice string,
123 or a dictionary with retrieved data (key is not present if data is not found), 128 or a dictionary with retrieved data (key is not present if data is not found),
124 key can be: 129 key can be:
125 - node: full revision number (40 bits) 130 - node: full revision number (40 bits)
126 - branch: branch name 131 - branch: branch name
127 - date: ISO 8601 format date 132 - date: ISO 8601 format date
128 - tag: latest tag used in hierarchie 133 - tag: latest tag used in hierarchie
129 """ 134 - distance: number of commits since the last tag
135 """
136 global repos_cache_dict
137 if as_string:
138 global repos_cache
139 if repos_cache is not None:
140 return repos_cache
141 else:
142 if repos_cache_dict is not None:
143 return repos_cache_dict
144
130 if sys.platform == "android": 145 if sys.platform == "android":
131 # FIXME: workaround to avoid trouble on android, need to be fixed properly 146 # FIXME: workaround to avoid trouble on android, need to be fixed properly
132 return u"Cagou android build" 147 repos_cache = u"Cagou android build"
133 from distutils.spawn import find_executable 148 return repos_cache
134 import subprocess 149
135 KEYS=("node", "node_short", "branch", "date", "tag") 150 KEYS=("node", "node_short", "branch", "date", "tag", "distance")
136 ori_cwd = os.getcwd() 151 ori_cwd = os.getcwd()
137 152
138 if is_path: 153 if is_path:
139 repos_root = os.path.abspath(module) 154 repos_root = os.path.abspath(module)
140 else: 155 else:
141 repos_root = os.path.abspath(os.path.dirname(module.__file__)) 156 repos_root = os.path.abspath(os.path.dirname(module.__file__))
142 157
143 hg_path = find_executable('hg') 158 try:
159 hg_path = procutils.which('hg')[0]
160 except IndexError:
161 log.warning(u"Can't find hg executable")
162 hg_path = None
163 hg_data = {}
144 164
145 if hg_path is not None: 165 if hg_path is not None:
146 os.chdir(repos_root) 166 os.chdir(repos_root)
147 try: 167 try:
148 hg_data_raw = subprocess.check_output(["hg","log", "-r", "-1", "--template","{node}\n{node|short}\n{branch}\n{date|isodate}\n{latesttag}"]) 168 hg_data_raw = subprocess.check_output(["hg","log", "-r", "-1", "--template","{node}\n"
169 "{node|short}\n"
170 "{branch}\n"
171 "{date|isodate}\n"
172 "{latesttag}\n"
173 "{latesttagdistance}"])
149 except subprocess.CalledProcessError: 174 except subprocess.CalledProcessError:
150 hg_data = {} 175 hg_data = {}
151 else: 176 else:
152 hg_data = dict(zip(KEYS, hg_data_raw.split('\n'))) 177 hg_data = dict(zip(KEYS, hg_data_raw.split('\n')))
153 try: 178 try:
154 hg_data['modified'] = '+' in subprocess.check_output(["hg","id","-i"]) 179 hg_data['modified'] = '+' in subprocess.check_output(["hg","id","-i"])
155 except subprocess.CalledProcessError: 180 except subprocess.CalledProcessError:
156 pass 181 pass
157 else: 182 else:
158 hg_data = {} 183 hg_data = {}
159
160 if not hg_data:
161 # .hg_data pickle method
162 log.debug(u"Mercurial not available or working, trying other methods")
163 if save_dir_path is None:
164 log.debug(u"trying .hg_data method")
165
166 try:
167 with open(os.path.join(repos_root, '.hg_data')) as f:
168 import cPickle as pickle
169 hg_data = pickle.load(f)
170 except IOError as e:
171 log.debug(u"Can't access .hg_data file: {}".format(e))
172 except pickle.UnpicklingError:
173 log.warning(u"Error while reading {}, can't get repos data".format(f.name))
174 184
175 if not hg_data: 185 if not hg_data:
176 # .hg/dirstate method 186 # .hg/dirstate method
177 log.debug(u"trying dirstate method") 187 log.debug(u"trying dirstate method")
178 if is_path: 188 if is_path:
179 os.chdir(repos_root) 189 os.chdir(repos_root)
180 else: 190 else:
181 os.chdir(os.path.abspath(os.path.join('..', repos_root))) 191 os.chdir(os.path.abspath(os.path.dirname(repos_root)))
182 try: 192 try:
183 with open('.hg/dirstate') as hg_dirstate: 193 with open('.hg/dirstate') as hg_dirstate:
184 hg_data['node'] = hg_dirstate.read(20).encode('hex') 194 hg_data['node'] = hg_dirstate.read(20).encode('hex')
185 hg_data['node_short'] = hg_data['node'][:12] 195 hg_data['node_short'] = hg_data['node'][:12]
186 except IOError: 196 except IOError:
187 log.warning(u"Can't access repository data") 197 log.debug(u"Can't access repository data")
188 198
189 # we restore original working dir 199 # we restore original working dir
190 os.chdir(ori_cwd) 200 os.chdir(ori_cwd)
191 201
192 # data saving 202 if not hg_data:
193 if save_dir_path is not None and hg_data: 203 log.debug(u"Mercurial not available or working, trying package version")
194 if not os.path.isdir(save_dir_path): 204 try:
195 log.warning(u"Given path is not a directory, can't save data") 205 import pkg_resources
196 else: 206 pkg_version = pkg_resources.get_distribution(C.APP_NAME_FILE).version
197 import cPickle as pickle 207 version, hg_node, hg_distance = pkg_version.split('-')
198 dest_path = os.path.join(save_dir_path, ".hg_data") 208 except ImportError:
199 try: 209 log.warning("pkg_resources not available, can't get package data")
200 with open(dest_path, 'w') as f: 210 except pkg_resources.DistributionNotFound:
201 pickle.dump(hg_data, f, 2) 211 log.warning("can't retrieve package data")
202 except IOError as e: 212 except ValueError:
203 log.warning(u"Can't save file to {path}: {reason}".format( 213 log.warning(u"package version doesn't fit: {pkg_version}".format(pkg_version=pkg_version))
204 path=dest_path, reason=e)) 214 else:
215 if version != C.APP_VERSION:
216 log.error("Incompatible version ({version}) and pkg_version ({pkg_version})".format(
217 version=C.APP_VERSION, pkg_version=pkg_version))
205 else: 218 else:
206 log.debug(u"repository data saved to {}".format(dest_path)) 219 hg_data = {'node_short': hg_node, 'distance': hg_distance}
220
221 repos_cache_dict = hg_data
207 222
208 if as_string: 223 if as_string:
209 if not hg_data: 224 if not hg_data:
210 return u'repository data unknown' 225 repos_cache = NO_REPOS_DATA
211 strings = [u'rev', hg_data['node_short']] 226 else:
212 try: 227 strings = [u'rev', hg_data['node_short']]
213 if hg_data['modified']: 228 try:
214 strings.append(u"[M]") 229 if hg_data['modified']:
215 except KeyError: 230 strings.append(u"[M]")
216 pass 231 except KeyError:
217 try: 232 pass
218 strings.extend([u'({branch} {date})'.format(**hg_data)]) 233 try:
219 except KeyError: 234 strings.extend([u'({branch} {date})'.format(**hg_data)])
220 pass 235 except KeyError:
221 236 pass
222 return u' '.join(strings) 237 try:
238 strings.extend([u'+{distance}'.format(**hg_data)])
239 except KeyError:
240 pass
241 repos_cache = u' '.join(strings)
242 return repos_cache
223 else: 243 else:
224 return hg_data 244 return hg_data