Salome HOME
FSAt package --binaries: substitute also the generated components paths
[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 and 
388                                        "product" in config.APPLICATION.profile):
389         launcher_name = config.APPLICATION.profile.launcher_name
390         launcher_package = produce_relative_launcher(config,
391                                              logger,
392                                              tmp_working_dir,
393                                              launcher_name,
394                                              binaries_dir_name,
395                                              not(options.without_commercial))
396     
397         d_products["launcher"] = (launcher_package, launcher_name)
398     else:
399         # No profile, it means that there has to be some environment files
400         env_file = produce_relative_env_files(config,
401                                                logger,
402                                                tmp_working_dir,
403                                                binaries_dir_name)
404
405         d_products["environment file"] = (env_file, "env_launch.sh")
406         
407         # And provide a script for the creation of an application EDF style
408         appli_script = product_appli_creation_script(config,
409                                                     logger,
410                                                     tmp_working_dir,
411                                                     binaries_dir_name)
412         
413         d_products["appli script"] = (appli_script, "create_appli.py")
414    
415     return d_products
416
417 def source_package(sat, config, logger, options, tmp_working_dir):
418     '''Prepare a dictionary that stores all the needed directories and files to
419        add in a source package.
420     
421     :param config Config: The global configuration.
422     :param logger Logger: the logging instance
423     :param options OptResult: the options of the launched command
424     :param tmp_working_dir str: The temporary local directory containing some 
425                                 specific directories or files needed in the 
426                                 binary package
427     :return: the dictionary that stores all the needed directories and files to
428              add in a source package.
429              {label : (path_on_local_machine, path_in_archive)}
430     :rtype: dict
431     '''
432     
433     # Get all the products that are prepared using an archive
434     logger.write("Find archive products ... ")
435     d_archives, l_pinfo_vcs = get_archives(config, logger)
436     logger.write("Done\n")
437     d_archives_vcs = {}
438     if not options.with_vcs and len(l_pinfo_vcs) > 0:
439         # Make archives with the products that are not prepared using an archive
440         # (git, cvs, svn, etc)
441         logger.write("Construct archives for vcs products ... ")
442         d_archives_vcs = get_archives_vcs(l_pinfo_vcs,
443                                           sat,
444                                           config,
445                                           logger,
446                                           tmp_working_dir)
447         logger.write("Done\n")
448
449     # Create a project
450     logger.write("Create the project ... ")
451     d_project = create_project_for_src_package(config,
452                                                 tmp_working_dir,
453                                                 options.with_vcs)
454     logger.write("Done\n")
455     
456     # Add salomeTools
457     tmp_sat = add_salomeTools(config, tmp_working_dir)
458     d_sat = {"salomeTools" : (tmp_sat, "salomeTools")}
459     
460     # Add a sat symbolic link if not win
461     if not src.architecture.is_windows():
462         tmp_satlink_path = os.path.join(tmp_working_dir, 'sat')
463         try:
464             t = os.getcwd()
465         except:
466             # In the jobs, os.getcwd() can fail
467             t = config.USER.workdir
468         os.chdir(tmp_working_dir)
469         if os.path.lexists(tmp_satlink_path):
470             os.remove(tmp_satlink_path)
471         os.symlink(os.path.join('salomeTools', 'sat'), 'sat')
472         os.chdir(t)
473         
474         d_sat["sat link"] = (tmp_satlink_path, "sat")
475     
476     return src.merge_dicts(d_archives, d_archives_vcs, d_project, d_sat)
477
478 def get_archives(config, logger):
479     '''Find all the products that are get using an archive and all the products
480        that are get using a vcs (git, cvs, svn) repository.
481     
482     :param config Config: The global configuration.
483     :param logger Logger: the logging instance
484     :return: the dictionary {name_product : 
485              (local path of its archive, path in the package of its archive )}
486              and the list of specific configuration corresponding to the vcs 
487              products
488     :rtype: (Dict, List)
489     '''
490     # Get the list of product informations
491     l_products_name = config.APPLICATION.products.keys()
492     l_product_info = src.product.get_products_infos(l_products_name,
493                                                     config)
494     d_archives = {}
495     l_pinfo_vcs = []
496     for p_name, p_info in l_product_info:
497         # ignore the native and fixed products
498         if (src.product.product_is_native(p_info) 
499                 or src.product.product_is_fixed(p_info)):
500             continue
501         if p_info.get_source == "archive":
502             archive_path = p_info.archive_info.archive_name
503             archive_name = os.path.basename(archive_path)
504         else:
505             l_pinfo_vcs.append((p_name, p_info))
506             
507         d_archives[p_name] = (archive_path,
508                               os.path.join(ARCHIVE_DIR, archive_name))
509     return d_archives, l_pinfo_vcs
510
511 def add_salomeTools(config, tmp_working_dir):
512     '''Prepare a version of salomeTools that has a specific site.pyconf file 
513        configured for a source package.
514
515     :param config Config: The global configuration.
516     :param tmp_working_dir str: The temporary local directory containing some 
517                                 specific directories or files needed in the 
518                                 source package
519     :return: The path to the local salomeTools directory to add in the package
520     :rtype: str
521     '''
522     # Copy sat in the temporary working directory
523     sat_tmp_path = src.Path(os.path.join(tmp_working_dir, "salomeTools"))
524     sat_running_path = src.Path(config.VARS.salometoolsway)
525     sat_running_path.copy(sat_tmp_path)
526     
527     # Update the site.pyconf file that contains the path to the project
528     site_pyconf_name = "site.pyconf"
529     site_pyconf_dir = os.path.join(tmp_working_dir, "salomeTools", "data")
530     site_pyconf_file = os.path.join(site_pyconf_dir, site_pyconf_name)
531     ff = open(site_pyconf_file, "w")
532     ff.write(SITE_TEMPLATE)
533     ff.close()
534     
535     return sat_tmp_path.path
536
537 def get_archives_vcs(l_pinfo_vcs, sat, config, logger, tmp_working_dir):
538     '''For sources package that require that all products are get using an 
539        archive, one has to create some archive for the vcs products.
540        So this method calls the clean and source command of sat and then create
541        the archives.
542
543     :param l_pinfo_vcs List: The list of specific configuration corresponding to
544                              each vcs product
545     :param sat Sat: The Sat instance that can be called to clean and source the
546                     products
547     :param config Config: The global configuration.
548     :param logger Logger: the logging instance
549     :param tmp_working_dir str: The temporary local directory containing some 
550                                 specific directories or files needed in the 
551                                 source package
552     :return: the dictionary that stores all the archives to add in the source 
553              package. {label : (path_on_local_machine, path_in_archive)}
554     :rtype: dict
555     '''
556     # clean the source directory of all the vcs products, then use the source 
557     # command and thus construct an archive that will not contain the patches
558     l_prod_names = [pn for pn, __ in l_pinfo_vcs]
559     # clean
560     logger.write(_("clean sources\n"))
561     args_clean = config.VARS.application
562     args_clean += " --sources --products "
563     args_clean += ",".join(l_prod_names)
564     sat.clean(args_clean, batch=True, verbose=0, logger_add_link = logger)
565     # source
566     logger.write(_("get sources"))
567     args_source = config.VARS.application
568     args_source += " --products "
569     args_source += ",".join(l_prod_names)
570     sat.source(args_source, batch=True, verbose=0, logger_add_link = logger)
571
572     # make the new archives
573     d_archives_vcs = {}
574     for pn, pinfo in l_pinfo_vcs:
575         path_archive = make_archive(pn, pinfo, tmp_working_dir)
576         d_archives_vcs[pn] = (path_archive,
577                               os.path.join(ARCHIVE_DIR, pn + ".tgz"))
578     return d_archives_vcs
579
580 def make_archive(prod_name, prod_info, where):
581     '''Create an archive of a product by searching its source directory.
582
583     :param prod_name str: The name of the product.
584     :param prod_info Config: The specific configuration corresponding to the 
585                              product
586     :param where str: The path of the repository where to put the resulting 
587                       archive
588     :return: The path of the resulting archive
589     :rtype: str
590     '''
591     path_targz_prod = os.path.join(where, prod_name + ".tgz")
592     tar_prod = tarfile.open(path_targz_prod, mode='w:gz')
593     local_path = prod_info.source_dir
594     tar_prod.add(local_path, arcname=prod_name)
595     tar_prod.close()
596     return path_targz_prod       
597
598 def create_project_for_src_package(config, tmp_working_dir, with_vcs):
599     '''Create a specific project for a source package.
600
601     :param config Config: The global configuration.
602     :param tmp_working_dir str: The temporary local directory containing some 
603                                 specific directories or files needed in the 
604                                 source package
605     :param with_vcs boolean: True if the package is with vcs products (not 
606                              transformed into archive products)
607     :return: The dictionary 
608              {"project" : (produced project, project path in the archive)}
609     :rtype: Dict
610     '''
611
612     # Create in the working temporary directory the full project tree
613     project_tmp_dir = os.path.join(tmp_working_dir, PROJECT_DIR)
614     products_pyconf_tmp_dir = os.path.join(project_tmp_dir,
615                                          "products")
616     compil_scripts_tmp_dir = os.path.join(project_tmp_dir,
617                                          "products",
618                                          "compil_scripts")
619     env_scripts_tmp_dir = os.path.join(project_tmp_dir,
620                                          "products",
621                                          "env_scripts")
622     patches_tmp_dir = os.path.join(project_tmp_dir,
623                                          "products",
624                                          "patches")
625     application_tmp_dir = os.path.join(project_tmp_dir,
626                                          "applications")
627     for directory in [project_tmp_dir,
628                       compil_scripts_tmp_dir,
629                       env_scripts_tmp_dir,
630                       patches_tmp_dir,
631                       application_tmp_dir]:
632         src.ensure_path_exists(directory)
633
634     # Create the pyconf that contains the information of the project
635     project_pyconf_name = "project.pyconf"        
636     project_pyconf_file = os.path.join(project_tmp_dir, project_pyconf_name)
637     ff = open(project_pyconf_file, "w")
638     ff.write(PROJECT_TEMPLATE)
639     ff.close()
640     
641     # Loop over the products to get there pyconf and all the scripts 
642     # (compilation, environment, patches)
643     # and create the pyconf file to add to the project
644     lproducts_name = config.APPLICATION.products.keys()
645     l_products = src.product.get_products_infos(lproducts_name, config)
646     for p_name, p_info in l_products:
647         # ignore native and fixed products
648         if (src.product.product_is_native(p_info) or 
649                 src.product.product_is_fixed(p_info)):
650             continue
651         find_product_scripts_and_pyconf(p_name,
652                                         p_info,
653                                         config,
654                                         with_vcs,
655                                         compil_scripts_tmp_dir,
656                                         env_scripts_tmp_dir,
657                                         patches_tmp_dir,
658                                         products_pyconf_tmp_dir)
659     
660     find_application_pyconf(config, application_tmp_dir)
661     
662     d_project = {"project" : (project_tmp_dir, PROJECT_DIR )}
663     return d_project
664
665 def find_product_scripts_and_pyconf(p_name,
666                                     p_info,
667                                     config,
668                                     with_vcs,
669                                     compil_scripts_tmp_dir,
670                                     env_scripts_tmp_dir,
671                                     patches_tmp_dir,
672                                     products_pyconf_tmp_dir):
673     '''Create a specific pyconf file for a given product. Get its environment 
674        script, its compilation script and patches and put it in the temporary
675        working directory. This method is used in the source package in order to
676        construct the specific project.
677
678     :param p_name str: The name of the product.
679     :param p_info Config: The specific configuration corresponding to the 
680                              product
681     :param config Config: The global configuration.
682     :param with_vcs boolean: True if the package is with vcs products (not 
683                              transformed into archive products)
684     :param compil_scripts_tmp_dir str: The path to the temporary compilation 
685                                        scripts directory of the project.
686     :param env_scripts_tmp_dir str: The path to the temporary environment script 
687                                     directory of the project.
688     :param patches_tmp_dir str: The path to the temporary patch scripts 
689                                 directory of the project.
690     :param products_pyconf_tmp_dir str: The path to the temporary product 
691                                         scripts directory of the project.
692     '''
693     
694     # read the pyconf of the product
695     product_pyconf_path = src.find_file_in_lpath(p_name + ".pyconf",
696                                            config.PATHS.PRODUCTPATH)
697     product_pyconf_cfg = src.pyconf.Config(product_pyconf_path)
698
699     # find the compilation script if any
700     if src.product.product_has_script(p_info):
701         compil_script_path = src.Path(p_info.compil_script)
702         compil_script_path.copy(compil_scripts_tmp_dir)
703         product_pyconf_cfg[p_info.section].compil_script = os.path.basename(
704                                                     p_info.compil_script)
705     # find the environment script if any
706     if src.product.product_has_env_script(p_info):
707         env_script_path = src.Path(p_info.environ.env_script)
708         env_script_path.copy(env_scripts_tmp_dir)
709         product_pyconf_cfg[p_info.section].environ.env_script = os.path.basename(
710                                                 p_info.environ.env_script)
711     # find the patches if any
712     if src.product.product_has_patches(p_info):
713         patches = src.pyconf.Sequence()
714         for patch_path in p_info.patches:
715             p_path = src.Path(patch_path)
716             p_path.copy(patches_tmp_dir)
717             patches.append(os.path.basename(patch_path), "")
718
719         product_pyconf_cfg[p_info.section].patches = patches
720     
721     if with_vcs:
722         # put in the pyconf file the resolved values
723         for info in ["git_info", "cvs_info", "svn_info"]:
724             if info in p_info:
725                 for key in p_info[info]:
726                     product_pyconf_cfg[p_info.section][info][key] = p_info[
727                                                                       info][key]
728     else:
729         # if the product is not archive, then make it become archive.
730         if src.product.product_is_vcs(p_info):
731             product_pyconf_cfg[p_info.section].get_source = "archive"
732             if not "archive_info" in product_pyconf_cfg[p_info.section]:
733                 product_pyconf_cfg[p_info.section].addMapping("archive_info",
734                                         src.pyconf.Mapping(product_pyconf_cfg),
735                                         "")
736             product_pyconf_cfg[p_info.section
737                               ].archive_info.archive_name = p_info.name + ".tgz"
738     
739     # write the pyconf file to the temporary project location
740     product_tmp_pyconf_path = os.path.join(products_pyconf_tmp_dir,
741                                            p_name + ".pyconf")
742     ff = open(product_tmp_pyconf_path, 'w')
743     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
744     product_pyconf_cfg.__save__(ff, 1)
745     ff.close()
746
747 def find_application_pyconf(config, application_tmp_dir):
748     '''Find the application pyconf file and put it in the specific temporary 
749        directory containing the specific project of a source package.
750
751     :param config Config: The global configuration.
752     :param application_tmp_dir str: The path to the temporary application 
753                                        scripts directory of the project.
754     '''
755     # read the pyconf of the application
756     application_name = config.VARS.application
757     application_pyconf_path = src.find_file_in_lpath(
758                                             application_name + ".pyconf",
759                                             config.PATHS.APPLICATIONPATH)
760     application_pyconf_cfg = src.pyconf.Config(application_pyconf_path)
761     
762     # Change the workdir
763     application_pyconf_cfg.APPLICATION.workdir = src.pyconf.Reference(
764                                     application_pyconf_cfg,
765                                     src.pyconf.DOLLAR,
766                                     'VARS.salometoolsway + $VARS.sep + ".."')
767
768     # Prevent from compilation in base
769     application_pyconf_cfg.APPLICATION.no_base = "yes"
770     
771     # write the pyconf file to the temporary application location
772     application_tmp_pyconf_path = os.path.join(application_tmp_dir,
773                                                application_name + ".pyconf")
774     ff = open(application_tmp_pyconf_path, 'w')
775     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
776     application_pyconf_cfg.__save__(ff, 1)
777     ff.close()
778
779 def project_package(project_file_path, tmp_working_dir):
780     '''Prepare a dictionary that stores all the needed directories and files to
781        add in a project package.
782     
783     :param project_file_path str: The path to the local project.
784     :param tmp_working_dir str: The temporary local directory containing some 
785                                 specific directories or files needed in the 
786                                 project package
787     :return: the dictionary that stores all the needed directories and files to
788              add in a project package.
789              {label : (path_on_local_machine, path_in_archive)}
790     :rtype: dict
791     '''
792     d_project = {}
793     # Read the project file and get the directories to add to the package
794     project_pyconf_cfg = src.pyconf.Config(project_file_path)
795     paths = {"ARCHIVEPATH" : "archives",
796              "APPLICATIONPATH" : "applications",
797              "PRODUCTPATH" : "products",
798              "JOBPATH" : "jobs",
799              "MACHINEPATH" : "machines"}
800     # Loop over the project paths and add it
801     for path in paths:
802         if path not in project_pyconf_cfg:
803             continue
804         # Add the directory to the files to add in the package
805         d_project[path] = (project_pyconf_cfg[path], paths[path])
806         # Modify the value of the path in the package
807         project_pyconf_cfg[path] = src.pyconf.Reference(
808                                     project_pyconf_cfg,
809                                     src.pyconf.DOLLAR,
810                                     'project_path + "/' + paths[path] + '"')
811     
812     # Modify some values
813     if "project_path" not in project_pyconf_cfg:
814         project_pyconf_cfg.addMapping("project_path",
815                                       src.pyconf.Mapping(project_pyconf_cfg),
816                                       "")
817     project_pyconf_cfg.project_path = src.pyconf.Reference(project_pyconf_cfg,
818                                                            src.pyconf.DOLLAR,
819                                                            'PWD')
820     
821     # Write the project pyconf file
822     project_file_name = os.path.basename(project_file_path)
823     project_pyconf_tmp_path = os.path.join(tmp_working_dir, project_file_name)
824     ff = open(project_pyconf_tmp_path, 'w')
825     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
826     project_pyconf_cfg.__save__(ff, 1)
827     ff.close()
828     d_project["Project hat file"] = (project_pyconf_tmp_path, project_file_name)
829     
830     return d_project
831
832 def add_readme(config, package_type, where):
833     readme_path = os.path.join(where, "README")
834     f = open(readme_path, 'w')
835     # prepare substitution dictionary
836     d = dict()
837     if package_type == BINARY:
838         d['application'] = config.VARS.application
839         d['user'] = config.VARS.user
840         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
841         d['version'] = config.INTERNAL.sat_version
842         d['dist'] = config.VARS.dist
843         if 'profile' in config.APPLICATION:
844             d['launcher'] = config.APPLICATION.profile.launcher_name
845             readme_template_path = os.path.join(config.VARS.internal_dir,
846                                                 "README_BIN.template")
847         else:
848             d['env_file'] = 'env_launch.sh'
849             readme_template_path = os.path.join(config.VARS.internal_dir,
850                                                "README_BIN_NO_PROFILE.template")
851             
852     if package_type == SOURCE:
853         d['application'] = config.VARS.application
854         d['user'] = config.VARS.user
855         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
856         d['version'] = config.INTERNAL.sat_version
857         if 'profile' in config.APPLICATION:
858             d['profile'] = config.APPLICATION.profile.product
859             d['launcher'] = config.APPLICATION.profile.launcher_name
860         readme_template_path = os.path.join(config.VARS.internal_dir,
861                                     "README_SRC.template")
862
863     if package_type == PROJECT:
864         d['user'] = config.VARS.user
865         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
866         d['version'] = config.INTERNAL.sat_version
867         readme_template_path = os.path.join(config.VARS.internal_dir,
868                                     "README_PROJECT.template")
869
870     if package_type == SAT:
871         d['user'] = config.VARS.user
872         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
873         d['version'] = config.INTERNAL.sat_version
874         readme_template_path = os.path.join(config.VARS.internal_dir,
875                                     "README_SAT.template")
876     
877     f.write(src.template.substitute(readme_template_path, d))
878     
879     return readme_path
880         
881
882 def description():
883     '''method that is called when salomeTools is called with --help option.
884     
885     :return: The text to display for the package command description.
886     :rtype: str
887     '''
888     return _("The package command creates an archive.\nThere are 4 kinds of "
889              "archive:\n  1- The binary archive. It contains all the product "
890              "installation directories and a launcher,\n  2- The sources archive."
891              " It contains the products archives, a project corresponding to "
892              "the application and salomeTools,\n  3- The project archive. It "
893              "contains a project (give the project file path as argument),\n  4-"
894              " The salomeTools archive. It contains salomeTools.\n\nexample:"
895              "\nsat package SALOME-master --sources")
896   
897 def run(args, runner, logger):
898     '''method that is called when salomeTools is called with package parameter.
899     '''
900     
901     # Parse the options
902     (options, args) = parser.parse_args(args)
903        
904     # Check that a type of package is called, and only one
905     all_option_types = (options.binaries,
906                         options.sources,
907                         options.project not in ["", None],
908                         options.sat)
909
910     # Check if no option for package type
911     if all_option_types.count(True) == 0:
912         msg = _("Error: Precise a type for the package\nUse one of the "
913                 "following options: --binaries, --sources, --project or"
914                 " --salometools")
915         logger.write(src.printcolors.printcError(msg), 1)
916         logger.write("\n", 1)
917         return 1
918     
919     # Check for only one option for package type
920     if all_option_types.count(True) > 1:
921         msg = _("Error: You can use only one type for the package\nUse only one"
922                 " of the following options: --binaries, --sources, --project or"
923                 " --salometools")
924         logger.write(src.printcolors.printcError(msg), 1)
925         logger.write("\n", 1)
926         return 1
927     
928     # Get the package type
929     if options.binaries:
930         package_type = BINARY
931     if options.sources:
932         package_type = SOURCE
933     if options.project:
934         package_type = PROJECT
935     if options.sat:
936         package_type = SAT
937
938     # The repository where to put the package if not Binary or Source
939     package_default_path = runner.cfg.USER.workdir
940     
941     if package_type in [BINARY, SOURCE]:
942         # Check that the command has been called with an application
943         src.check_config_has_application(runner.cfg)
944
945         # Display information
946         logger.write(_("Packaging application %s\n") % src.printcolors.printcLabel(
947                                                     runner.cfg.VARS.application), 1)
948         
949         # Get the default directory where to put the packages
950         package_default_path = os.path.join(runner.cfg.APPLICATION.workdir,
951                                             "PACKAGE")
952         src.ensure_path_exists(package_default_path)
953         
954     elif package_type == PROJECT:
955         # check that the project is visible by SAT
956         if options.project not in runner.cfg.PROJECTS.project_file_paths:
957             site_path = os.path.join(runner.cfg.VARS.salometoolsway,
958                                      "data",
959                                      "site.pyconf")
960             msg = _("ERROR: the project %(proj)s is not visible by salomeTools."
961                     "\nPlease add it in the %(site)s file." % {
962                                   "proj" : options.project, "site" : site_path})
963             logger.write(src.printcolors.printcError(msg), 1)
964             logger.write("\n", 1)
965             return 1
966     
967     # Print
968     src.printcolors.print_value(logger, "Package type", package_type, 2)
969
970     # get the name of the archive or construct it
971     if options.name:
972         if os.path.basename(options.name) == options.name:
973             # only a name (not a path)
974             archive_name = options.name           
975             dir_name = package_default_path
976         else:
977             archive_name = os.path.basename(options.name)
978             dir_name = os.path.dirname(options.name)
979         
980         # suppress extension
981         if archive_name[-len(".tgz"):] == ".tgz":
982             archive_name = archive_name[:-len(".tgz")]
983         if archive_name[-len(".tar.gz"):] == ".tar.gz":
984             archive_name = archive_name[:-len(".tar.gz")]
985         
986     else:
987         dir_name = package_default_path
988         if package_type == BINARY:
989             archive_name = (runner.cfg.APPLICATION.name +
990                             "-" +
991                             runner.cfg.VARS.dist)
992             
993         if package_type == SOURCE:
994             archive_name = (runner.cfg.APPLICATION.name +
995                             "-" +
996                             "SRC")
997             if options.with_vcs:
998                 archive_name = (runner.cfg.APPLICATION.name +
999                             "-" +
1000                             "SRC" +
1001                             "-" +
1002                             "VCS")
1003
1004         if package_type == PROJECT:
1005             project_name, __ = os.path.splitext(
1006                                             os.path.basename(options.project))
1007             archive_name = ("PROJECT" +
1008                             "-" +
1009                             project_name)
1010  
1011         if package_type == SAT:
1012             archive_name = ("salomeTools" +
1013                             "-" +
1014                             runner.cfg.INTERNAL.sat_version)
1015  
1016     path_targz = os.path.join(dir_name, archive_name + ".tgz")
1017     
1018     # Print the path of the package
1019     src.printcolors.print_value(logger, "Package path", path_targz, 2)
1020
1021     # Create a working directory for all files that are produced during the
1022     # package creation and that will be removed at the end of the command
1023     tmp_working_dir = os.path.join(runner.cfg.VARS.tmp_root,
1024                                    runner.cfg.VARS.datehour)
1025     src.ensure_path_exists(tmp_working_dir)
1026     logger.write("\n", 5)
1027     logger.write(_("The temporary working directory: %s\n" % tmp_working_dir),5)
1028     
1029     logger.write("\n", 3)
1030
1031     msg = _("Preparation of files to add to the archive")
1032     logger.write(src.printcolors.printcLabel(msg), 2)
1033     logger.write("\n", 2)
1034
1035     if package_type == BINARY:           
1036         d_files_to_add = binary_package(runner.cfg,
1037                                         logger,
1038                                         options,
1039                                         tmp_working_dir)
1040         if not(d_files_to_add):
1041             return 1
1042
1043     if package_type == SOURCE:
1044         d_files_to_add = source_package(runner,
1045                                         runner.cfg,
1046                                         logger, 
1047                                         options,
1048                                         tmp_working_dir)          
1049     
1050     if package_type == PROJECT:
1051         d_files_to_add = project_package(options.project, tmp_working_dir)
1052
1053     if package_type == SAT:
1054         d_files_to_add = {"salomeTools" : (runner.cfg.VARS.salometoolsway, "")}
1055     
1056     # Add the README file in the package
1057     local_readme_tmp_path = add_readme(runner.cfg,
1058                                        package_type,
1059                                        tmp_working_dir)
1060     d_files_to_add["README"] = (local_readme_tmp_path, "README")
1061
1062     # Add the additional files of option add_files
1063     if options.add_files:
1064         for file_path in options.add_files:
1065             if not os.path.exists(file_path):
1066                 msg = _("WARNING: the file %s is not accessible.\n" % file_path)
1067                 continue
1068             file_name = os.path.basename(file_path)
1069             d_files_to_add[file_name] = (file_path, file_name)
1070
1071     logger.write("\n", 2)
1072
1073     logger.write(src.printcolors.printcLabel(_("Actually do the package")), 2)
1074     logger.write("\n", 2)
1075     
1076     try:
1077         # Creating the object tarfile
1078         tar = tarfile.open(path_targz, mode='w:gz')
1079         
1080         # Add the files to the tarfile object
1081         res = add_files(tar, archive_name, d_files_to_add, logger)
1082         tar.close()
1083     except KeyboardInterrupt:
1084         logger.write(src.printcolors.printcError("\nERROR: forced interruption\n"), 1)
1085         logger.write(_("Removing the temporary working directory ... "), 1)
1086         # remove the working directory
1087         shutil.rmtree(tmp_working_dir)
1088         logger.write(_("OK"), 1)
1089         logger.write(_("\n"), 1)
1090         return 1
1091     
1092     # remove the working directory    
1093     shutil.rmtree(tmp_working_dir)
1094     
1095     # Print again the path of the package
1096     logger.write("\n", 2)
1097     src.printcolors.print_value(logger, "Package path", path_targz, 2)
1098     
1099     return res