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