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