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