]> SALOME platform Git repositories - tools/sat.git/blob - commands/package.py
Salome HOME
bug fix
[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         else:
756             d['env_file'] = 'env_launch.sh'
757             readme_template_path = os.path.join(config.VARS.internal_dir,
758                                                "README_BIN_NO_PROFILE.template")
759             
760     if package_type == SOURCE:
761         d['application'] = config.VARS.application
762         d['user'] = config.VARS.user
763         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
764         d['version'] = config.INTERNAL.sat_version
765         if 'profile' in config.APPLICATION:
766             d['profile'] = config.APPLICATION.profile.product
767             d['launcher'] = config.APPLICATION.profile.launcher_name
768         readme_template_path = os.path.join(config.VARS.internal_dir,
769                                     "README_SRC.template")
770
771     if package_type == PROJECT:
772         d['user'] = config.VARS.user
773         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
774         d['version'] = config.INTERNAL.sat_version
775         readme_template_path = os.path.join(config.VARS.internal_dir,
776                                     "README_PROJECT.template")
777
778     if package_type == SAT:
779         d['user'] = config.VARS.user
780         d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
781         d['version'] = config.INTERNAL.sat_version
782         readme_template_path = os.path.join(config.VARS.internal_dir,
783                                     "README_SAT.template")
784     
785     f.write(src.template.substitute(readme_template_path, d))
786     
787     return readme_path
788         
789
790 def description():
791     '''method that is called when salomeTools is called with --help option.
792     
793     :return: The text to display for the package command description.
794     :rtype: str
795     '''
796     return _("The package command creates an archive.\nThere are 4 kinds of "
797              "archive:\n  1- The binary archive. It contains all the product "
798              "installation directories and a launcher,\n  2- The sources archive."
799              " It contains the products archives, a project corresponding to "
800              "the application and salomeTools,\n  3- The project archive. It "
801              "contains a project (give the project file path as argument),\n  4-"
802              " The salomeTools archive. It contains salomeTools.\n\nexample:"
803              "\nsat package SALOME-master --sources")
804   
805 def run(args, runner, logger):
806     '''method that is called when salomeTools is called with package parameter.
807     '''
808     
809     # Parse the options
810     (options, args) = parser.parse_args(args)
811        
812     # Check that a type of package is called, and only one
813     all_option_types = (options.binaries,
814                         options.sources,
815                         options.project not in ["", None],
816                         options.sat)
817
818     # Check if no option for package type
819     if all_option_types.count(True) == 0:
820         msg = _("Error: Precise a type for the package\nUse one of the "
821                 "following options: --binaries, --sources, --project or"
822                 " --salometools")
823         logger.write(src.printcolors.printcError(msg), 1)
824         logger.write("\n", 1)
825         return 1
826     
827     # Check for only one option for package type
828     if all_option_types.count(True) > 1:
829         msg = _("Error: You can use only one type for the package\nUse only one"
830                 " of the following options: --binaries, --sources, --project or"
831                 " --sat")
832         logger.write(src.printcolors.printcError(msg), 1)
833         logger.write("\n", 1)
834         return 1
835     
836     # Get the package type
837     if options.binaries:
838         package_type = BINARY
839     if options.sources:
840         package_type = SOURCE
841     if options.project:
842         package_type = PROJECT
843     if options.sat:
844         package_type = SAT
845
846     # The repository where to put the package if not Binary or Source
847     package_default_path = runner.cfg.USER.workdir
848     
849     if package_type in [BINARY, SOURCE]:
850         # Check that the command has been called with an application
851         src.check_config_has_application(runner.cfg)
852
853         # Display information
854         logger.write(_("Packaging application %s\n") % src.printcolors.printcLabel(
855                                                     runner.cfg.VARS.application), 1)
856         
857         # Get the default directory where to put the packages
858         package_default_path = os.path.join(runner.cfg.APPLICATION.workdir,
859                                             "PACKAGE")
860         src.ensure_path_exists(package_default_path)
861         
862     elif package_type == PROJECT:
863         # check that the project is visible by SAT
864         if options.project not in runner.cfg.PROJECTS.project_file_paths:
865             site_path = os.path.join(runner.cfg.VARS.salometoolsway,
866                                      "data",
867                                      "site.pyconf")
868             msg = _("ERROR: the project %(proj)s is not visible by salomeTools."
869                     "\nPlease add it in the %(site)s file." % {
870                                   "proj" : options.project, "site" : site_path})
871             logger.write(src.printcolors.printcError(msg), 1)
872             logger.write("\n", 1)
873             return 1
874     
875     # Print
876     src.printcolors.print_value(logger, "Package type", package_type, 2)
877
878     # get the name of the archive or construct it
879     if options.name:
880         if os.path.basename(options.name) == options.name:
881             # only a name (not a path)
882             archive_name = options.name           
883             dir_name = package_default_path
884         else:
885             archive_name = os.path.basename(options.name)
886             dir_name = os.path.dirname(options.name)
887         
888         # suppress extension
889         if archive_name[-len(".tgz"):] == ".tgz":
890             archive_name = archive_name[:-len(".tgz")]
891         if archive_name[-len(".tar.gz"):] == ".tar.gz":
892             archive_name = archive_name[:-len(".tar.gz")]
893         
894     else:
895         dir_name = package_default_path
896         if package_type == BINARY:
897             archive_name = (runner.cfg.APPLICATION.name +
898                             "-" +
899                             runner.cfg.VARS.dist)
900             
901         if package_type == SOURCE:
902             archive_name = (runner.cfg.APPLICATION.name +
903                             "-" +
904                             "SRC")
905             if options.with_vcs:
906                 archive_name = (runner.cfg.APPLICATION.name +
907                             "-" +
908                             "SRC" +
909                             "-" +
910                             "VCS")
911
912         if package_type == PROJECT:
913             project_name, __ = os.path.splitext(
914                                             os.path.basename(options.project))
915             archive_name = ("PROJECT" +
916                             "-" +
917                             project_name)
918  
919         if package_type == SAT:
920             archive_name = ("salomeTools" +
921                             "-" +
922                             runner.cfg.INTERNAL.sat_version)
923  
924     path_targz = os.path.join(dir_name, archive_name + ".tgz")
925     
926     # Print the path of the package
927     src.printcolors.print_value(logger, "Package path", path_targz, 2)
928
929     # Create a working directory for all files that are produced during the
930     # package creation and that will be removed at the end of the command
931     tmp_working_dir = os.path.join(runner.cfg.VARS.tmp_root,
932                                    runner.cfg.VARS.datehour)
933     src.ensure_path_exists(tmp_working_dir)
934     logger.write("\n", 5)
935     logger.write(_("The temporary working directory: %s\n" % tmp_working_dir),5)
936     
937     logger.write("\n", 3)
938
939     msg = _("Preparation of files to add to the archive")
940     logger.write(src.printcolors.printcLabel(msg), 2)
941     logger.write("\n", 2)
942
943     if package_type == BINARY:           
944         d_files_to_add = binary_package(runner.cfg,
945                                         logger,
946                                         options,
947                                         tmp_working_dir)
948         if not(d_files_to_add):
949             return 1
950
951     if package_type == SOURCE:
952         d_files_to_add = source_package(runner,
953                                         runner.cfg,
954                                         logger, 
955                                         options,
956                                         tmp_working_dir)          
957     
958     if package_type == PROJECT:
959         d_files_to_add = project_package(options.project, tmp_working_dir)
960
961     if package_type == SAT:
962         d_files_to_add = {"salomeTools" : (runner.cfg.VARS.salometoolsway, "")}
963     
964     # Add the README file in the package
965     local_readme_tmp_path = add_readme(runner.cfg,
966                                        package_type,
967                                        tmp_working_dir)
968     d_files_to_add["README"] = (local_readme_tmp_path, "README")
969
970     # Add the additional files of option add_files
971     if options.add_files:
972         for file_path in options.add_files:
973             if not os.path.exists(file_path):
974                 msg = _("WARNING: the file %s is not accessible.\n" % file_path)
975                 continue
976             file_name = os.path.basename(file_path)
977             d_files_to_add[file_name] = (file_path, file_name)
978
979     logger.write("\n", 2)
980
981     logger.write(src.printcolors.printcLabel(_("Actually do the package")), 2)
982     logger.write("\n", 2)
983     
984     try:
985         # Creating the object tarfile
986         tar = tarfile.open(path_targz, mode='w:gz')
987         
988         # Add the files to the tarfile object
989         res = add_files(tar, archive_name, d_files_to_add, logger)
990         tar.close()
991     except KeyboardInterrupt:
992         logger.write(src.printcolors.printcError("\nERROR: forced interruption\n"), 1)
993         logger.write(_("Removing the temporary working directory ... "), 1)
994         # remove the working directory
995         shutil.rmtree(tmp_working_dir)
996         logger.write(_("OK"), 1)
997         logger.write(_("\n"), 1)
998         return 1
999     
1000     # remove the working directory    
1001     shutil.rmtree(tmp_working_dir)
1002     
1003     # Print again the path of the package
1004     logger.write("\n", 2)
1005     src.printcolors.print_value(logger, "Package path", path_targz, 2)
1006     
1007     return res