Salome HOME
Do not filter the fixed products when creating SRC packages
[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     else:
456         # Provide a script for the creation of an application EDF style
457         appli_script = product_appli_creation_script(config,
458                                                     logger,
459                                                     tmp_working_dir,
460                                                     binaries_dir_name)
461         
462         d_products["appli script"] = (appli_script, "create_appli.py")
463
464     # Put also the environment file
465     env_file = produce_relative_env_files(config,
466                                            logger,
467                                            tmp_working_dir,
468                                            binaries_dir_name)
469
470     d_products["environment file"] = (env_file, "env_launch.sh")
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):
706             continue
707         find_product_scripts_and_pyconf(p_name,
708                                         p_info,
709                                         config,
710                                         with_vcs,
711                                         compil_scripts_tmp_dir,
712                                         env_scripts_tmp_dir,
713                                         patches_tmp_dir,
714                                         products_pyconf_tmp_dir)
715     
716     find_application_pyconf(config, application_tmp_dir)
717     
718     d_project = {"project" : (project_tmp_dir, PROJECT_DIR )}
719     return d_project
720
721 def find_product_scripts_and_pyconf(p_name,
722                                     p_info,
723                                     config,
724                                     with_vcs,
725                                     compil_scripts_tmp_dir,
726                                     env_scripts_tmp_dir,
727                                     patches_tmp_dir,
728                                     products_pyconf_tmp_dir):
729     '''Create a specific pyconf file for a given product. Get its environment 
730        script, its compilation script and patches and put it in the temporary
731        working directory. This method is used in the source package in order to
732        construct the specific project.
733
734     :param p_name str: The name of the product.
735     :param p_info Config: The specific configuration corresponding to the 
736                              product
737     :param config Config: The global configuration.
738     :param with_vcs boolean: True if the package is with vcs products (not 
739                              transformed into archive products)
740     :param compil_scripts_tmp_dir str: The path to the temporary compilation 
741                                        scripts directory of the project.
742     :param env_scripts_tmp_dir str: The path to the temporary environment script 
743                                     directory of the project.
744     :param patches_tmp_dir str: The path to the temporary patch scripts 
745                                 directory of the project.
746     :param products_pyconf_tmp_dir str: The path to the temporary product 
747                                         scripts directory of the project.
748     '''
749     
750     # read the pyconf of the product
751     product_pyconf_path = src.find_file_in_lpath(p_name + ".pyconf",
752                                            config.PATHS.PRODUCTPATH)
753     product_pyconf_cfg = src.pyconf.Config(product_pyconf_path)
754
755     # find the compilation script if any
756     if src.product.product_has_script(p_info):
757         compil_script_path = src.Path(p_info.compil_script)
758         compil_script_path.copy(compil_scripts_tmp_dir)
759         product_pyconf_cfg[p_info.section].compil_script = os.path.basename(
760                                                     p_info.compil_script)
761     # find the environment script if any
762     if src.product.product_has_env_script(p_info):
763         env_script_path = src.Path(p_info.environ.env_script)
764         env_script_path.copy(env_scripts_tmp_dir)
765         product_pyconf_cfg[p_info.section].environ.env_script = os.path.basename(
766                                                 p_info.environ.env_script)
767     # find the patches if any
768     if src.product.product_has_patches(p_info):
769         patches = src.pyconf.Sequence()
770         for patch_path in p_info.patches:
771             p_path = src.Path(patch_path)
772             p_path.copy(patches_tmp_dir)
773             patches.append(os.path.basename(patch_path), "")
774
775         product_pyconf_cfg[p_info.section].patches = patches
776     
777     if with_vcs:
778         # put in the pyconf file the resolved values
779         for info in ["git_info", "cvs_info", "svn_info"]:
780             if info in p_info:
781                 for key in p_info[info]:
782                     product_pyconf_cfg[p_info.section][info][key] = p_info[
783                                                                       info][key]
784     else:
785         # if the product is not archive, then make it become archive.
786         if src.product.product_is_vcs(p_info):
787             product_pyconf_cfg[p_info.section].get_source = "archive"
788             if not "archive_info" in product_pyconf_cfg[p_info.section]:
789                 product_pyconf_cfg[p_info.section].addMapping("archive_info",
790                                         src.pyconf.Mapping(product_pyconf_cfg),
791                                         "")
792             product_pyconf_cfg[p_info.section
793                               ].archive_info.archive_name = p_info.name + ".tgz"
794     
795     # write the pyconf file to the temporary project location
796     product_tmp_pyconf_path = os.path.join(products_pyconf_tmp_dir,
797                                            p_name + ".pyconf")
798     ff = open(product_tmp_pyconf_path, 'w')
799     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
800     product_pyconf_cfg.__save__(ff, 1)
801     ff.close()
802
803 def find_application_pyconf(config, application_tmp_dir):
804     '''Find the application pyconf file and put it in the specific temporary 
805        directory containing the specific project of a source package.
806
807     :param config Config: The global configuration.
808     :param application_tmp_dir str: The path to the temporary application 
809                                        scripts directory of the project.
810     '''
811     # read the pyconf of the application
812     application_name = config.VARS.application
813     application_pyconf_path = src.find_file_in_lpath(
814                                             application_name + ".pyconf",
815                                             config.PATHS.APPLICATIONPATH)
816     application_pyconf_cfg = src.pyconf.Config(application_pyconf_path)
817     
818     # Change the workdir
819     application_pyconf_cfg.APPLICATION.workdir = src.pyconf.Reference(
820                                     application_pyconf_cfg,
821                                     src.pyconf.DOLLAR,
822                                     'VARS.salometoolsway + $VARS.sep + ".."')
823
824     # Prevent from compilation in base
825     application_pyconf_cfg.APPLICATION.no_base = "yes"
826     
827     # write the pyconf file to the temporary application location
828     application_tmp_pyconf_path = os.path.join(application_tmp_dir,
829                                                application_name + ".pyconf")
830     ff = open(application_tmp_pyconf_path, 'w')
831     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
832     application_pyconf_cfg.__save__(ff, 1)
833     ff.close()
834
835 def project_package(project_file_path, tmp_working_dir):
836     '''Prepare a dictionary that stores all the needed directories and files to
837        add in a project package.
838     
839     :param project_file_path str: The path to the local project.
840     :param tmp_working_dir str: The temporary local directory containing some 
841                                 specific directories or files needed in the 
842                                 project package
843     :return: the dictionary that stores all the needed directories and files to
844              add in a project package.
845              {label : (path_on_local_machine, path_in_archive)}
846     :rtype: dict
847     '''
848     d_project = {}
849     # Read the project file and get the directories to add to the package
850     project_pyconf_cfg = src.pyconf.Config(project_file_path)
851     paths = {"ARCHIVEPATH" : "archives",
852              "APPLICATIONPATH" : "applications",
853              "PRODUCTPATH" : "products",
854              "JOBPATH" : "jobs",
855              "MACHINEPATH" : "machines"}
856     # Loop over the project paths and add it
857     for path in paths:
858         if path not in project_pyconf_cfg:
859             continue
860         # Add the directory to the files to add in the package
861         d_project[path] = (project_pyconf_cfg[path], paths[path])
862         # Modify the value of the path in the package
863         project_pyconf_cfg[path] = src.pyconf.Reference(
864                                     project_pyconf_cfg,
865                                     src.pyconf.DOLLAR,
866                                     'project_path + "/' + paths[path] + '"')
867     
868     # Modify some values
869     if "project_path" not in project_pyconf_cfg:
870         project_pyconf_cfg.addMapping("project_path",
871                                       src.pyconf.Mapping(project_pyconf_cfg),
872                                       "")
873     project_pyconf_cfg.project_path = src.pyconf.Reference(project_pyconf_cfg,
874                                                            src.pyconf.DOLLAR,
875                                                            'PWD')
876     
877     # Write the project pyconf file
878     project_file_name = os.path.basename(project_file_path)
879     project_pyconf_tmp_path = os.path.join(tmp_working_dir, project_file_name)
880     ff = open(project_pyconf_tmp_path, 'w')
881     ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
882     project_pyconf_cfg.__save__(ff, 1)
883     ff.close()
884     d_project["Project hat file"] = (project_pyconf_tmp_path, project_file_name)
885     
886     return d_project
887
888 def add_readme(config, package_type, where):
889     readme_path = os.path.join(where, "README")
890     f = open(readme_path, 'w')
891     # prepare substitution dictionary
892     d = dict()
893     if package_type == BINARY:
894         d['application'] = config.VARS.application
895         d['user'] = config.VARS.user
896         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
897         d['version'] = config.INTERNAL.sat_version
898         d['dist'] = config.VARS.dist
899         if 'profile' in config.APPLICATION:
900             d['launcher'] = config.APPLICATION.profile.launcher_name
901             readme_template_path = os.path.join(config.VARS.internal_dir,
902                                                 "README_BIN.template")
903         else:
904             d['env_file'] = 'env_launch.sh'
905             readme_template_path = os.path.join(config.VARS.internal_dir,
906                                                "README_BIN_NO_PROFILE.template")
907             
908     if package_type == SOURCE:
909         d['application'] = config.VARS.application
910         d['user'] = config.VARS.user
911         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
912         d['version'] = config.INTERNAL.sat_version
913         if 'profile' in config.APPLICATION:
914             d['profile'] = config.APPLICATION.profile.product
915             d['launcher'] = config.APPLICATION.profile.launcher_name
916         readme_template_path = os.path.join(config.VARS.internal_dir,
917                                     "README_SRC.template")
918
919     if package_type == PROJECT:
920         d['user'] = config.VARS.user
921         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
922         d['version'] = config.INTERNAL.sat_version
923         readme_template_path = os.path.join(config.VARS.internal_dir,
924                                     "README_PROJECT.template")
925
926     if package_type == SAT:
927         d['user'] = config.VARS.user
928         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
929         d['version'] = config.INTERNAL.sat_version
930         readme_template_path = os.path.join(config.VARS.internal_dir,
931                                     "README_SAT.template")
932     
933     f.write(src.template.substitute(readme_template_path, d))
934     
935     return readme_path
936
937 def update_config(config, prop, value):
938     '''Remove from config.APPLICATION.products the products that have the property given as input.
939     
940     :param config Config: The global config.
941     :param prop str: The property to filter
942     :param value str: The value of the property to filter
943     '''
944     src.check_config_has_application(config)
945     l_product_to_remove = []
946     for product_name in config.APPLICATION.products.keys():
947         prod_cfg = src.product.get_product_config(config, product_name)
948         if src.get_property_in_product_cfg(prod_cfg, prop) == value:
949             l_product_to_remove.append(product_name)
950     for product_name in l_product_to_remove:
951         config.APPLICATION.products.__delitem__(product_name)
952
953 def description():
954     '''method that is called when salomeTools is called with --help option.
955     
956     :return: The text to display for the package command description.
957     :rtype: str
958     '''
959     return _("The package command creates an archive.\nThere are 4 kinds of "
960              "archive:\n  1- The binary archive. It contains all the product "
961              "installation directories and a launcher,\n  2- The sources archive."
962              " It contains the products archives, a project corresponding to "
963              "the application and salomeTools,\n  3- The project archive. It "
964              "contains a project (give the project file path as argument),\n  4-"
965              " The salomeTools archive. It contains salomeTools.\n\nexample:"
966              "\nsat package SALOME-master --sources")
967   
968 def run(args, runner, logger):
969     '''method that is called when salomeTools is called with package parameter.
970     '''
971     
972     # Parse the options
973     (options, args) = parser.parse_args(args)
974        
975     # Check that a type of package is called, and only one
976     all_option_types = (options.binaries,
977                         options.sources,
978                         options.project not in ["", None],
979                         options.sat)
980
981     # Check if no option for package type
982     if all_option_types.count(True) == 0:
983         msg = _("Error: Precise a type for the package\nUse one of the "
984                 "following options: --binaries, --sources, --project or"
985                 " --salometools")
986         logger.write(src.printcolors.printcError(msg), 1)
987         logger.write("\n", 1)
988         return 1
989     
990     # Check for only one option for package type
991     if all_option_types.count(True) > 1:
992         msg = _("Error: You can use only one type for the package\nUse only one"
993                 " of the following options: --binaries, --sources, --project or"
994                 " --salometools")
995         logger.write(src.printcolors.printcError(msg), 1)
996         logger.write("\n", 1)
997         return 1
998     
999     # Get the package type
1000     if options.binaries:
1001         package_type = BINARY
1002     if options.sources:
1003         package_type = SOURCE
1004     if options.project:
1005         package_type = PROJECT
1006     if options.sat:
1007         package_type = SAT
1008
1009     # The repository where to put the package if not Binary or Source
1010     package_default_path = runner.cfg.USER.workdir
1011     
1012     if package_type in [BINARY, SOURCE]:
1013         # Check that the command has been called with an application
1014         src.check_config_has_application(runner.cfg)
1015
1016         # Display information
1017         logger.write(_("Packaging application %s\n") % src.printcolors.printcLabel(
1018                                                     runner.cfg.VARS.application), 1)
1019         
1020         # Get the default directory where to put the packages
1021         package_default_path = os.path.join(runner.cfg.APPLICATION.workdir,
1022                                             "PACKAGE")
1023         src.ensure_path_exists(package_default_path)
1024         
1025     elif package_type == PROJECT:
1026         # check that the project is visible by SAT
1027         if options.project not in runner.cfg.PROJECTS.project_file_paths:
1028             site_path = os.path.join(runner.cfg.VARS.salometoolsway,
1029                                      "data",
1030                                      "site.pyconf")
1031             msg = _("ERROR: the project %(proj)s is not visible by salomeTools."
1032                     "\nPlease add it in the %(site)s file." % {
1033                                   "proj" : options.project, "site" : site_path})
1034             logger.write(src.printcolors.printcError(msg), 1)
1035             logger.write("\n", 1)
1036             return 1
1037     
1038     # Remove the products that are filtered by the --without_property option
1039     if options.without_property:
1040         [prop, value] = options.without_property.split(":")
1041         update_config(runner.cfg, prop, value)
1042     
1043     # Print
1044     src.printcolors.print_value(logger, "Package type", package_type, 2)
1045
1046     # get the name of the archive or construct it
1047     if options.name:
1048         if os.path.basename(options.name) == options.name:
1049             # only a name (not a path)
1050             archive_name = options.name           
1051             dir_name = package_default_path
1052         else:
1053             archive_name = os.path.basename(options.name)
1054             dir_name = os.path.dirname(options.name)
1055         
1056         # suppress extension
1057         if archive_name[-len(".tgz"):] == ".tgz":
1058             archive_name = archive_name[:-len(".tgz")]
1059         if archive_name[-len(".tar.gz"):] == ".tar.gz":
1060             archive_name = archive_name[:-len(".tar.gz")]
1061         
1062     else:
1063         dir_name = package_default_path
1064         if package_type == BINARY:
1065             archive_name = (runner.cfg.APPLICATION.name +
1066                             "-" +
1067                             runner.cfg.VARS.dist)
1068             
1069         if package_type == SOURCE:
1070             archive_name = (runner.cfg.APPLICATION.name +
1071                             "-" +
1072                             "SRC")
1073             if options.with_vcs:
1074                 archive_name = (runner.cfg.APPLICATION.name +
1075                             "-" +
1076                             "SRC" +
1077                             "-" +
1078                             "VCS")
1079
1080         if package_type == PROJECT:
1081             project_name, __ = os.path.splitext(
1082                                             os.path.basename(options.project))
1083             archive_name = ("PROJECT" +
1084                             "-" +
1085                             project_name)
1086  
1087         if package_type == SAT:
1088             archive_name = ("salomeTools" +
1089                             "-" +
1090                             runner.cfg.INTERNAL.sat_version)
1091  
1092     path_targz = os.path.join(dir_name, archive_name + ".tgz")
1093     
1094     # Print the path of the package
1095     src.printcolors.print_value(logger, "Package path", path_targz, 2)
1096
1097     # Create a working directory for all files that are produced during the
1098     # package creation and that will be removed at the end of the command
1099     tmp_working_dir = os.path.join(runner.cfg.VARS.tmp_root,
1100                                    runner.cfg.VARS.datehour)
1101     src.ensure_path_exists(tmp_working_dir)
1102     logger.write("\n", 5)
1103     logger.write(_("The temporary working directory: %s\n" % tmp_working_dir),5)
1104     
1105     logger.write("\n", 3)
1106
1107     msg = _("Preparation of files to add to the archive")
1108     logger.write(src.printcolors.printcLabel(msg), 2)
1109     logger.write("\n", 2)
1110
1111     if package_type == BINARY:           
1112         d_files_to_add = binary_package(runner.cfg,
1113                                         logger,
1114                                         options,
1115                                         tmp_working_dir)
1116         if not(d_files_to_add):
1117             return 1
1118
1119     if package_type == SOURCE:
1120         d_files_to_add = source_package(runner,
1121                                         runner.cfg,
1122                                         logger, 
1123                                         options,
1124                                         tmp_working_dir)          
1125     
1126     if package_type == PROJECT:
1127         d_files_to_add = project_package(options.project, tmp_working_dir)
1128
1129     if package_type == SAT:
1130         d_files_to_add = {"salomeTools" : (runner.cfg.VARS.salometoolsway, "")}
1131     
1132     # Add the README file in the package
1133     local_readme_tmp_path = add_readme(runner.cfg,
1134                                        package_type,
1135                                        tmp_working_dir)
1136     d_files_to_add["README"] = (local_readme_tmp_path, "README")
1137
1138     # Add the additional files of option add_files
1139     if options.add_files:
1140         for file_path in options.add_files:
1141             if not os.path.exists(file_path):
1142                 msg = _("WARNING: the file %s is not accessible.\n" % file_path)
1143                 continue
1144             file_name = os.path.basename(file_path)
1145             d_files_to_add[file_name] = (file_path, file_name)
1146
1147     logger.write("\n", 2)
1148
1149     logger.write(src.printcolors.printcLabel(_("Actually do the package")), 2)
1150     logger.write("\n", 2)
1151     
1152     try:
1153         # Creating the object tarfile
1154         tar = tarfile.open(path_targz, mode='w:gz')
1155         
1156         # get the filtering function if needed
1157         filter_function = None
1158         if package_type == BINARY:
1159             filter_function = exclude_VCS_info
1160
1161         # Add the files to the tarfile object
1162         res = add_files(tar, archive_name, d_files_to_add, logger, f_exclude=filter_function)
1163         tar.close()
1164     except KeyboardInterrupt:
1165         logger.write(src.printcolors.printcError("\nERROR: forced interruption\n"), 1)
1166         logger.write(_("Removing the temporary working directory ... "), 1)
1167         # remove the working directory
1168         shutil.rmtree(tmp_working_dir)
1169         logger.write(_("OK"), 1)
1170         logger.write(_("\n"), 1)
1171         return 1
1172     
1173     # remove the working directory    
1174     shutil.rmtree(tmp_working_dir)
1175     
1176     # Print again the path of the package
1177     logger.write("\n", 2)
1178     src.printcolors.print_value(logger, "Package path", path_targz, 2)
1179     
1180     return res