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