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