Salome HOME
sat package: an the option --without_property to ba able to filter some products...
[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
25 import src
26
27 from application import get_SALOME_modules
28
29 BINARY = "binary"
30 SOURCE = "Source"
31 PROJECT = "Project"
32 SAT = "Sat"
33
34 ARCHIVE_DIR = "ARCHIVES"
35 PROJECT_DIR = "PROJECT"
36
37 IGNORED_DIRS = [".git"]
38 IGNORED_EXTENSIONS = [".la", ".cmake"]
39
40 PROJECT_TEMPLATE = """#!/usr/bin/env python
41 #-*- coding:utf-8 -*-
42
43 # The path to the archive root directory
44 root_path : $PWD + "/../"
45 # path to the PROJECT
46 project_path : $PWD + "/"
47
48 # Where to search the archives of the products
49 ARCHIVEPATH : $root_path + "ARCHIVES"
50 # Where to search the pyconf of the applications
51 APPLICATIONPATH : $project_path + "applications/"
52 # Where to search the pyconf of the products
53 PRODUCTPATH : $project_path + "products/"
54 # Where to search the pyconf of the jobs of the project
55 JOBPATH : $project_path + "jobs/"
56 # Where to search the pyconf of the machines of the project
57 MACHINEPATH : $project_path + "machines/"
58 """
59
60 SITE_TEMPLATE = ("""#!/usr/bin/env python
61 #-*- coding:utf-8 -*-
62
63 SITE :
64 {   
65     log :
66     {
67         log_dir : $USER.workdir + "/LOGS"
68     }
69     test :{
70            tmp_dir_with_application : '/tmp' + $VARS.sep + $VARS.user + """
71 """$VARS.sep + $APPLICATION.name + $VARS.sep + 'test'
72            tmp_dir : '/tmp' + $VARS.sep + $VARS.user + $VARS.sep + 'test'
73            timeout : 150
74            }
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_property', 'string', 'without_property',
108     _('Optional: Filter the products by their properties.\n\tSyntax: '
109       '--without_property <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     for name in d_content.keys():
133         # display information
134         len_points = max_len - len(name)
135         logger.write(name + " " + len_points * "." + " ", 3)
136         # Get the local path and the path in archive 
137         # of the directory or file to add
138         local_path, archive_path = d_content[name]
139         in_archive = os.path.join(name_archive, archive_path)
140         # Add it in the archive
141         try:
142             tar.add(local_path, arcname=in_archive, exclude=f_exclude)
143             logger.write(src.printcolors.printcSuccess(_("OK")), 3)
144         except Exception as e:
145             logger.write(src.printcolors.printcError(_("KO ")), 3)
146             logger.write(str(e), 3)
147             success = 1
148         logger.write("\n", 3)
149     return success
150
151 def exclude_VCS_info(filename):
152     ''' The function that is used to exclude from package the link to the 
153         VCS repositories (like .git)
154
155     :param filename Str: The filname to exclude (or not).
156     :return: True if the file has to be exclude
157     :rtype: Boolean
158     '''
159     for dir_name in IGNORED_DIRS:
160         if dir_name in filename:
161             return True
162     for extension in IGNORED_EXTENSIONS:
163         if filename.endswith(extension):
164             return True
165     return False
166
167 def produce_relative_launcher(config,
168                               logger,
169                               file_dir,
170                               file_name,
171                               binaries_dir_name,
172                               with_commercial=True):
173     '''Create a specific SALOME launcher for the binary package. This launcher 
174        uses relative paths.
175     
176     :param config Config: The global configuration.
177     :param logger Logger: the logging instance
178     :param file_dir str: the directory where to put the launcher
179     :param file_name str: The launcher name
180     :param binaries_dir_name str: the name of the repository where the binaries
181                                   are, in the archive.
182     :return: the path of the produced launcher
183     :rtype: str
184     '''
185     
186     # Get the launcher template
187     profile_install_dir = os.path.join(binaries_dir_name,
188                                        config.APPLICATION.profile.product)
189     withProfile = src.fileEnviron.withProfile
190     withProfile = withProfile.replace(
191         "ABSOLUTE_APPLI_PATH'] = 'PROFILE_INSTALL_DIR'",
192         "ABSOLUTE_APPLI_PATH'] = out_dir_Path + '" + config.VARS.sep + profile_install_dir + "'")
193     withProfile = withProfile.replace(
194         "os.path.join( 'PROFILE_INSTALL_DIR'",
195         "os.path.join( out_dir_Path, '" + profile_install_dir + "'")
196
197     before, after = withProfile.split(
198                                 "# here your local standalone environment\n")
199
200     # create an environment file writer
201     writer = src.environment.FileEnvWriter(config,
202                                            logger,
203                                            file_dir,
204                                            src_root=None)
205     
206     filepath = os.path.join(file_dir, file_name)
207     # open the file and write into it
208     launch_file = open(filepath, "w")
209     launch_file.write(before)
210     # Write
211     writer.write_cfgForPy_file(launch_file,
212                                for_package = binaries_dir_name,
213                                with_commercial=with_commercial)
214     launch_file.write(after)
215     launch_file.close()
216     
217     # Little hack to put out_dir_Path outside the strings
218     src.replace_in_file(filepath, 'r"out_dir_Path', 'out_dir_Path + r"' )
219     
220     # change the rights in order to make the file executable for everybody
221     os.chmod(filepath,
222              stat.S_IRUSR |
223              stat.S_IRGRP |
224              stat.S_IROTH |
225              stat.S_IWUSR |
226              stat.S_IXUSR |
227              stat.S_IXGRP |
228              stat.S_IXOTH)
229
230     return filepath
231
232 def produce_relative_env_files(config,
233                               logger,
234                               file_dir,
235                               binaries_dir_name):
236     '''Create some specific environment files for the binary package. These 
237        files use relative paths.
238     
239     :param config Config: The global configuration.
240     :param logger Logger: the logging instance
241     :param file_dir str: the directory where to put the files
242     :param binaries_dir_name str: the name of the repository where the binaries
243                                   are, in the archive.
244     :return: the list of path of the produced environment files
245     :rtype: List
246     '''  
247     # create an environment file writer
248     writer = src.environment.FileEnvWriter(config,
249                                            logger,
250                                            file_dir,
251                                            src_root=None)
252     
253     # Write
254     filepath = writer.write_env_file("env_launch.sh",
255                           False, # for launch
256                           "bash",
257                           for_package = binaries_dir_name)
258
259     # Little hack to put out_dir_Path as environment variable
260     src.replace_in_file(filepath, '"out_dir_Path', '"${out_dir_Path}' )
261
262     # change the rights in order to make the file executable for everybody
263     os.chmod(filepath,
264              stat.S_IRUSR |
265              stat.S_IRGRP |
266              stat.S_IROTH |
267              stat.S_IWUSR |
268              stat.S_IXUSR |
269              stat.S_IXGRP |
270              stat.S_IXOTH)
271     
272     return filepath
273
274 def product_appli_creation_script(config,
275                                   logger,
276                                   file_dir,
277                                   binaries_dir_name):
278     '''Create a script that can produce an application (EDF style) in the binary
279        package.
280     
281     :param config Config: The global configuration.
282     :param logger Logger: the logging instance
283     :param file_dir str: the directory where to put the file
284     :param binaries_dir_name str: the name of the repository where the binaries
285                                   are, in the archive.
286     :return: the path of the produced script file
287     :rtype: Str
288     '''
289     template_name = "create_appli.py.for_bin_packages.template"
290     template_path = os.path.join(config.VARS.internal_dir, template_name)
291     text_to_fill = open(template_path, "r").read()
292     text_to_fill = text_to_fill.replace("TO BE FILLED 1",
293                                         '"' + binaries_dir_name + '"')
294     
295     text_to_add = ""
296     for product_name in get_SALOME_modules(config):
297         product_info = src.product.get_product_config(config, product_name)
298        
299         if src.product.product_is_smesh_plugin(product_info):
300             continue
301
302         if 'install_dir' in product_info and bool(product_info.install_dir):
303             if src.product.product_is_cpp(product_info):
304                 # cpp module
305                 for cpp_name in src.product.get_product_components(product_info):
306                     line_to_add = ("<module name=\"" + 
307                                    cpp_name + 
308                                    "\" gui=\"yes\" path=\"''' + "
309                                    "os.path.join(dir_bin_name, \"" + 
310                                    cpp_name + "\") + '''\"/>")
311             else:
312                 # regular module
313                 line_to_add = ("<module name=\"" + 
314                                product_name + 
315                                "\" gui=\"yes\" path=\"''' + "
316                                "os.path.join(dir_bin_name, \"" + 
317                                product_name + "\") + '''\"/>")
318             text_to_add += line_to_add + "\n"
319     
320     filled_text = text_to_fill.replace("TO BE FILLED 2", text_to_add)
321     
322     tmp_file_path = os.path.join(file_dir, "create_appli.py")
323     ff = open(tmp_file_path, "w")
324     ff.write(filled_text)
325     ff.close()
326     
327     # change the rights in order to make the file executable for everybody
328     os.chmod(tmp_file_path,
329              stat.S_IRUSR |
330              stat.S_IRGRP |
331              stat.S_IROTH |
332              stat.S_IWUSR |
333              stat.S_IXUSR |
334              stat.S_IXGRP |
335              stat.S_IXOTH)
336     
337     return tmp_file_path
338
339 def binary_package(config, logger, options, tmp_working_dir):
340     '''Prepare a dictionary that stores all the needed directories and files to
341        add in a binary package.
342     
343     :param config Config: The global configuration.
344     :param logger Logger: the logging instance
345     :param options OptResult: the options of the launched command
346     :param tmp_working_dir str: The temporary local directory containing some 
347                                 specific directories or files needed in the 
348                                 binary package
349     :return: the dictionary that stores all the needed directories and files to
350              add in a binary package.
351              {label : (path_on_local_machine, path_in_archive)}
352     :rtype: dict
353     '''
354
355     # Get the list of product installation to add to the archive
356     l_products_name = config.APPLICATION.products.keys()
357     l_product_info = src.product.get_products_infos(l_products_name,
358                                                     config)
359     l_install_dir = []
360     l_source_dir = []
361     l_not_installed = []
362     l_sources_not_present = []
363     for prod_name, prod_info in l_product_info:
364
365         # Add the sources of the products that have the property 
366         # sources_in_package : "yes"
367         if src.get_property_in_product_cfg(prod_info,
368                                            "sources_in_package") == "yes":
369             if os.path.exists(prod_info.source_dir):
370                 l_source_dir.append((prod_name, prod_info.source_dir))
371             else:
372                 l_sources_not_present.append(prod_name)
373
374         # ignore the native and fixed products for install directories
375         if (src.product.product_is_native(prod_info) 
376                 or src.product.product_is_fixed(prod_info)
377                 or not src.product.product_compiles(prod_info)):
378             continue
379         if src.product.check_installation(prod_info):
380             l_install_dir.append((prod_name, prod_info.install_dir))
381         else:
382             l_not_installed.append(prod_name)
383         
384         # Add also the cpp generated modules (if any)
385         if src.product.product_is_cpp(prod_info):
386             # cpp module
387             for name_cpp in src.product.get_product_components(prod_info):
388                 install_dir = os.path.join(config.APPLICATION.workdir,
389                                            "INSTALL", name_cpp) 
390                 if os.path.exists(install_dir):
391                     l_install_dir.append((name_cpp, install_dir))
392                 else:
393                     l_not_installed.append(name_cpp)
394         
395     # Print warning or error if there are some missing products
396     if len(l_not_installed) > 0:
397         text_missing_prods = ""
398         for p_name in l_not_installed:
399             text_missing_prods += "-" + p_name + "\n"
400         if not options.force_creation:
401             msg = _("ERROR: there are missing products installations:")
402             logger.write("%s\n%s" % (src.printcolors.printcError(msg),
403                                      text_missing_prods),
404                          1)
405             return None
406         else:
407             msg = _("WARNING: there are missing products installations:")
408             logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
409                                      text_missing_prods),
410                          1)
411
412     # Do the same for sources
413     if len(l_sources_not_present) > 0:
414         text_missing_prods = ""
415         for p_name in l_sources_not_present:
416             text_missing_prods += "-" + p_name + "\n"
417         if not options.force_creation:
418             msg = _("ERROR: there are missing products sources:")
419             logger.write("%s\n%s" % (src.printcolors.printcError(msg),
420                                      text_missing_prods),
421                          1)
422             return None
423         else:
424             msg = _("WARNING: there are missing products sources:")
425             logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
426                                      text_missing_prods),
427                          1)
428  
429     # construct the name of the directory that will contain the binaries
430     binaries_dir_name = "BINARIES-" + config.VARS.dist
431     
432     # construct the correlation table between the product names, there 
433     # actual install directories and there install directory in archive
434     d_products = {}
435     for prod_name, install_dir in l_install_dir:
436         path_in_archive = os.path.join(binaries_dir_name, prod_name)
437         d_products[prod_name] = (install_dir, path_in_archive)
438         
439     for prod_name, source_dir in l_source_dir:
440         path_in_archive = os.path.join("SOURCES", prod_name)
441         d_products[prod_name + " (sources)"] = (source_dir, path_in_archive)
442
443     # create the relative launcher and add it to the files to add
444     if ("profile" in config.APPLICATION and 
445                                        "product" in config.APPLICATION.profile):
446         launcher_name = config.APPLICATION.profile.launcher_name
447         launcher_package = produce_relative_launcher(config,
448                                              logger,
449                                              tmp_working_dir,
450                                              launcher_name,
451                                              binaries_dir_name,
452                                              not(options.without_commercial))
453     
454         d_products["launcher"] = (launcher_package, launcher_name)
455
456     # Put also some environment files
457     env_file = produce_relative_env_files(config,
458                                            logger,
459                                            tmp_working_dir,
460                                            binaries_dir_name)
461
462     d_products["environment file"] = (env_file, "env_launch.sh")
463     
464     # And provide a script for the creation of an application EDF style
465     appli_script = product_appli_creation_script(config,
466                                                 logger,
467                                                 tmp_working_dir,
468                                                 binaries_dir_name)
469     
470     d_products["appli script"] = (appli_script, "create_appli.py")
471    
472     return d_products
473
474 def source_package(sat, config, logger, options, tmp_working_dir):
475     '''Prepare a dictionary that stores all the needed directories and files to
476        add in a source package.
477     
478     :param config Config: The global configuration.
479     :param logger Logger: the logging instance
480     :param options OptResult: the options of the launched command
481     :param tmp_working_dir str: The temporary local directory containing some 
482                                 specific directories or files needed in the 
483                                 binary package
484     :return: the dictionary that stores all the needed directories and files to
485              add in a source package.
486              {label : (path_on_local_machine, path_in_archive)}
487     :rtype: dict
488     '''
489     
490     # Get all the products that are prepared using an archive
491     logger.write("Find archive products ... ")
492     d_archives, l_pinfo_vcs = get_archives(config, logger)
493     logger.write("Done\n")
494     d_archives_vcs = {}
495     if not options.with_vcs and len(l_pinfo_vcs) > 0:
496         # Make archives with the products that are not prepared using an archive
497         # (git, cvs, svn, etc)
498         logger.write("Construct archives for vcs products ... ")
499         d_archives_vcs = get_archives_vcs(l_pinfo_vcs,
500                                           sat,
501                                           config,
502                                           logger,
503                                           tmp_working_dir)
504         logger.write("Done\n")
505
506     # Create a project
507     logger.write("Create the project ... ")
508     d_project = create_project_for_src_package(config,
509                                                 tmp_working_dir,
510                                                 options.with_vcs)
511     logger.write("Done\n")
512     
513     # Add salomeTools
514     tmp_sat = add_salomeTools(config, tmp_working_dir)
515     d_sat = {"salomeTools" : (tmp_sat, "salomeTools")}
516     
517     # Add a sat symbolic link if not win
518     if not src.architecture.is_windows():
519         tmp_satlink_path = os.path.join(tmp_working_dir, 'sat')
520         try:
521             t = os.getcwd()
522         except:
523             # In the jobs, os.getcwd() can fail
524             t = config.USER.workdir
525         os.chdir(tmp_working_dir)
526         if os.path.lexists(tmp_satlink_path):
527             os.remove(tmp_satlink_path)
528         os.symlink(os.path.join('salomeTools', 'sat'), 'sat')
529         os.chdir(t)
530         
531         d_sat["sat link"] = (tmp_satlink_path, "sat")
532     
533     return src.merge_dicts(d_archives, d_archives_vcs, d_project, d_sat)
534
535 def get_archives(config, logger):
536     '''Find all the products that are get using an archive and all the products
537        that are get using a vcs (git, cvs, svn) repository.
538     
539     :param config Config: The global configuration.
540     :param logger Logger: the logging instance
541     :return: the dictionary {name_product : 
542              (local path of its archive, path in the package of its archive )}
543              and the list of specific configuration corresponding to the vcs 
544              products
545     :rtype: (Dict, List)
546     '''
547     # Get the list of product informations
548     l_products_name = config.APPLICATION.products.keys()
549     l_product_info = src.product.get_products_infos(l_products_name,
550                                                     config)
551     d_archives = {}
552     l_pinfo_vcs = []
553     for p_name, p_info in l_product_info:
554         # ignore the native and fixed products
555         if (src.product.product_is_native(p_info) 
556                 or src.product.product_is_fixed(p_info)):
557             continue
558         if p_info.get_source == "archive":
559             archive_path = p_info.archive_info.archive_name
560             archive_name = os.path.basename(archive_path)
561         else:
562             l_pinfo_vcs.append((p_name, p_info))
563             
564         d_archives[p_name] = (archive_path,
565                               os.path.join(ARCHIVE_DIR, archive_name))
566     return d_archives, l_pinfo_vcs
567
568 def add_salomeTools(config, tmp_working_dir):
569     '''Prepare a version of salomeTools that has a specific site.pyconf file 
570        configured for a source package.
571
572     :param config Config: The global configuration.
573     :param tmp_working_dir str: The temporary local directory containing some 
574                                 specific directories or files needed in the 
575                                 source package
576     :return: The path to the local salomeTools directory to add in the package
577     :rtype: str
578     '''
579     # Copy sat in the temporary working directory
580     sat_tmp_path = src.Path(os.path.join(tmp_working_dir, "salomeTools"))
581     sat_running_path = src.Path(config.VARS.salometoolsway)
582     sat_running_path.copy(sat_tmp_path)
583     
584     # Update the site.pyconf file that contains the path to the project
585     site_pyconf_name = "site.pyconf"
586     site_pyconf_dir = os.path.join(tmp_working_dir, "salomeTools", "data")
587     site_pyconf_file = os.path.join(site_pyconf_dir, site_pyconf_name)
588     ff = open(site_pyconf_file, "w")
589     ff.write(SITE_TEMPLATE)
590     ff.close()
591     
592     return sat_tmp_path.path
593
594 def get_archives_vcs(l_pinfo_vcs, sat, config, logger, tmp_working_dir):
595     '''For sources package that require that all products are get using an 
596        archive, one has to create some archive for the vcs products.
597        So this method calls the clean and source command of sat and then create
598        the archives.
599
600     :param l_pinfo_vcs List: The list of specific configuration corresponding to
601                              each vcs product
602     :param sat Sat: The Sat instance that can be called to clean and source the
603                     products
604     :param config Config: The global configuration.
605     :param logger Logger: the logging instance
606     :param tmp_working_dir str: The temporary local directory containing some 
607                                 specific directories or files needed in the 
608                                 source package
609     :return: the dictionary that stores all the archives to add in the source 
610              package. {label : (path_on_local_machine, path_in_archive)}
611     :rtype: dict
612     '''
613     # clean the source directory of all the vcs products, then use the source 
614     # command and thus construct an archive that will not contain the patches
615     l_prod_names = [pn for pn, __ in l_pinfo_vcs]
616     # clean
617     logger.write(_("clean sources\n"))
618     args_clean = config.VARS.application
619     args_clean += " --sources --products "
620     args_clean += ",".join(l_prod_names)
621     sat.clean(args_clean, batch=True, verbose=0, logger_add_link = logger)
622     # source
623     logger.write(_("get sources"))
624     args_source = config.VARS.application
625     args_source += " --products "
626     args_source += ",".join(l_prod_names)
627     sat.source(args_source, batch=True, verbose=0, logger_add_link = logger)
628
629     # make the new archives
630     d_archives_vcs = {}
631     for pn, pinfo in l_pinfo_vcs:
632         path_archive = make_archive(pn, pinfo, tmp_working_dir)
633         d_archives_vcs[pn] = (path_archive,
634                               os.path.join(ARCHIVE_DIR, pn + ".tgz"))
635     return d_archives_vcs
636
637 def make_archive(prod_name, prod_info, where):
638     '''Create an archive of a product by searching its source directory.
639
640     :param prod_name str: The name of the product.
641     :param prod_info Config: The specific configuration corresponding to the 
642                              product
643     :param where str: The path of the repository where to put the resulting 
644                       archive
645     :return: The path of the resulting archive
646     :rtype: str
647     '''
648     path_targz_prod = os.path.join(where, prod_name + ".tgz")
649     tar_prod = tarfile.open(path_targz_prod, mode='w:gz')
650     local_path = prod_info.source_dir
651     tar_prod.add(local_path, arcname=prod_name)
652     tar_prod.close()
653     return path_targz_prod       
654
655 def create_project_for_src_package(config, tmp_working_dir, with_vcs):
656     '''Create a specific project for a source package.
657
658     :param config Config: The global configuration.
659     :param tmp_working_dir str: The temporary local directory containing some 
660                                 specific directories or files needed in the 
661                                 source package
662     :param with_vcs boolean: True if the package is with vcs products (not 
663                              transformed into archive products)
664     :return: The dictionary 
665              {"project" : (produced project, project path in the archive)}
666     :rtype: Dict
667     '''
668
669     # Create in the working temporary directory the full project tree
670     project_tmp_dir = os.path.join(tmp_working_dir, PROJECT_DIR)
671     products_pyconf_tmp_dir = os.path.join(project_tmp_dir,
672                                          "products")
673     compil_scripts_tmp_dir = os.path.join(project_tmp_dir,
674                                          "products",
675                                          "compil_scripts")
676     env_scripts_tmp_dir = os.path.join(project_tmp_dir,
677                                          "products",
678                                          "env_scripts")
679     patches_tmp_dir = os.path.join(project_tmp_dir,
680                                          "products",
681                                          "patches")
682     application_tmp_dir = os.path.join(project_tmp_dir,
683                                          "applications")
684     for directory in [project_tmp_dir,
685                       compil_scripts_tmp_dir,
686                       env_scripts_tmp_dir,
687                       patches_tmp_dir,
688                       application_tmp_dir]:
689         src.ensure_path_exists(directory)
690
691     # Create the pyconf that contains the information of the project
692     project_pyconf_name = "project.pyconf"        
693     project_pyconf_file = os.path.join(project_tmp_dir, project_pyconf_name)
694     ff = open(project_pyconf_file, "w")
695     ff.write(PROJECT_TEMPLATE)
696     ff.close()
697     
698     # Loop over the products to get there pyconf and all the scripts 
699     # (compilation, environment, patches)
700     # and create the pyconf file to add to the project
701     lproducts_name = config.APPLICATION.products.keys()
702     l_products = src.product.get_products_infos(lproducts_name, config)
703     for p_name, p_info in l_products:
704         # ignore native and fixed products
705         if (src.product.product_is_native(p_info) or 
706                 src.product.product_is_fixed(p_info)):
707             continue
708         find_product_scripts_and_pyconf(p_name,
709                                         p_info,
710                                         config,
711                                         with_vcs,
712                                         compil_scripts_tmp_dir,
713                                         env_scripts_tmp_dir,
714                                         patches_tmp_dir,
715                                         products_pyconf_tmp_dir)
716     
717     find_application_pyconf(config, application_tmp_dir)
718     
719     d_project = {"project" : (project_tmp_dir, PROJECT_DIR )}
720     return d_project
721
722 def find_product_scripts_and_pyconf(p_name,
723                                     p_info,
724                                     config,
725                                     with_vcs,
726                                     compil_scripts_tmp_dir,
727                                     env_scripts_tmp_dir,
728                                     patches_tmp_dir,
729                                     products_pyconf_tmp_dir):
730     '''Create a specific pyconf file for a given product. Get its environment 
731        script, its compilation script and patches and put it in the temporary
732        working directory. This method is used in the source package in order to
733        construct the specific project.
734
735     :param p_name str: The name of the product.
736     :param p_info Config: The specific configuration corresponding to the 
737                              product
738     :param config Config: The global configuration.
739     :param with_vcs boolean: True if the package is with vcs products (not 
740                              transformed into archive products)
741     :param compil_scripts_tmp_dir str: The path to the temporary compilation 
742                                        scripts directory of the project.
743     :param env_scripts_tmp_dir str: The path to the temporary environment script 
744                                     directory of the project.
745     :param patches_tmp_dir str: The path to the temporary patch scripts 
746                                 directory of the project.
747     :param products_pyconf_tmp_dir str: The path to the temporary product 
748                                         scripts directory of the project.
749     '''
750     
751     # read the pyconf of the product
752     product_pyconf_path = src.find_file_in_lpath(p_name + ".pyconf",
753                                            config.PATHS.PRODUCTPATH)
754     product_pyconf_cfg = src.pyconf.Config(product_pyconf_path)
755
756     # find the compilation script if any
757     if src.product.product_has_script(p_info):
758         compil_script_path = src.Path(p_info.compil_script)
759         compil_script_path.copy(compil_scripts_tmp_dir)
760         product_pyconf_cfg[p_info.section].compil_script = os.path.basename(
761                                                     p_info.compil_script)
762     # find the environment script if any
763     if src.product.product_has_env_script(p_info):
764         env_script_path = src.Path(p_info.environ.env_script)
765         env_script_path.copy(env_scripts_tmp_dir)
766         product_pyconf_cfg[p_info.section].environ.env_script = os.path.basename(
767                                                 p_info.environ.env_script)
768     # find the patches if any
769     if src.product.product_has_patches(p_info):
770         patches = src.pyconf.Sequence()
771         for patch_path in p_info.patches:
772             p_path = src.Path(patch_path)
773             p_path.copy(patches_tmp_dir)
774             patches.append(os.path.basename(patch_path), "")
775
776         product_pyconf_cfg[p_info.section].patches = patches
777     
778     if with_vcs:
779         # put in the pyconf file the resolved values
780         for info in ["git_info", "cvs_info", "svn_info"]:
781             if info in p_info:
782                 for key in p_info[info]:
783                     product_pyconf_cfg[p_info.section][info][key] = p_info[
784                                                                       info][key]
785     else:
786         # if the product is not archive, then make it become archive.
787         if src.product.product_is_vcs(p_info):
788             product_pyconf_cfg[p_info.section].get_source = "archive"
789             if not "archive_info" in product_pyconf_cfg[p_info.section]:
790                 product_pyconf_cfg[p_info.section].addMapping("archive_info",
791                                         src.pyconf.Mapping(product_pyconf_cfg),
792                                         "")
793             product_pyconf_cfg[p_info.section
794                               ].archive_info.archive_name = p_info.name + ".tgz"
795     
796     # write the pyconf file to the temporary project location
797     product_tmp_pyconf_path = os.path.join(products_pyconf_tmp_dir,
798                                            p_name + ".pyconf")
799     ff = open(product_tmp_pyconf_path, 'w')
800     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
801     product_pyconf_cfg.__save__(ff, 1)
802     ff.close()
803
804 def find_application_pyconf(config, application_tmp_dir):
805     '''Find the application pyconf file and put it in the specific temporary 
806        directory containing the specific project of a source package.
807
808     :param config Config: The global configuration.
809     :param application_tmp_dir str: The path to the temporary application 
810                                        scripts directory of the project.
811     '''
812     # read the pyconf of the application
813     application_name = config.VARS.application
814     application_pyconf_path = src.find_file_in_lpath(
815                                             application_name + ".pyconf",
816                                             config.PATHS.APPLICATIONPATH)
817     application_pyconf_cfg = src.pyconf.Config(application_pyconf_path)
818     
819     # Change the workdir
820     application_pyconf_cfg.APPLICATION.workdir = src.pyconf.Reference(
821                                     application_pyconf_cfg,
822                                     src.pyconf.DOLLAR,
823                                     'VARS.salometoolsway + $VARS.sep + ".."')
824
825     # Prevent from compilation in base
826     application_pyconf_cfg.APPLICATION.no_base = "yes"
827     
828     # write the pyconf file to the temporary application location
829     application_tmp_pyconf_path = os.path.join(application_tmp_dir,
830                                                application_name + ".pyconf")
831     ff = open(application_tmp_pyconf_path, 'w')
832     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
833     application_pyconf_cfg.__save__(ff, 1)
834     ff.close()
835
836 def project_package(project_file_path, tmp_working_dir):
837     '''Prepare a dictionary that stores all the needed directories and files to
838        add in a project package.
839     
840     :param project_file_path str: The path to the local project.
841     :param tmp_working_dir str: The temporary local directory containing some 
842                                 specific directories or files needed in the 
843                                 project package
844     :return: the dictionary that stores all the needed directories and files to
845              add in a project package.
846              {label : (path_on_local_machine, path_in_archive)}
847     :rtype: dict
848     '''
849     d_project = {}
850     # Read the project file and get the directories to add to the package
851     project_pyconf_cfg = src.pyconf.Config(project_file_path)
852     paths = {"ARCHIVEPATH" : "archives",
853              "APPLICATIONPATH" : "applications",
854              "PRODUCTPATH" : "products",
855              "JOBPATH" : "jobs",
856              "MACHINEPATH" : "machines"}
857     # Loop over the project paths and add it
858     for path in paths:
859         if path not in project_pyconf_cfg:
860             continue
861         # Add the directory to the files to add in the package
862         d_project[path] = (project_pyconf_cfg[path], paths[path])
863         # Modify the value of the path in the package
864         project_pyconf_cfg[path] = src.pyconf.Reference(
865                                     project_pyconf_cfg,
866                                     src.pyconf.DOLLAR,
867                                     'project_path + "/' + paths[path] + '"')
868     
869     # Modify some values
870     if "project_path" not in project_pyconf_cfg:
871         project_pyconf_cfg.addMapping("project_path",
872                                       src.pyconf.Mapping(project_pyconf_cfg),
873                                       "")
874     project_pyconf_cfg.project_path = src.pyconf.Reference(project_pyconf_cfg,
875                                                            src.pyconf.DOLLAR,
876                                                            'PWD')
877     
878     # Write the project pyconf file
879     project_file_name = os.path.basename(project_file_path)
880     project_pyconf_tmp_path = os.path.join(tmp_working_dir, project_file_name)
881     ff = open(project_pyconf_tmp_path, 'w')
882     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
883     project_pyconf_cfg.__save__(ff, 1)
884     ff.close()
885     d_project["Project hat file"] = (project_pyconf_tmp_path, project_file_name)
886     
887     return d_project
888
889 def add_readme(config, package_type, where):
890     readme_path = os.path.join(where, "README")
891     f = open(readme_path, 'w')
892     # prepare substitution dictionary
893     d = dict()
894     if package_type == BINARY:
895         d['application'] = config.VARS.application
896         d['user'] = config.VARS.user
897         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
898         d['version'] = config.INTERNAL.sat_version
899         d['dist'] = config.VARS.dist
900         if 'profile' in config.APPLICATION:
901             d['launcher'] = config.APPLICATION.profile.launcher_name
902             readme_template_path = os.path.join(config.VARS.internal_dir,
903                                                 "README_BIN.template")
904         else:
905             d['env_file'] = 'env_launch.sh'
906             readme_template_path = os.path.join(config.VARS.internal_dir,
907                                                "README_BIN_NO_PROFILE.template")
908             
909     if package_type == SOURCE:
910         d['application'] = config.VARS.application
911         d['user'] = config.VARS.user
912         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
913         d['version'] = config.INTERNAL.sat_version
914         if 'profile' in config.APPLICATION:
915             d['profile'] = config.APPLICATION.profile.product
916             d['launcher'] = config.APPLICATION.profile.launcher_name
917         readme_template_path = os.path.join(config.VARS.internal_dir,
918                                     "README_SRC.template")
919
920     if package_type == PROJECT:
921         d['user'] = config.VARS.user
922         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
923         d['version'] = config.INTERNAL.sat_version
924         readme_template_path = os.path.join(config.VARS.internal_dir,
925                                     "README_PROJECT.template")
926
927     if package_type == SAT:
928         d['user'] = config.VARS.user
929         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
930         d['version'] = config.INTERNAL.sat_version
931         readme_template_path = os.path.join(config.VARS.internal_dir,
932                                     "README_SAT.template")
933     
934     f.write(src.template.substitute(readme_template_path, d))
935     
936     return readme_path
937
938 def update_config(config, prop, value):
939     '''Remove from config.APPLICATION.products the products that have the property given as input.
940     
941     :param config Config: The global config.
942     :param prop str: The property to filter
943     :param value str: The value of the property to filter
944     '''
945     src.check_config_has_application(config)
946     l_product_to_remove = []
947     for product_name in config.APPLICATION.products.keys():
948         prod_cfg = src.product.get_product_config(config, product_name)
949         if src.get_property_in_product_cfg(prod_cfg, prop) == value:
950             l_product_to_remove.append(product_name)
951     for product_name in l_product_to_remove:
952         config.APPLICATION.products.__delitem__(product_name)
953
954 def description():
955     '''method that is called when salomeTools is called with --help option.
956     
957     :return: The text to display for the package command description.
958     :rtype: str
959     '''
960     return _("The package command creates an archive.\nThere are 4 kinds of "
961              "archive:\n  1- The binary archive. It contains all the product "
962              "installation directories and a launcher,\n  2- The sources archive."
963              " It contains the products archives, a project corresponding to "
964              "the application and salomeTools,\n  3- The project archive. It "
965              "contains a project (give the project file path as argument),\n  4-"
966              " The salomeTools archive. It contains salomeTools.\n\nexample:"
967              "\nsat package SALOME-master --sources")
968   
969 def run(args, runner, logger):
970     '''method that is called when salomeTools is called with package parameter.
971     '''
972     
973     # Parse the options
974     (options, args) = parser.parse_args(args)
975        
976     # Check that a type of package is called, and only one
977     all_option_types = (options.binaries,
978                         options.sources,
979                         options.project not in ["", None],
980                         options.sat)
981
982     # Check if no option for package type
983     if all_option_types.count(True) == 0:
984         msg = _("Error: Precise a type for the package\nUse one of the "
985                 "following options: --binaries, --sources, --project or"
986                 " --salometools")
987         logger.write(src.printcolors.printcError(msg), 1)
988         logger.write("\n", 1)
989         return 1
990     
991     # Check for only one option for package type
992     if all_option_types.count(True) > 1:
993         msg = _("Error: You can use only one type for the package\nUse only one"
994                 " of the following options: --binaries, --sources, --project or"
995                 " --salometools")
996         logger.write(src.printcolors.printcError(msg), 1)
997         logger.write("\n", 1)
998         return 1
999     
1000     # Get the package type
1001     if options.binaries:
1002         package_type = BINARY
1003     if options.sources:
1004         package_type = SOURCE
1005     if options.project:
1006         package_type = PROJECT
1007     if options.sat:
1008         package_type = SAT
1009
1010     # The repository where to put the package if not Binary or Source
1011     package_default_path = runner.cfg.USER.workdir
1012     
1013     if package_type in [BINARY, SOURCE]:
1014         # Check that the command has been called with an application
1015         src.check_config_has_application(runner.cfg)
1016
1017         # Display information
1018         logger.write(_("Packaging application %s\n") % src.printcolors.printcLabel(
1019                                                     runner.cfg.VARS.application), 1)
1020         
1021         # Get the default directory where to put the packages
1022         package_default_path = os.path.join(runner.cfg.APPLICATION.workdir,
1023                                             "PACKAGE")
1024         src.ensure_path_exists(package_default_path)
1025         
1026     elif package_type == PROJECT:
1027         # check that the project is visible by SAT
1028         if options.project not in runner.cfg.PROJECTS.project_file_paths:
1029             site_path = os.path.join(runner.cfg.VARS.salometoolsway,
1030                                      "data",
1031                                      "site.pyconf")
1032             msg = _("ERROR: the project %(proj)s is not visible by salomeTools."
1033                     "\nPlease add it in the %(site)s file." % {
1034                                   "proj" : options.project, "site" : site_path})
1035             logger.write(src.printcolors.printcError(msg), 1)
1036             logger.write("\n", 1)
1037             return 1
1038     
1039     # Remove the products that are filtered by the --without_property option
1040     if options.without_property:
1041         [prop, value] = options.without_property.split(":")
1042         update_config(runner.cfg, prop, value)
1043     
1044     # Print
1045     src.printcolors.print_value(logger, "Package type", package_type, 2)
1046
1047     # get the name of the archive or construct it
1048     if options.name:
1049         if os.path.basename(options.name) == options.name:
1050             # only a name (not a path)
1051             archive_name = options.name           
1052             dir_name = package_default_path
1053         else:
1054             archive_name = os.path.basename(options.name)
1055             dir_name = os.path.dirname(options.name)
1056         
1057         # suppress extension
1058         if archive_name[-len(".tgz"):] == ".tgz":
1059             archive_name = archive_name[:-len(".tgz")]
1060         if archive_name[-len(".tar.gz"):] == ".tar.gz":
1061             archive_name = archive_name[:-len(".tar.gz")]
1062         
1063     else:
1064         dir_name = package_default_path
1065         if package_type == BINARY:
1066             archive_name = (runner.cfg.APPLICATION.name +
1067                             "-" +
1068                             runner.cfg.VARS.dist)
1069             
1070         if package_type == SOURCE:
1071             archive_name = (runner.cfg.APPLICATION.name +
1072                             "-" +
1073                             "SRC")
1074             if options.with_vcs:
1075                 archive_name = (runner.cfg.APPLICATION.name +
1076                             "-" +
1077                             "SRC" +
1078                             "-" +
1079                             "VCS")
1080
1081         if package_type == PROJECT:
1082             project_name, __ = os.path.splitext(
1083                                             os.path.basename(options.project))
1084             archive_name = ("PROJECT" +
1085                             "-" +
1086                             project_name)
1087  
1088         if package_type == SAT:
1089             archive_name = ("salomeTools" +
1090                             "-" +
1091                             runner.cfg.INTERNAL.sat_version)
1092  
1093     path_targz = os.path.join(dir_name, archive_name + ".tgz")
1094     
1095     # Print the path of the package
1096     src.printcolors.print_value(logger, "Package path", path_targz, 2)
1097
1098     # Create a working directory for all files that are produced during the
1099     # package creation and that will be removed at the end of the command
1100     tmp_working_dir = os.path.join(runner.cfg.VARS.tmp_root,
1101                                    runner.cfg.VARS.datehour)
1102     src.ensure_path_exists(tmp_working_dir)
1103     logger.write("\n", 5)
1104     logger.write(_("The temporary working directory: %s\n" % tmp_working_dir),5)
1105     
1106     logger.write("\n", 3)
1107
1108     msg = _("Preparation of files to add to the archive")
1109     logger.write(src.printcolors.printcLabel(msg), 2)
1110     logger.write("\n", 2)
1111
1112     if package_type == BINARY:           
1113         d_files_to_add = binary_package(runner.cfg,
1114                                         logger,
1115                                         options,
1116                                         tmp_working_dir)
1117         if not(d_files_to_add):
1118             return 1
1119
1120     if package_type == SOURCE:
1121         d_files_to_add = source_package(runner,
1122                                         runner.cfg,
1123                                         logger, 
1124                                         options,
1125                                         tmp_working_dir)          
1126     
1127     if package_type == PROJECT:
1128         d_files_to_add = project_package(options.project, tmp_working_dir)
1129
1130     if package_type == SAT:
1131         d_files_to_add = {"salomeTools" : (runner.cfg.VARS.salometoolsway, "")}
1132     
1133     # Add the README file in the package
1134     local_readme_tmp_path = add_readme(runner.cfg,
1135                                        package_type,
1136                                        tmp_working_dir)
1137     d_files_to_add["README"] = (local_readme_tmp_path, "README")
1138
1139     # Add the additional files of option add_files
1140     if options.add_files:
1141         for file_path in options.add_files:
1142             if not os.path.exists(file_path):
1143                 msg = _("WARNING: the file %s is not accessible.\n" % file_path)
1144                 continue
1145             file_name = os.path.basename(file_path)
1146             d_files_to_add[file_name] = (file_path, file_name)
1147
1148     logger.write("\n", 2)
1149
1150     logger.write(src.printcolors.printcLabel(_("Actually do the package")), 2)
1151     logger.write("\n", 2)
1152     
1153     try:
1154         # Creating the object tarfile
1155         tar = tarfile.open(path_targz, mode='w:gz')
1156         
1157         # get the filtering function if needed
1158         filter_function = None
1159         if package_type == BINARY:
1160             filter_function = exclude_VCS_info
1161
1162         # Add the files to the tarfile object
1163         res = add_files(tar, archive_name, d_files_to_add, logger, f_exclude=filter_function)
1164         tar.close()
1165     except KeyboardInterrupt:
1166         logger.write(src.printcolors.printcError("\nERROR: forced interruption\n"), 1)
1167         logger.write(_("Removing the temporary working directory ... "), 1)
1168         # remove the working directory
1169         shutil.rmtree(tmp_working_dir)
1170         logger.write(_("OK"), 1)
1171         logger.write(_("\n"), 1)
1172         return 1
1173     
1174     # remove the working directory    
1175     shutil.rmtree(tmp_working_dir)
1176     
1177     # Print again the path of the package
1178     logger.write("\n", 2)
1179     src.printcolors.print_value(logger, "Package path", path_targz, 2)
1180     
1181     return res