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