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