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