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