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