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