Salome HOME
Merge branch 'BR_8_3' of https://codev-tuleap.cea.fr/plugins/git/spns/SAT into BR_8_3
[tools/sat.git] / src / compilation.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 import os
20 import subprocess
21 import sys
22 import shutil
23
24 import src
25
26 C_COMPILE_ENV_LIST = ["CC",
27                       "CXX",
28                       "F77",
29                       "CFLAGS",
30                       "CXXFLAGS",
31                       "LIBS",
32                       "LDFLAGS"]
33
34 class Builder:
35     """Class to handle all construction steps, like cmake, configure, make, ...
36     """
37     def __init__(self,
38                  config,
39                  logger,
40                  product_info,
41                  options = src.options.OptResult(),
42                  check_src=True):
43         self.config = config
44         self.logger = logger
45         self.options = options
46         self.product_info = product_info
47         self.build_dir = src.Path(self.product_info.build_dir)
48         self.source_dir = src.Path(self.product_info.source_dir)
49         self.install_dir = src.Path(self.product_info.install_dir)
50         self.header = ""
51         self.debug_mode = False
52         if "debug" in self.product_info and self.product_info.debug == "yes":
53             self.debug_mode = True
54
55     ##
56     # Shortcut method to log in log file.
57     def log(self, text, level, showInfo=True):
58         self.logger.write(text, level, showInfo)
59         self.logger.logTxtFile.write(src.printcolors.cleancolor(text))
60         self.logger.flush()
61
62     ##
63     # Shortcut method to log a command.
64     def log_command(self, command):
65         self.log("> %s\n" % command, 5)
66
67     ##
68     # Prepares the environment.
69     # Build two environment: one for building and one for testing (launch).
70     def prepare(self):
71
72         if not self.build_dir.exists():
73             # create build dir
74             self.build_dir.make()
75
76         self.log('  build_dir   = %s\n' % str(self.build_dir), 4)
77         self.log('  install_dir = %s\n' % str(self.install_dir), 4)
78         self.log('\n', 4)
79
80         # add products in depend and opt_depend list recursively
81         environ_info = src.product.get_product_dependencies(self.config,
82                                                             self.product_info)
83         #environ_info.append(self.product_info.name)
84
85         # create build environment
86         self.build_environ = src.environment.SalomeEnviron(self.config,
87                                       src.environment.Environ(dict(os.environ)),
88                                       True)
89         self.build_environ.silent = (self.config.USER.output_verbose_level < 5)
90         self.build_environ.set_full_environ(self.logger, environ_info)
91         
92         # create runtime environment
93         self.launch_environ = src.environment.SalomeEnviron(self.config,
94                                       src.environment.Environ(dict(os.environ)),
95                                       False)
96         self.launch_environ.silent = True # no need to show here
97         self.launch_environ.set_full_environ(self.logger, environ_info)
98
99         for ee in C_COMPILE_ENV_LIST:
100             vv = self.build_environ.get(ee)
101             if len(vv) > 0:
102                 self.log("  %s = %s\n" % (ee, vv), 4, False)
103
104         return 0
105
106     ##
107     # Runs cmake with the given options.
108     def cmake(self, options=""):
109
110         cmake_option = options
111         # cmake_option +=' -DCMAKE_VERBOSE_MAKEFILE=ON -DSALOME_CMAKE_DEBUG=ON'
112         if 'cmake_options' in self.product_info:
113             cmake_option += " %s " % " ".join(
114                                         self.product_info.cmake_options.split())
115
116         # add debug option
117         if self.debug_mode:
118             cmake_option += " -DCMAKE_BUILD_TYPE=Debug"
119         else :
120             cmake_option += " -DCMAKE_BUILD_TYPE=Release"
121         
122         # In case CMAKE_GENERATOR is defined in environment, 
123         # use it in spite of automatically detect it
124         if 'cmake_generator' in self.config.APPLICATION:
125             cmake_option += " -DCMAKE_GENERATOR=%s" \
126                                        % self.config.APPLICATION.cmake_generator
127         
128         command = ("cmake %s -DCMAKE_INSTALL_PREFIX=%s %s" %
129                             (cmake_option, self.install_dir, self.source_dir))
130
131         self.log_command(command)
132         # for key in sorted(self.build_environ.environ.environ.keys()):
133             # print key, "  ", self.build_environ.environ.environ[key]
134         res = subprocess.call(command,
135                               shell=True,
136                               cwd=str(self.build_dir),
137                               env=self.build_environ.environ.environ,
138                               stdout=self.logger.logTxtFile,
139                               stderr=subprocess.STDOUT)
140
141         self.put_txt_log_in_appli_log_dir("cmake")
142         if res == 0:
143             return res
144         else:
145             return 1
146
147     ##
148     # Runs build_configure with the given options.
149     def build_configure(self, options=""):
150
151         if 'buildconfigure_options' in self.product_info:
152             options += " %s " % self.product_info.buildconfigure_options
153
154         command = str('%s/build_configure') % (self.source_dir)
155         command = command + " " + options
156         self.log_command(command)
157
158         res = subprocess.call(command,
159                               shell=True,
160                               cwd=str(self.build_dir),
161                               env=self.build_environ.environ.environ,
162                               stdout=self.logger.logTxtFile,
163                               stderr=subprocess.STDOUT)
164         self.put_txt_log_in_appli_log_dir("build_configure")
165         if res == 0:
166             return res
167         else:
168             return 1
169
170     ##
171     # Runs configure with the given options.
172     def configure(self, options=""):
173
174         if 'configure_options' in self.product_info:
175             options += " %s " % self.product_info.configure_options
176
177         command = "%s/configure --prefix=%s" % (self.source_dir,
178                                                 str(self.install_dir))
179
180         command = command + " " + options
181         self.log_command(command)
182
183         res = subprocess.call(command,
184                               shell=True,
185                               cwd=str(self.build_dir),
186                               env=self.build_environ.environ.environ,
187                               stdout=self.logger.logTxtFile,
188                               stderr=subprocess.STDOUT)
189         
190         self.put_txt_log_in_appli_log_dir("configure")
191         if res == 0:
192             return res
193         else:
194             return 1
195
196     def hack_libtool(self):
197         if not os.path.exists(str(self.build_dir + 'libtool')):
198             return
199
200         lf = open(os.path.join(str(self.build_dir), "libtool"), 'r')
201         for line in lf.readlines():
202             if 'hack_libtool' in line:
203                 return
204
205         # fix libtool by replacing CC="<compil>" with hack_libtool function
206         hack_command='''sed -i "s%^CC=\\"\(.*\)\\"%hack_libtool() { \\n\\
207 if test \\"\$(echo \$@ | grep -E '\\\\\\-L/usr/lib(/../lib)?(64)? ')\\" == \\\"\\\" \\n\\
208   then\\n\\
209     cmd=\\"\\1 \$@\\"\\n\\
210   else\\n\\
211     cmd=\\"\\1 \\"\`echo \$@ | sed -r -e 's|(.*)-L/usr/lib(/../lib)?(64)? (.*)|\\\\\\1\\\\\\4 -L/usr/lib\\\\\\3|g'\`\\n\\
212   fi\\n\\
213   \$cmd\\n\\
214 }\\n\\
215 CC=\\"hack_libtool\\"%g" libtool'''
216
217         self.log_command(hack_command)
218         subprocess.call(hack_command,
219                         shell=True,
220                         cwd=str(self.build_dir),
221                         env=self.build_environ.environ.environ,
222                         stdout=self.logger.logTxtFile,
223                         stderr=subprocess.STDOUT)
224
225
226     ##
227     # Runs make to build the module.
228     def make(self, nb_proc, make_opt=""):
229
230         # make
231         command = 'make'
232         command = command + " -j" + str(nb_proc)
233         command = command + " " + make_opt
234         self.log_command(command)
235         res = subprocess.call(command,
236                               shell=True,
237                               cwd=str(self.build_dir),
238                               env=self.build_environ.environ.environ,
239                               stdout=self.logger.logTxtFile,
240                               stderr=subprocess.STDOUT)
241         self.put_txt_log_in_appli_log_dir("make")
242         if res == 0:
243             return res
244         else:
245             return 1
246     
247     ##
248     # Runs msbuild to build the module.
249     def wmake(self,nb_proc, opt_nb_proc = None):
250
251         hh = 'MSBUILD /m:%s' % str(nb_proc)
252         if self.debug_mode:
253             hh += " " + src.printcolors.printcWarning("DEBUG")
254         # make
255         command = 'msbuild'
256         command = command + " /maxcpucount:" + str(nb_proc)
257         if self.debug_mode:
258             command = command + " /p:Configuration=Debug"
259         else:
260             command = command + " /p:Configuration=Release"
261         command = command + " ALL_BUILD.vcxproj"
262
263         self.log_command(command)
264         res = subprocess.call(command,
265                               shell=True,
266                               cwd=str(self.build_dir),
267                               env=self.build_environ.environ.environ,
268                               stdout=self.logger.logTxtFile,
269                               stderr=subprocess.STDOUT)
270         
271         self.put_txt_log_in_appli_log_dir("make")
272         if res == 0:
273             return res
274         else:
275             return 1
276
277     ##
278     # Runs 'make install'.
279     def install(self):
280         if self.config.VARS.dist_name=="Win":
281             command = 'msbuild INSTALL.vcxproj'
282             if self.debug_mode:
283                 command = command + " /p:Configuration=Debug"
284             else:
285                 command = command + " /p:Configuration=Release"
286         else :
287             command = 'make install'
288
289         self.log_command(command)
290
291         res = subprocess.call(command,
292                               shell=True,
293                               cwd=str(self.build_dir),
294                               env=self.build_environ.environ.environ,
295                               stdout=self.logger.logTxtFile,
296                               stderr=subprocess.STDOUT)
297         
298         self.put_txt_log_in_appli_log_dir("makeinstall")
299         if res == 0:
300             return res
301         else:
302             return 1
303
304     ##
305     # Runs 'make_check'.
306     def check(self, command=""):
307         if src.architecture.is_windows():
308             cmd = 'msbuild RUN_TESTS.vcxproj'
309         else :
310             if self.product_info.build_source=="autotools" :
311                 cmd = 'make check'
312             else:
313                 cmd = 'make test'
314         
315         if command:
316             cmd = command
317         
318         self.log_command(cmd)
319
320         res = subprocess.call(cmd,
321                               shell=True,
322                               cwd=str(self.build_dir),
323                               env=self.launch_environ.environ.environ,
324                               stdout=self.logger.logTxtFile,
325                               stderr=subprocess.STDOUT)
326
327         if res == 0:
328             return res
329         else:
330             return 1
331       
332     ##
333     # Performs a default build for this module.
334     def do_default_build(self,
335                          build_conf_options="",
336                          configure_options="",
337                          show_warning=True):
338         use_autotools = False
339         if 'use_autotools' in self.product_info:
340             uc = self.product_info.use_autotools
341             if uc in ['always', 'yes']: 
342                 use_autotools = True
343             elif uc == 'option': 
344                 use_autotools = self.options.autotools
345
346
347         self.use_autotools = use_autotools
348
349         use_ctest = False
350         if 'use_ctest' in self.product_info:
351             uc = self.product_info.use_ctest
352             if uc in ['always', 'yes']: 
353                 use_ctest = True
354             elif uc == 'option': 
355                 use_ctest = self.options.ctest
356
357         self.use_ctest = use_ctest
358
359         if show_warning:
360             cmd = ""
361             if use_autotools: cmd = "(autotools)"
362             if use_ctest: cmd = "(ctest)"
363             
364             self.log("\n", 4, False)
365             self.log("%(module)s: Run default compilation method %(cmd)s\n" % \
366                 { "module": self.module, "cmd": cmd }, 4)
367
368         if use_autotools:
369             if not self.prepare(): return self.get_result()
370             if not self.build_configure(
371                                    build_conf_options): return self.get_result()
372             if not self.configure(configure_options): return self.get_result()
373             if not self.make(): return self.get_result()
374             if not self.install(): return self.get_result()
375             if not self.clean(): return self.get_result()
376            
377         else: # CMake
378             if self.config.VARS.dist_name=='Win':
379                 if not self.wprepare(): return self.get_result()
380                 if not self.cmake(): return self.get_result()
381                 if not self.wmake(): return self.get_result()
382                 if not self.install(): return self.get_result()
383                 if not self.clean(): return self.get_result()
384             else :
385                 if not self.prepare(): return self.get_result()
386                 if not self.cmake(): return self.get_result()
387                 if not self.make(): return self.get_result()
388                 if not self.install(): return self.get_result()
389                 if not self.clean(): return self.get_result()
390
391         return self.get_result()
392
393     ##
394     # Performs a build with a script.
395     def do_python_script_build(self, script, nb_proc):
396         # script found
397         self.logger.write(_("Compile %(product)s using script %(script)s\n") % \
398             { 'product': self.product_info.name,
399              'script': src.printcolors.printcLabel(script) }, 4)
400         try:
401             import imp
402             product = self.product_info.name
403             pymodule = imp.load_source(product + "_compile_script", script)
404             self.nb_proc = nb_proc
405             retcode = pymodule.compil(self.config, self, self.logger)
406         except:
407             __, exceptionValue, exceptionTraceback = sys.exc_info()
408             self.logger.write(str(exceptionValue), 1)
409             import traceback
410             traceback.print_tb(exceptionTraceback)
411             traceback.print_exc()
412             retcode = 1
413         finally:
414             self.put_txt_log_in_appli_log_dir("script")
415
416         return retcode
417
418     def complete_environment(self, make_options):
419         assert self.build_environ is not None
420         # pass additional variables to environment 
421         # (may be used by the build script)
422         self.build_environ.set("SOURCE_DIR", str(self.source_dir))
423         self.build_environ.set("INSTALL_DIR", str(self.install_dir))
424         self.build_environ.set("PRODUCT_INSTALL", str(self.install_dir))
425         self.build_environ.set("BUILD_DIR", str(self.build_dir))
426         self.build_environ.set("PRODUCT_BUILD", str(self.build_dir))
427         self.build_environ.set("MAKE_OPTIONS", make_options)
428         self.build_environ.set("DIST_NAME", self.config.VARS.dist_name)
429         self.build_environ.set("DIST_VERSION", self.config.VARS.dist_version)
430         self.build_environ.set("DIST", self.config.VARS.dist)
431         self.build_environ.set("VERSION", self.product_info.version)
432
433     def do_batch_script_build(self, script, nb_proc):
434
435         if src.architecture.is_windows():
436             make_options = "/maxcpucount:%s" % nb_proc
437         else :
438             make_options = "-j%s" % nb_proc
439
440         self.log_command("  " + _("Run build script %s\n") % script)
441         self.complete_environment(make_options)
442         
443         res = subprocess.call(script, 
444                               shell=True,
445                               stdout=self.logger.logTxtFile,
446                               stderr=subprocess.STDOUT,
447                               cwd=str(self.build_dir),
448                               env=self.build_environ.environ.environ)
449
450         self.put_txt_log_in_appli_log_dir("script")
451         if res == 0:
452             return res
453         else:
454             return 1
455     
456     def do_script_build(self, script, number_of_proc=0):
457         # define make options (may not be used by the script)
458         if number_of_proc==0:
459             nb_proc = src.get_cfg_param(self.product_info,"nb_proc", 0)
460             if nb_proc == 0: 
461                 nb_proc = self.config.VARS.nb_proc
462         else:
463             nb_proc = min(number_of_proc, self.config.VARS.nb_proc)
464             
465         extension = script.split('.')[-1]
466         if extension in ["bat","sh"]:
467             return self.do_batch_script_build(script, nb_proc)
468         if extension == "py":
469             return self.do_python_script_build(script, nb_proc)
470         
471         msg = _("The script %s must have .sh, .bat or .py extension." % script)
472         raise src.SatException(msg)
473     
474     def put_txt_log_in_appli_log_dir(self, file_name):
475         '''Put the txt log (that contain the system logs, like make command
476            output) in the directory <APPLICATION DIR>/LOGS/<product_name>/
477     
478         :param file_name Str: the name of the file to write
479         '''
480         if self.logger.logTxtFile == sys.__stdout__:
481             return
482         dir_where_to_put = os.path.join(self.config.APPLICATION.workdir,
483                                         "LOGS",
484                                         self.product_info.name)
485         file_path = os.path.join(dir_where_to_put, file_name)
486         src.ensure_path_exists(dir_where_to_put)
487         # write the logTxtFile copy it to the destination, and then recreate 
488         # it as it was
489         self.logger.logTxtFile.close()
490         shutil.move(self.logger.txtFilePath, file_path)
491         self.logger.logTxtFile = open(str(self.logger.txtFilePath), 'w')
492         self.logger.logTxtFile.write(open(file_path, "r").read())
493