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