Salome HOME
553aab9100c0ba2d063a9ebabdcbd10531f87c85
[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 as SP
26 import time
27 import tarfile
28 import psutil
29 import time
30  
31
32 import debug as DBG
33 import utilsSat as UTS
34 import src
35
36 from . import printcolors
37
38 def show_in_editor(editor, filePath, logger):
39     '''open filePath using editor.
40     
41     :param editor str: The editor to use.
42     :param filePath str: The path to the file to open.
43     '''
44     # default editor is vi
45     if editor is None or len(editor) == 0:
46         editor = 'vi'
47     
48     if '%s' not in editor:
49         editor += ' %s'
50
51     try:
52         # launch cmd using subprocess.Popen
53         cmd = editor % filePath
54         logger.write('Launched command:\n' + cmd + '\n', 5)
55         p = SP.Popen(cmd, shell=True)
56         p.communicate()
57     except:
58         logger.write(printcolors.printcError(_("Unable to edit file %s\n") 
59                                              % filePath), 1)
60
61 def show_in_webbrowser(editor, filePath, logger):
62     '''open filePath using web browser firefox, chromium etc...
63     if file is xml, previous http sever is done before to fix new security problems
64     
65     :param editor str: The web browser to use.
66     :param filePath str: The path to the file to open.
67     '''
68     # default editor is firefox
69     if editor is None or len(editor) == 0:
70         editor = 'firefox'
71     
72     path, namefile = os.path.split(filePath)
73     basefile, ext = os.path.splitext(namefile)
74
75     # previouly http.server 8765 kill ... or not ? TODO wait and see REX
76     for proc in psutil.process_iter():
77       # help(proc)
78       cmdline = " ".join(proc.cmdline())
79       if "python3 -m http.server 8765" in cmdline:
80         print("kill previous process '%s'" % cmdline)
81         proc.kill()  # TODO may be not owner ? -> change 8765+n
82         
83     # The argument -u will make it so that stdout and stderr are not buffered 
84     # and will be written in real time.
85     cmd = """
86 set -x
87 cd %(path)s
88 python3 -m http.server 8765 &> /dev/null &
89 %(editor)s http://localhost:8765/%(namefile)s
90 """ % {"path": path, "editor": editor, "namefile": namefile}
91
92     # print("show_in_webbrowser:\n%s" % cmd)
93     
94     try:
95         # launch cmd using subprocess.Popen
96         logger.write('Launched command:\n%s\n' % cmd, 5)
97         p = SP.Popen(cmd, shell=True, stdout=SP.PIPE, stderr=SP.STDOUT)
98         res_out, res_err = p.communicate() # res_err = None as stderr=SP.STDOUT
99         # print("Launched command stdout:\n%s" % res_out)
100     except Exception as e:
101         logger.write(printcolors.printcError(_("Unable to display file %s\n%s\n") 
102                                              % (filePath, e)), 1)
103     
104
105 def git_describe(repo_path):
106     '''Use git describe --tags command to return tag description of the git repository"
107     :param repo_path str: The git repository to describe
108     '''
109     git_cmd="cd %s;git describe --tags" % repo_path
110     p = SP.Popen(git_cmd, shell=True, stdin=SP.PIPE, stdout=SP.PIPE, stderr=SP.PIPE)
111     p.wait()
112     if p.returncode != 0:
113         return False
114     else:
115         tag_description=p.stdout.readlines()[0].strip()
116         # with python3 this utf8 bytes should be decoded
117         if isinstance(tag_description, bytes):
118             tag_description=tag_description.decode("utf-8", "ignore")
119         return tag_description
120
121
122 def git_extract(from_what, tag, git_options, where, logger, environment=None):
123   '''Extracts sources from a git repository.
124
125   :param from_what str: The remote git repository.
126   :param tag str: The tag.
127   :param git_options str: git options
128   :param where str: The path where to extract.
129   :param logger Logger: The logger instance to use.
130   :param environment src.environment.Environ: The environment to source when extracting.
131   :return: True if the extraction is successful
132   :rtype: boolean
133   '''
134   DBG.write("git_extract", [from_what, tag, str(where)])
135   if not where.exists():
136     where.make()
137   where_git = os.path.join(str(where), ".git")
138   if tag == "master" or tag == "HEAD":
139     if src.architecture.is_windows():
140       cmd = "git clone %(git_options)s %(remote)s %(where)s"
141     else:
142       cmd = r"""
143 set -x
144 git clone %(git_options)s %(remote)s %(where)s
145 res=$?
146 if [ $res -eq 0 ]; then
147   touch -d "$(git --git-dir=%(where_git)s  log -1 --format=date_format)" %(where)s
148 fi
149 exit $res
150 """
151     cmd = cmd % {'git_options': git_options, 'remote': from_what, 'tag': tag, 'where': str(where), 'where_git': where_git}
152   else:
153     # NOTICE: this command only works with recent version of git
154     #         because --work-tree does not work with an absolute path
155     if src.architecture.is_windows():
156       cmd = "rm -rf %(where)s && git clone %(git_options)s %(remote)s %(where)s && git --git-dir=%(where_git)s --work-tree=%(where)s checkout %(tag)s"
157     else:
158 # for sat compile --update : changes the date of directory, only for branches, not tag
159       cmd = r"""
160 set -x
161 rm -rf %(where)s
162 git clone %(git_options)s %(remote)s %(where)s && \
163 git --git-dir=%(where_git)s --work-tree=%(where)s checkout %(tag)s
164 res=$?
165 git --git-dir=%(where_git)s status | grep HEAD
166 if [ $res -eq 0 -a $? -ne 0 ]; then
167   touch -d "$(git --git-dir=%(where_git)s  log -1 --format=date_format)" %(where)s
168 fi
169 exit $res
170 """
171     cmd = cmd % {'git_options': git_options,
172                  'remote': from_what,
173                  'tag': tag,
174                  'where': str(where),
175                  'where_git': where_git}
176
177
178   cmd=cmd.replace('date_format', '"%ai"')
179   logger.logTxtFile.write("\n" + cmd + "\n")
180   logger.logTxtFile.flush()
181
182   DBG.write("cmd", cmd)
183   # git commands may fail sometimes for various raisons 
184   # (big module, network troubles, tuleap maintenance)
185   # therefore we give several tries
186   i_try = 0
187   max_number_of_tries = 3
188   sleep_delay = 30  # seconds
189   while (True):
190     i_try += 1
191     rc = UTS.Popen(cmd, cwd=str(where.dir()), env=environment.environ.environ, logger=logger)
192     if rc.isOk() or (i_try>=max_number_of_tries):
193       break
194     logger.write('\ngit command failed! Wait %d seconds and give an other try (%d/%d)\n' % \
195                  (sleep_delay, i_try + 1, max_number_of_tries), 3)
196     time.sleep(sleep_delay) # wait a little
197
198   return rc.isOk()
199
200
201 def git_extract_sub_dir(from_what, tag, git_options, where, sub_dir, logger, environment=None):
202   '''Extracts sources from a subtree sub_dir of a git repository.
203
204   :param from_what str: The remote git repository.
205   :param tag str: The tag.
206   :param git_options str: git options
207   :param where str: The path where to extract.
208   :param sub_dir str: The relative path of subtree to extract.
209   :param logger Logger: The logger instance to use.
210   :param environment src.environment.Environ: The environment to source when extracting.
211   :return: True if the extraction is successful
212   :rtype: boolean
213   '''
214   strWhere = str(where)
215   tmpWhere = strWhere + '_tmp'
216   parentWhere = os.path.dirname(strWhere)
217   if not os.path.exists(parentWhere):
218     logger.error("not existing directory: %s" % parentWhere)
219     return False
220   if os.path.isdir(strWhere):
221     logger.error("do not override existing directory: %s" % strWhere)
222     return False
223   aDict = {'git_options': git_options,
224            'remote': from_what,
225            'tag': tag,
226            'sub_dir': sub_dir,
227            'where': strWhere,
228            'parentWhere': parentWhere,
229            'tmpWhere': tmpWhere,
230            }
231   DBG.write("git_extract_sub_dir", aDict)
232   if not src.architecture.is_windows():
233     cmd = r"""
234 set -x
235 export tmpDir=%(tmpWhere)s && \
236 rm -rf $tmpDir
237 git clone %(git_options)s %(remote)s $tmpDir && \
238 cd $tmpDir && \
239 git checkout %(tag)s && \
240 mv %(sub_dir)s %(where)s && \
241 git log -1 > %(where)s/README_git_log.txt && \
242 rm -rf $tmpDir
243 """ % aDict
244   else:
245     cmd = r"""
246
247 set tmpDir=%(tmpWhere)s && \
248 rm -rf $tmpDir
249 git clone %(git_options)s %(remote)s $tmpDir && \
250 cd $tmpDir && \
251 git checkout %(tag)s && \
252 mv %(sub_dir)s %(where)s && \
253 git log -1 > %(where)s/README_git_log.txt && \
254 rm -rf $tmpDir
255 """ % aDict
256
257   DBG.write("cmd", cmd)
258
259   for nbtry in range(0,3): # retries case of network problem
260     rc = UTS.Popen(cmd, cwd=parentWhere, env=environment.environ.environ, logger=logger)
261     if rc.isOk(): break
262     time.sleep(30) # wait a little
263
264   return rc.isOk()
265
266 def archive_extract(from_what, where, logger):
267     '''Extracts sources from an archive.
268     
269     :param from_what str: The path to the archive.
270     :param where str: The path where to extract.
271     :param logger Logger: The logger instance to use.
272     :return: True if the extraction is successful
273     :rtype: boolean
274     '''
275     try:
276         archive = tarfile.open(from_what)
277         for i in archive.getmembers():
278             archive.extract(i, path=str(where))
279         return True, os.path.commonprefix(archive.getnames())
280     except Exception as exc:
281         logger.write("archive_extract: %s\n" % exc)
282         return False, None
283
284 def cvs_extract(protocol, user, server, base, tag, product, where,
285                 logger, checkout=False, environment=None):
286     '''Extracts sources from a cvs repository.
287     
288     :param protocol str: The cvs protocol.
289     :param user str: The user to be used.
290     :param server str: The remote cvs server.
291     :param base str: .
292     :param tag str: The tag.
293     :param product str: The product.
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 cvs.
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
303     opttag = ''
304     if tag is not None and len(tag) > 0:
305         opttag = '-r ' + tag
306
307     cmd = 'export'
308     if checkout:
309         cmd = 'checkout'
310     elif len(opttag) == 0:
311         opttag = '-DNOW'
312     
313     if len(protocol) > 0:
314         root = "%s@%s:%s" % (user, server, base)
315         command = "cvs -d :%(protocol)s:%(root)s %(command)s -d %(where)s %(tag)s %(product)s" % \
316             { 'protocol': protocol, 'root': root, 'where': str(where.base()),
317               'tag': opttag, 'product': product, 'command': cmd }
318     else:
319         command = "cvs -d %(root)s %(command)s -d %(where)s %(tag)s %(base)s/%(product)s" % \
320             { 'root': server, 'base': base, 'where': str(where.base()),
321               'tag': opttag, 'product': product, 'command': cmd }
322
323     logger.write(command + "\n", 5)
324
325     if not where.dir().exists():
326         where.dir().make()
327
328     logger.logTxtFile.write("\n" + command + "\n")
329     logger.logTxtFile.flush()        
330     res = SP.call(command, cwd=str(where.dir()),
331                            env=environment.environ.environ,
332                            shell=True,
333                            stdout=logger.logTxtFile,
334                            stderr=SP.STDOUT)
335     return (res == 0)
336
337 def svn_extract(user,
338                 from_what,
339                 tag,
340                 where,
341                 logger,
342                 checkout=False,
343                 environment=None):
344     '''Extracts sources from a svn repository.
345     
346     :param user str: The user to be used.
347     :param from_what str: The remote git repository.
348     :param tag str: The tag.
349     :param where str: The path where to extract.
350     :param logger Logger: The logger instance to use.
351     :param checkout boolean: If true use checkout svn.
352     :param environment src.environment.Environ: The environment to source when
353                                                 extracting.
354     :return: True if the extraction is successful
355     :rtype: boolean
356     '''
357     if not where.exists():
358         where.make()
359
360     if checkout:
361         command = "svn checkout --username %(user)s %(remote)s %(where)s" % \
362             { 'remote': from_what, 'user' : user, 'where': str(where) }
363     else:
364         command = ""
365         if os.path.exists(str(where)):
366             command = "/bin/rm -rf %(where)s && " % \
367                 { 'remote': from_what, 'where': str(where) }
368         
369         if tag == "master":
370             command += "svn export --username %(user)s %(remote)s %(where)s" % \
371                 { 'remote': from_what, 'user' : user, 'where': str(where) }       
372         else:
373             command += "svn export -r %(tag)s --username %(user)s %(remote)s %(where)s" % \
374                 { 'tag' : tag, 'remote': from_what, 'user' : user, 'where': str(where) }
375     
376     logger.logTxtFile.write(command + "\n")
377     
378     logger.write(command + "\n", 5)
379     logger.logTxtFile.write("\n" + command + "\n")
380     logger.logTxtFile.flush()
381     res = SP.call(command, cwd=str(where.dir()),
382                            env=environment.environ.environ,
383                            shell=True,
384                            stdout=logger.logTxtFile,
385                            stderr=SP.STDOUT)
386     return (res == 0)
387
388 def get_pkg_check_cmd(dist_name):
389     '''Build the command to use for checking if a linux package is installed or not.'''
390
391     if dist_name in ["CO","FD","MG","MD","CO","OS"]: # linux using rpm
392         linux="RH"  
393         manager_msg_err="Error : command failed because sat was not able to find apt command"
394     else:
395         linux="DB"
396         manager_msg_err="Error : command failed because sat was not able to find rpm command"
397
398     # 1- search for an installed package manager (rpm on rh, apt on db)
399     cmd_which_rpm=["which", "rpm"]
400     cmd_which_apt=["which", "apt"]
401     with open(os.devnull, 'w') as devnull:
402         # 1) we search for apt (debian based systems)
403         completed=SP.call(cmd_which_apt,stdout=devnull, stderr=SP.STDOUT)
404         if completed==0 and linux=="DB":
405             cmd_is_package_installed=["apt", "list", "--installed"]
406         else:
407             # 2) if apt not found search for rpm (redhat)
408             completed=SP.call(cmd_which_rpm,stdout=devnull, stderr=SP.STDOUT) # only 3.8! ,capture_output=True)
409             if completed==0 and linux=="RH":
410                 cmd_is_package_installed=["rpm", "-q"]
411             else:
412                 # no package manager was found corresponding to dist_name
413                 raise src.SatException(manager_msg_err)
414     return cmd_is_package_installed
415
416 def check_system_pkg(check_cmd,pkg):
417     '''Check if a package is installed
418     :param check_cmd list: the list of command to use system package manager
419     :param user str: the pkg name to check
420     :rtype: str
421     :return: a string with package name with status un message
422     '''
423     # build command
424     FNULL = open(os.devnull, 'w')
425     cmd_is_package_installed=[]
426     for cmd in check_cmd:
427         cmd_is_package_installed.append(cmd)
428     cmd_is_package_installed.append(pkg)
429
430
431     if check_cmd[0]=="apt":
432         # special treatment for apt
433         # apt output is too messy for being used
434         # some debian packages have version numbers in their name, we need to add a *
435         # also apt do not return status, we need to use grep
436         # and apt output is too messy for being used 
437         cmd_is_package_installed[-1]+="*" # we don't specify in pyconf the exact name because of version numbers
438         p=SP.Popen(cmd_is_package_installed, stdout=SP.PIPE, stderr=FNULL)
439         try:
440             output = SP.check_output(['grep', pkg], stdin=p.stdout)
441             msg_status=src.printcolors.printcSuccess("OK")
442         except:
443             msg_status=src.printcolors.printcError("KO")
444             msg_status+=" (package is not installed!)\n"
445     else:
446         p=SP.Popen(cmd_is_package_installed, stdout=SP.PIPE, stderr=FNULL)
447         output, err = p.communicate()
448         rc = p.returncode
449         if rc==0:
450             msg_status=src.printcolors.printcSuccess("OK")
451             # in python3 output is a byte and should be decoded
452             if isinstance(output, bytes):
453                 output = output.decode("utf-8", "ignore")
454             msg_status+=" (" + output.replace('\n',' ') + ")\n" # remove output trailing \n
455         else:
456             msg_status=src.printcolors.printcError("KO")
457             msg_status+=" (package is not installed!)\n"
458
459     return msg_status