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