Salome HOME
9a2d0956c73ebc5dc34f9ca2d9ab79c18c4a7936
[tools/sat.git] / commands / compile.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2012  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
21 import src
22
23 # Compatibility python 2/3 for input function
24 # input stays input for python 3 and input = raw_input for python 2
25 try: 
26     input = raw_input
27 except NameError: 
28     pass
29
30 # Define all possible option for the compile command :  sat compile <options>
31 parser = src.options.Options()
32 parser.add_option('p', 'products', 'list2', 'products',
33     _('Optional: products to configure. This option can be'
34     ' passed several time to configure several products.'))
35 parser.add_option('', 'with_fathers', 'boolean', 'fathers',
36     _("Optional: build all necessary products to the given product (KERNEL is "
37       "build before building GUI)."), False)
38 parser.add_option('', 'with_children', 'boolean', 'children',
39     _("Optional: build all products using the given product (all SMESH plugins"
40       " are build after SMESH)."), False)
41 parser.add_option('', 'clean_all', 'boolean', 'clean_all',
42     _("Optional: clean BUILD dir and INSTALL dir before building product."),
43     False)
44 parser.add_option('', 'clean_install', 'boolean', 'clean_install',
45     _("Optional: clean INSTALL dir before building product."), False)
46 parser.add_option('', 'make_flags', 'string', 'makeflags',
47     _("Optional: add extra options to the 'make' command."))
48 parser.add_option('', 'show', 'boolean', 'no_compile',
49     _("Optional: DO NOT COMPILE just show if products are installed or not."),
50     False)
51 parser.add_option('', 'stop_first_fail', 'boolean', 'stop_first_fail', _(
52                   "Optional: Stops the command at first product compilation"
53                   " fail."), False)
54
55 def get_products_list(options, cfg, logger):
56     '''method that gives the product list with their informations from 
57        configuration regarding the passed options.
58     
59     :param options Options: The Options instance that stores the commands 
60                             arguments
61     :param cfg Config: The global configuration
62     :param logger Logger: The logger instance to use for the display and 
63                           logging
64     :return: The list of (product name, product_informations).
65     :rtype: List
66     '''
67     # Get the products to be prepared, regarding the options
68     if options.products is None:
69         # No options, get all products sources
70         products = cfg.APPLICATION.products
71     else:
72         # if option --products, check that all products of the command line
73         # are present in the application.
74         products = options.products
75         for p in products:
76             if p not in cfg.APPLICATION.products:
77                 raise src.SatException(_("Product %(product)s "
78                             "not defined in application %(application)s") %
79                         { 'product': p, 'application': cfg.VARS.application} )
80     
81     # Construct the list of tuple containing 
82     # the products name and their definition
83     products_infos = src.product.get_products_infos(products, cfg)
84     
85     products_infos = [pi for pi in products_infos if not(
86                                      src.product.product_is_fixed(pi[1]))]
87     
88     return products_infos
89
90 def get_children(config, p_name_p_info):
91     l_res = []
92     p_name, __ = p_name_p_info
93     # Get all products of the application
94     products = config.APPLICATION.products
95     products_infos = src.product.get_products_infos(products, config)
96     for p_name_potential_child, p_info_potential_child in products_infos:
97         if ("depend" in p_info_potential_child and 
98                 p_name in p_info_potential_child.depend):
99             l_res.append(p_name_potential_child)
100     return l_res
101
102 def get_recursive_children(config, p_name_p_info, without_native_fixed=False):
103     """ Get the recursive list of the product that depend on 
104         the product defined by prod_info
105     
106     :param config Config: The global configuration
107     :param prod_info Config: The specific config of the product
108     :param without_native_fixed boolean: If true, do not include the fixed
109                                          or native products in the result
110     :return: The list of product_informations.
111     :rtype: List
112     """
113     p_name, __ = p_name_p_info
114     # Initialization of the resulting list
115     l_children = []
116     
117     # Get the direct children (not recursive)
118     l_direct_children = get_children(config, p_name_p_info)
119     # Minimal case : no child
120     if l_direct_children == []:
121         return []
122     # Add the children and call the function to get the children of the
123     # children
124     for child_name in l_direct_children:
125         l_children_name = [pn_pi[0] for pn_pi in l_children]
126         if child_name not in l_children_name:
127             if child_name not in config.APPLICATION.products:
128                 msg = _("The product %(child_name)s that is in %(product_nam"
129                         "e)s children is not present in application "
130                         "%(appli_name)s" % {"child_name" : child_name, 
131                                     "product_name" : p_name.name, 
132                                     "appli_name" : config.VARS.application})
133                 raise src.SatException(msg)
134             prod_info_child = src.product.get_product_config(config,
135                                                               child_name)
136             pname_pinfo_child = (prod_info_child.name, prod_info_child)
137             # Do not append the child if it is native or fixed and 
138             # the corresponding parameter is called
139             if without_native_fixed:
140                 if not(src.product.product_is_native(prod_info_child) or 
141                        src.product.product_is_fixed(prod_info_child)):
142                     l_children.append(pname_pinfo_child)
143             else:
144                 l_children.append(pname_pinfo_child)
145             # Get the children of the children
146             l_grand_children = get_recursive_children(config,
147                                 pname_pinfo_child,
148                                 without_native_fixed = without_native_fixed)
149             l_children += l_grand_children
150     return l_children
151
152 def get_recursive_fathers(config, p_name_p_info, without_native_fixed=False):
153     """ Get the recursive list of the dependencies of the product defined by
154         prod_info
155     
156     :param config Config: The global configuration
157     :param prod_info Config: The specific config of the product
158     :param without_native_fixed boolean: If true, do not include the fixed
159                                          or native products in the result
160     :return: The list of product_informations.
161     :rtype: List
162     """
163     p_name, p_info = p_name_p_info
164     # Initialization of the resulting list
165     l_fathers = []
166     # Minimal case : no dependencies
167     if "depend" not in p_info or p_info.depend == []:
168         return []
169     # Add the dependencies and call the function to get the dependencies of the
170     # dependencies
171     for father_name in p_info.depend:
172         l_fathers_name = [pn_pi[0] for pn_pi in l_fathers]
173         if father_name not in l_fathers_name:
174             if father_name not in config.APPLICATION.products:
175                 msg = _("The product %(father_name)s that is in %(product_nam"
176                         "e)s dependencies is not present in application "
177                         "%(appli_name)s" % {"father_name" : father_name, 
178                                     "product_name" : p_name, 
179                                     "appli_name" : config.VARS.application})
180                 raise src.SatException(msg)
181             prod_info_father = src.product.get_product_config(config,
182                                                               father_name)
183             pname_pinfo_father = (prod_info_father.name, prod_info_father)
184             # Do not append the father if it is native or fixed and 
185             # the corresponding parameter is called
186             if without_native_fixed:
187                 if not(src.product.product_is_native(prod_info_father) or 
188                        src.product.product_is_fixed(prod_info_father)):
189                     l_fathers.append(pname_pinfo_father)
190             else:
191                 l_fathers.append(pname_pinfo_father)
192             # Get the dependencies of the dependency
193             l_grand_fathers = get_recursive_fathers(config,
194                                 pname_pinfo_father,
195                                 without_native_fixed = without_native_fixed)
196             for item in l_grand_fathers:
197                 if item not in l_fathers:
198                     l_fathers.append(item)
199     return l_fathers
200
201 def sort_products(config, p_infos):
202     """ Sort the p_infos regarding the dependencies between the products
203     
204     :param config Config: The global configuration
205     :param p_infos list: List of (str, Config) => (product_name, product_info)
206     """
207     l_prod_sorted = src.deepcopy_list(p_infos)
208     for prod in p_infos:
209         l_fathers = get_recursive_fathers(config,
210                                           prod,
211                                           without_native_fixed=True)
212         l_fathers = [father for father in l_fathers if father in p_infos]
213         if l_fathers == []:
214             continue
215         for p_sorted in l_prod_sorted:
216             if p_sorted in l_fathers:
217                 l_fathers.remove(p_sorted)
218             if l_fathers==[]:
219                 l_prod_sorted.remove(prod)
220                 l_prod_sorted.insert(l_prod_sorted.index(p_sorted)+1, prod)
221                 break
222         
223     return l_prod_sorted
224
225 def extend_with_fathers(config, p_infos):
226     p_infos_res = src.deepcopy_list(p_infos)
227     for p_name_p_info in p_infos:
228         fathers = get_recursive_fathers(config,
229                                         p_name_p_info,
230                                         without_native_fixed=True)
231         for p_name_p_info_father in fathers:
232             if p_name_p_info_father not in p_infos_res:
233                 p_infos_res.append(p_name_p_info_father)
234     return p_infos_res
235
236 def extend_with_children(config, p_infos):
237     p_infos_res = src.deepcopy_list(p_infos)
238     for p_name_p_info in p_infos:
239         children = get_recursive_children(config,
240                                         p_name_p_info,
241                                         without_native_fixed=True)
242         for p_name_p_info_child in children:
243             if p_name_p_info_child not in p_infos_res:
244                 p_infos_res.append(p_name_p_info_child)
245     return p_infos_res    
246
247 def check_dependencies(config, p_name_p_info):
248     l_depends_not_installed = []
249     fathers = get_recursive_fathers(config, p_name_p_info, without_native_fixed=True)
250     for p_name_father, p_info_father in fathers:
251         if not(src.product.check_installation(p_info_father)):
252             l_depends_not_installed.append(p_name_father)
253     return l_depends_not_installed
254
255 def log_step(logger, header, step):
256     logger.write("\r%s%s" % (header, " " * 30), 3)
257     logger.write("\r%s%s" % (header, step), 3)
258     logger.write("\n==== %s \n" % src.printcolors.printcInfo(step), 4)
259     logger.flush()
260
261 def log_res_step(logger, res):
262     if res == 0:
263         logger.write("%s \n" % src.printcolors.printcSuccess("OK"), 4)
264         logger.flush()
265     else:
266         logger.write("%s \n" % src.printcolors.printcError("KO"), 4)
267         logger.flush()
268
269 def compile_all_products(sat, config, options, products_infos, logger):
270     '''Execute the proper configuration commands 
271        in each product build directory.
272
273     :param config Config: The global configuration
274     :param products_info list: List of 
275                                  (str, Config) => (product_name, product_info)
276     :param logger Logger: The logger instance to use for the display and logging
277     :return: the number of failing commands.
278     :rtype: int
279     '''
280     res = 0
281     for p_name_info in products_infos:
282         
283         p_name, p_info = p_name_info
284         
285         # Logging
286         len_end_line = 30
287         logger.write("\n", 4, False)
288         logger.write("################ ", 4)
289         header = _("Compilation of %s") % src.printcolors.printcLabel(p_name)
290         header += " %s " % ("." * (len_end_line - len(p_name)))
291         logger.write(header, 3)
292         logger.write("\n", 4, False)
293         logger.flush()
294
295         # Do nothing if the product is not compilable
296         if ("properties" in p_info and "compilation" in p_info.properties and 
297                                             p_info.properties.compilation == "no"):
298             log_step(logger, header, "ignored")
299             logger.write("\n", 3, False)
300             continue
301
302         # Do nothing if the product is native
303         if src.product.product_is_native(p_info):
304             log_step(logger, header, "native")
305             logger.write("\n", 3, False)
306             continue
307
308         # Clean the build and the install directories 
309         # if the corresponding options was called
310         if options.clean_all:
311             log_step(logger, header, "CLEAN BUILD AND INSTALL")
312             sat.clean(config.VARS.application + 
313                       " --products " + p_name + 
314                       " --build --install",
315                       batch=True,
316                       verbose=0,
317                       logger_add_link = logger)
318         
319         # Clean the the install directory 
320         # if the corresponding option was called
321         if options.clean_install and not options.clean_all:
322             log_step(logger, header, "CLEAN INSTALL")
323             sat.clean(config.VARS.application + 
324                       " --products " + p_name + 
325                       " --install",
326                       batch=True,
327                       verbose=0,
328                       logger_add_link = logger)
329         
330         # Check if it was already successfully installed
331         if src.product.check_installation(p_info):
332             logger.write(_("Already installed\n"))
333             continue
334         
335         # If the show option was called, do not launch the compilation
336         if options.no_compile:
337             logger.write(_("Not installed\n"))
338             continue
339         
340         # Check if the dependencies are installed
341         l_depends_not_installed = check_dependencies(config, p_name_info)
342         if len(l_depends_not_installed) > 0:
343             log_step(logger, header, "")
344             logger.write(src.printcolors.printcError(
345                     _("ERROR : the following product(s) is(are) mandatory: ")))
346             for prod_name in l_depends_not_installed:
347                 logger.write(src.printcolors.printcError(prod_name + " "))
348             logger.write("\n")
349             continue
350         
351         # Call the function to compile the product
352         res_prod, len_end_line, error_step = compile_product(sat,
353                                                              p_name_info,
354                                                              config,
355                                                              options,
356                                                              logger,
357                                                              header,
358                                                              len_end_line)
359         
360         if res_prod != 0:
361             # Clean the install directory if there is any
362             logger.write(_("Cleaning the install directory if there is any\n"),
363                          5)
364             sat.clean(config.VARS.application + 
365                       " --products " + p_name + 
366                       " --install",
367                       batch=True,
368                       verbose=0,
369                       logger_add_link = logger)
370             res += 1
371             
372         # Log the result
373         if res_prod > 0:
374             logger.write("\r%s%s" % (header, " " * len_end_line), 3)
375             logger.write("\r" + header + src.printcolors.printcError("KO ") + error_step)
376             logger.write("\n==== %(KO)s in compile of %(name)s \n" %
377                 { "name" : p_name , "KO" : src.printcolors.printcInfo("ERROR")}, 4)
378             logger.flush()
379         else:
380             logger.write("\r%s%s" % (header, " " * len_end_line), 3)
381             logger.write("\r" + header + src.printcolors.printcSuccess("OK"))
382             logger.write(_("\nINSTALL directory = %s" % 
383                            src.printcolors.printcInfo(p_info.install_dir)), 3)
384             logger.write("\n==== %s \n" % src.printcolors.printcInfo("OK"), 4)
385             logger.write("\n==== Compilation of %(name)s %(OK)s \n" %
386                 { "name" : p_name , "OK" : src.printcolors.printcInfo("OK")}, 4)
387             logger.flush()
388         logger.write("\n", 3, False)
389         
390         
391         if res_prod != 0 and options.stop_first_fail:
392             break
393         
394     return res
395
396 def compile_product(sat, p_name_info, config, options, logger, header, len_end):
397     '''Execute the proper configuration command(s) 
398        in the product build directory.
399     
400     :param p_name_info tuple: (str, Config) => (product_name, product_info)
401     :param config Config: The global configuration
402     :param logger Logger: The logger instance to use for the display 
403                           and logging
404     :return: 1 if it fails, else 0.
405     :rtype: int
406     '''
407     
408     p_name, p_info = p_name_info
409        
410     # Execute "sat configure", "sat make" and "sat install"
411     res = 0
412     error_step = ""
413     
414     # Logging and sat command call for configure step
415     len_end_line = len_end
416     log_step(logger, header, "CONFIGURE")
417     res_c = sat.configure(config.VARS.application + " --products " + p_name,
418                           verbose = 0,
419                           logger_add_link = logger)
420     log_res_step(logger, res_c)
421     res += res_c
422     
423     if res_c > 0:
424         error_step = "CONFIGURE"
425     else:    
426         # Logging and sat command call for make step
427         # Logging take account of the fact that the product has a compilation 
428         # script or not
429         if src.product.product_has_script(p_info):
430             # if the product has a compilation script, 
431             # it is executed during make step
432             scrit_path_display = src.printcolors.printcLabel(
433                                                         p_info.compil_script)
434             log_step(logger, header, "SCRIPT " + scrit_path_display)
435             len_end_line = len(scrit_path_display)
436         else:
437             log_step(logger, header, "MAKE")
438         make_arguments = config.VARS.application + " --products " + p_name
439         # Get the make_flags option if there is any
440         if options.makeflags:
441             make_arguments += " --option -j" + options.makeflags
442         res_m = sat.make(make_arguments,
443                          verbose = 0,
444                          logger_add_link = logger)
445         log_res_step(logger, res_m)
446         res += res_m
447         
448         if res_m > 0:
449             error_step = "MAKE"
450         else: 
451             # Logging and sat command call for make install step
452             log_step(logger, header, "MAKE INSTALL")
453             res_mi = sat.makeinstall(config.VARS.application + 
454                                      " --products " + 
455                                      p_name,
456                                     verbose = 0,
457                                     logger_add_link = logger)
458
459             log_res_step(logger, res_mi)
460             res += res_mi
461             
462             if res_mi > 0:
463                 error_step = "MAKE INSTALL"
464
465     # Check that the install directory exists
466     if res==0 and not(os.path.exists(p_info.install_dir)):
467         res = 1
468         error_step = "NO INSTALL DIR"
469         msg = _("Error: despite the fact that all the steps ended successfully,"
470                 " no install directory was found !")
471         logger.write(src.printcolors.printcError(msg), 4)
472         logger.write("\n", 4)
473     
474     # Add the config file corresponding to the dependencies/versions of the 
475     # product that have been successfully compiled
476     if res==0:       
477         logger.write(_("Add the config file in installation directory\n"), 5)
478         add_compile_config_file(p_info, config)
479     
480     return res, len_end_line, error_step
481
482 def add_compile_config_file(p_info, config):
483     '''Execute the proper configuration command(s) 
484        in the product build directory.
485     
486     :param p_info Config: The specific config of the product
487     :param config Config: The global configuration
488     '''
489     # Create the compile config
490     compile_cfg = src.pyconf.Config()
491     for prod_name in p_info.depend:
492         if prod_name not in compile_cfg:
493             compile_cfg.addMapping(prod_name,
494                                    src.pyconf.Mapping(compile_cfg),
495                                    "")
496         prod_dep_info = src.product.get_product_config(config, prod_name, False)
497         compile_cfg[prod_name] = prod_dep_info.version
498     # Write it in the install directory of the product
499     compile_cfg_path = os.path.join(p_info.install_dir, src.CONFIG_FILENAME)
500     f = open(compile_cfg_path, 'w')
501     compile_cfg.__save__(f)
502     f.close()
503     
504 def description():
505     '''method that is called when salomeTools is called with --help option.
506     
507     :return: The text to display for the compile command description.
508     :rtype: str
509     '''
510     return _("The compile command constructs the products of the application"
511              "\n\nexample:\nsat compile SALOME-master --products KERNEL,GUI,"
512              "MEDCOUPLING --clean_all")
513   
514 def run(args, runner, logger):
515     '''method that is called when salomeTools is called with compile parameter.
516     '''
517     
518     # Parse the options
519     (options, args) = parser.parse_args(args)
520
521     # Warn the user if he invoked the clean_all option 
522     # without --products option
523     if (options.clean_all and 
524         options.products is None and 
525         not runner.options.batch):
526         rep = input(_("You used --clean_all without specifying a product"
527                           " are you sure you want to continue? [Yes/No] "))
528         if rep.upper() != _("YES").upper():
529             return 0
530         
531     # check that the command has been called with an application
532     src.check_config_has_application( runner.cfg )
533
534     # Print some informations
535     logger.write(_('Executing the compile commands in the build '
536                                 'directories of the products of '
537                                 'the application %s\n') % 
538                 src.printcolors.printcLabel(runner.cfg.VARS.application), 1)
539     
540     info = [
541             (_("SOURCE directory"),
542              os.path.join(runner.cfg.APPLICATION.workdir, 'SOURCES')),
543             (_("BUILD directory"),
544              os.path.join(runner.cfg.APPLICATION.workdir, 'BUILD'))
545             ]
546     src.print_info(logger, info)
547
548     # Get the list of products to treat
549     products_infos = get_products_list(options, runner.cfg, logger)
550
551     if options.fathers:
552         # Extend the list with all recursive dependencies of the given products
553         products_infos = extend_with_fathers(runner.cfg, products_infos)
554
555     if options.children:
556         # Extend the list with all products that use the given products
557         products_infos = extend_with_children(runner.cfg, products_infos)
558
559     # Sort the list regarding the dependencies of the products
560     products_infos = sort_products(runner.cfg, products_infos)
561
562     
563     # Call the function that will loop over all the products and execute
564     # the right command(s)
565     res = compile_all_products(runner, runner.cfg, options, products_infos, logger)
566     
567     # Print the final state
568     nb_products = len(products_infos)
569     if res == 0:
570         final_status = "OK"
571     else:
572         final_status = "KO"
573    
574     logger.write(_("\nCompilation: %(status)s (%(valid_result)d/%(nb_products)d)\n") % \
575         { 'status': src.printcolors.printc(final_status), 
576           'valid_result': nb_products - res,
577           'nb_products': nb_products }, 1)    
578     
579     code = res
580     if code != 0:
581         code = 1
582     return code