Salome HOME
option compile --update
[tools/sat.git] / src / system.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2013  CEA/DEN
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18
19 '''
20 In this file : all functions that do a system call, 
21 like open a browser or an editor, or call a git command
22 '''
23
24 import os
25 import subprocess
26 import time
27 import tarfile
28
29 import debug as DBG
30 import utilsSat as UTS
31 import src
32
33 from . import printcolors
34
35 def show_in_editor(editor, filePath, logger):
36     '''open filePath using editor.
37     
38     :param editor str: The editor to use.
39     :param filePath str: The path to the file to open.
40     '''
41     # default editor is vi
42     if editor is None or len(editor) == 0:
43         editor = 'vi'
44     
45     if '%s' not in editor:
46         editor += ' %s'
47
48     try:
49         # launch cmd using subprocess.Popen
50         cmd = editor % filePath
51         logger.write('Launched command:\n' + cmd + '\n', 5)
52         p = subprocess.Popen(cmd, shell=True)
53         p.communicate()
54     except:
55         logger.write(printcolors.printcError(_("Unable to edit file %s\n") 
56                                              % filePath), 1)
57
58 def git_describe(repo_path):
59     '''Use git describe --tags command to return tag description of the git repository"
60     :param repo_path str: The git repository to describe
61     '''
62     git_cmd="cd %s;git describe --tags" % repo_path
63     p = subprocess.Popen(git_cmd, shell=True,
64                     stdin=subprocess.PIPE,
65                     stdout=subprocess.PIPE,
66                     stderr=subprocess.PIPE)
67     p.wait()
68     if p.returncode != 0:
69         return False
70     else:
71         tag_description=p.stdout.readlines()[0].strip()
72         # with python3 this utf8 bytes should be decoded
73         if isinstance(tag_description, bytes):
74             tag_description=tag_description.decode("utf-8", "ignore")
75         return tag_description
76
77
78 def git_extract(from_what, tag, where, logger, environment=None):
79   '''Extracts sources from a git repository.
80
81   :param from_what str: The remote git repository.
82   :param tag str: The tag.
83   :param where str: The path where to extract.
84   :param logger Logger: The logger instance to use.
85   :param environment src.environment.Environ: The environment to source when extracting.
86   :return: True if the extraction is successful
87   :rtype: boolean
88   '''
89   DBG.write("git_extract", [from_what, tag, str(where)])
90   if not where.exists():
91     where.make()
92   where_git = os.path.join(str(where), ".git")
93   if tag == "master" or tag == "HEAD":
94     if src.architecture.is_windows():
95       cmd = "git clone %(remote)s %(where)s"
96     else:
97       cmd = r"""
98 set -x
99 git clone %(remote)s %(where)s
100 touch -d "$(git --git-dir=%(where_git)s  log -1 --format=date_format)" %(where)s
101 """
102 #git --git-dir=%(where_git)s  log -1 --format=date_format > %(where)s/last_commit_date.txt
103     cmd = cmd % {'remote': from_what, 'tag': tag, 'where': str(where), 'where_git': where_git}
104   else:
105     # NOTICE: this command only works with recent version of git
106     #         because --work-tree does not work with an absolute path
107     if src.architecture.is_windows():
108       cmd = "rmdir %(where)s && git clone %(remote)s %(where)s && git --git-dir=%(where_git)s --work-tree=%(where)s checkout %(tag)s"
109     else:
110       cmd = r"""
111 set -x
112 rmdir %(where)s
113 git clone %(remote)s %(where)s && \
114 git --git-dir=%(where_git)s --work-tree=%(where)s checkout %(tag)s
115 touch -d "$(git --git-dir=%(where_git)s  log -1 --format=date_format)" %(where)s
116 """
117     cmd = cmd % {'remote': from_what,
118                  'tag': tag,
119                  'where': str(where),
120                  'where_git': where_git}
121
122
123   cmd=cmd.replace('date_format','"%ai"')
124   logger.logTxtFile.write("\n" + cmd + "\n")
125   logger.logTxtFile.flush()
126
127   DBG.write("cmd", cmd)
128
129   # git commands may fail sometimes for various raisons 
130   # (big module, network troubles, tuleap maintenance)
131   # therefore we give several tries
132   i_try = 0
133   max_number_of_tries = 3
134   sleep_delay = 30  # seconds
135   while (True):
136     i_try += 1
137     rc = UTS.Popen(cmd, cwd=str(where.dir()), env=environment.environ.environ, logger=logger)
138     if rc.isOk() or (i_try>=max_number_of_tries):
139       break
140     logger.write('\ngit command failed! Wait %d seconds and give an other try (%d/%d)\n' % \
141                  (sleep_delay, i_try + 1, max_number_of_tries), 3)
142     time.sleep(sleep_delay) # wait a little
143
144   return rc.isOk()
145
146
147 def git_extract_sub_dir(from_what, tag, where, sub_dir, logger, environment=None):
148   '''Extracts sources from a subtree sub_dir of a git repository.
149
150   :param from_what str: The remote git repository.
151   :param tag str: The tag.
152   :param where str: The path where to extract.
153   :param sub_dir str: The relative path of subtree to extract.
154   :param logger Logger: The logger instance to use.
155   :param environment src.environment.Environ: The environment to source when extracting.
156   :return: True if the extraction is successful
157   :rtype: boolean
158   '''
159   strWhere = str(where)
160   tmpWhere = strWhere + '_tmp'
161   parentWhere = os.path.dirname(strWhere)
162   if not os.path.exists(parentWhere):
163     logger.error("not existing directory: %s" % parentWhere)
164     return False
165   if os.path.isdir(strWhere):
166     logger.error("do not override existing directory: %s" % strWhere)
167     return False
168   aDict = {'remote': from_what,
169            'tag': tag,
170            'sub_dir': sub_dir,
171            'where': strWhere,
172            'parentWhere': parentWhere,
173            'tmpWhere': tmpWhere,
174            }
175   DBG.write("git_extract_sub_dir", aDict)
176   if not src.architecture.is_windows():
177     cmd = r"""
178 set -x
179 export tmpDir=%(tmpWhere)s && \
180 rm -rf $tmpDir
181 git clone %(remote)s $tmpDir && \
182 cd $tmpDir && \
183 git checkout %(tag)s && \
184 mv %(sub_dir)s %(where)s && \
185 git log -1 > %(where)s/README_git_log.txt && \
186 rm -rf $tmpDir
187 """ % aDict
188   else:
189     cmd = r"""
190
191 set tmpDir=%(tmpWhere)s && \
192 rm -rf $tmpDir
193 git clone %(remote)s $tmpDir && \
194 cd $tmpDir && \
195 git checkout %(tag)s && \
196 mv %(sub_dir)s %(where)s && \
197 git log -1 > %(where)s/README_git_log.txt && \
198 rm -rf $tmpDir
199 """ % aDict
200
201   DBG.write("cmd", cmd)
202
203   for nbtry in range(0,3): # retries case of network problem
204     rc = UTS.Popen(cmd, cwd=parentWhere, env=environment.environ.environ, logger=logger)
205     if rc.isOk(): break
206     time.sleep(30) # wait a little
207
208   return rc.isOk()
209
210 def archive_extract(from_what, where, logger):
211     '''Extracts sources from an archive.
212     
213     :param from_what str: The path to the archive.
214     :param where str: The path where to extract.
215     :param logger Logger: The logger instance to use.
216     :return: True if the extraction is successful
217     :rtype: boolean
218     '''
219     try:
220         archive = tarfile.open(from_what)
221         for i in archive.getmembers():
222             archive.extract(i, path=str(where))
223         return True, os.path.commonprefix(archive.getnames())
224     except Exception as exc:
225         logger.write("archive_extract: %s\n" % exc)
226         return False, None
227
228 def cvs_extract(protocol, user, server, base, tag, product, where,
229                 logger, checkout=False, environment=None):
230     '''Extracts sources from a cvs repository.
231     
232     :param protocol str: The cvs protocol.
233     :param user str: The user to be used.
234     :param server str: The remote cvs server.
235     :param base str: .
236     :param tag str: The tag.
237     :param product str: The product.
238     :param where str: The path where to extract.
239     :param logger Logger: The logger instance to use.
240     :param checkout boolean: If true use checkout cvs.
241     :param environment src.environment.Environ: The environment to source when
242                                                 extracting.
243     :return: True if the extraction is successful
244     :rtype: boolean
245     '''
246
247     opttag = ''
248     if tag is not None and len(tag) > 0:
249         opttag = '-r ' + tag
250
251     cmd = 'export'
252     if checkout:
253         cmd = 'checkout'
254     elif len(opttag) == 0:
255         opttag = '-DNOW'
256     
257     if len(protocol) > 0:
258         root = "%s@%s:%s" % (user, server, base)
259         command = "cvs -d :%(protocol)s:%(root)s %(command)s -d %(where)s %(tag)s %(product)s" % \
260             { 'protocol': protocol, 'root': root, 'where': str(where.base()),
261               'tag': opttag, 'product': product, 'command': cmd }
262     else:
263         command = "cvs -d %(root)s %(command)s -d %(where)s %(tag)s %(base)s/%(product)s" % \
264             { 'root': server, 'base': base, 'where': str(where.base()),
265               'tag': opttag, 'product': product, 'command': cmd }
266
267     logger.write(command + "\n", 5)
268
269     if not where.dir().exists():
270         where.dir().make()
271
272     logger.logTxtFile.write("\n" + command + "\n")
273     logger.logTxtFile.flush()        
274     res = subprocess.call(command,
275                           cwd=str(where.dir()),
276                           env=environment.environ.environ,
277                           shell=True,
278                           stdout=logger.logTxtFile,
279                           stderr=subprocess.STDOUT)
280     return (res == 0)
281
282 def svn_extract(user,
283                 from_what,
284                 tag,
285                 where,
286                 logger,
287                 checkout=False,
288                 environment=None):
289     '''Extracts sources from a svn repository.
290     
291     :param user str: The user to be used.
292     :param from_what str: The remote git repository.
293     :param tag str: The tag.
294     :param where str: The path where to extract.
295     :param logger Logger: The logger instance to use.
296     :param checkout boolean: If true use checkout svn.
297     :param environment src.environment.Environ: The environment to source when
298                                                 extracting.
299     :return: True if the extraction is successful
300     :rtype: boolean
301     '''
302     if not where.exists():
303         where.make()
304
305     if checkout:
306         command = "svn checkout --username %(user)s %(remote)s %(where)s" % \
307             { 'remote': from_what, 'user' : user, 'where': str(where) }
308     else:
309         command = ""
310         if os.path.exists(str(where)):
311             command = "/bin/rm -rf %(where)s && " % \
312                 { 'remote': from_what, 'where': str(where) }
313         
314         if tag == "master":
315             command += "svn export --username %(user)s %(remote)s %(where)s" % \
316                 { 'remote': from_what, 'user' : user, 'where': str(where) }       
317         else:
318             command += "svn export -r %(tag)s --username %(user)s %(remote)s %(where)s" % \
319                 { 'tag' : tag, 'remote': from_what, 'user' : user, 'where': str(where) }
320     
321     logger.logTxtFile.write(command + "\n")
322     
323     logger.write(command + "\n", 5)
324     logger.logTxtFile.write("\n" + command + "\n")
325     logger.logTxtFile.flush()
326     res = subprocess.call(command,
327                           cwd=str(where.dir()),
328                           env=environment.environ.environ,
329                           shell=True,
330                           stdout=logger.logTxtFile,
331                           stderr=subprocess.STDOUT)
332     return (res == 0)
333
334 def get_pkg_check_cmd(dist_name):
335     '''Build the command to use for checking if a linux package is installed or not.'''
336
337     if dist_name in ["CO","FD","MG","MD","CO","OS"]: # linux using rpm
338         linux="RH"  
339         manager_msg_err="Error : command failed because sat was not able to find apt command"
340     else:
341         linux="DB"
342         manager_msg_err="Error : command failed because sat was not able to find rpm command"
343
344     # 1- search for an installed package manager (rpm on rh, apt on db)
345     cmd_which_rpm=["which", "rpm"]
346     cmd_which_apt=["which", "apt"]
347     with open(os.devnull, 'w') as devnull:
348         # 1) we search for apt (debian based systems)
349         completed=subprocess.call(cmd_which_apt,stdout=devnull, stderr=subprocess.STDOUT)
350         if completed==0 and linux=="DB":
351             cmd_is_package_installed=["apt", "list", "--installed"]
352         else:
353             # 2) if apt not found search for rpm (redhat)
354             completed=subprocess.call(cmd_which_rpm,stdout=devnull, stderr=subprocess.STDOUT) # only 3.8! ,capture_output=True)
355             if completed==0 and linux=="RH":
356                 cmd_is_package_installed=["rpm", "-q"]
357             else:
358                 # no package manager was found corresponding to dist_name
359                 raise src.SatException(manager_msg_err)
360     return cmd_is_package_installed
361
362 def check_system_pkg(check_cmd,pkg):
363     '''Check if a package is installed
364     :param check_cmd list: the list of command to use system package manager
365     :param user str: the pkg name to check
366     :rtype: str
367     :return: a string with package name with status un message
368     '''
369     # build command
370     cmd_is_package_installed=[]
371     for cmd in check_cmd:
372         cmd_is_package_installed.append(cmd)
373     cmd_is_package_installed.append(pkg)
374     if check_cmd[0]=="apt":
375         # special treatment for apt
376         # (some debian packages have version numbers in their name, and also
377         # apt do not return status)
378         cmd_is_package_installed[-1]+="*" # we don't specify in pyconf the exact name because of version numbers
379         cmd_is_package_installed.append('|')
380         cmd_is_package_installed.append('grep') # add a grep to get an exit status
381         cmd_is_package_installed.append(cmd) 
382         
383     p=subprocess.Popen(cmd_is_package_installed, 
384                        stdout=subprocess.PIPE, 
385                        stderr=subprocess.PIPE)
386     output, err = p.communicate()
387
388     rc = p.returncode
389     if rc==0:
390         msg_status=src.printcolors.printcSuccess("OK")
391         if check_cmd[0]=="rpm": # apt output is too messy for being used
392             # in python3 output is a byte and should be decoded
393             if isinstance(output, bytes):
394                 output = output.decode("utf-8", "ignore")
395             msg_status+=" (" + output.replace('\n',' ') + ")\n" # remove output trailing \n
396     else:
397         msg_status=src.printcolors.printcError("KO") 
398         msg_status+=" (package is not installed!)\n"
399
400     return msg_status
401