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