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