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