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