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