Salome HOME
4c469bcf5b3aee24b909da3a304bd9c00f640e60
[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 import re
21 import src
22 import src.debug as DBG
23
24 # Compatibility python 2/3 for input function
25 # input stays input for python 3 and input = raw_input for python 2
26 try: 
27     input = raw_input
28 except NameError: 
29     pass
30
31
32 # Define all possible option for the compile command :  sat compile <options>
33 parser = src.options.Options()
34 parser.add_option('p', 'products', 'list2', 'products',
35     _('Optional: products to compile. This option accepts a comma separated list.'))
36 parser.add_option('', 'with_fathers', 'boolean', 'fathers',
37     _("Optional: build all necessary products to the given product (KERNEL is "
38       "build before building GUI)."), False)
39 parser.add_option('', 'with_children', 'boolean', 'children',
40     _("Optional: build all products using the given product (all SMESH plugins"
41       " are build after SMESH)."), False)
42 parser.add_option('', 'clean_all', 'boolean', 'clean_all',
43     _("Optional: clean BUILD dir and INSTALL dir before building product."),
44     False)
45 parser.add_option('', 'clean_install', 'boolean', 'clean_install',
46     _("Optional: clean INSTALL dir before building product."), False)
47 parser.add_option('', 'make_flags', 'string', 'makeflags',
48     _("Optional: add extra options to the 'make' command."))
49 parser.add_option('', 'show', 'boolean', 'no_compile',
50     _("Optional: DO NOT COMPILE just show if products are installed or not."),
51     False)
52 parser.add_option('', 'stop_first_fail', 'boolean', 'stop_first_fail', _(
53                   "Optional: Stops the command at first product compilation"
54                   " fail."), False)
55 parser.add_option('', 'check', 'boolean', 'check', _(
56                   "Optional: execute the unit tests after compilation"), False)
57
58 parser.add_option('', 'clean_build_after', 'boolean', 'clean_build_after', 
59                   _('Optional: remove the build directory after successful compilation'), False)
60
61
62 # from sat product infos, represent the product dependencies in a simple python graph
63 # keys are nodes, the list of dependencies are values
64 def get_dependencies_graph(p_infos):
65     graph={}
66     for (p_name,p_info) in p_infos:
67         graph[p_name]=p_info.depend
68     return graph
69
70 # this recursive function calculates all the dependencies of node start
71 def depth_search_graph(graph, start, visited=[]):
72     visited= visited+ [start]
73     for node in graph[start]:  # for all nodes in start dependencies
74         if node not in visited:
75             visited=depth_search_graph(graph, node, visited)
76     return visited
77
78 # find a path from start node to end (a group of nodes)
79 def find_path_graph(graph, start, end, path=[]):
80     path = path + [start]
81     if start in end:
82         return path
83     if not graph.has_key(start):
84         return None
85     for node in graph[start]:
86         if node not in path:
87             newpath = find_path_graph(graph, node, end, path)
88             if newpath: return newpath
89     return None
90
91 # Topological sorting algo
92 # return in sorted_nodes the list of sorted nodes
93 def depth_first_topo_graph(graph, start, visited=[], sorted_nodes=[]):
94     visited = visited + [start]
95     for node in graph[start]:
96         if node not in visited:
97             visited,sorted_nodes=depth_first_topo_graph(graph, node, visited,sorted_nodes)
98         else:
99             assert node in sorted_nodes, 'Error : cycle detection for node %s and %s !' % (start,node)
100     
101     sorted_nodes = sorted_nodes + [start]
102     return visited,sorted_nodes
103
104
105 # check for p_name that all dependencies are installed
106 def check_dependencies(config, p_name_p_info, all_products_dict):
107     l_depends_not_installed = []
108     for prod in p_name_p_info[1]["depend_all"]:
109         # for each dependency, check the install
110         prod_name, prod_info=all_products_dict[prod]
111         if not(src.product.check_installation(prod_info)):
112             l_depends_not_installed.append(prod_name)
113     return l_depends_not_installed   # non installed deps
114
115 def log_step(logger, header, step):
116     logger.write("\r%s%s" % (header, " " * 30), 3)
117     logger.write("\r%s%s" % (header, step), 3)
118     logger.flush()
119
120 def log_res_step(logger, res):
121     if res == 0:
122         logger.write("%s \n" % src.printcolors.printcSuccess("OK"), 4)
123         logger.flush()
124     else:
125         logger.write("%s \n" % src.printcolors.printcError("KO"), 4)
126         logger.flush()
127
128 def compile_all_products(sat, config, options, products_infos, all_products_dict, logger):
129     '''Execute the proper configuration commands 
130        in each product build directory.
131
132     :param config Config: The global configuration
133     :param products_info list: List of 
134                                  (str, Config) => (product_name, product_info)
135     :param all_products_dict: Dict of all products 
136     :param logger Logger: The logger instance to use for the display and logging
137     :return: the number of failing commands.
138     :rtype: int
139     '''
140     res = 0
141     for p_name_info in products_infos:
142         
143         p_name, p_info = p_name_info
144         
145         # Logging
146         len_end_line = 30
147         header = _("Compilation of %s") % src.printcolors.printcLabel(p_name)
148         header += " %s " % ("." * (len_end_line - len(p_name)))
149         logger.write(header, 3)
150         logger.flush()
151
152         # Do nothing if the product is not compilable
153         if not src.product.product_compiles(p_info):
154             log_step(logger, header, "ignored")
155             logger.write("\n", 3, False)
156             continue
157
158         # Do nothing if the product is native
159         if src.product.product_is_native(p_info):
160             log_step(logger, header, "native")
161             logger.write("\n", 3, False)
162             continue
163
164         # Do nothing if the product is fixed (already compiled by third party)
165         if src.product.product_is_fixed(p_info):
166             log_step(logger, header, "native")
167             logger.write("\n", 3, False)
168             continue
169
170         # Clean the build and the install directories 
171         # if the corresponding options was called
172         if options.clean_all:
173             log_step(logger, header, "CLEAN BUILD AND INSTALL ")
174             sat.clean(config.VARS.application + 
175                       " --products " + p_name + 
176                       " --build --install",
177                       batch=True,
178                       verbose=0,
179                       logger_add_link = logger)
180
181
182         # Clean the the install directory 
183         # if the corresponding option was called
184         if options.clean_install and not options.clean_all:
185             log_step(logger, header, "CLEAN INSTALL ")
186             sat.clean(config.VARS.application + 
187                       " --products " + p_name + 
188                       " --install",
189                       batch=True,
190                       verbose=0,
191                       logger_add_link = logger)
192         
193         # Recompute the product information to get the right install_dir
194         # (it could change if there is a clean of the install directory)
195         p_info = src.product.get_product_config(config, p_name)
196         
197         # Check if sources was already successfully installed
198         check_source = src.product.check_source(p_info)
199         if not options.no_compile: # don't check sources with option --show!
200             if not check_source:
201                 logger.write(_("Sources of product not found (try 'sat -h prepare') \n"))
202                 res += 1 # one more error
203                 continue
204         
205         if src.product.product_is_salome(p_info):
206             # For salome modules, we check if the sources of configuration modules are present
207             # configuration modules have the property "configure_dependency"
208
209             # get the list of all modules in application 
210             all_products_infos = src.product.get_products_infos(config.APPLICATION.products,
211                                                                 config)
212             check_source = True
213             # for configuration modules, check if sources are present
214             for prod in all_products_dict:
215                 product_name, product_info = all_products_dict[prod]
216                 if ("properties" in product_info and
217                     "configure_dependency" in product_info.properties and
218                     product_info.properties.configure_dependency == "yes"):
219                     check_source = check_source and src.product.check_source(product_info)
220                     if not check_source:
221                         logger.write(_("\nERROR : SOURCES of %s not found! It is required for" 
222                                        " the configuration\n" % product_name))
223                         logger.write(_("        Get it with the command : sat prepare %s -p %s \n" % 
224                                       (config.APPLICATION.name, product_name)))
225             if not check_source:
226                 # if at least one configuration module is not present, we stop compilation
227                 res += 1
228                 continue
229         
230         # Check if it was already successfully installed
231         if src.product.check_installation(p_info):
232             logger.write(_("Already installed"))
233             logger.write(_(" in %s" % p_info.install_dir), 4)
234             logger.write(_("\n"))
235             continue
236         
237         # If the show option was called, do not launch the compilation
238         if options.no_compile:
239             logger.write(_("Not installed in %s\n" % p_info.install_dir))
240             continue
241         
242         # Check if the dependencies are installed
243         l_depends_not_installed = check_dependencies(config, p_name_info, all_products_dict)
244         if len(l_depends_not_installed) > 0:
245             log_step(logger, header, "")
246             logger.write(src.printcolors.printcError(
247                     _("ERROR : the following mandatory product(s) is(are) not installed: ")))
248             for prod_name in l_depends_not_installed:
249                 logger.write(src.printcolors.printcError(prod_name + " "))
250             logger.write("\n")
251             continue
252         
253         # Call the function to compile the product
254         res_prod, len_end_line, error_step = compile_product(
255              sat, p_name_info, config, options, logger, header, len_end_line)
256         
257         if res_prod != 0:
258             res += 1
259             
260             if error_step != "CHECK":
261                 # Clean the install directory if there is any
262                 logger.write(_(
263                             "Cleaning the install directory if there is any\n"),
264                              5)
265                 sat.clean(config.VARS.application + 
266                           " --products " + p_name + 
267                           " --install",
268                           batch=True,
269                           verbose=0,
270                           logger_add_link = logger)
271         else:
272             # Clean the build directory if the compilation and tests succeed
273             if options.clean_build_after:
274                 log_step(logger, header, "CLEAN BUILD")
275                 sat.clean(config.VARS.application + 
276                           " --products " + p_name + 
277                           " --build",
278                           batch=True,
279                           verbose=0,
280                           logger_add_link = logger)
281
282         # Log the result
283         if res_prod > 0:
284             logger.write("\r%s%s" % (header, " " * len_end_line), 3)
285             logger.write("\r" + header + src.printcolors.printcError("KO ") + error_step)
286             logger.write("\n==== %(KO)s in compile of %(name)s \n" %
287                 { "name" : p_name , "KO" : src.printcolors.printcInfo("ERROR")}, 4)
288             if error_step == "CHECK":
289                 logger.write(_("\nINSTALL directory = %s" % 
290                            src.printcolors.printcInfo(p_info.install_dir)), 3)
291             logger.flush()
292         else:
293             logger.write("\r%s%s" % (header, " " * len_end_line), 3)
294             logger.write("\r" + header + src.printcolors.printcSuccess("OK"))
295             logger.write(_("\nINSTALL directory = %s" % 
296                            src.printcolors.printcInfo(p_info.install_dir)), 3)
297             logger.write("\n==== %s \n" % src.printcolors.printcInfo("OK"), 4)
298             logger.write("\n==== Compilation of %(name)s %(OK)s \n" %
299                 { "name" : p_name , "OK" : src.printcolors.printcInfo("OK")}, 4)
300             logger.flush()
301         logger.write("\n", 3, False)
302         
303         
304         if res_prod != 0 and options.stop_first_fail:
305             break
306         
307     return res
308
309 def compile_product(sat, p_name_info, config, options, logger, header, len_end):
310     '''Execute the proper configuration command(s) 
311        in the product build directory.
312     
313     :param p_name_info tuple: (str, Config) => (product_name, product_info)
314     :param config Config: The global configuration
315     :param logger Logger: The logger instance to use for the display 
316                           and logging
317     :param header Str: the header to display when logging
318     :param len_end Int: the lenght of the the end of line (used in display)
319     :return: 1 if it fails, else 0.
320     :rtype: int
321     '''
322     
323     p_name, p_info = p_name_info
324           
325     # Get the build procedure from the product configuration.
326     # It can be :
327     # build_sources : autotools -> build_configure, configure, make, make install
328     # build_sources : cmake     -> cmake, make, make install
329     # build_sources : script    -> script executions
330     res = 0
331     if (src.product.product_is_autotools(p_info) or 
332                                           src.product.product_is_cmake(p_info)):
333         res, len_end_line, error_step = compile_product_cmake_autotools(sat,
334                                                                   p_name_info,
335                                                                   config,
336                                                                   options,
337                                                                   logger,
338                                                                   header,
339                                                                   len_end)
340     if src.product.product_has_script(p_info):
341         res, len_end_line, error_step = compile_product_script(sat,
342                                                                   p_name_info,
343                                                                   config,
344                                                                   options,
345                                                                   logger,
346                                                                   header,
347                                                                   len_end)
348
349     # Check that the install directory exists
350     if res==0 and not(os.path.exists(p_info.install_dir)):
351         res = 1
352         error_step = "NO INSTALL DIR"
353         msg = _("Error: despite the fact that all the steps ended successfully,"
354                 " no install directory was found !")
355         logger.write(src.printcolors.printcError(msg), 4)
356         logger.write("\n", 4)
357         return res, len_end, error_step
358     
359     # Add the config file corresponding to the dependencies/versions of the 
360     # product that have been successfully compiled
361     if res==0:       
362         logger.write(_("Add the config file in installation directory\n"), 5)
363         src.product.add_compile_config_file(p_info, config)
364         
365         if options.check:
366             # Do the unit tests (call the check command)
367             log_step(logger, header, "CHECK")
368             res_check = sat.check(
369                               config.VARS.application + " --products " + p_name,
370                               verbose = 0,
371                               logger_add_link = logger)
372             if res_check != 0:
373                 error_step = "CHECK"
374                 
375             res += res_check
376     
377     return res, len_end_line, error_step
378
379 def compile_product_cmake_autotools(sat,
380                                     p_name_info,
381                                     config,
382                                     options,
383                                     logger,
384                                     header,
385                                     len_end):
386     '''Execute the proper build procedure for autotools or cmake
387        in the product build directory.
388     
389     :param p_name_info tuple: (str, Config) => (product_name, product_info)
390     :param config Config: The global configuration
391     :param logger Logger: The logger instance to use for the display 
392                           and logging
393     :param header Str: the header to display when logging
394     :param len_end Int: the lenght of the the end of line (used in display)
395     :return: 1 if it fails, else 0.
396     :rtype: int
397     '''
398     p_name, p_info = p_name_info
399     
400     # Execute "sat configure", "sat make" and "sat install"
401     res = 0
402     error_step = ""
403     
404     # Logging and sat command call for configure step
405     len_end_line = len_end
406     log_step(logger, header, "CONFIGURE")
407     res_c = sat.configure(config.VARS.application + " --products " + p_name,
408                           verbose = 0,
409                           logger_add_link = logger)
410     log_res_step(logger, res_c)
411     res += res_c
412     
413     if res_c > 0:
414         error_step = "CONFIGURE"
415     else:
416         # Logging and sat command call for make step
417         # Logging take account of the fact that the product has a compilation 
418         # script or not
419         if src.product.product_has_script(p_info):
420             # if the product has a compilation script, 
421             # it is executed during make step
422             scrit_path_display = src.printcolors.printcLabel(
423                                                         p_info.compil_script)
424             log_step(logger, header, "SCRIPT " + scrit_path_display)
425             len_end_line = len(scrit_path_display)
426         else:
427             log_step(logger, header, "MAKE")
428         make_arguments = config.VARS.application + " --products " + p_name
429         # Get the make_flags option if there is any
430         if options.makeflags:
431             make_arguments += " --option -j" + options.makeflags
432         res_m = sat.make(make_arguments,
433                          verbose = 0,
434                          logger_add_link = logger)
435         log_res_step(logger, res_m)
436         res += res_m
437         
438         if res_m > 0:
439             error_step = "MAKE"
440         else: 
441             # Logging and sat command call for make install step
442             log_step(logger, header, "MAKE INSTALL")
443             res_mi = sat.makeinstall(config.VARS.application + 
444                                      " --products " + 
445                                      p_name,
446                                     verbose = 0,
447                                     logger_add_link = logger)
448
449             log_res_step(logger, res_mi)
450             res += res_mi
451             
452             if res_mi > 0:
453                 error_step = "MAKE INSTALL"
454                 
455     return res, len_end_line, error_step 
456
457 def compile_product_script(sat,
458                            p_name_info,
459                            config,
460                            options,
461                            logger,
462                            header,
463                            len_end):
464     '''Execute the script build procedure in the product build directory.
465     
466     :param p_name_info tuple: (str, Config) => (product_name, product_info)
467     :param config Config: The global configuration
468     :param logger Logger: The logger instance to use for the display 
469                           and logging
470     :param header Str: the header to display when logging
471     :param len_end Int: the lenght of the the end of line (used in display)
472     :return: 1 if it fails, else 0.
473     :rtype: int
474     '''
475     p_name, p_info = p_name_info
476     
477     # Execute "sat configure", "sat make" and "sat install"
478     error_step = ""
479     
480     # Logging and sat command call for the script step
481     scrit_path_display = src.printcolors.printcLabel(p_info.compil_script)
482     log_step(logger, header, "SCRIPT " + scrit_path_display)
483     len_end_line = len_end + len(scrit_path_display)
484     res = sat.script(config.VARS.application + " --products " + p_name,
485                      verbose = 0,
486                      logger_add_link = logger)
487     log_res_step(logger, res)
488               
489     return res, len_end_line, error_step 
490
491     
492 def description():
493     '''method that is called when salomeTools is called with --help option.
494     
495     :return: The text to display for the compile command description.
496     :rtype: str
497     '''
498     return _("The compile command constructs the products of the application"
499              "\n\nexample:\nsat compile SALOME-master --products KERNEL,GUI,"
500              "MEDCOUPLING --clean_all")
501   
502 def run(args, runner, logger):
503     '''method that is called when salomeTools is called with compile parameter.
504     '''
505     # DBG.write("compile runner.cfg", runner.cfg, True)
506     # Parse the options
507     (options, args) = parser.parse_args(args)
508
509     # Warn the user if he invoked the clean_all option 
510     # without --products option
511     if (options.clean_all and 
512         options.products is None and 
513         not runner.options.batch):
514         rep = input(_("You used --clean_all without specifying a product"
515                           " are you sure you want to continue? [Yes/No] "))
516         if rep.upper() != _("YES").upper():
517             return 0
518         
519     # check that the command has been called with an application
520     src.check_config_has_application( runner.cfg )
521
522     # Print some informations
523     logger.write(_('Executing the compile commands in the build '
524                                 'directories of the products of '
525                                 'the application %s\n') % 
526                 src.printcolors.printcLabel(runner.cfg.VARS.application), 1)
527     
528     info = [
529             (_("SOURCE directory"),
530              os.path.join(runner.cfg.APPLICATION.workdir, 'SOURCES')),
531             (_("BUILD directory"),
532              os.path.join(runner.cfg.APPLICATION.workdir, 'BUILD'))
533             ]
534     src.print_info(logger, info)
535
536     # Get the list of all application products, and create its dependency graph
537     all_products_infos = src.product.get_products_infos(runner.cfg.APPLICATION.products,
538                                                         runner.cfg)
539     all_products_graph=get_dependencies_graph(all_products_infos)
540     logger.write("Dependency graph of all application products : %s\n" % all_products_graph, 6)
541
542     # Get the list of products we have to compile
543     products_infos = src.product.get_products_list(options, runner.cfg, logger)
544     products_list = [pi[0] for pi in products_infos]
545
546     logger.write("Product we have to compile (as specified by user) : %s\n" % products_list, 5)
547     if options.fathers:
548         # Extend the list with all recursive dependencies of the given products
549         visited=[]
550         for p_name in products_list:
551             visited=depth_search_graph(all_products_graph, p_name, visited)
552         products_list = visited
553
554     logger.write("Product list to compile with fathers : %s\n" % products_list, 5)
555     if options.children:
556         # Extend the list with all products that depends upon the given products
557         children=[]
558         for n in all_products_graph:
559             # for all products (that are not in products_list):
560             # if we we find a path from the product to the product list,
561             # then we product is a child and we add it to the children list 
562             if (n not in children) and (n not in products_list):
563                 if find_path_graph(all_products_graph, n, products_list):
564                     children = children + [n]
565         # complete products_list (the products we have to compile) with the list of children
566         products_list = products_list + children
567         logger.write("Product list to compile with children : %s\n" % products_list, 5)
568
569     # Sort the list of all products (topological sort).
570     # the products listed first do not depend upon products listed after
571     visited_nodes=[]
572     sorted_nodes=[]
573     for n in all_products_graph:
574         if n not in visited_nodes:
575             visited_nodes,sorted_nodes=depth_first_topo_graph(all_products_graph, n, visited_nodes,sorted_nodes)
576     logger.write("Complete depndency graph topological search (sorting): %s\n" % sorted_nodes, 6)
577
578 #   use the sorted list of all products to sort the list of products we have to compile
579     sorted_product_list=[]
580     for n in sorted_nodes:
581         if n in products_list:
582             sorted_product_list.append(n)
583     logger.write("Sorted list of products to compile : %s\n" % sorted_product_list, 5)
584
585     
586     # from the sorted list of products to compile, build a sorted list of products infos
587     #  a- create a dict to facilitate products_infos sorting
588     all_products_dict={}
589     for (pname,pinfo) in all_products_infos:
590         all_products_dict[pname]=(pname,pinfo)
591     #  b- build a sorted list of products infos in products_infos
592     products_infos=[]
593     for product in sorted_product_list:
594         products_infos.append(all_products_dict[product])
595
596     # for all products to compile, store in "depend_all" field the complete dependencies (recursive) 
597     # (will be used by check_dependencies funvtion)
598     for pi in products_infos:
599         dep_prod=[]
600         dep_prod=depth_search_graph(all_products_graph,pi[0], dep_prod)
601         pi[1]["depend_all"]=dep_prod[1:]
602         
603
604     # Call the function that will loop over all the products and execute
605     # the right command(s)
606     res = compile_all_products(runner, runner.cfg, options, products_infos, all_products_dict, logger)
607     
608     # Print the final state
609     nb_products = len(products_infos)
610     if res == 0:
611         final_status = "OK"
612     else:
613         final_status = "KO"
614    
615     logger.write(_("\nCompilation: %(status)s (%(valid_result)d/%(nb_products)d)\n") % \
616         { 'status': src.printcolors.printc(final_status), 
617           'valid_result': nb_products - res,
618           'nb_products': nb_products }, 1)    
619     
620     code = res
621     if code != 0:
622         code = 1
623     return code