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