]> SALOME platform Git repositories - tools/sat.git/blob - commands/package.py
Salome HOME
4472fd42ea94cf6a8822f07d12553bbeaf739635
[tools/sat.git] / commands / package.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 stat
21 import shutil
22 import datetime
23 import tarfile
24
25 import src
26
27 from application import get_SALOME_modules
28
29 BINARY = "binary"
30 SOURCE = "Source"
31 PROJECT = "Project"
32 SAT = "Sat"
33
34 ARCHIVE_DIR = "ARCHIVES"
35 PROJECT_DIR = "PROJECT"
36
37 PROJECT_TEMPLATE = """#!/usr/bin/env python
38 #-*- coding:utf-8 -*-
39
40 # The path to the archive root directory
41 root_path : $PWD + "/../"
42 # path to the PROJECT
43 project_path : $PWD + "/"
44
45 # Where to search the archives of the products
46 ARCHIVEPATH : $root_path + "ARCHIVES"
47 # Where to search the pyconf of the applications
48 APPLICATIONPATH : $project_path + "applications/"
49 # Where to search the pyconf of the products
50 PRODUCTPATH : $project_path + "products/"
51 # Where to search the pyconf of the jobs of the project
52 JOBPATH : $project_path + "jobs/"
53 # Where to search the pyconf of the machines of the project
54 MACHINEPATH : $project_path + "machines/"
55 """
56
57 SITE_TEMPLATE = ("""#!/usr/bin/env python
58 #-*- coding:utf-8 -*-
59
60 SITE :
61 {   
62     log :
63     {
64         log_dir : $USER.workdir + "/LOGS"
65     }
66     test :{
67            tmp_dir_with_application : '/tmp' + $VARS.sep + $VARS.user + """
68 """$VARS.sep + $APPLICATION.name + $VARS.sep + 'test'
69            tmp_dir : '/tmp' + $VARS.sep + $VARS.user + $VARS.sep + 'test'
70            timeout : 150
71            }
72 }
73
74 PROJECTS :
75 {
76 project_file_paths : [$VARS.salometoolsway + $VARS.sep + \"..\" + $VARS.sep"""
77 """ + \"""" + PROJECT_DIR + """\" + $VARS.sep + "project.pyconf"]
78 }
79 """)
80
81 # Define all possible option for the package command :  sat package <options>
82 parser = src.options.Options()
83 parser.add_option('b', 'binaries', 'boolean', 'binaries',
84     _('Optional: Produce a binary package.'), False)
85 parser.add_option('f', 'force_creation', 'boolean', 'force_creation',
86     _('Optional: Only binary package: produce the archive even if '
87       'there are some missing products.'), False)
88 parser.add_option('s', 'sources', 'boolean', 'sources',
89     _('Optional: Produce a compilable archive of the sources of the '
90       'application.'), False)
91 parser.add_option('', 'with_vcs', 'boolean', 'with_vcs',
92     _('Optional: Only source package: do not make archive of vcs products.'),
93     False)
94 parser.add_option('p', 'project', 'string', 'project',
95     _('Optional: Produce an archive that contains a project.'), "")
96 parser.add_option('t', 'salometools', 'boolean', 'sat',
97     _('Optional: Produce an archive that contains salomeTools.'), False)
98 parser.add_option('n', 'name', 'string', 'name',
99     _('Optional: The name or full path of the archive.'), None)
100 parser.add_option('', 'add_files', 'list2', 'add_files',
101     _('Optional: The list of additional files to add to the archive.'), [])
102 parser.add_option('', 'without_commercial', 'boolean', 'without_commercial',
103     _('Optional: do not add commercial licence.'), False)
104
105 def add_files(tar, name_archive, d_content, logger):
106     '''Create an archive containing all directories and files that are given in
107        the d_content argument.
108     
109     :param tar tarfile: The tarfile instance used to make the archive.
110     :param name_archive str: The name of the archive to make.
111     :param d_content dict: The dictionary that contain all directories and files
112                            to add in the archive.
113                            d_content[label] = 
114                                         (path_on_local_machine, path_in_archive)
115     :param logger Logger: the logging instance
116     :return: 0 if success, 1 if not.
117     :rtype: int
118     '''
119     # get the max length of the messages in order to make the display
120     max_len = len(max(d_content.keys(), key=len))
121     
122     success = 0
123     # loop over each directory or file stored in the d_content dictionary
124     for name in d_content.keys():
125         # display information
126         len_points = max_len - len(name)
127         logger.write(name + " " + len_points * "." + " ", 3)
128         # Get the local path and the path in archive 
129         # of the directory or file to add
130         local_path, archive_path = d_content[name]
131         in_archive = os.path.join(name_archive, archive_path)
132         # Add it in the archive
133         try:
134             tar.add(local_path, arcname=in_archive)
135             logger.write(src.printcolors.printcSuccess(_("OK")), 3)
136         except Exception as e:
137             logger.write(src.printcolors.printcError(_("KO ")), 3)
138             logger.write(str(e), 3)
139             success = 1
140         logger.write("\n", 3)
141     return success
142
143 def produce_relative_launcher(config,
144                               logger,
145                               file_dir,
146                               file_name,
147                               binaries_dir_name,
148                               with_commercial=True):
149     '''Create a specific SALOME launcher for the binary package. This launcher 
150        uses relative paths.
151     
152     :param config Config: The global configuration.
153     :param logger Logger: the logging instance
154     :param file_dir str: the directory where to put the launcher
155     :param file_name str: The launcher name
156     :param binaries_dir_name str: the name of the repository where the binaries
157                                   are, in the archive.
158     :return: the path of the produced launcher
159     :rtype: str
160     '''
161     
162     # Get the launcher template
163     profile_install_dir = os.path.join(binaries_dir_name,
164                                        config.APPLICATION.profile.product)
165     withProfile = src.fileEnviron.withProfile
166     withProfile = withProfile.replace(
167         "ABSOLUTE_APPLI_PATH'] = 'PROFILE_INSTALL_DIR'",
168         "ABSOLUTE_APPLI_PATH'] = out_dir_Path + '" + config.VARS.sep + profile_install_dir + "'")
169     withProfile = withProfile.replace(
170         "os.path.join( 'PROFILE_INSTALL_DIR'",
171         "os.path.join( out_dir_Path, '" + profile_install_dir + "'")
172
173     before, after = withProfile.split(
174                                 "# here your local standalone environment\n")
175
176     # create an environment file writer
177     writer = src.environment.FileEnvWriter(config,
178                                            logger,
179                                            file_dir,
180                                            src_root=None)
181     
182     filepath = os.path.join(file_dir, file_name)
183     # open the file and write into it
184     launch_file = open(filepath, "w")
185     launch_file.write(before)
186     # Write
187     writer.write_cfgForPy_file(launch_file,
188                                for_package = binaries_dir_name,
189                                with_commercial=with_commercial)
190     launch_file.write(after)
191     launch_file.close()
192     
193     # Little hack to put out_dir_Path outside the strings
194     src.replace_in_file(filepath, 'r"out_dir_Path', 'out_dir_Path + r"' )
195     
196     # change the rights in order to make the file executable for everybody
197     os.chmod(filepath,
198              stat.S_IRUSR |
199              stat.S_IRGRP |
200              stat.S_IROTH |
201              stat.S_IWUSR |
202              stat.S_IXUSR |
203              stat.S_IXGRP |
204              stat.S_IXOTH)
205
206     return filepath
207
208 def produce_relative_env_files(config,
209                               logger,
210                               file_dir,
211                               binaries_dir_name):
212     '''Create some specific environment files for the binary package. These 
213        files use relative paths.
214     
215     :param config Config: The global configuration.
216     :param logger Logger: the logging instance
217     :param file_dir str: the directory where to put the files
218     :param binaries_dir_name str: the name of the repository where the binaries
219                                   are, in the archive.
220     :return: the list of path of the produced environment files
221     :rtype: List
222     '''  
223     # create an environment file writer
224     writer = src.environment.FileEnvWriter(config,
225                                            logger,
226                                            file_dir,
227                                            src_root=None)
228     
229     # Write
230     filepath = writer.write_env_file("env_launch.sh",
231                           False, # for launch
232                           "bash",
233                           for_package = binaries_dir_name)
234
235     # Little hack to put out_dir_Path as environment variable
236     src.replace_in_file(filepath, '"out_dir_Path', '"${out_dir_Path}' )
237
238     # change the rights in order to make the file executable for everybody
239     os.chmod(filepath,
240              stat.S_IRUSR |
241              stat.S_IRGRP |
242              stat.S_IROTH |
243              stat.S_IWUSR |
244              stat.S_IXUSR |
245              stat.S_IXGRP |
246              stat.S_IXOTH)
247     
248     return filepath
249
250 def product_appli_creation_script(config,
251                                   logger,
252                                   file_dir,
253                                   binaries_dir_name):
254     '''Create a script that can produce an application (EDF style) in the binary
255        package.
256     
257     :param config Config: The global configuration.
258     :param logger Logger: the logging instance
259     :param file_dir str: the directory where to put the file
260     :param binaries_dir_name str: the name of the repository where the binaries
261                                   are, in the archive.
262     :return: the path of the produced script file
263     :rtype: Str
264     '''
265     template_name = "create_appli.py.for_bin_packages.template"
266     template_path = os.path.join(config.VARS.internal_dir, template_name)
267     text_to_fill = open(template_path, "r").read()
268     text_to_fill = text_to_fill.replace("TO BE FILLED 1",
269                                         '"' + binaries_dir_name + '"')
270     
271     text_to_add = ""
272     for product_name in get_SALOME_modules(config):
273         product_info = src.product.get_product_config(config, product_name)
274        
275         if src.product.product_is_smesh_plugin(product_info):
276             continue
277
278         if 'install_dir' in product_info and bool(product_info.install_dir):
279             if src.product.product_is_cpp(product_info):
280                 # cpp module
281                 for cpp_name in src.product.get_product_components(product_info):
282                     line_to_add = ("<module name=\"" + 
283                                    cpp_name + 
284                                    "\" gui=\"yes\" path=\"''' + "
285                                    "os.path.join(dir_bin_name, \"" + 
286                                    cpp_name + "\") + '''\"/>")
287             else:
288                 # regular module
289                 line_to_add = ("<module name=\"" + 
290                                product_name + 
291                                "\" gui=\"yes\" path=\"''' + "
292                                "os.path.join(dir_bin_name, \"" + 
293                                product_name + "\") + '''\"/>")
294             text_to_add += line_to_add + "\n"
295     
296     filled_text = text_to_fill.replace("TO BE FILLED 2", text_to_add)
297     
298     tmp_file_path = os.path.join(file_dir, "create_appli.py")
299     ff = open(tmp_file_path, "w")
300     ff.write(filled_text)
301     ff.close()
302     
303     # change the rights in order to make the file executable for everybody
304     os.chmod(tmp_file_path,
305              stat.S_IRUSR |
306              stat.S_IRGRP |
307              stat.S_IROTH |
308              stat.S_IWUSR |
309              stat.S_IXUSR |
310              stat.S_IXGRP |
311              stat.S_IXOTH)
312     
313     return tmp_file_path
314
315 def binary_package(config, logger, options, tmp_working_dir):
316     '''Prepare a dictionary that stores all the needed directories and files to
317        add in a binary package.
318     
319     :param config Config: The global configuration.
320     :param logger Logger: the logging instance
321     :param options OptResult: the options of the launched command
322     :param tmp_working_dir str: The temporary local directory containing some 
323                                 specific directories or files needed in the 
324                                 binary package
325     :return: the dictionary that stores all the needed directories and files to
326              add in a binary package.
327              {label : (path_on_local_machine, path_in_archive)}
328     :rtype: dict
329     '''
330
331     # Get the list of product installation to add to the archive
332     l_products_name = config.APPLICATION.products.keys()
333     l_product_info = src.product.get_products_infos(l_products_name,
334                                                     config)
335     l_install_dir = []
336     l_not_installed = []
337     for prod_name, prod_info in l_product_info:
338         # ignore the native and fixed products
339         if (src.product.product_is_native(prod_info) 
340                 or src.product.product_is_fixed(prod_info)
341                 or not src.product.product_compiles(prod_info)):
342             continue
343         if src.product.check_installation(prod_info):
344             l_install_dir.append((prod_name, prod_info.install_dir))
345         else:
346             l_not_installed.append(prod_name)
347         
348         # Add also the cpp generated modules (if any)
349         if src.product.product_is_cpp(prod_info):
350             # cpp module
351             for name_cpp in src.product.get_product_components(prod_info):
352                 install_dir = os.path.join(config.APPLICATION.workdir,
353                                            "INSTALL", name_cpp) 
354                 if os.path.exists(install_dir):
355                     l_install_dir.append((name_cpp, install_dir))
356                 else:
357                     l_not_installed.append(name_cpp)
358     
359     # Print warning or error if there are some missing products
360     if len(l_not_installed) > 0:
361         text_missing_prods = ""
362         for p_name in l_not_installed:
363             text_missing_prods += "-" + p_name + "\n"
364         if not options.force_creation:
365             msg = _("ERROR: there are missing products installations:")
366             logger.write("%s\n%s" % (src.printcolors.printcError(msg),
367                                      text_missing_prods),
368                          1)
369             return None
370         else:
371             msg = _("WARNING: there are missing products installations:")
372             logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
373                                      text_missing_prods),
374                          1)
375     
376     # construct the name of the directory that will contain the binaries
377     binaries_dir_name = "BINARIES-" + config.VARS.dist
378     
379     # construct the correlation table between the product names, there 
380     # actual install directories and there install directory in archive
381     d_products = {}
382     for prod_name, install_dir in l_install_dir:
383         path_in_archive = os.path.join(binaries_dir_name, prod_name)
384         d_products[prod_name] = (install_dir, path_in_archive)
385
386     # create the relative launcher and add it to the files to add
387     if "profile" in config.APPLICATION:
388         launcher_name = config.APPLICATION.profile.launcher_name
389         launcher_package = produce_relative_launcher(config,
390                                              logger,
391                                              tmp_working_dir,
392                                              launcher_name,
393                                              binaries_dir_name,
394                                              not(options.without_commercial))
395     
396         d_products["launcher"] = (launcher_package, launcher_name)
397     else:
398         # No profile, it means that there has to be some environment files
399         env_file = produce_relative_env_files(config,
400                                                logger,
401                                                tmp_working_dir,
402                                                binaries_dir_name)
403
404         d_products["environment file"] = (env_file, "env_launch.sh")
405         
406         # And provide a script for the creation of an application EDF style
407         appli_script = product_appli_creation_script(config,
408                                                     logger,
409                                                     tmp_working_dir,
410                                                     binaries_dir_name)
411         
412         d_products["appli script"] = (appli_script, "create_appli.py")
413    
414     return d_products
415
416 def source_package(sat, config, logger, options, tmp_working_dir):
417     '''Prepare a dictionary that stores all the needed directories and files to
418        add in a source package.
419     
420     :param config Config: The global configuration.
421     :param logger Logger: the logging instance
422     :param options OptResult: the options of the launched command
423     :param tmp_working_dir str: The temporary local directory containing some 
424                                 specific directories or files needed in the 
425                                 binary package
426     :return: the dictionary that stores all the needed directories and files to
427              add in a source package.
428              {label : (path_on_local_machine, path_in_archive)}
429     :rtype: dict
430     '''
431     
432     # Get all the products that are prepared using an archive
433     logger.write("Find archive products ... ")
434     d_archives, l_pinfo_vcs = get_archives(config, logger)
435     logger.write("Done\n")
436     d_archives_vcs = {}
437     if not options.with_vcs and len(l_pinfo_vcs) > 0:
438         # Make archives with the products that are not prepared using an archive
439         # (git, cvs, svn, etc)
440         logger.write("Construct archives for vcs products ... ")
441         d_archives_vcs = get_archives_vcs(l_pinfo_vcs,
442                                           sat,
443                                           config,
444                                           logger,
445                                           tmp_working_dir)
446         logger.write("Done\n")
447
448     # Create a project
449     logger.write("Create the project ... ")
450     d_project = create_project_for_src_package(config,
451                                                 tmp_working_dir,
452                                                 options.with_vcs)
453     logger.write("Done\n")
454     
455     # Add salomeTools
456     tmp_sat = add_salomeTools(config, tmp_working_dir)
457     d_sat = {"salomeTools" : (tmp_sat, "salomeTools")}
458     
459     # Add a sat symbolic link if not win
460     if not src.architecture.is_windows():
461         tmp_satlink_path = os.path.join(tmp_working_dir, 'sat')
462         try:
463             t = os.getcwd()
464         except:
465             # In the jobs, os.getcwd() can fail
466             t = config.USER.workdir
467         os.chdir(tmp_working_dir)
468         if os.path.lexists(tmp_satlink_path):
469             os.remove(tmp_satlink_path)
470         os.symlink(os.path.join('salomeTools', 'sat'), 'sat')
471         os.chdir(t)
472         
473         d_sat["sat link"] = (tmp_satlink_path, "sat")
474     
475     return src.merge_dicts(d_archives, d_archives_vcs, d_project, d_sat)
476
477 def get_archives(config, logger):
478     '''Find all the products that are get using an archive and all the products
479        that are get using a vcs (git, cvs, svn) repository.
480     
481     :param config Config: The global configuration.
482     :param logger Logger: the logging instance
483     :return: the dictionary {name_product : 
484              (local path of its archive, path in the package of its archive )}
485              and the list of specific configuration corresponding to the vcs 
486              products
487     :rtype: (Dict, List)
488     '''
489     # Get the list of product informations
490     l_products_name = config.APPLICATION.products.keys()
491     l_product_info = src.product.get_products_infos(l_products_name,
492                                                     config)
493     d_archives = {}
494     l_pinfo_vcs = []
495     for p_name, p_info in l_product_info:
496         # ignore the native and fixed products
497         if (src.product.product_is_native(p_info) 
498                 or src.product.product_is_fixed(p_info)):
499             continue
500         if p_info.get_source == "archive":
501             archive_path = p_info.archive_info.archive_name
502             archive_name = os.path.basename(archive_path)
503         else:
504             l_pinfo_vcs.append((p_name, p_info))
505             
506         d_archives[p_name] = (archive_path,
507                               os.path.join(ARCHIVE_DIR, archive_name))
508     return d_archives, l_pinfo_vcs
509
510 def add_salomeTools(config, tmp_working_dir):
511     '''Prepare a version of salomeTools that has a specific site.pyconf file 
512        configured for a source package.
513
514     :param config Config: The global configuration.
515     :param tmp_working_dir str: The temporary local directory containing some 
516                                 specific directories or files needed in the 
517                                 source package
518     :return: The path to the local salomeTools directory to add in the package
519     :rtype: str
520     '''
521     # Copy sat in the temporary working directory
522     sat_tmp_path = src.Path(os.path.join(tmp_working_dir, "salomeTools"))
523     sat_running_path = src.Path(config.VARS.salometoolsway)
524     sat_running_path.copy(sat_tmp_path)
525     
526     # Update the site.pyconf file that contains the path to the project
527     site_pyconf_name = "site.pyconf"
528     site_pyconf_dir = os.path.join(tmp_working_dir, "salomeTools", "data")
529     site_pyconf_file = os.path.join(site_pyconf_dir, site_pyconf_name)
530     ff = open(site_pyconf_file, "w")
531     ff.write(SITE_TEMPLATE)
532     ff.close()
533     
534     return sat_tmp_path.path
535
536 def get_archives_vcs(l_pinfo_vcs, sat, config, logger, tmp_working_dir):
537     '''For sources package that require that all products are get using an 
538        archive, one has to create some archive for the vcs products.
539        So this method calls the clean and source command of sat and then create
540        the archives.
541
542     :param l_pinfo_vcs List: The list of specific configuration corresponding to
543                              each vcs product
544     :param sat Sat: The Sat instance that can be called to clean and source the
545                     products
546     :param config Config: The global configuration.
547     :param logger Logger: the logging instance
548     :param tmp_working_dir str: The temporary local directory containing some 
549                                 specific directories or files needed in the 
550                                 source package
551     :return: the dictionary that stores all the archives to add in the source 
552              package. {label : (path_on_local_machine, path_in_archive)}
553     :rtype: dict
554     '''
555     # clean the source directory of all the vcs products, then use the source 
556     # command and thus construct an archive that will not contain the patches
557     l_prod_names = [pn for pn, __ in l_pinfo_vcs]
558     # clean
559     logger.write(_("clean sources\n"))
560     args_clean = config.VARS.application
561     args_clean += " --sources --products "
562     args_clean += ",".join(l_prod_names)
563     sat.clean(args_clean, batch=True, verbose=0, logger_add_link = logger)
564     # source
565     logger.write(_("get sources"))
566     args_source = config.VARS.application
567     args_source += " --products "
568     args_source += ",".join(l_prod_names)
569     sat.source(args_source, batch=True, verbose=0, logger_add_link = logger)
570
571     # make the new archives
572     d_archives_vcs = {}
573     for pn, pinfo in l_pinfo_vcs:
574         path_archive = make_archive(pn, pinfo, tmp_working_dir)
575         d_archives_vcs[pn] = (path_archive,
576                               os.path.join(ARCHIVE_DIR, pn + ".tgz"))
577     return d_archives_vcs
578
579 def make_archive(prod_name, prod_info, where):
580     '''Create an archive of a product by searching its source directory.
581
582     :param prod_name str: The name of the product.
583     :param prod_info Config: The specific configuration corresponding to the 
584                              product
585     :param where str: The path of the repository where to put the resulting 
586                       archive
587     :return: The path of the resulting archive
588     :rtype: str
589     '''
590     path_targz_prod = os.path.join(where, prod_name + ".tgz")
591     tar_prod = tarfile.open(path_targz_prod, mode='w:gz')
592     local_path = prod_info.source_dir
593     tar_prod.add(local_path, arcname=prod_name)
594     tar_prod.close()
595     return path_targz_prod       
596
597 def create_project_for_src_package(config, tmp_working_dir, with_vcs):
598     '''Create a specific project for a source package.
599
600     :param config Config: The global configuration.
601     :param tmp_working_dir str: The temporary local directory containing some 
602                                 specific directories or files needed in the 
603                                 source package
604     :param with_vcs boolean: True if the package is with vcs products (not 
605                              transformed into archive products)
606     :return: The dictionary 
607              {"project" : (produced project, project path in the archive)}
608     :rtype: Dict
609     '''
610
611     # Create in the working temporary directory the full project tree
612     project_tmp_dir = os.path.join(tmp_working_dir, PROJECT_DIR)
613     products_pyconf_tmp_dir = os.path.join(project_tmp_dir,
614                                          "products")
615     compil_scripts_tmp_dir = os.path.join(project_tmp_dir,
616                                          "products",
617                                          "compil_scripts")
618     env_scripts_tmp_dir = os.path.join(project_tmp_dir,
619                                          "products",
620                                          "env_scripts")
621     patches_tmp_dir = os.path.join(project_tmp_dir,
622                                          "products",
623                                          "patches")
624     application_tmp_dir = os.path.join(project_tmp_dir,
625                                          "applications")
626     for directory in [project_tmp_dir,
627                       compil_scripts_tmp_dir,
628                       env_scripts_tmp_dir,
629                       patches_tmp_dir,
630                       application_tmp_dir]:
631         src.ensure_path_exists(directory)
632
633     # Create the pyconf that contains the information of the project
634     project_pyconf_name = "project.pyconf"        
635     project_pyconf_file = os.path.join(project_tmp_dir, project_pyconf_name)
636     ff = open(project_pyconf_file, "w")
637     ff.write(PROJECT_TEMPLATE)
638     ff.close()
639     
640     # Loop over the products to get there pyconf and all the scripts 
641     # (compilation, environment, patches)
642     # and create the pyconf file to add to the project
643     lproducts_name = config.APPLICATION.products.keys()
644     l_products = src.product.get_products_infos(lproducts_name, config)
645     for p_name, p_info in l_products:
646         # ignore native and fixed products
647         if (src.product.product_is_native(p_info) or 
648                 src.product.product_is_fixed(p_info)):
649             continue
650         find_product_scripts_and_pyconf(p_name,
651                                         p_info,
652                                         config,
653                                         with_vcs,
654                                         compil_scripts_tmp_dir,
655                                         env_scripts_tmp_dir,
656                                         patches_tmp_dir,
657                                         products_pyconf_tmp_dir)
658     
659     find_application_pyconf(config, application_tmp_dir)
660     
661     d_project = {"project" : (project_tmp_dir, PROJECT_DIR )}
662     return d_project
663
664 def find_product_scripts_and_pyconf(p_name,
665                                     p_info,
666                                     config,
667                                     with_vcs,
668                                     compil_scripts_tmp_dir,
669                                     env_scripts_tmp_dir,
670                                     patches_tmp_dir,
671                                     products_pyconf_tmp_dir):
672     '''Create a specific pyconf file for a given product. Get its environment 
673        script, its compilation script and patches and put it in the temporary
674        working directory. This method is used in the source package in order to
675        construct the specific project.
676
677     :param p_name str: The name of the product.
678     :param p_info Config: The specific configuration corresponding to the 
679                              product
680     :param config Config: The global configuration.
681     :param with_vcs boolean: True if the package is with vcs products (not 
682                              transformed into archive products)
683     :param compil_scripts_tmp_dir str: The path to the temporary compilation 
684                                        scripts directory of the project.
685     :param env_scripts_tmp_dir str: The path to the temporary environment script 
686                                     directory of the project.
687     :param patches_tmp_dir str: The path to the temporary patch scripts 
688                                 directory of the project.
689     :param products_pyconf_tmp_dir str: The path to the temporary product 
690                                         scripts directory of the project.
691     '''
692     
693     # read the pyconf of the product
694     product_pyconf_path = src.find_file_in_lpath(p_name + ".pyconf",
695                                            config.PATHS.PRODUCTPATH)
696     product_pyconf_cfg = src.pyconf.Config(product_pyconf_path)
697
698     # find the compilation script if any
699     if src.product.product_has_script(p_info):
700         compil_script_path = src.Path(p_info.compil_script)
701         compil_script_path.copy(compil_scripts_tmp_dir)
702         product_pyconf_cfg[p_info.section].compil_script = os.path.basename(
703                                                     p_info.compil_script)
704     # find the environment script if any
705     if src.product.product_has_env_script(p_info):
706         env_script_path = src.Path(p_info.environ.env_script)
707         env_script_path.copy(env_scripts_tmp_dir)
708         product_pyconf_cfg[p_info.section].environ.env_script = os.path.basename(
709                                                 p_info.environ.env_script)
710     # find the patches if any
711     if src.product.product_has_patches(p_info):
712         patches = src.pyconf.Sequence()
713         for patch_path in p_info.patches:
714             p_path = src.Path(patch_path)
715             p_path.copy(patches_tmp_dir)
716             patches.append(os.path.basename(patch_path), "")
717
718         product_pyconf_cfg[p_info.section].patches = patches
719     
720     if with_vcs:
721         # put in the pyconf file the resolved values
722         for info in ["git_info", "cvs_info", "svn_info"]:
723             if info in p_info:
724                 for key in p_info[info]:
725                     product_pyconf_cfg[p_info.section][info][key] = p_info[
726                                                                       info][key]
727     else:
728         # if the product is not archive, then make it become archive.
729         if src.product.product_is_vcs(p_info):
730             product_pyconf_cfg[p_info.section].get_source = "archive"
731             if not "archive_info" in product_pyconf_cfg[p_info.section]:
732                 product_pyconf_cfg[p_info.section].addMapping("archive_info",
733                                         src.pyconf.Mapping(product_pyconf_cfg),
734                                         "")
735             product_pyconf_cfg[p_info.section
736                               ].archive_info.archive_name = p_info.name + ".tgz"
737     
738     # write the pyconf file to the temporary project location
739     product_tmp_pyconf_path = os.path.join(products_pyconf_tmp_dir,
740                                            p_name + ".pyconf")
741     ff = open(product_tmp_pyconf_path, 'w')
742     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
743     product_pyconf_cfg.__save__(ff, 1)
744     ff.close()
745
746 def find_application_pyconf(config, application_tmp_dir):
747     '''Find the application pyconf file and put it in the specific temporary 
748        directory containing the specific project of a source package.
749
750     :param config Config: The global configuration.
751     :param application_tmp_dir str: The path to the temporary application 
752                                        scripts directory of the project.
753     '''
754     # read the pyconf of the application
755     application_name = config.VARS.application
756     application_pyconf_path = src.find_file_in_lpath(
757                                             application_name + ".pyconf",
758                                             config.PATHS.APPLICATIONPATH)
759     application_pyconf_cfg = src.pyconf.Config(application_pyconf_path)
760     
761     # Change the workdir
762     application_pyconf_cfg.APPLICATION.workdir = src.pyconf.Reference(
763                                     application_pyconf_cfg,
764                                     src.pyconf.DOLLAR,
765                                     'VARS.salometoolsway + $VARS.sep + ".."')
766
767     # Prevent from compilation in base
768     application_pyconf_cfg.APPLICATION.no_base = "yes"
769     
770     # write the pyconf file to the temporary application location
771     application_tmp_pyconf_path = os.path.join(application_tmp_dir,
772                                                application_name + ".pyconf")
773     ff = open(application_tmp_pyconf_path, 'w')
774     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
775     application_pyconf_cfg.__save__(ff, 1)
776     ff.close()
777
778 def project_package(project_file_path, tmp_working_dir):
779     '''Prepare a dictionary that stores all the needed directories and files to
780        add in a project package.
781     
782     :param project_file_path str: The path to the local project.
783     :param tmp_working_dir str: The temporary local directory containing some 
784                                 specific directories or files needed in the 
785                                 project package
786     :return: the dictionary that stores all the needed directories and files to
787              add in a project package.
788              {label : (path_on_local_machine, path_in_archive)}
789     :rtype: dict
790     '''
791     d_project = {}
792     # Read the project file and get the directories to add to the package
793     project_pyconf_cfg = src.pyconf.Config(project_file_path)
794     paths = {"ARCHIVEPATH" : "archives",
795              "APPLICATIONPATH" : "applications",
796              "PRODUCTPATH" : "products",
797              "JOBPATH" : "jobs",
798              "MACHINEPATH" : "machines"}
799     # Loop over the project paths and add it
800     for path in paths:
801         if path not in project_pyconf_cfg:
802             continue
803         # Add the directory to the files to add in the package
804         d_project[path] = (project_pyconf_cfg[path], paths[path])
805         # Modify the value of the path in the package
806         project_pyconf_cfg[path] = src.pyconf.Reference(
807                                     project_pyconf_cfg,
808                                     src.pyconf.DOLLAR,
809                                     'project_path + "/' + paths[path] + '"')
810     
811     # Modify some values
812     if "project_path" not in project_pyconf_cfg:
813         project_pyconf_cfg.addMapping("project_path",
814                                       src.pyconf.Mapping(project_pyconf_cfg),
815                                       "")
816     project_pyconf_cfg.project_path = src.pyconf.Reference(project_pyconf_cfg,
817                                                            src.pyconf.DOLLAR,
818                                                            'PWD')
819     
820     # Write the project pyconf file
821     project_file_name = os.path.basename(project_file_path)
822     project_pyconf_tmp_path = os.path.join(tmp_working_dir, project_file_name)
823     ff = open(project_pyconf_tmp_path, 'w')
824     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
825     project_pyconf_cfg.__save__(ff, 1)
826     ff.close()
827     d_project["Project hat file"] = (project_pyconf_tmp_path, project_file_name)
828     
829     return d_project
830
831 def add_readme(config, package_type, where):
832     readme_path = os.path.join(where, "README")
833     f = open(readme_path, 'w')
834     # prepare substitution dictionary
835     d = dict()
836     if package_type == BINARY:
837         d['application'] = config.VARS.application
838         d['user'] = config.VARS.user
839         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
840         d['version'] = config.INTERNAL.sat_version
841         d['dist'] = config.VARS.dist
842         if 'profile' in config.APPLICATION:
843             d['launcher'] = config.APPLICATION.profile.launcher_name
844             readme_template_path = os.path.join(config.VARS.internal_dir,
845                                                 "README_BIN.template")
846         else:
847             d['env_file'] = 'env_launch.sh'
848             readme_template_path = os.path.join(config.VARS.internal_dir,
849                                                "README_BIN_NO_PROFILE.template")
850             
851     if package_type == SOURCE:
852         d['application'] = config.VARS.application
853         d['user'] = config.VARS.user
854         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
855         d['version'] = config.INTERNAL.sat_version
856         if 'profile' in config.APPLICATION:
857             d['profile'] = config.APPLICATION.profile.product
858             d['launcher'] = config.APPLICATION.profile.launcher_name
859         readme_template_path = os.path.join(config.VARS.internal_dir,
860                                     "README_SRC.template")
861
862     if package_type == PROJECT:
863         d['user'] = config.VARS.user
864         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
865         d['version'] = config.INTERNAL.sat_version
866         readme_template_path = os.path.join(config.VARS.internal_dir,
867                                     "README_PROJECT.template")
868
869     if package_type == SAT:
870         d['user'] = config.VARS.user
871         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
872         d['version'] = config.INTERNAL.sat_version
873         readme_template_path = os.path.join(config.VARS.internal_dir,
874                                     "README_SAT.template")
875     
876     f.write(src.template.substitute(readme_template_path, d))
877     
878     return readme_path
879         
880
881 def description():
882     '''method that is called when salomeTools is called with --help option.
883     
884     :return: The text to display for the package command description.
885     :rtype: str
886     '''
887     return _("The package command creates an archive.\nThere are 4 kinds of "
888              "archive:\n  1- The binary archive. It contains all the product "
889              "installation directories and a launcher,\n  2- The sources archive."
890              " It contains the products archives, a project corresponding to "
891              "the application and salomeTools,\n  3- The project archive. It "
892              "contains a project (give the project file path as argument),\n  4-"
893              " The salomeTools archive. It contains salomeTools.\n\nexample:"
894              "\nsat package SALOME-master --sources")
895   
896 def run(args, runner, logger):
897     '''method that is called when salomeTools is called with package parameter.
898     '''
899     
900     # Parse the options
901     (options, args) = parser.parse_args(args)
902        
903     # Check that a type of package is called, and only one
904     all_option_types = (options.binaries,
905                         options.sources,
906                         options.project not in ["", None],
907                         options.sat)
908
909     # Check if no option for package type
910     if all_option_types.count(True) == 0:
911         msg = _("Error: Precise a type for the package\nUse one of the "
912                 "following options: --binaries, --sources, --project or"
913                 " --salometools")
914         logger.write(src.printcolors.printcError(msg), 1)
915         logger.write("\n", 1)
916         return 1
917     
918     # Check for only one option for package type
919     if all_option_types.count(True) > 1:
920         msg = _("Error: You can use only one type for the package\nUse only one"
921                 " of the following options: --binaries, --sources, --project or"
922                 " --salometools")
923         logger.write(src.printcolors.printcError(msg), 1)
924         logger.write("\n", 1)
925         return 1
926     
927     # Get the package type
928     if options.binaries:
929         package_type = BINARY
930     if options.sources:
931         package_type = SOURCE
932     if options.project:
933         package_type = PROJECT
934     if options.sat:
935         package_type = SAT
936
937     # The repository where to put the package if not Binary or Source
938     package_default_path = runner.cfg.USER.workdir
939     
940     if package_type in [BINARY, SOURCE]:
941         # Check that the command has been called with an application
942         src.check_config_has_application(runner.cfg)
943
944         # Display information
945         logger.write(_("Packaging application %s\n") % src.printcolors.printcLabel(
946                                                     runner.cfg.VARS.application), 1)
947         
948         # Get the default directory where to put the packages
949         package_default_path = os.path.join(runner.cfg.APPLICATION.workdir,
950                                             "PACKAGE")
951         src.ensure_path_exists(package_default_path)
952         
953     elif package_type == PROJECT:
954         # check that the project is visible by SAT
955         if options.project not in runner.cfg.PROJECTS.project_file_paths:
956             site_path = os.path.join(runner.cfg.VARS.salometoolsway,
957                                      "data",
958                                      "site.pyconf")
959             msg = _("ERROR: the project %(proj)s is not visible by salomeTools."
960                     "\nPlease add it in the %(site)s file." % {
961                                   "proj" : options.project, "site" : site_path})
962             logger.write(src.printcolors.printcError(msg), 1)
963             logger.write("\n", 1)
964             return 1
965     
966     # Print
967     src.printcolors.print_value(logger, "Package type", package_type, 2)
968
969     # get the name of the archive or construct it
970     if options.name:
971         if os.path.basename(options.name) == options.name:
972             # only a name (not a path)
973             archive_name = options.name           
974             dir_name = package_default_path
975         else:
976             archive_name = os.path.basename(options.name)
977             dir_name = os.path.dirname(options.name)
978         
979         # suppress extension
980         if archive_name[-len(".tgz"):] == ".tgz":
981             archive_name = archive_name[:-len(".tgz")]
982         if archive_name[-len(".tar.gz"):] == ".tar.gz":
983             archive_name = archive_name[:-len(".tar.gz")]
984         
985     else:
986         dir_name = package_default_path
987         if package_type == BINARY:
988             archive_name = (runner.cfg.APPLICATION.name +
989                             "-" +
990                             runner.cfg.VARS.dist)
991             
992         if package_type == SOURCE:
993             archive_name = (runner.cfg.APPLICATION.name +
994                             "-" +
995                             "SRC")
996             if options.with_vcs:
997                 archive_name = (runner.cfg.APPLICATION.name +
998                             "-" +
999                             "SRC" +
1000                             "-" +
1001                             "VCS")
1002
1003         if package_type == PROJECT:
1004             project_name, __ = os.path.splitext(
1005                                             os.path.basename(options.project))
1006             archive_name = ("PROJECT" +
1007                             "-" +
1008                             project_name)
1009  
1010         if package_type == SAT:
1011             archive_name = ("salomeTools" +
1012                             "-" +
1013                             runner.cfg.INTERNAL.sat_version)
1014  
1015     path_targz = os.path.join(dir_name, archive_name + ".tgz")
1016     
1017     # Print the path of the package
1018     src.printcolors.print_value(logger, "Package path", path_targz, 2)
1019
1020     # Create a working directory for all files that are produced during the
1021     # package creation and that will be removed at the end of the command
1022     tmp_working_dir = os.path.join(runner.cfg.VARS.tmp_root,
1023                                    runner.cfg.VARS.datehour)
1024     src.ensure_path_exists(tmp_working_dir)
1025     logger.write("\n", 5)
1026     logger.write(_("The temporary working directory: %s\n" % tmp_working_dir),5)
1027     
1028     logger.write("\n", 3)
1029
1030     msg = _("Preparation of files to add to the archive")
1031     logger.write(src.printcolors.printcLabel(msg), 2)
1032     logger.write("\n", 2)
1033
1034     if package_type == BINARY:           
1035         d_files_to_add = binary_package(runner.cfg,
1036                                         logger,
1037                                         options,
1038                                         tmp_working_dir)
1039         if not(d_files_to_add):
1040             return 1
1041
1042     if package_type == SOURCE:
1043         d_files_to_add = source_package(runner,
1044                                         runner.cfg,
1045                                         logger, 
1046                                         options,
1047                                         tmp_working_dir)          
1048     
1049     if package_type == PROJECT:
1050         d_files_to_add = project_package(options.project, tmp_working_dir)
1051
1052     if package_type == SAT:
1053         d_files_to_add = {"salomeTools" : (runner.cfg.VARS.salometoolsway, "")}
1054     
1055     # Add the README file in the package
1056     local_readme_tmp_path = add_readme(runner.cfg,
1057                                        package_type,
1058                                        tmp_working_dir)
1059     d_files_to_add["README"] = (local_readme_tmp_path, "README")
1060
1061     # Add the additional files of option add_files
1062     if options.add_files:
1063         for file_path in options.add_files:
1064             if not os.path.exists(file_path):
1065                 msg = _("WARNING: the file %s is not accessible.\n" % file_path)
1066                 continue
1067             file_name = os.path.basename(file_path)
1068             d_files_to_add[file_name] = (file_path, file_name)
1069
1070     logger.write("\n", 2)
1071
1072     logger.write(src.printcolors.printcLabel(_("Actually do the package")), 2)
1073     logger.write("\n", 2)
1074     
1075     try:
1076         # Creating the object tarfile
1077         tar = tarfile.open(path_targz, mode='w:gz')
1078         
1079         # Add the files to the tarfile object
1080         res = add_files(tar, archive_name, d_files_to_add, logger)
1081         tar.close()
1082     except KeyboardInterrupt:
1083         logger.write(src.printcolors.printcError("\nERROR: forced interruption\n"), 1)
1084         logger.write(_("Removing the temporary working directory ... "), 1)
1085         # remove the working directory
1086         shutil.rmtree(tmp_working_dir)
1087         logger.write(_("OK"), 1)
1088         logger.write(_("\n"), 1)
1089         return 1
1090     
1091     # remove the working directory    
1092     shutil.rmtree(tmp_working_dir)
1093     
1094     # Print again the path of the package
1095     logger.write("\n", 2)
1096     src.printcolors.print_value(logger, "Package path", path_targz, 2)
1097     
1098     return res