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