Salome HOME
integration patch pour version windows
[tools/sat.git] / commands / package.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2012  CEA/DEN
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18
19 import os
20 import stat
21 import shutil
22 import datetime
23 import tarfile
24 import codecs
25 import string
26 import glob
27 import pprint as PP
28
29 import src
30
31 from application import get_SALOME_modules
32 import src.debug as DBG
33
34 BINARY = "binary"
35 SOURCE = "Source"
36 PROJECT = "Project"
37 SAT = "Sat"
38
39 ARCHIVE_DIR = "ARCHIVES"
40 PROJECT_DIR = "PROJECT"
41
42 IGNORED_DIRS = [".git", ".svn"]
43 IGNORED_EXTENSIONS = []
44
45 PACKAGE_EXT=".tar.gz" # the extension we use for the packages
46
47 PROJECT_TEMPLATE = """#!/usr/bin/env python
48 #-*- coding:utf-8 -*-
49
50 # The path to the archive root directory
51 root_path : $PWD + "/../"
52 # path to the PROJECT
53 project_path : $PWD + "/"
54
55 # Where to search the archives of the products
56 ARCHIVEPATH : $root_path + "ARCHIVES"
57 # Where to search the pyconf of the applications
58 APPLICATIONPATH : $project_path + "applications/"
59 # Where to search the pyconf of the products
60 PRODUCTPATH : $project_path + "products/"
61 # Where to search the pyconf of the jobs of the project
62 JOBPATH : $project_path + "jobs/"
63 # Where to search the pyconf of the machines of the project
64 MACHINEPATH : $project_path + "machines/"
65 """
66
67 LOCAL_TEMPLATE = ("""#!/usr/bin/env python
68 #-*- coding:utf-8 -*-
69
70   LOCAL :
71   {
72     base : 'default'
73     workdir : 'default'
74     log_dir : 'default'
75     archive_dir : 'default'
76     VCS : None
77     tag : None
78   }
79
80 PROJECTS :
81 {
82 project_file_paths : [$VARS.salometoolsway + $VARS.sep + \"..\" + $VARS.sep"""
83 """ + \"""" + PROJECT_DIR + """\" + $VARS.sep + "project.pyconf"]
84 }
85 """)
86
87 # Define all possible option for the package command :  sat package <options>
88 parser = src.options.Options()
89 parser.add_option('b', 'binaries', 'boolean', 'binaries',
90     _('Optional: Produce a binary package.'), False)
91 parser.add_option('f', 'force_creation', 'boolean', 'force_creation',
92     _('Optional: Only binary package: produce the archive even if '
93       'there are some missing products.'), False)
94 parser.add_option('s', 'sources', 'boolean', 'sources',
95     _('Optional: Produce a compilable archive of the sources of the '
96       'application.'), False)
97 parser.add_option('', 'with_vcs', 'boolean', 'with_vcs',
98     _('Optional: Do not make archive for products in VCS mode (git, cvs, svn). ' 
99       'Sat prepare will use VCS mode instead to retrieve them'),
100     False)
101 parser.add_option('', 'ftp', 'boolean', 'ftp',
102     _('Optional: Do not embed archives for products in archive mode.' 
103     'Sat prepare will use ftp instead to retrieve them'),
104     False)
105 parser.add_option('p', 'project', 'string', 'project',
106     _('Optional: Produce an archive that contains a project.'), "")
107 parser.add_option('t', 'salometools', 'boolean', 'sat',
108     _('Optional: Produce an archive that contains salomeTools.'), False)
109 parser.add_option('n', 'name', 'string', 'name',
110     _('Optional: The name or full path of the archive.'), None)
111 parser.add_option('', 'add_files', 'list2', 'add_files',
112     _('Optional: The list of additional files to add to the archive.'), [])
113 parser.add_option('', 'without_properties', 'properties', 'without_properties',
114     _('Optional: Filter the products by their properties.\n\tSyntax: '
115       '--without_properties <property>:<value>'))
116
117
118 def add_files(tar, name_archive, d_content, logger, f_exclude=None):
119     '''Create an archive containing all directories and files that are given in
120        the d_content argument.
121     
122     :param tar tarfile: The tarfile instance used to make the archive.
123     :param name_archive str: The name of the archive to make.
124     :param d_content dict: The dictionary that contain all directories and files
125                            to add in the archive.
126                            d_content[label] = 
127                                         (path_on_local_machine, path_in_archive)
128     :param logger Logger: the logging instance
129     :param f_exclude Function: the function that filters
130     :return: 0 if success, 1 if not.
131     :rtype: int
132     '''
133     # get the max length of the messages in order to make the display
134     max_len = len(max(d_content.keys(), key=len))
135     
136     success = 0
137     # loop over each directory or file stored in the d_content dictionary
138     names = sorted(d_content.keys())
139     DBG.write("add tar names", names)
140
141     # used to avoid duplications (for pip install in python, or single_install_dir cases)
142     already_added=set() 
143     for name in names:
144         # display information
145         len_points = max_len - len(name) + 3
146         local_path, archive_path = d_content[name]
147         in_archive = os.path.join(name_archive, archive_path)
148         logger.write(name + " " + len_points * "." + " "+ in_archive + " ", 3)
149         # Get the local path and the path in archive 
150         # of the directory or file to add
151         # Add it in the archive
152         try:
153             key=local_path+"->"+in_archive
154             if key not in already_added:
155                 tar.add(local_path, arcname=in_archive, exclude=f_exclude)
156                 already_added.add(key)
157             logger.write(src.printcolors.printcSuccess(_("OK")), 3)
158         except Exception as e:
159             logger.write(src.printcolors.printcError(_("KO ")), 3)
160             logger.write(str(e), 3)
161             success = 1
162         logger.write("\n", 3)
163     return success
164
165 def exclude_VCS_and_extensions(filename):
166     ''' The function that is used to exclude from package the link to the 
167         VCS repositories (like .git)
168
169     :param filename Str: The filname to exclude (or not).
170     :return: True if the file has to be exclude
171     :rtype: Boolean
172     '''
173     for dir_name in IGNORED_DIRS:
174         if dir_name in filename:
175             return True
176     for extension in IGNORED_EXTENSIONS:
177         if filename.endswith(extension):
178             return True
179     return False
180
181 def produce_relative_launcher(config,
182                               logger,
183                               file_dir,
184                               file_name,
185                               binaries_dir_name):
186     '''Create a specific SALOME launcher for the binary package. This launcher 
187        uses relative paths.
188     
189     :param config Config: The global configuration.
190     :param logger Logger: the logging instance
191     :param file_dir str: the directory where to put the launcher
192     :param file_name str: The launcher name
193     :param binaries_dir_name str: the name of the repository where the binaries
194                                   are, in the archive.
195     :return: the path of the produced launcher
196     :rtype: str
197     '''
198     
199     # get KERNEL installation path 
200     kernel_root_dir = os.path.join(binaries_dir_name, "KERNEL")
201
202     # set kernel bin dir (considering fhs property)
203     kernel_cfg = src.product.get_product_config(config, "KERNEL")
204     if src.get_property_in_product_cfg(kernel_cfg, "fhs"):
205         bin_kernel_install_dir = os.path.join(kernel_root_dir,"bin") 
206     else:
207         bin_kernel_install_dir = os.path.join(kernel_root_dir,"bin","salome") 
208
209     # check if the application contains an application module
210     # check also if the application has a distene product, 
211     # in this case get its licence file name
212     l_product_info = src.product.get_products_infos(config.APPLICATION.products.keys(), config)
213     salome_application_name="Not defined" 
214     distene_licence_file_name=False
215     for prod_name, prod_info in l_product_info:
216         # look for a "salome application" and a distene product
217         if src.get_property_in_product_cfg(prod_info, "is_distene") == "yes":
218             distene_licence_file_name = src.product.product_has_licence(prod_info, 
219                                             config.PATHS.LICENCEPATH) 
220         if src.get_property_in_product_cfg(prod_info, "is_salome_application") == "yes":
221             salome_application_name=prod_info.name
222
223     # if the application contains an application module, we set ABSOLUTE_APPLI_PATH to it
224     # if not we set it to KERNEL_INSTALL_DIR, which is sufficient, except for salome test
225     if salome_application_name == "Not defined":
226         app_root_dir=kernel_root_dir
227     else:
228         app_root_dir=os.path.join(binaries_dir_name, salome_application_name)
229
230     additional_env={}
231     additional_env['sat_bin_kernel_install_dir'] = "out_dir_Path + " +\
232                                                    config.VARS.sep + bin_kernel_install_dir
233     if "python3" in config.APPLICATION and config.APPLICATION.python3 == "yes":
234         additional_env['sat_python_version'] = 3
235     else:
236         additional_env['sat_python_version'] = 2
237
238     additional_env['ABSOLUTE_APPLI_PATH'] = "out_dir_Path" + config.VARS.sep + app_root_dir
239
240     # create an environment file writer
241     writer = src.environment.FileEnvWriter(config,
242                                            logger,
243                                            file_dir,
244                                            src_root=None,
245                                            env_info=None)
246     
247     filepath = os.path.join(file_dir, file_name)
248     # Write
249     writer.write_env_file(filepath,
250                           False,  # for launch
251                           "cfgForPy",
252                           additional_env=additional_env,
253                           no_path_init="False",
254                           for_package = binaries_dir_name)
255     
256     # Little hack to put out_dir_Path outside the strings
257     src.replace_in_file(filepath, 'r"out_dir_Path', 'out_dir_Path + r"' )
258     src.replace_in_file(filepath, "'out_dir_Path + ", "out_dir_Path + '" )
259     
260     # A hack to put a call to a file for distene licence.
261     # It does nothing to an application that has no distene product
262     if distene_licence_file_name:
263         logger.write("Application has a distene licence file! We use it in package launcher", 5)
264         hack_for_distene_licence(filepath, distene_licence_file_name)
265        
266     # change the rights in order to make the file executable for everybody
267     os.chmod(filepath,
268              stat.S_IRUSR |
269              stat.S_IRGRP |
270              stat.S_IROTH |
271              stat.S_IWUSR |
272              stat.S_IXUSR |
273              stat.S_IXGRP |
274              stat.S_IXOTH)
275
276     return filepath
277
278 def hack_for_distene_licence(filepath, licence_file):
279     '''Replace the distene licence env variable by a call to a file.
280     
281     :param filepath Str: The path to the launcher to modify.
282     '''  
283     shutil.move(filepath, filepath + "_old")
284     fileout= filepath
285     filein = filepath + "_old"
286     fin = open(filein, "r")
287     fout = open(fileout, "w")
288     text = fin.readlines()
289     # Find the Distene section
290     num_line = -1
291     for i,line in enumerate(text):
292         if "# Set DISTENE License" in line:
293             num_line = i
294             break
295     if num_line == -1:
296         # No distene product, there is nothing to do
297         fin.close()
298         for line in text:
299             fout.write(line)
300         fout.close()
301         return
302     del text[num_line +1]
303     del text[num_line +1]
304     text_to_insert ="""    try:
305         distene_licence_file="%s"
306         if sys.version_info[0] >= 3 and sys.version_info[1] >= 5:
307             import importlib.util
308             spec_dist = importlib.util.spec_from_file_location("distene_licence", distene_licence_file)
309             distene=importlib.util.module_from_spec(spec_dist)
310             spec_dist.loader.exec_module(distene)
311         else:
312             import imp
313             distene = imp.load_source('distene_licence', distene_licence_file)
314         distene.set_distene_variables(context)
315     except:
316         pass\n"""  % licence_file
317     text.insert(num_line + 1, text_to_insert)
318     for line in text:
319         fout.write(line)
320     fin.close()    
321     fout.close()
322     return
323     
324 def produce_relative_env_files(config,
325                               logger,
326                               file_dir,
327                               binaries_dir_name):
328     '''Create some specific environment files for the binary package. These 
329        files use relative paths.
330     
331     :param config Config: The global configuration.
332     :param logger Logger: the logging instance
333     :param file_dir str: the directory where to put the files
334     :param binaries_dir_name str: the name of the repository where the binaries
335                                   are, in the archive.
336     :return: the list of path of the produced environment files
337     :rtype: List
338     '''  
339     # create an environment file writer
340     writer = src.environment.FileEnvWriter(config,
341                                            logger,
342                                            file_dir,
343                                            src_root=None)
344     
345     if src.architecture.is_windows():
346       shell = "bat"
347       filename  = "env_launch.bat"
348     else:
349       shell = "bash"
350       filename  = "env_launch.sh"
351
352     # Write
353     filepath = writer.write_env_file(filename,
354                           False, # for launch
355                           shell,
356                           for_package = binaries_dir_name)
357
358     # Little hack to put out_dir_Path as environment variable
359     if src.architecture.is_windows() :
360       src.replace_in_file(filepath, '"out_dir_Path', '"%out_dir_Path%' )
361     else:
362       src.replace_in_file(filepath, '"out_dir_Path', '"${out_dir_Path}' )
363
364     # change the rights in order to make the file executable for everybody
365     os.chmod(filepath,
366              stat.S_IRUSR |
367              stat.S_IRGRP |
368              stat.S_IROTH |
369              stat.S_IWUSR |
370              stat.S_IXUSR |
371              stat.S_IXGRP |
372              stat.S_IXOTH)
373     
374     return filepath
375
376 def produce_install_bin_file(config,
377                              logger,
378                              file_dir,
379                              d_sub,
380                              file_name):
381     '''Create a bash shell script which do substitutions in BIRARIES dir 
382        in order to use it for extra compilations.
383     
384     :param config Config: The global configuration.
385     :param logger Logger: the logging instance
386     :param file_dir str: the directory where to put the files
387     :param d_sub, dict: the dictionnary that contains the substitutions to be done
388     :param file_name str: the name of the install script file
389     :return: the produced file
390     :rtype: str
391     '''  
392     # Write
393     filepath = os.path.join(file_dir, file_name)
394     # open the file and write into it
395     # use codec utf-8 as sat variables are in unicode
396     with codecs.open(filepath, "w", 'utf-8') as installbin_file:
397         installbin_template_path = os.path.join(config.VARS.internal_dir,
398                                         "INSTALL_BIN.template")
399         
400         # build the name of the directory that will contain the binaries
401         binaries_dir_name = config.INTERNAL.config.binary_dir + config.VARS.dist
402         # build the substitution loop
403         loop_cmd = "for f in $(grep -RIl"
404         for key in d_sub:
405             loop_cmd += " -e "+ key
406         loop_cmd += ' ' + config.INTERNAL.config.install_dir +\
407                     '); do\n     sed -i "\n'
408         for key in d_sub:
409             loop_cmd += "        s?" + key + "?$(pwd)/" + d_sub[key] + "?g\n"
410         loop_cmd += '            " $f\ndone'
411
412         d={}
413         d["BINARIES_DIR"] = binaries_dir_name
414         d["SUBSTITUTION_LOOP"]=loop_cmd
415         d["INSTALL_DIR"]=config.INTERNAL.config.install_dir
416         
417         # substitute the template and write it in file
418         content=src.template.substitute(installbin_template_path, d)
419         installbin_file.write(content)
420         # change the rights in order to make the file executable for everybody
421         os.chmod(filepath,
422                  stat.S_IRUSR |
423                  stat.S_IRGRP |
424                  stat.S_IROTH |
425                  stat.S_IWUSR |
426                  stat.S_IXUSR |
427                  stat.S_IXGRP |
428                  stat.S_IXOTH)
429     
430     return filepath
431
432 def product_appli_creation_script(config,
433                                   logger,
434                                   file_dir,
435                                   binaries_dir_name):
436     '''Create a script that can produce an application (EDF style) in the binary
437        package.
438     
439     :param config Config: The global configuration.
440     :param logger Logger: the logging instance
441     :param file_dir str: the directory where to put the file
442     :param binaries_dir_name str: the name of the repository where the binaries
443                                   are, in the archive.
444     :return: the path of the produced script file
445     :rtype: Str
446     '''
447     template_name = "create_appli.py.for_bin_packages.template"
448     template_path = os.path.join(config.VARS.internal_dir, template_name)
449     text_to_fill = open(template_path, "r").read()
450     text_to_fill = text_to_fill.replace("TO BE FILLED 1",
451                                         '"' + binaries_dir_name + '"')
452     
453     text_to_add = ""
454     for product_name in get_SALOME_modules(config):
455         product_info = src.product.get_product_config(config, product_name)
456        
457         if src.product.product_is_smesh_plugin(product_info):
458             continue
459
460         if 'install_dir' in product_info and bool(product_info.install_dir):
461             if src.product.product_is_cpp(product_info):
462                 # cpp module
463                 for cpp_name in src.product.get_product_components(product_info):
464                     line_to_add = ("<module name=\"" + 
465                                    cpp_name + 
466                                    "\" gui=\"yes\" path=\"''' + "
467                                    "os.path.join(dir_bin_name, \"" + 
468                                    cpp_name + "\") + '''\"/>")
469             else:
470                 # regular module
471                 line_to_add = ("<module name=\"" + 
472                                product_name + 
473                                "\" gui=\"yes\" path=\"''' + "
474                                "os.path.join(dir_bin_name, \"" + 
475                                product_name + "\") + '''\"/>")
476             text_to_add += line_to_add + "\n"
477     
478     filled_text = text_to_fill.replace("TO BE FILLED 2", text_to_add)
479     
480     tmp_file_path = os.path.join(file_dir, "create_appli.py")
481     ff = open(tmp_file_path, "w")
482     ff.write(filled_text)
483     ff.close()
484     
485     # change the rights in order to make the file executable for everybody
486     os.chmod(tmp_file_path,
487              stat.S_IRUSR |
488              stat.S_IRGRP |
489              stat.S_IROTH |
490              stat.S_IWUSR |
491              stat.S_IXUSR |
492              stat.S_IXGRP |
493              stat.S_IXOTH)
494     
495     return tmp_file_path
496
497 def binary_package(config, logger, options, tmp_working_dir):
498     '''Prepare a dictionary that stores all the needed directories and files to
499        add in a binary package.
500     
501     :param config Config: The global configuration.
502     :param logger Logger: the logging instance
503     :param options OptResult: the options of the launched command
504     :param tmp_working_dir str: The temporary local directory containing some 
505                                 specific directories or files needed in the 
506                                 binary package
507     :return: the dictionary that stores all the needed directories and files to
508              add in a binary package.
509              {label : (path_on_local_machine, path_in_archive)}
510     :rtype: dict
511     '''
512
513     # Get the list of product installation to add to the archive
514     l_products_name = sorted(config.APPLICATION.products.keys())
515     l_product_info = src.product.get_products_infos(l_products_name,
516                                                     config)
517     l_install_dir = []
518     l_source_dir = []
519     l_not_installed = []
520     l_sources_not_present = []
521     generate_mesa_launcher = False  # a flag to know if we generate a mesa launcher
522     if ("APPLICATION" in config  and
523         "properties"  in config.APPLICATION  and
524         "mesa_launcher_in_package"    in config.APPLICATION.properties  and
525         config.APPLICATION.properties.mesa_launcher_in_package == "yes") :
526             generate_mesa_launcher=True
527
528     for prod_name, prod_info in l_product_info:
529         # skip product with property not_in_package set to yes
530         if src.get_property_in_product_cfg(prod_info, "not_in_package") == "yes":
531             continue  
532
533         # Add the sources of the products that have the property 
534         # sources_in_package : "yes"
535         if src.get_property_in_product_cfg(prod_info,
536                                            "sources_in_package") == "yes":
537             if os.path.exists(prod_info.source_dir):
538                 l_source_dir.append((prod_name, prod_info.source_dir))
539             else:
540                 l_sources_not_present.append(prod_name)
541
542         # ignore the native and fixed products for install directories
543         if (src.product.product_is_native(prod_info) 
544                 or src.product.product_is_fixed(prod_info)
545                 or not src.product.product_compiles(prod_info)):
546             continue
547         if src.product.check_installation(config, prod_info):
548             l_install_dir.append((prod_name, prod_info.install_dir))
549         else:
550             l_not_installed.append(prod_name)
551         
552         # Add also the cpp generated modules (if any)
553         if src.product.product_is_cpp(prod_info):
554             # cpp module
555             for name_cpp in src.product.get_product_components(prod_info):
556                 install_dir = os.path.join(config.APPLICATION.workdir,
557                                            config.INTERNAL.config.install_dir,
558                                            name_cpp) 
559                 if os.path.exists(install_dir):
560                     l_install_dir.append((name_cpp, install_dir))
561                 else:
562                     l_not_installed.append(name_cpp)
563         
564     # check the name of the directory that (could) contains the binaries 
565     # from previous detar
566     binaries_from_detar = os.path.join(
567                               config.APPLICATION.workdir,
568                               config.INTERNAL.config.binary_dir + config.VARS.dist)
569     if os.path.exists(binaries_from_detar):
570          logger.write("""
571 WARNING: existing binaries directory from previous detar installation:
572          %s
573          To make new package from this, you have to: 
574          1) install binaries in INSTALL directory with the script "install_bin.sh" 
575             see README file for more details
576          2) or recompile everything in INSTALL with "sat compile" command 
577             this step is long, and requires some linux packages to be installed 
578             on your system\n
579 """ % binaries_from_detar)
580     
581     # Print warning or error if there are some missing products
582     if len(l_not_installed) > 0:
583         text_missing_prods = ""
584         for p_name in l_not_installed:
585             text_missing_prods += "-" + p_name + "\n"
586         if not options.force_creation:
587             msg = _("ERROR: there are missing products installations:")
588             logger.write("%s\n%s" % (src.printcolors.printcError(msg),
589                                      text_missing_prods),
590                          1)
591             return None
592         else:
593             msg = _("WARNING: there are missing products installations:")
594             logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
595                                      text_missing_prods),
596                          1)
597
598     # Do the same for sources
599     if len(l_sources_not_present) > 0:
600         text_missing_prods = ""
601         for p_name in l_sources_not_present:
602             text_missing_prods += "-" + p_name + "\n"
603         if not options.force_creation:
604             msg = _("ERROR: there are missing products sources:")
605             logger.write("%s\n%s" % (src.printcolors.printcError(msg),
606                                      text_missing_prods),
607                          1)
608             return None
609         else:
610             msg = _("WARNING: there are missing products sources:")
611             logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
612                                      text_missing_prods),
613                          1)
614  
615     # construct the name of the directory that will contain the binaries
616     binaries_dir_name = config.INTERNAL.config.binary_dir + config.VARS.dist
617     
618     # construct the correlation table between the product names, there 
619     # actual install directories and there install directory in archive
620     d_products = {}
621     for prod_name, install_dir in l_install_dir:
622         path_in_archive = os.path.join(binaries_dir_name, os.path.basename(install_dir))
623         d_products[prod_name + " (bin)"] = (install_dir, path_in_archive)
624         
625     for prod_name, source_dir in l_source_dir:
626         path_in_archive = os.path.join("SOURCES", prod_name)
627         d_products[prod_name + " (sources)"] = (source_dir, path_in_archive)
628
629     # for packages of SALOME applications including KERNEL, 
630     # we produce a salome launcher or a virtual application (depending on salome version)
631     if 'KERNEL' in config.APPLICATION.products:
632         VersionSalome = src.get_salome_version(config)
633         # Case where SALOME has the launcher that uses the SalomeContext API
634         if VersionSalome >= 730:
635             # create the relative launcher and add it to the files to add
636             launcher_name = src.get_launcher_name(config)
637             launcher_package = produce_relative_launcher(config,
638                                                  logger,
639                                                  tmp_working_dir,
640                                                  launcher_name,
641                                                  binaries_dir_name)
642             d_products["launcher"] = (launcher_package, launcher_name)
643
644             # if the application contains mesa products, we generate in addition to the 
645             # classical salome launcher a launcher using mesa and called mesa_salome 
646             # (the mesa launcher will be used for remote usage through ssh).
647             if generate_mesa_launcher:
648                 #if there is one : store the use_mesa property
649                 restore_use_mesa_option=None
650                 if ('properties' in config.APPLICATION and 
651                     'use_mesa' in config.APPLICATION.properties):
652                     restore_use_mesa_option = config.APPLICATION.properties.use_mesa
653
654                 # activate mesa property, and generate a mesa launcher
655                 src.activate_mesa_property(config)  #activate use_mesa property
656                 launcher_mesa_name="mesa_"+launcher_name
657                 launcher_package_mesa = produce_relative_launcher(config,
658                                                      logger,
659                                                      tmp_working_dir,
660                                                      launcher_mesa_name,
661                                                      binaries_dir_name)
662                 d_products["launcher (mesa)"] = (launcher_package_mesa, launcher_mesa_name)
663
664                 # if there was a use_mesa value, we restore it
665                 # else we set it to the default value "no"
666                 if restore_use_mesa_option != None:
667                     config.APPLICATION.properties.use_mesa=restore_use_mesa_option
668                 else:
669                     config.APPLICATION.properties.use_mesa="no"
670
671             if options.sources:
672                 # if we mix binaries and sources, we add a copy of the launcher, 
673                 # prefixed  with "bin",in order to avoid clashes
674                 d_products["launcher (copy)"] = (launcher_package, "bin"+launcher_name)
675         else:
676             # Provide a script for the creation of an application EDF style
677             appli_script = product_appli_creation_script(config,
678                                                         logger,
679                                                         tmp_working_dir,
680                                                         binaries_dir_name)
681             
682             d_products["appli script"] = (appli_script, "create_appli.py")
683
684     # Put also the environment file
685     env_file = produce_relative_env_files(config,
686                                            logger,
687                                            tmp_working_dir,
688                                            binaries_dir_name)
689
690     if src.architecture.is_windows():
691       filename  = "env_launch.bat"
692     else:
693       filename  = "env_launch.sh"
694     d_products["environment file"] = (env_file, filename)      
695
696     return d_products
697
698 def source_package(sat, config, logger, options, tmp_working_dir):
699     '''Prepare a dictionary that stores all the needed directories and files to
700        add in a source package.
701     
702     :param config Config: The global configuration.
703     :param logger Logger: the logging instance
704     :param options OptResult: the options of the launched command
705     :param tmp_working_dir str: The temporary local directory containing some 
706                                 specific directories or files needed in the 
707                                 binary package
708     :return: the dictionary that stores all the needed directories and files to
709              add in a source package.
710              {label : (path_on_local_machine, path_in_archive)}
711     :rtype: dict
712     '''
713     
714     d_archives={}
715     # Get all the products that are prepared using an archive
716     # unless ftp mode is specified (in this case the user of the
717     # archive will get the sources through the ftp mode of sat prepare
718     if not options.ftp:
719         logger.write("Find archive products ... ")
720         d_archives, l_pinfo_vcs = get_archives(config, logger)
721         logger.write("Done\n")
722
723     d_archives_vcs = {}
724     if not options.with_vcs and len(l_pinfo_vcs) > 0:
725         # Make archives with the products that are not prepared using an archive
726         # (git, cvs, svn, etc)
727         logger.write("Construct archives for vcs products ... ")
728         d_archives_vcs = get_archives_vcs(l_pinfo_vcs,
729                                           sat,
730                                           config,
731                                           logger,
732                                           tmp_working_dir)
733         logger.write("Done\n")
734
735     # Create a project
736     logger.write("Create the project ... ")
737     d_project = create_project_for_src_package(config,
738                                                tmp_working_dir,
739                                                options.with_vcs,
740                                                options.ftp)
741     logger.write("Done\n")
742     
743     # Add salomeTools
744     tmp_sat = add_salomeTools(config, tmp_working_dir)
745     d_sat = {"salomeTools" : (tmp_sat, "salomeTools")}
746     
747     # Add a sat symbolic link if not win
748     if not src.architecture.is_windows():
749         tmp_satlink_path = os.path.join(tmp_working_dir, 'sat')
750         try:
751             t = os.getcwd()
752         except:
753             # In the jobs, os.getcwd() can fail
754             t = config.LOCAL.workdir
755         os.chdir(tmp_working_dir)
756         if os.path.lexists(tmp_satlink_path):
757             os.remove(tmp_satlink_path)
758         os.symlink(os.path.join('salomeTools', 'sat'), 'sat')
759         os.chdir(t)
760         
761         d_sat["sat link"] = (tmp_satlink_path, "sat")
762     
763     d_source = src.merge_dicts(d_archives, d_archives_vcs, d_project, d_sat)
764     return d_source
765
766 def get_archives(config, logger):
767     '''Find all the products that are get using an archive and all the products
768        that are get using a vcs (git, cvs, svn) repository.
769     
770     :param config Config: The global configuration.
771     :param logger Logger: the logging instance
772     :return: the dictionary {name_product : 
773              (local path of its archive, path in the package of its archive )}
774              and the list of specific configuration corresponding to the vcs 
775              products
776     :rtype: (Dict, List)
777     '''
778     # Get the list of product informations
779     l_products_name = config.APPLICATION.products.keys()
780     l_product_info = src.product.get_products_infos(l_products_name,
781                                                     config)
782     d_archives = {}
783     l_pinfo_vcs = []
784     for p_name, p_info in l_product_info:
785         # skip product with property not_in_package set to yes
786         if src.get_property_in_product_cfg(p_info, "not_in_package") == "yes":
787             continue  
788         # ignore the native and fixed products
789         if (src.product.product_is_native(p_info) 
790                 or src.product.product_is_fixed(p_info)):
791             continue
792         if p_info.get_source == "archive":
793             archive_path = p_info.archive_info.archive_name
794             archive_name = os.path.basename(archive_path)
795             d_archives[p_name] = (archive_path,
796                                   os.path.join(ARCHIVE_DIR, archive_name))
797             if (src.appli_test_property(config,"pip", "yes") and 
798                 src.product.product_test_property(p_info,"pip", "yes")):
799                 # if pip mode is activated, and product is managed by pip
800                 pip_wheels_dir=os.path.join(config.LOCAL.archive_dir,"wheels")
801                 pip_wheel_pattern=os.path.join(pip_wheels_dir, 
802                     "%s-%s*" % (p_info.name, p_info.version))
803                 pip_wheel_path=glob.glob(pip_wheel_pattern)
804                 msg_pip_not_found="Error in get_archive, pip wheel for "\
805                                   "product %s-%s was not found in %s directory"
806                 msg_pip_two_or_more="Error in get_archive, several pip wheels for "\
807                                   "product %s-%s were found in %s directory"
808                 if len(pip_wheel_path)==0:
809                     raise src.SatException(msg_pip_not_found %\
810                         (p_info.name, p_info.version, pip_wheels_dir))
811                 if len(pip_wheel_path)>1:
812                     raise src.SatException(msg_pip_two_or_more %\
813                         (p_info.name, p_info.version, pip_wheels_dir))
814
815                 pip_wheel_name=os.path.basename(pip_wheel_path[0])
816                 d_archives[p_name+" (pip wheel)"]=(pip_wheel_path[0], 
817                     os.path.join(ARCHIVE_DIR, "wheels", pip_wheel_name))
818         else:
819             # this product is not managed by archive, 
820             # an archive of the vcs directory will be created by get_archive_vcs
821             l_pinfo_vcs.append((p_name, p_info)) 
822             
823     return d_archives, l_pinfo_vcs
824
825 def add_salomeTools(config, tmp_working_dir):
826     '''Prepare a version of salomeTools that has a specific local.pyconf file 
827        configured for a source package.
828
829     :param config Config: The global configuration.
830     :param tmp_working_dir str: The temporary local directory containing some 
831                                 specific directories or files needed in the 
832                                 source package
833     :return: The path to the local salomeTools directory to add in the package
834     :rtype: str
835     '''
836     # Copy sat in the temporary working directory
837     sat_tmp_path = src.Path(os.path.join(tmp_working_dir, "salomeTools"))
838     sat_running_path = src.Path(config.VARS.salometoolsway)
839     sat_running_path.copy(sat_tmp_path)
840     
841     # Update the local.pyconf file that contains the path to the project
842     local_pyconf_name = "local.pyconf"
843     local_pyconf_dir = os.path.join(tmp_working_dir, "salomeTools", "data")
844     local_pyconf_file = os.path.join(local_pyconf_dir, local_pyconf_name)
845     # Remove the .pyconf file in the root directory of salomeTools if there is
846     # any. (For example when launching jobs, a pyconf file describing the jobs 
847     # can be here and is not useful) 
848     files_or_dir_SAT = os.listdir(os.path.join(tmp_working_dir, "salomeTools"))
849     for file_or_dir in files_or_dir_SAT:
850         if file_or_dir.endswith(".pyconf") or file_or_dir.endswith(".txt"):
851             file_path = os.path.join(tmp_working_dir,
852                                      "salomeTools",
853                                      file_or_dir)
854             os.remove(file_path)
855     
856     ff = open(local_pyconf_file, "w")
857     ff.write(LOCAL_TEMPLATE)
858     ff.close()
859     
860     return sat_tmp_path.path
861
862 def get_archives_vcs(l_pinfo_vcs, sat, config, logger, tmp_working_dir):
863     '''For sources package that require that all products are get using an 
864        archive, one has to create some archive for the vcs products.
865        So this method calls the clean and source command of sat and then create
866        the archives.
867
868     :param l_pinfo_vcs List: The list of specific configuration corresponding to
869                              each vcs product
870     :param sat Sat: The Sat instance that can be called to clean and source the
871                     products
872     :param config Config: The global configuration.
873     :param logger Logger: the logging instance
874     :param tmp_working_dir str: The temporary local directory containing some 
875                                 specific directories or files needed in the 
876                                 source package
877     :return: the dictionary that stores all the archives to add in the source 
878              package. {label : (path_on_local_machine, path_in_archive)}
879     :rtype: dict
880     '''
881     # clean the source directory of all the vcs products, then use the source 
882     # command and thus construct an archive that will not contain the patches
883     l_prod_names = [pn for pn, __ in l_pinfo_vcs]
884     if False: # clean is dangerous in user/SOURCES, fixed in tmp_local_working_dir
885       logger.write(_("\nclean sources\n"))
886       args_clean = config.VARS.application
887       args_clean += " --sources --products "
888       args_clean += ",".join(l_prod_names)
889       logger.write("WARNING: get_archives_vcs clean\n         '%s'\n" % args_clean, 1)
890       sat.clean(args_clean, batch=True, verbose=0, logger_add_link = logger)
891     if True:
892       # source
893       logger.write(_("get sources\n"))
894       args_source = config.VARS.application
895       args_source += " --products "
896       args_source += ",".join(l_prod_names)
897       svgDir = sat.cfg.APPLICATION.workdir
898       tmp_local_working_dir = os.path.join(sat.cfg.APPLICATION.workdir, "tmp_package")  # to avoid too much big files in /tmp
899       sat.cfg.APPLICATION.workdir = tmp_local_working_dir
900       # DBG.write("SSS sat config.APPLICATION.workdir", sat.cfg.APPLICATION, True)
901       # DBG.write("sat config id", id(sat.cfg), True)
902       # shit as config is not same id() as for sat.source()
903       # sat.source(args_source, batch=True, verbose=5, logger_add_link = logger)
904       import source
905       source.run(args_source, sat, logger) #use this mode as runner.cfg reference
906       
907       # make the new archives
908       d_archives_vcs = {}
909       for pn, pinfo in l_pinfo_vcs:
910           path_archive = make_archive(pn, pinfo, tmp_local_working_dir)
911           logger.write("make archive vcs '%s'\n" % path_archive)
912           d_archives_vcs[pn] = (path_archive,
913                                 os.path.join(ARCHIVE_DIR, pn + ".tgz"))
914       sat.cfg.APPLICATION.workdir = svgDir
915       # DBG.write("END sat config", sat.cfg.APPLICATION, True)
916     return d_archives_vcs
917
918 def make_archive(prod_name, prod_info, where):
919     '''Create an archive of a product by searching its source directory.
920
921     :param prod_name str: The name of the product.
922     :param prod_info Config: The specific configuration corresponding to the 
923                              product
924     :param where str: The path of the repository where to put the resulting 
925                       archive
926     :return: The path of the resulting archive
927     :rtype: str
928     '''
929     path_targz = os.path.join(dir_name, archive_name + PACKAGE_EXT)
930     tar_prod = tarfile.open(path_targz_prod, mode='w:gz')
931     local_path = prod_info.source_dir
932     tar_prod.add(local_path,
933                  arcname=prod_name,
934                  exclude=exclude_VCS_and_extensions)
935     tar_prod.close()
936     return path_targz_prod       
937
938 def create_project_for_src_package(config, tmp_working_dir, with_vcs, with_ftp):
939     '''Create a specific project for a source package.
940
941     :param config Config: The global configuration.
942     :param tmp_working_dir str: The temporary local directory containing some 
943                                 specific directories or files needed in the 
944                                 source package
945     :param with_vcs boolean: True if the package is with vcs products (not 
946                              transformed into archive products)
947     :param with_ftp boolean: True if the package use ftp servers to get archives
948     :return: The dictionary 
949              {"project" : (produced project, project path in the archive)}
950     :rtype: Dict
951     '''
952
953     # Create in the working temporary directory the full project tree
954     project_tmp_dir = os.path.join(tmp_working_dir, PROJECT_DIR)
955     products_pyconf_tmp_dir = os.path.join(project_tmp_dir,
956                                          "products")
957     compil_scripts_tmp_dir = os.path.join(project_tmp_dir,
958                                          "products",
959                                          "compil_scripts")
960     env_scripts_tmp_dir = os.path.join(project_tmp_dir,
961                                          "products",
962                                          "env_scripts")
963     patches_tmp_dir = os.path.join(project_tmp_dir,
964                                          "products",
965                                          "patches")
966     application_tmp_dir = os.path.join(project_tmp_dir,
967                                          "applications")
968     for directory in [project_tmp_dir,
969                       compil_scripts_tmp_dir,
970                       env_scripts_tmp_dir,
971                       patches_tmp_dir,
972                       application_tmp_dir]:
973         src.ensure_path_exists(directory)
974
975     # Create the pyconf that contains the information of the project
976     project_pyconf_name = "project.pyconf"        
977     project_pyconf_file = os.path.join(project_tmp_dir, project_pyconf_name)
978     ff = open(project_pyconf_file, "w")
979     ff.write(PROJECT_TEMPLATE)
980     if with_ftp and len(config.PATHS.ARCHIVEFTP) > 0:
981         ftp_path='ARCHIVEFTP : "'+config.PATHS.ARCHIVEFTP[0]
982         for ftpserver in config.PATHS.ARCHIVEFTP[1:]:
983             ftp_path=ftp_path+":"+ftpserver
984         ftp_path+='"'
985         ff.write("# ftp servers where to search for prerequisite archives\n")
986         ff.write(ftp_path)
987     # add licence paths if any
988     if len(config.PATHS.LICENCEPATH) > 0:  
989         licence_path='LICENCEPATH : "'+config.PATHS.LICENCEPATH[0]
990         for path in config.PATHS.LICENCEPATH[1:]:
991             licence_path=licence_path+":"+path
992         licence_path+='"'
993         ff.write("\n# Where to search for licences\n")
994         ff.write(licence_path)
995         
996
997     ff.close()
998     
999     # Loop over the products to get there pyconf and all the scripts 
1000     # (compilation, environment, patches)
1001     # and create the pyconf file to add to the project
1002     lproducts_name = config.APPLICATION.products.keys()
1003     l_products = src.product.get_products_infos(lproducts_name, config)
1004     for p_name, p_info in l_products:
1005         # skip product with property not_in_package set to yes
1006         if src.get_property_in_product_cfg(p_info, "not_in_package") == "yes":
1007             continue  
1008         find_product_scripts_and_pyconf(p_name,
1009                                         p_info,
1010                                         config,
1011                                         with_vcs,
1012                                         compil_scripts_tmp_dir,
1013                                         env_scripts_tmp_dir,
1014                                         patches_tmp_dir,
1015                                         products_pyconf_tmp_dir)
1016     
1017     find_application_pyconf(config, application_tmp_dir)
1018     
1019     d_project = {"project" : (project_tmp_dir, PROJECT_DIR )}
1020     return d_project
1021
1022 def find_product_scripts_and_pyconf(p_name,
1023                                     p_info,
1024                                     config,
1025                                     with_vcs,
1026                                     compil_scripts_tmp_dir,
1027                                     env_scripts_tmp_dir,
1028                                     patches_tmp_dir,
1029                                     products_pyconf_tmp_dir):
1030     '''Create a specific pyconf file for a given product. Get its environment 
1031        script, its compilation script and patches and put it in the temporary
1032        working directory. This method is used in the source package in order to
1033        construct the specific project.
1034
1035     :param p_name str: The name of the product.
1036     :param p_info Config: The specific configuration corresponding to the 
1037                              product
1038     :param config Config: The global configuration.
1039     :param with_vcs boolean: True if the package is with vcs products (not 
1040                              transformed into archive products)
1041     :param compil_scripts_tmp_dir str: The path to the temporary compilation 
1042                                        scripts directory of the project.
1043     :param env_scripts_tmp_dir str: The path to the temporary environment script 
1044                                     directory of the project.
1045     :param patches_tmp_dir str: The path to the temporary patch scripts 
1046                                 directory of the project.
1047     :param products_pyconf_tmp_dir str: The path to the temporary product 
1048                                         scripts directory of the project.
1049     '''
1050     
1051     # read the pyconf of the product
1052     product_pyconf_cfg = src.pyconf.Config(p_info.from_file)
1053
1054     # find the compilation script if any
1055     if src.product.product_has_script(p_info):
1056         compil_script_path = src.Path(p_info.compil_script)
1057         compil_script_path.copy(compil_scripts_tmp_dir)
1058
1059     # find the environment script if any
1060     if src.product.product_has_env_script(p_info):
1061         env_script_path = src.Path(p_info.environ.env_script)
1062         env_script_path.copy(env_scripts_tmp_dir)
1063
1064     # find the patches if any
1065     if src.product.product_has_patches(p_info):
1066         patches = src.pyconf.Sequence()
1067         for patch_path in p_info.patches:
1068             p_path = src.Path(patch_path)
1069             p_path.copy(patches_tmp_dir)
1070             patches.append(os.path.basename(patch_path), "")
1071
1072     if (not with_vcs) and src.product.product_is_vcs(p_info):
1073         # in non vcs mode, if the product is not archive, then make it become archive.
1074
1075         # depending upon the incremental mode, select impacted sections
1076         if "properties" in p_info and "incremental" in p_info.properties and\
1077             p_info.properties.incremental == "yes":
1078             sections = ["default", "default_win", p_info.section, p_info.section+"_win"]
1079         else:
1080             sections = [p_info.section]
1081         for section in sections:
1082             if section in product_pyconf_cfg and "get_source" in product_pyconf_cfg[section]:
1083                 DBG.write("sat package set archive mode to archive for product %s and section %s" %\
1084                           (p_name,section))
1085                 product_pyconf_cfg[section].get_source = "archive"
1086                 if not "archive_info" in product_pyconf_cfg[section]:
1087                     product_pyconf_cfg[section].addMapping("archive_info",
1088                                         src.pyconf.Mapping(product_pyconf_cfg),
1089                                         "")
1090                     product_pyconf_cfg[section].archive_info.archive_name =\
1091                         p_info.name + ".tgz"
1092     
1093     # write the pyconf file to the temporary project location
1094     product_tmp_pyconf_path = os.path.join(products_pyconf_tmp_dir,
1095                                            p_name + ".pyconf")
1096     ff = open(product_tmp_pyconf_path, 'w')
1097     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
1098     product_pyconf_cfg.__save__(ff, 1)
1099     ff.close()
1100
1101 def find_application_pyconf(config, application_tmp_dir):
1102     '''Find the application pyconf file and put it in the specific temporary 
1103        directory containing the specific project of a source package.
1104
1105     :param config Config: The global configuration.
1106     :param application_tmp_dir str: The path to the temporary application 
1107                                        scripts directory of the project.
1108     '''
1109     # read the pyconf of the application
1110     application_name = config.VARS.application
1111     application_pyconf_path = src.find_file_in_lpath(
1112                                             application_name + ".pyconf",
1113                                             config.PATHS.APPLICATIONPATH)
1114     application_pyconf_cfg = src.pyconf.Config(application_pyconf_path)
1115     
1116     # Change the workdir
1117     application_pyconf_cfg.APPLICATION.workdir = src.pyconf.Reference(
1118                                     application_pyconf_cfg,
1119                                     src.pyconf.DOLLAR,
1120                                     'VARS.salometoolsway + $VARS.sep + ".."')
1121
1122     # Prevent from compilation in base
1123     application_pyconf_cfg.APPLICATION.no_base = "yes"
1124     
1125     #remove products that are not in config (which were filtered by --without_properties)
1126     for product_name in application_pyconf_cfg.APPLICATION.products.keys():
1127         if product_name not in config.APPLICATION.products.keys():
1128             application_pyconf_cfg.APPLICATION.products.__delitem__(product_name)
1129
1130     # write the pyconf file to the temporary application location
1131     application_tmp_pyconf_path = os.path.join(application_tmp_dir,
1132                                                application_name + ".pyconf")
1133
1134     ff = open(application_tmp_pyconf_path, 'w')
1135     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
1136     application_pyconf_cfg.__save__(ff, 1)
1137     ff.close()
1138
1139 def sat_package(config, tmp_working_dir, options, logger):
1140     '''Prepare a dictionary that stores all the needed directories and files to
1141        add in a salomeTool package.
1142     
1143     :param tmp_working_dir str: The temporary local working directory 
1144     :param options OptResult: the options of the launched command
1145     :return: the dictionary that stores all the needed directories and files to
1146              add in a salomeTool package.
1147              {label : (path_on_local_machine, path_in_archive)}
1148     :rtype: dict
1149     '''
1150     d_project = {}
1151
1152     # we include sat himself
1153     d_project["all_sat"]=(config.VARS.salometoolsway, "")
1154
1155     # and we overwrite local.pyconf with a clean wersion.
1156     local_pyconf_tmp_path = os.path.join(tmp_working_dir, "local.pyconf")
1157     local_file_path = os.path.join(config.VARS.datadir, "local.pyconf")
1158     local_cfg = src.pyconf.Config(local_file_path)
1159     local_cfg.PROJECTS.project_file_paths=src.pyconf.Sequence(local_cfg.PROJECTS)
1160     local_cfg.LOCAL["base"] = "default"
1161     local_cfg.LOCAL["workdir"] = "default"
1162     local_cfg.LOCAL["log_dir"] = "default"
1163     local_cfg.LOCAL["archive_dir"] = "default"
1164     local_cfg.LOCAL["VCS"] = "None"
1165     local_cfg.LOCAL["tag"] = src.get_salometool_version(config)
1166
1167     # if the archive contains a project, we write its relative path in local.pyconf
1168     if options.project:
1169         project_arch_path = os.path.join("projects", options.project, 
1170                                          os.path.basename(options.project_file_path))
1171         local_cfg.PROJECTS.project_file_paths.append(project_arch_path, "")
1172
1173     ff = open(local_pyconf_tmp_path, 'w')
1174     local_cfg.__save__(ff, 1)
1175     ff.close()
1176     d_project["local.pyconf"]=(local_pyconf_tmp_path, "data/local.pyconf")
1177     return d_project
1178     
1179
1180 def project_package(config, name_project, project_file_path, ftp_mode, tmp_working_dir, embedded_in_sat, logger):
1181     '''Prepare a dictionary that stores all the needed directories and files to
1182        add in a project package.
1183     
1184     :param project_file_path str: The path to the local project.
1185     :param ftp_mode boolean: Do not embed archives, the archive will rely on ftp mode to retrieve them.
1186     :param tmp_working_dir str: The temporary local directory containing some 
1187                                 specific directories or files needed in the 
1188                                 project package
1189     :param embedded_in_sat boolean : the project package is embedded in a sat package
1190     :return: the dictionary that stores all the needed directories and files to
1191              add in a project package.
1192              {label : (path_on_local_machine, path_in_archive)}
1193     :rtype: dict
1194     '''
1195     d_project = {}
1196     # Read the project file and get the directories to add to the package
1197     
1198     try: 
1199       project_pyconf_cfg = config.PROJECTS.projects.__getattr__(name_project)
1200     except:
1201       logger.write("""
1202 WARNING: inexisting config.PROJECTS.projects.%s, try to read now from:\n%s\n""" % (name_project, project_file_path))
1203       project_pyconf_cfg = src.pyconf.Config(project_file_path)
1204       project_pyconf_cfg.PWD = os.path.dirname(project_file_path)
1205     
1206     paths = {"APPLICATIONPATH" : "applications",
1207              "PRODUCTPATH" : "products",
1208              "JOBPATH" : "jobs",
1209              "MACHINEPATH" : "machines"}
1210     if not ftp_mode:
1211         paths["ARCHIVEPATH"] = "archives"
1212
1213     # Loop over the project paths and add it
1214     project_file_name = os.path.basename(project_file_path)
1215     for path in paths:
1216         if path not in project_pyconf_cfg:
1217             continue
1218         if embedded_in_sat:
1219             dest_path = os.path.join("projects", name_project, paths[path])
1220             project_file_dest = os.path.join("projects", name_project, project_file_name)
1221         else:
1222             dest_path = paths[path]
1223             project_file_dest = project_file_name
1224
1225         # Add the directory to the files to add in the package
1226         d_project[path] = (project_pyconf_cfg[path], dest_path)
1227
1228         # Modify the value of the path in the package
1229         project_pyconf_cfg[path] = src.pyconf.Reference(
1230                                     project_pyconf_cfg,
1231                                     src.pyconf.DOLLAR,
1232                                     'project_path + "/' + paths[path] + '"')
1233     
1234     # Modify some values
1235     if "project_path" not in project_pyconf_cfg:
1236         project_pyconf_cfg.addMapping("project_path",
1237                                       src.pyconf.Mapping(project_pyconf_cfg),
1238                                       "")
1239     project_pyconf_cfg.project_path = src.pyconf.Reference(project_pyconf_cfg,
1240                                                            src.pyconf.DOLLAR,
1241                                                            'PWD')
1242     # we don't want to export these two fields
1243     project_pyconf_cfg.__delitem__("file_path")
1244     project_pyconf_cfg.__delitem__("PWD")
1245     if ftp_mode:
1246         project_pyconf_cfg.__delitem__("ARCHIVEPATH")
1247     
1248     # Write the project pyconf file
1249     project_pyconf_tmp_path = os.path.join(tmp_working_dir, project_file_name)
1250     ff = open(project_pyconf_tmp_path, 'w')
1251     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
1252     project_pyconf_cfg.__save__(ff, 1)
1253     ff.close()
1254     d_project["Project hat file"] = (project_pyconf_tmp_path, project_file_dest)
1255     
1256     return d_project
1257
1258 def add_readme(config, options, where):
1259     readme_path = os.path.join(where, "README")
1260     with codecs.open(readme_path, "w", 'utf-8') as f:
1261
1262     # templates for building the header
1263         readme_header="""
1264 # This package was generated with sat $version
1265 # Date: $date
1266 # User: $user
1267 # Distribution : $dist
1268
1269 In the following, $$ROOT represents the directory where you have installed 
1270 SALOME (the directory where this file is located).
1271
1272 """
1273         readme_compilation_with_binaries="""
1274
1275 compilation based on the binaries used as prerequisites
1276 =======================================================
1277
1278 If you fail to compile the complete application (for example because
1279 you are not root on your system and cannot install missing packages), you
1280 may try a partial compilation based on the binaries.
1281 For that it is necessary to copy the binaries from BINARIES to INSTALL,
1282 and do some substitutions on cmake and .la files (replace the build directories
1283 with local paths).
1284 The procedure to do it is:
1285  1) Remove or rename INSTALL directory if it exists
1286  2) Execute the shell script install_bin.sh:
1287  > cd $ROOT
1288  > ./install_bin.sh
1289  3) Use SalomeTool (as explained in Sources section) and compile only the 
1290     modules you need to (with -p option)
1291
1292 """
1293         readme_header_tpl=string.Template(readme_header)
1294         readme_template_path_bin = os.path.join(config.VARS.internal_dir,
1295                 "README_BIN.template")
1296         readme_template_path_bin_launcher = os.path.join(config.VARS.internal_dir,
1297                 "README_LAUNCHER.template")
1298         readme_template_path_bin_virtapp = os.path.join(config.VARS.internal_dir,
1299                 "README_BIN_VIRTUAL_APP.template")
1300         readme_template_path_src = os.path.join(config.VARS.internal_dir,
1301                 "README_SRC.template")
1302         readme_template_path_pro = os.path.join(config.VARS.internal_dir,
1303                 "README_PROJECT.template")
1304         readme_template_path_sat = os.path.join(config.VARS.internal_dir,
1305                 "README_SAT.template")
1306
1307         # prepare substitution dictionary
1308         d = dict()
1309         d['user'] = config.VARS.user
1310         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
1311         d['version'] = src.get_salometool_version(config)
1312         d['dist'] = config.VARS.dist
1313         f.write(readme_header_tpl.substitute(d)) # write the general header (common)
1314
1315         if options.binaries or options.sources:
1316             d['application'] = config.VARS.application
1317             f.write("# Application: " + d['application'] + "\n")
1318             if 'KERNEL' in config.APPLICATION.products:
1319                 VersionSalome = src.get_salome_version(config)
1320                 # Case where SALOME has the launcher that uses the SalomeContext API
1321                 if VersionSalome >= 730:
1322                     d['launcher'] = config.APPLICATION.profile.launcher_name
1323                 else:
1324                     d['virtual_app'] = 'runAppli' # this info is not used now)
1325
1326         # write the specific sections
1327         if options.binaries:
1328             f.write(src.template.substitute(readme_template_path_bin, d))
1329             if "virtual_app" in d:
1330                 f.write(src.template.substitute(readme_template_path_bin_virtapp, d))
1331             if "launcher" in d:
1332                 f.write(src.template.substitute(readme_template_path_bin_launcher, d))
1333
1334         if options.sources:
1335             f.write(src.template.substitute(readme_template_path_src, d))
1336
1337         if options.binaries and options.sources and not src.architecture.is_windows():
1338             f.write(readme_compilation_with_binaries)
1339
1340         if options.project:
1341             f.write(src.template.substitute(readme_template_path_pro, d))
1342
1343         if options.sat:
1344             f.write(src.template.substitute(readme_template_path_sat, d))
1345     
1346     return readme_path
1347
1348 def update_config(config, prop, value):
1349     '''Remove from config.APPLICATION.products the products that have the property given as input.
1350     
1351     :param config Config: The global config.
1352     :param prop str: The property to filter
1353     :param value str: The value of the property to filter
1354     '''
1355     # if there is no APPLICATION (ex sat package -t) : nothing to do
1356     if "APPLICATION" in config:
1357         l_product_to_remove = []
1358         for product_name in config.APPLICATION.products.keys():
1359             prod_cfg = src.product.get_product_config(config, product_name)
1360             if src.get_property_in_product_cfg(prod_cfg, prop) == value:
1361                 l_product_to_remove.append(product_name)
1362         for product_name in l_product_to_remove:
1363             config.APPLICATION.products.__delitem__(product_name)
1364
1365 def description():
1366     '''method that is called when salomeTools is called with --help option.
1367     
1368     :return: The text to display for the package command description.
1369     :rtype: str
1370     '''
1371     return _("""
1372 The package command creates a tar file archive of a product.
1373 There are four kinds of archive, which can be mixed:
1374
1375  1 - The binary archive. 
1376      It contains the product installation directories plus a launcher.
1377  2 - The sources archive. 
1378      It contains the product archives, a project (the application plus salomeTools).
1379  3 - The project archive. 
1380      It contains a project (give the project file path as argument).
1381  4 - The salomeTools archive. 
1382      It contains code utility salomeTools.
1383
1384 example:
1385  >> sat package SALOME-master --binaries --sources""")
1386   
1387 def run(args, runner, logger):
1388     '''method that is called when salomeTools is called with package parameter.
1389     '''
1390     
1391     # Parse the options
1392     (options, args) = parser.parse_args(args)
1393
1394     # Check that a type of package is called, and only one
1395     all_option_types = (options.binaries,
1396                         options.sources,
1397                         options.project not in ["", None],
1398                         options.sat)
1399
1400     # Check if no option for package type
1401     if all_option_types.count(True) == 0:
1402         msg = _("Error: Precise a type for the package\nUse one of the "
1403                 "following options: --binaries, --sources, --project or"
1404                 " --salometools")
1405         logger.write(src.printcolors.printcError(msg), 1)
1406         logger.write("\n", 1)
1407         return 1
1408     
1409     # The repository where to put the package if not Binary or Source
1410     package_default_path = runner.cfg.LOCAL.workdir
1411     
1412     # if the package contains binaries or sources:
1413     if options.binaries or options.sources:
1414         # Check that the command has been called with an application
1415         src.check_config_has_application(runner.cfg)
1416
1417         # Display information
1418         logger.write(_("Packaging application %s\n") % src.printcolors.printcLabel(
1419                                                     runner.cfg.VARS.application), 1)
1420         
1421         # Get the default directory where to put the packages
1422         package_default_path = os.path.join(runner.cfg.APPLICATION.workdir, "PACKAGE")
1423         src.ensure_path_exists(package_default_path)
1424         
1425     # if the package contains a project:
1426     if options.project:
1427         # check that the project is visible by SAT
1428         projectNameFile = options.project + ".pyconf"
1429         foundProject = None
1430         for i in runner.cfg.PROJECTS.project_file_paths:
1431             baseName = os.path.basename(i)
1432             if baseName == projectNameFile:
1433                 foundProject = i
1434                 break
1435
1436         if foundProject is None:
1437             local_path = os.path.join(runner.cfg.VARS.salometoolsway, "data", "local.pyconf")
1438             msg = _("""ERROR: the project %(1)s is not visible by salomeTools.
1439 known projects are:
1440 %(2)s
1441
1442 Please add it in file:
1443 %(3)s""" % \
1444                     {"1": options.project, "2": "\n  ".join(runner.cfg.PROJECTS.project_file_paths), "3": local_path})
1445             logger.write(src.printcolors.printcError(msg), 1)
1446             logger.write("\n", 1)
1447             return 1
1448         else:
1449             options.project_file_path = foundProject
1450             src.printcolors.print_value(logger, "Project path", options.project_file_path, 2)
1451     
1452     # Remove the products that are filtered by the --without_properties option
1453     if options.without_properties:
1454         app = runner.cfg.APPLICATION
1455         logger.trace("without_properties all products:\n %s\n" % PP.pformat(sorted(app.products.keys())))
1456         prop, value = options.without_properties
1457         update_config(runner.cfg, prop, value)
1458         logger.warning("without_properties selected products:\n %s\n" % PP.pformat(sorted(app.products.keys())))
1459
1460     # Remove from config the products that have the not_in_package property
1461     update_config(runner.cfg, "not_in_package", "yes")
1462     
1463     # get the name of the archive or build it
1464     if options.name:
1465         if os.path.basename(options.name) == options.name:
1466             # only a name (not a path)
1467             archive_name = options.name           
1468             dir_name = package_default_path
1469         else:
1470             archive_name = os.path.basename(options.name)
1471             dir_name = os.path.dirname(options.name)
1472         
1473         # suppress extension
1474         if archive_name[-len(".tgz"):] == ".tgz":
1475             archive_name = archive_name[:-len(".tgz")]
1476         if archive_name[-len(".tar.gz"):] == ".tar.gz":
1477             archive_name = archive_name[:-len(".tar.gz")]
1478         
1479     else:
1480         archive_name=""
1481         dir_name = package_default_path
1482         if options.binaries or options.sources:
1483             archive_name = runner.cfg.APPLICATION.name
1484
1485         if options.binaries:
1486             archive_name += "-"+runner.cfg.VARS.dist
1487             
1488         if options.sources:
1489             archive_name += "-SRC"
1490             if options.with_vcs:
1491                 archive_name += "-VCS"
1492
1493         if options.sat:
1494             archive_name += ("salomeTools_" + src.get_salometool_version(runner.cfg))
1495
1496         if options.project:
1497             if options.sat:
1498                 archive_name += "_" 
1499             project_name = options.project
1500             archive_name += ("satproject_" + project_name)
1501  
1502         if len(archive_name)==0: # no option worked 
1503             msg = _("Error: Cannot name the archive\n"
1504                     " check if at least one of the following options was "
1505                     "selected : --binaries, --sources, --project or"
1506                     " --salometools")
1507             logger.write(src.printcolors.printcError(msg), 1)
1508             logger.write("\n", 1)
1509             return 1
1510  
1511     path_targz = os.path.join(dir_name, archive_name + PACKAGE_EXT)
1512     
1513     src.printcolors.print_value(logger, "Package path", path_targz, 2)
1514
1515     # Create a working directory for all files that are produced during the
1516     # package creation and that will be removed at the end of the command
1517     tmp_working_dir = os.path.join(runner.cfg.VARS.tmp_root, runner.cfg.VARS.datehour)
1518     src.ensure_path_exists(tmp_working_dir)
1519     logger.write("\n", 5)
1520     logger.write(_("The temporary working directory: %s\n" % tmp_working_dir),5)
1521     
1522     logger.write("\n", 3)
1523
1524     msg = _("Preparation of files to add to the archive")
1525     logger.write(src.printcolors.printcLabel(msg), 2)
1526     logger.write("\n", 2)
1527     
1528     d_files_to_add={}  # content of the archive
1529
1530     # a dict to hold paths that will need to be substitute for users recompilations
1531     d_paths_to_substitute={}  
1532
1533     if options.binaries:
1534         d_bin_files_to_add = binary_package(runner.cfg,
1535                                             logger,
1536                                             options,
1537                                             tmp_working_dir)
1538         # for all binaries dir, store the substitution that will be required 
1539         # for extra compilations
1540         for key in d_bin_files_to_add:
1541             if key.endswith("(bin)"):
1542                 source_dir = d_bin_files_to_add[key][0]
1543                 path_in_archive = d_bin_files_to_add[key][1].replace(
1544                    runner.cfg.INTERNAL.config.binary_dir + runner.cfg.VARS.dist,
1545                    runner.cfg.INTERNAL.config.install_dir)
1546                 if os.path.basename(source_dir)==os.path.basename(path_in_archive):
1547                     # if basename is the same we will just substitute the dirname 
1548                     d_paths_to_substitute[os.path.dirname(source_dir)]=\
1549                         os.path.dirname(path_in_archive)
1550                 else:
1551                     d_paths_to_substitute[source_dir]=path_in_archive
1552
1553         d_files_to_add.update(d_bin_files_to_add)
1554
1555     if options.sources:
1556         d_files_to_add.update(source_package(runner,
1557                                         runner.cfg,
1558                                         logger, 
1559                                         options,
1560                                         tmp_working_dir))
1561         if options.binaries:
1562             # for archives with bin and sources we provide a shell script able to 
1563             # install binaries for compilation
1564             file_install_bin=produce_install_bin_file(runner.cfg,logger,
1565                                                       tmp_working_dir,
1566                                                       d_paths_to_substitute,
1567                                                       "install_bin.sh")
1568             d_files_to_add.update({"install_bin" : (file_install_bin, "install_bin.sh")})
1569             logger.write("substitutions that need to be done later : \n", 5)
1570             logger.write(str(d_paths_to_substitute), 5)
1571             logger.write("\n", 5)
1572     else:
1573         # --salomeTool option is not considered when --sources is selected, as this option
1574         # already brings salomeTool!
1575         if options.sat:
1576             d_files_to_add.update(sat_package(runner.cfg, tmp_working_dir, 
1577                                   options, logger))
1578         
1579     if options.project:
1580         DBG.write("config for package %s" % project_name, runner.cfg)
1581         d_files_to_add.update(project_package(runner.cfg, project_name, options.project_file_path, options.ftp, tmp_working_dir, options.sat, logger))
1582
1583     if not(d_files_to_add):
1584         msg = _("Error: Empty dictionnary to build the archive!\n")
1585         logger.write(src.printcolors.printcError(msg), 1)
1586         logger.write("\n", 1)
1587         return 1
1588
1589     # Add the README file in the package
1590     local_readme_tmp_path = add_readme(runner.cfg, options, tmp_working_dir)
1591     d_files_to_add["README"] = (local_readme_tmp_path, "README")
1592
1593     # Add the additional files of option add_files
1594     if options.add_files:
1595         for file_path in options.add_files:
1596             if not os.path.exists(file_path):
1597                 msg = _("WARNING: the file %s is not accessible.\n" % file_path)
1598                 continue
1599             file_name = os.path.basename(file_path)
1600             d_files_to_add[file_name] = (file_path, file_name)
1601
1602     logger.write("\n", 2)
1603     logger.write(src.printcolors.printcLabel(_("Actually do the package")), 2)
1604     logger.write("\n", 2)
1605     logger.write("\nfiles and directories to add:\n%s\n\n" % PP.pformat(d_files_to_add), 5)
1606
1607     res = 0
1608     try:
1609         # Creating the object tarfile
1610         tar = tarfile.open(path_targz, mode='w:gz')
1611         
1612         # get the filtering function if needed
1613         filter_function = exclude_VCS_and_extensions
1614
1615         # Add the files to the tarfile object
1616         res = add_files(tar, archive_name, d_files_to_add, logger, f_exclude=filter_function)
1617         tar.close()
1618     except KeyboardInterrupt:
1619         logger.write(src.printcolors.printcError("\nERROR: forced interruption\n"), 1)
1620         logger.write(_("Removing the temporary working directory '%s'... ") % tmp_working_dir, 1)
1621         # remove the working directory
1622         shutil.rmtree(tmp_working_dir)
1623         logger.write(_("OK"), 1)
1624         logger.write(_("\n"), 1)
1625         return 1
1626     
1627     # case if no application, only package sat as 'sat package -t'
1628     try:
1629         app = runner.cfg.APPLICATION
1630     except:
1631         app = None
1632
1633     # unconditionaly remove the tmp_local_working_dir
1634     if app is not None:
1635         tmp_local_working_dir = os.path.join(app.workdir, "tmp_package")
1636         if os.path.isdir(tmp_local_working_dir):
1637             shutil.rmtree(tmp_local_working_dir)
1638
1639     # remove the tmp directory, unless user has registered as developer
1640     if os.path.isdir(tmp_working_dir) and (not DBG.isDeveloper()):
1641         shutil.rmtree(tmp_working_dir)
1642     
1643     # Print again the path of the package
1644     logger.write("\n", 2)
1645     src.printcolors.print_value(logger, "Package path", path_targz, 2)
1646     
1647     return res