Salome HOME
bug fix: sat generate
[tools/sat.git] / src / product.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 '''In this file are implemented the methods 
19    relative to the product notion of salomeTools
20 '''
21
22 import os
23 import re
24
25 import src
26
27 AVAILABLE_VCS = ['git', 'svn', 'cvs']
28 config_expression = "^config-\d+$"
29
30 def get_product_config(config, product_name, with_install_dir=True):
31     '''Get the specific configuration of a product from the global configuration
32     
33     :param config Config: The global configuration
34     :param product_name str: The name of the product
35     :param with_install_dir boolean: If false, do not provide an install 
36                                      directory (at false only for internal use 
37                                      of the function get_install_dir)
38     :return: the specific configuration of the product
39     :rtype: Config
40     '''
41     
42     # Get the version of the product from the application definition
43     version = config.APPLICATION.products[product_name]
44     # if no version, then take the default one defined in the application
45     if isinstance(version, bool): 
46         version = config.APPLICATION.tag      
47     
48     # Define debug and dev modes
49     # Get the tag if a dictionary is given in APPLICATION.products for the
50     # current product 
51     debug = 'no'
52     dev = 'no'
53     base = 'maybe'
54     if isinstance(version, src.pyconf.Mapping):
55         dic_version = version
56         # Get the version/tag
57         if not 'tag' in dic_version:
58             version = config.APPLICATION.tag
59         else:
60             version = dic_version.tag
61         
62         # Get the debug if any
63         if 'debug' in dic_version:
64             debug = dic_version.debug
65         
66         # Get the dev if any
67         if 'dev' in dic_version:
68             dev = dic_version.dev
69         
70         # Get the dev if any
71         if 'base' in dic_version:
72             base = dic_version.base
73     
74     vv = version
75     # substitute some character with _ in order to get the correct definition
76     # in config.PRODUCTS. This is done because the pyconf tool does not handle
77     # the . and - characters 
78     for c in ".-": vv = vv.replace(c, "_")
79     
80     prod_info = None
81     if product_name in config.PRODUCTS:
82         # If it exists, get the information of the product_version
83         if "version_" + vv in config.PRODUCTS[product_name]:
84             # returns specific information for the given version
85             prod_info = config.PRODUCTS[product_name]["version_" + vv]
86             prod_info.section = "version_" + vv
87         # Get the standard informations
88         elif "default" in config.PRODUCTS[product_name]:
89             # returns the generic information (given version not found)
90             prod_info = config.PRODUCTS[product_name].default
91             prod_info.section = "default"
92         
93         # merge opt_depend in depend
94         if prod_info is not None and 'opt_depend' in prod_info:
95             for depend in prod_info.opt_depend:
96                 if depend in config.APPLICATION:
97                     prod_info.depend.append(depend,'')
98         
99         # In case of a product get with a vcs, 
100         # put the tag (equal to the version)
101         if prod_info is not None and prod_info.get_source in AVAILABLE_VCS:
102             
103             if prod_info.get_source == 'git':
104                 prod_info.git_info.tag = version
105             
106             if prod_info.get_source == 'svn':
107                 prod_info.svn_info.tag = version
108             
109             if prod_info.get_source == 'cvs':
110                 prod_info.cvs_info.tag = version
111         
112         # In case of a fixed product, 
113         # define the install_dir (equal to the version)
114         if prod_info is not None and prod_info.get_source=="fixed":
115             prod_info.install_dir = version
116         
117         # Check if the product is defined as native in the application
118         if prod_info is not None:
119             if version == "native":
120                 prod_info.get_source = "native"
121             elif prod_info.get_source == "native":
122                 msg = _("The product %(prod)s has version %(ver)s but is "
123                         "declared as native in its definition" %
124                         { 'prod': prod_info.name, 'ver': version})
125                 raise src.SatException(msg)
126
127     # If there is no definition but the product is declared as native,
128     # construct a new definition containing only the get_source key
129     if prod_info is None and version == "native":
130         prod_info = src.pyconf.Config()
131         prod_info.name = product_name
132         prod_info.get_source = "native"
133     
134     # If prod_info is still None, it means that there is no product definition
135     # in the config. The user has to provide it.
136     if prod_info is None:
137         msg = _("No definition found for the product %s\n"
138             "Please create a %s.pyconf file." % (product_name, product_name))
139         raise src.SatException(msg)
140     
141     # Set the debug, dev and version keys
142     prod_info.debug = debug
143     prod_info.dev = dev
144     prod_info.version = version
145     
146     # Set the archive_info if the product is get in archive mode
147     if prod_info.get_source == "archive":
148         if not "archive_info" in prod_info:
149             prod_info.addMapping("archive_info",
150                                  src.pyconf.Mapping(prod_info),
151                                  "")
152         if "archive_name" not in prod_info.archive_info: 
153             arch_name = product_name + "-" + version + ".tar.gz"
154             arch_path = src.find_file_in_lpath(arch_name,
155                                                config.PATHS.ARCHIVEPATH)
156             if not arch_path:
157                 msg = _("Archive %(arch_name)s for %(prod_name)s not found:"
158                             "\n" % {"arch_name" : arch_name,
159                                      "prod_name" : prod_info.name}) 
160                 raise src.SatException(msg)
161             prod_info.archive_info.archive_name = arch_path
162         else:
163             if (os.path.basename(prod_info.archive_info.archive_name) == 
164                                         prod_info.archive_info.archive_name):
165                 arch_name = prod_info.archive_info.archive_name
166                 arch_path = src.find_file_in_lpath(
167                                             arch_name,
168                                             config.PATHS.ARCHIVEPATH)
169                 if not arch_path:
170                     msg = _("Archive %(arch_name)s for %(prod_name)s not found:"
171                                 "\n" % {"arch_name" : arch_name,
172                                          "prod_name" : prod_info.name}) 
173                     raise src.SatException(msg)
174                 prod_info.archive_info.archive_name = arch_path
175         
176     # If the product compiles with a script, check the script existence
177     # and if it is executable
178     if product_has_script(prod_info):
179         # Check the compil_script key existence
180         if "compil_script" not in prod_info:
181             msg = _("No compilation script found for the product %s\n"
182                 "Please provide a \"compil_script\" key in its definition." 
183                 % (product_name))
184             raise src.SatException(msg)
185         
186         # Get the path of the script
187         script = prod_info.compil_script
188         script_name = os.path.basename(script)
189         if script == script_name:
190             # Only a name is given. Search in the default directory
191             script_path = src.find_file_in_lpath(script_name,
192                                                  config.PATHS.PRODUCTPATH,
193                                                  "compil_scripts")
194             if not script_path:
195                 raise src.SatException(_("Compilation script not found: %s") % 
196                                    script_name)
197             prod_info.compil_script = script_path
198        
199         # Check that the script is executable
200         if not os.access(prod_info.compil_script, os.X_OK):
201             raise src.SatException(
202                     _("Compilation script cannot be executed: %s") % 
203                     prod_info.compil_script)
204     
205     # Get the full paths of all the patches
206     if product_has_patches(prod_info):
207         patches = []
208         for patch in prod_info.patches:
209             patch_path = patch
210             # If only a filename, then search for the patch in the PRODUCTPATH
211             if os.path.basename(patch_path) == patch_path:
212                 # Search in the PRODUCTPATH/patches
213                 patch_path = src.find_file_in_lpath(patch,
214                                                     config.PATHS.PRODUCTPATH,
215                                                     "patches")
216                 if not patch_path:
217                     msg = _("Patch %(patch_name)s for %(prod_name)s not found:"
218                             "\n" % {"patch_name" : patch,
219                                      "prod_name" : prod_info.name}) 
220                     raise src.SatException(msg)
221             patches.append(patch_path)
222         prod_info.patches = patches
223
224     # Get the full paths of the environment scripts
225     if product_has_env_script(prod_info):
226         env_script_path = prod_info.environ.env_script
227         # If only a filename, then search for the environment script 
228         # in the PRODUCTPATH/env_scripts
229         if os.path.basename(env_script_path) == env_script_path:
230             # Search in the PRODUCTPATH/env_scripts
231             env_script_path = src.find_file_in_lpath(
232                                             prod_info.environ.env_script,
233                                             config.PATHS.PRODUCTPATH,
234                                             "env_scripts")
235             if not env_script_path:
236                 msg = _("Environment script %(env_name)s for %(prod_name)s not "
237                         "found.\n" % {"env_name" : env_script_path,
238                                        "prod_name" : prod_info.name}) 
239                 raise src.SatException(msg)
240
241         prod_info.environ.env_script = env_script_path
242     
243     if with_install_dir: 
244         # The variable with_install_dir is at false only for internal use 
245         # of the function get_install_dir
246         
247         # Set the install_dir key
248         prod_info.install_dir = get_install_dir(config, base, version, prod_info)
249                 
250     return prod_info
251
252 def get_install_dir(config, base, version, prod_info):
253     '''Compute the installation directory of a given product 
254     
255     :param config Config: The global configuration
256     :param base str: This corresponds to the value given by user in its 
257                      application.pyconf for the specific product. If "yes", the
258                     user wants the product to be in base. If "no", he wants the
259                     product to be in the application workdir
260     :param version str: The version of the product
261     :param product_info Config: The configuration specific to 
262                                the product
263     
264     :return: The path of the product installation
265     :rtype: str
266     '''
267     install_dir = ""
268     in_base = False
269     if (("install_dir" in prod_info and prod_info.install_dir == "base") 
270                                                             or base == "yes"):
271         in_base = True
272     if (base == "no" or ("no_base" in config.APPLICATION 
273                          and config.APPLICATION.no_base == "yes")):
274         in_base = False
275     
276     if in_base:
277         install_dir = get_base_install_dir(config, prod_info, version)
278     else:
279         if "install_dir" not in prod_info or prod_info.install_dir == "base":
280             # Set it to the default value (in application directory)
281             install_dir = os.path.join(config.APPLICATION.workdir,
282                                                 "INSTALL",
283                                                 prod_info.name)
284         else:
285             install_dir = prod_info.install_dir
286
287     return install_dir
288
289 def get_base_install_dir(config, prod_info, version):
290     '''Compute the installation directory of a product in base 
291     
292     :param config Config: The global configuration
293     :param product_info Config: The configuration specific to 
294                                the product
295     :param version str: The version of the product    
296     :return: The path of the product installation
297     :rtype: str
298     '''    
299     base_path = src.get_base_path(config) 
300     prod_dir = os.path.join(base_path, prod_info.name + "-" + version)
301     if not os.path.exists(prod_dir):
302         return os.path.join(prod_dir, "config-1")
303     
304     exists, install_dir = check_config_exists(config, prod_dir, prod_info)
305     if exists:
306         return install_dir
307     
308     # Find the first config-<i> directory that is available in the product
309     # directory
310     found = False 
311     label = 1
312     while not found:
313         install_dir = os.path.join(prod_dir, "config-%i" % label)
314         if os.path.exists(install_dir):
315             label+=1
316         else:
317             found = True
318             
319     return install_dir
320
321 def check_config_exists(config, prod_dir, prod_info):
322     '''Verify that the installation directory of a product in a base exists
323        Check all the config-<i> directory and verify the sat-config.pyconf file
324        that is in it 
325     
326     :param config Config: The global configuration
327     :param prod_dir str: The product installation directory path 
328                          (without config-<i>)
329     :param product_info Config: The configuration specific to 
330                                the product
331     :return: True or false is the installation is found or not 
332              and if it is found, the path of the found installation
333     :rtype: (boolean, str)
334     '''   
335     # check if the directories or files of the directory corresponds to the 
336     # directory installation of the product
337     l_dir_and_files = os.listdir(prod_dir)
338     for dir_or_file in l_dir_and_files:
339         oExpr = re.compile(config_expression)
340         if not(oExpr.search(dir_or_file)):
341             # not config-<i>, not interesting
342             continue
343         # check if there is the file sat-config.pyconf file in the installation
344         # directory    
345         config_file = os.path.join(prod_dir, dir_or_file, src.CONFIG_FILENAME)
346         if not os.path.exists(config_file):
347             continue
348         
349         # If there is no dependency, it is the right path
350         if len(prod_info.depend)==0:
351             return True, os.path.join(prod_dir, dir_or_file)
352         
353         # check if there is the config described in the file corresponds the 
354         # dependencies of the product
355         config_corresponds = True    
356         compile_cfg = src.pyconf.Config(config_file)
357         for prod_dep in prod_info.depend:
358             # if the dependency is not in the config, 
359             # the config does not correspond
360             if prod_dep not in compile_cfg:
361                 config_corresponds = False
362                 break
363             else:
364                 prod_dep_info = get_product_config(config, prod_dep, False)
365                 # If the version of the dependency does not correspond, 
366                 # the config does not correspond
367                 if prod_dep_info.version != compile_cfg[prod_dep]:
368                     config_corresponds = False
369                     break
370         if config_corresponds:
371             return True, os.path.join(prod_dir, dir_or_file)
372     
373     return False, None
374             
375             
376     
377 def get_products_infos(lproducts, config):
378     '''Get the specific configuration of a list of products
379     
380     :param lproducts List: The list of product names
381     :param config Config: The global configuration
382     :return: the list of tuples 
383              (product name, specific configuration of the product)
384     :rtype: [(str, Config)]
385     '''
386     products_infos = []
387     # Loop on product names
388     for prod in lproducts:       
389         # Get the specific configuration of the product
390         prod_info = get_product_config(config, prod)
391         if prod_info is not None:
392             products_infos.append((prod, prod_info))
393         else:
394             msg = _("The %s product has no definition "
395                     "in the configuration.") % prod
396             raise src.SatException(msg)
397     return products_infos
398
399 def get_product_dependencies(config, product_info):
400     '''Get recursively the list of products that are 
401        in the product_info dependencies
402     
403     :param config Config: The global configuration
404     :param product_info Config: The configuration specific to 
405                                the product
406     :return: the list of products in dependence
407     :rtype: list
408     '''
409     if "depend" not in product_info or product_info.depend == []:
410         return []
411     res = []
412     for prod in product_info.depend:
413         if prod == product_info.name:
414             continue
415         if prod not in res:
416             res.append(prod)
417         prod_info = get_product_config(config, prod)
418         dep_prod = get_product_dependencies(config, prod_info)
419         for prod_in_dep in dep_prod:
420             if prod_in_dep not in res:
421                 res.append(prod_in_dep)
422     return res
423
424 def check_installation(product_info):
425     '''Verify if a product is well installed. Checks install directory presence
426        and some additional files if it is defined in the config 
427     
428     :param product_info Config: The configuration specific to 
429                                the product
430     :return: True if it is well installed
431     :rtype: boolean
432     '''
433     if not product_compiles(product_info):
434         return True
435     install_dir = product_info.install_dir
436     if not os.path.exists(install_dir):
437         return False
438     if ("present_files" in product_info and 
439         "install" in product_info.present_files):
440         for file_relative_path in product_info.present_files.install:
441             file_path = os.path.join(install_dir, file_relative_path)
442             if not os.path.exists(file_path):
443                 return False
444     return True
445
446 def product_is_sample(product_info):
447     '''Know if a product has the sample type
448     
449     :param product_info Config: The configuration specific to 
450                                the product
451     :return: True if the product has the sample type, else False
452     :rtype: boolean
453     '''
454     if 'type' in product_info:
455         ptype = product_info.type
456         return ptype.lower() == 'sample'
457     else:
458         return False
459
460 def product_is_salome(product_info):
461     '''Know if a product is of type salome
462     
463     :param product_info Config: The configuration specific to 
464                                the product
465     :return: True if the product is salome, else False
466     :rtype: boolean
467     '''
468     if 'type' in product_info:
469         ptype = product_info.type
470         return ptype.lower() == 'salome'
471     else:
472         return False
473
474 def product_is_fixed(product_info):
475     '''Know if a product is fixed
476     
477     :param product_info Config: The configuration specific to 
478                                the product
479     :return: True if the product is fixed, else False
480     :rtype: boolean
481     '''
482     get_src = product_info.get_source
483     return get_src.lower() == 'fixed'
484
485 def product_is_native(product_info):
486     '''Know if a product is native
487     
488     :param product_info Config: The configuration specific to 
489                                the product
490     :return: True if the product is native, else False
491     :rtype: boolean
492     '''
493     get_src = product_info.get_source
494     return get_src.lower() == 'native'
495
496 def product_is_dev(product_info):
497     '''Know if a product is in dev mode
498     
499     :param product_info Config: The configuration specific to 
500                                the product
501     :return: True if the product is in dev mode, else False
502     :rtype: boolean
503     '''
504     dev = product_info.dev
505     return dev.lower() == 'yes'
506
507 def product_is_debug(product_info):
508     '''Know if a product is in debug mode
509     
510     :param product_info Config: The configuration specific to 
511                                the product
512     :return: True if the product is in debug mode, else False
513     :rtype: boolean
514     '''
515     debug = product_info.debug
516     return debug.lower() == 'yes'
517
518 def product_is_autotools(product_info):
519     '''Know if a product is compiled using the autotools
520     
521     :param product_info Config: The configuration specific to 
522                                the product
523     :return: True if the product is autotools, else False
524     :rtype: boolean
525     '''
526     build_src = product_info.build_source
527     return build_src.lower() == 'autotools'
528
529 def product_is_cmake(product_info):
530     '''Know if a product is compiled using the cmake
531     
532     :param product_info Config: The configuration specific to 
533                                the product
534     :return: True if the product is cmake, else False
535     :rtype: boolean
536     '''
537     build_src = product_info.build_source
538     return build_src.lower() == 'cmake'
539
540 def product_is_vcs(product_info):
541     '''Know if a product is download using git, svn or cvs (not archive)
542     
543     :param product_info Config: The configuration specific to 
544                                the product
545     :return: True if the product is vcs, else False
546     :rtype: boolean
547     '''
548     return product_info.get_source in AVAILABLE_VCS
549
550 def product_is_SALOME(product_info):
551     '''Know if a product is a SALOME module
552     
553     :param product_info Config: The configuration specific to 
554                                the product
555     :return: True if the product is a SALOME module, else False
556     :rtype: boolean
557     '''
558     return ("properties" in product_info and
559             "is_SALOME_module" in product_info.properties and
560             product_info.properties.is_SALOME_module == "yes")
561
562 def product_is_smesh_plugin(product_info):
563     '''Know if a product is a SMESH plugin
564     
565     :param product_info Config: The configuration specific to 
566                                the product
567     :return: True if the product is a SMESH plugin, else False
568     :rtype: boolean
569     '''
570     return ("properties" in product_info and
571             "smesh_plugin" in product_info.properties and
572             product_info.properties.smesh_plugin == "yes")
573
574 def product_is_cpp(product_info):
575     '''Know if a product is cpp
576     
577     :param product_info Config: The configuration specific to 
578                                the product
579     :return: True if the product is a cpp, else False
580     :rtype: boolean
581     '''
582     return ("properties" in product_info and
583             "cpp" in product_info.properties and
584             product_info.properties.cpp == "yes")
585
586 def product_compiles(product_info):
587     '''Know if a product compiles or not (some products do not have a 
588        compilation procedure)
589     
590     :param product_info Config: The configuration specific to 
591                                the product
592     :return: True if the product compiles, else False
593     :rtype: boolean
594     '''
595     return not("properties" in product_info and
596             "compilation" in product_info.properties and
597             product_info.properties.compilation == "no")
598
599 def product_has_script(product_info):
600     '''Know if a product has a compilation script
601     
602     :param product_info Config: The configuration specific to 
603                                the product
604     :return: True if the product it has a compilation script, else False
605     :rtype: boolean
606     '''
607     if "build_source" not in product_info:
608         # Native case
609         return False
610     build_src = product_info.build_source
611     return build_src.lower() == 'script'
612
613 def product_has_env_script(product_info):
614     '''Know if a product has an environment script
615     
616     :param product_info Config: The configuration specific to 
617                                the product
618     :return: True if the product it has an environment script, else False
619     :rtype: boolean
620     '''
621     return "environ" in product_info and "env_script" in product_info.environ
622
623 def product_has_patches(product_info):
624     '''Know if a product has one or more patches
625     
626     :param product_info Config: The configuration specific to 
627                                the product
628     :return: True if the product has one or more patches
629     :rtype: boolean
630     '''
631     return "patches" in product_info and len(product_info.patches) > 0
632
633 def product_has_logo(product_info):
634     '''Know if a product has a logo (YACSGEN generate)
635     
636     :param product_info Config: The configuration specific to 
637                                the product
638     :return: The path of the logo if the product has a logo, else False
639     :rtype: Str
640     '''
641     if ("properties" in product_info and
642             "logo" in product_info.properties):
643         return product_info.properties.logo
644     else:
645         return False
646
647 def product_has_salome_gui(product_info):
648     '''Know if a product has a SALOME gui
649     
650     :param product_info Config: The configuration specific to 
651                                the product
652     :return: True if the product has a SALOME gui, else False
653     :rtype: Boolean
654     '''
655     return ("properties" in product_info and
656             "has_salome_gui" in product_info.properties and
657             product_info.properties.has_salome_gui == "yes")
658
659 def product_is_mpi(product_info):
660     '''Know if a product has openmpi in its dependencies
661     
662     :param product_info Config: The configuration specific to 
663                                the product
664     :return: True if the product has openmpi inits dependencies
665     :rtype: boolean
666     '''
667     return "openmpi" in product_info.depend
668
669 def product_is_generated(product_info):
670     '''Know if a product is generated (YACSGEN)
671     
672     :param product_info Config: The configuration specific to 
673                                the product
674     :return: True if the product is generated
675     :rtype: boolean
676     '''
677     return ("properties" in product_info and
678             "generate" in product_info.properties and
679             product_info.properties.generate == "yes")
680
681 def get_product_components(product_info):
682     '''Get the component list to generate with the product
683     
684     :param product_info Config: The configuration specific to 
685                                the product
686     :return: The list of names of the components
687     :rtype: List
688     
689     '''
690     if not product_is_generated(product_info):
691         return []
692     
693     compo_list = []
694     if "component_name" in product_info:
695         compo_list = product_info.component_name
696     
697         if isinstance(compo_list, str):
698             compo_list = [ compo_list ]
699
700     return compo_list