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