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