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