3 # Copyright (C) 2010-2012 CEA/DEN
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.
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.
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
29 from application import get_SALOME_modules
36 ARCHIVE_DIR = "ARCHIVES"
37 PROJECT_DIR = "PROJECT"
39 IGNORED_DIRS = [".git", ".svn"]
40 IGNORED_EXTENSIONS = []
42 PROJECT_TEMPLATE = """#!/usr/bin/env python
45 # The path to the archive root directory
46 root_path : $PWD + "/../"
48 project_path : $PWD + "/"
50 # Where to search the archives of the products
51 ARCHIVEPATH : $root_path + "ARCHIVES"
52 # Where to search the pyconf of the applications
53 APPLICATIONPATH : $project_path + "applications/"
54 # Where to search the pyconf of the products
55 PRODUCTPATH : $project_path + "products/"
56 # Where to search the pyconf of the jobs of the project
57 JOBPATH : $project_path + "jobs/"
58 # Where to search the pyconf of the machines of the project
59 MACHINEPATH : $project_path + "machines/"
62 SITE_TEMPLATE = ("""#!/usr/bin/env python
69 log_dir : $USER.workdir + "/LOGS"
75 project_file_paths : [$VARS.salometoolsway + $VARS.sep + \"..\" + $VARS.sep"""
76 """ + \"""" + PROJECT_DIR + """\" + $VARS.sep + "project.pyconf"]
80 # Define all possible option for the package command : sat package <options>
81 parser = src.options.Options()
82 parser.add_option('b', 'binaries', 'boolean', 'binaries',
83 _('Optional: Produce a binary package.'), False)
84 parser.add_option('f', 'force_creation', 'boolean', 'force_creation',
85 _('Optional: Only binary package: produce the archive even if '
86 'there are some missing products.'), False)
87 parser.add_option('s', 'sources', 'boolean', 'sources',
88 _('Optional: Produce a compilable archive of the sources of the '
89 'application.'), False)
90 parser.add_option('', 'with_vcs', 'boolean', 'with_vcs',
91 _('Optional: Only source package: do not make archive of vcs products.'),
93 parser.add_option('p', 'project', 'string', 'project',
94 _('Optional: Produce an archive that contains a project.'), "")
95 parser.add_option('t', 'salometools', 'boolean', 'sat',
96 _('Optional: Produce an archive that contains salomeTools.'), False)
97 parser.add_option('n', 'name', 'string', 'name',
98 _('Optional: The name or full path of the archive.'), None)
99 parser.add_option('', 'add_files', 'list2', 'add_files',
100 _('Optional: The list of additional files to add to the archive.'), [])
101 parser.add_option('', 'without_commercial', 'boolean', 'without_commercial',
102 _('Optional: do not add commercial licence.'), False)
103 parser.add_option('', 'without_property', 'string', 'without_property',
104 _('Optional: Filter the products by their properties.\n\tSyntax: '
105 '--without_property <property>:<value>'))
108 def add_files(tar, name_archive, d_content, logger, f_exclude=None):
109 '''Create an archive containing all directories and files that are given in
110 the d_content argument.
112 :param tar tarfile: The tarfile instance used to make the archive.
113 :param name_archive str: The name of the archive to make.
114 :param d_content dict: The dictionary that contain all directories and files
115 to add in the archive.
117 (path_on_local_machine, path_in_archive)
118 :param logger Logger: the logging instance
119 :param f_exclude Function: the function that filters
120 :return: 0 if success, 1 if not.
123 # get the max length of the messages in order to make the display
124 max_len = len(max(d_content.keys(), key=len))
127 # loop over each directory or file stored in the d_content dictionary
128 for name in d_content.keys():
129 # display information
130 len_points = max_len - len(name)
131 logger.write(name + " " + len_points * "." + " ", 3)
132 # Get the local path and the path in archive
133 # of the directory or file to add
134 local_path, archive_path = d_content[name]
135 in_archive = os.path.join(name_archive, archive_path)
136 # Add it in the archive
138 tar.add(local_path, arcname=in_archive, exclude=f_exclude)
139 logger.write(src.printcolors.printcSuccess(_("OK")), 3)
140 except Exception as e:
141 logger.write(src.printcolors.printcError(_("KO ")), 3)
142 logger.write(str(e), 3)
144 logger.write("\n", 3)
147 def exclude_VCS_and_extensions(filename):
148 ''' The function that is used to exclude from package the link to the
149 VCS repositories (like .git)
151 :param filename Str: The filname to exclude (or not).
152 :return: True if the file has to be exclude
155 for dir_name in IGNORED_DIRS:
156 if dir_name in filename:
158 for extension in IGNORED_EXTENSIONS:
159 if filename.endswith(extension):
163 def produce_relative_launcher(config,
168 with_commercial=True):
169 '''Create a specific SALOME launcher for the binary package. This launcher
172 :param config Config: The global configuration.
173 :param logger Logger: the logging instance
174 :param file_dir str: the directory where to put the launcher
175 :param file_name str: The launcher name
176 :param binaries_dir_name str: the name of the repository where the binaries
178 :return: the path of the produced launcher
182 # Get the launcher template
183 profile_install_dir = os.path.join(binaries_dir_name,
184 config.APPLICATION.profile.product)
185 withProfile = src.fileEnviron.withProfile
186 withProfile = withProfile.replace(
187 "ABSOLUTE_APPLI_PATH'] = 'PROFILE_INSTALL_DIR'",
188 "ABSOLUTE_APPLI_PATH'] = out_dir_Path + '" + config.VARS.sep + profile_install_dir + "'")
189 withProfile = withProfile.replace(
190 "os.path.join( 'PROFILE_INSTALL_DIR'",
191 "os.path.join( out_dir_Path, '" + profile_install_dir + "'")
193 before, after = withProfile.split(
194 "# here your local standalone environment\n")
196 # create an environment file writer
197 writer = src.environment.FileEnvWriter(config,
202 filepath = os.path.join(file_dir, file_name)
203 # open the file and write into it
204 launch_file = open(filepath, "w")
205 launch_file.write(before)
207 writer.write_cfgForPy_file(launch_file,
208 for_package = binaries_dir_name,
209 with_commercial=with_commercial)
210 launch_file.write(after)
213 # Little hack to put out_dir_Path outside the strings
214 src.replace_in_file(filepath, 'r"out_dir_Path', 'out_dir_Path + r"' )
216 # change the rights in order to make the file executable for everybody
228 def produce_relative_env_files(config,
232 '''Create some specific environment files for the binary package. These
233 files use relative paths.
235 :param config Config: The global configuration.
236 :param logger Logger: the logging instance
237 :param file_dir str: the directory where to put the files
238 :param binaries_dir_name str: the name of the repository where the binaries
240 :return: the list of path of the produced environment files
243 # create an environment file writer
244 writer = src.environment.FileEnvWriter(config,
250 filepath = writer.write_env_file("env_launch.sh",
253 for_package = binaries_dir_name)
255 # Little hack to put out_dir_Path as environment variable
256 src.replace_in_file(filepath, '"out_dir_Path', '"${out_dir_Path}' )
258 # change the rights in order to make the file executable for everybody
270 def produce_install_bin_file(config,
275 '''Create a bash shell script which do substitutions in BIRARIES dir
276 in order to use it for extra compilations.
278 :param config Config: The global configuration.
279 :param logger Logger: the logging instance
280 :param file_dir str: the directory where to put the files
281 :param d_sub, dict: the dictionnary that contains the substitutions to be done
282 :param file_name str: the name of the install script file
283 :return: the produced file
287 filepath = os.path.join(file_dir, file_name)
288 # open the file and write into it
289 # use codec utf-8 as sat variables are in unicode
290 with codecs.open(filepath, "w", 'utf-8') as installbin_file:
291 installbin_template_path = os.path.join(config.VARS.internal_dir,
292 "INSTALL_BIN.template")
294 # build the name of the directory that will contain the binaries
295 binaries_dir_name = "BINARIES-" + config.VARS.dist
296 # build the substitution loop
297 loop_cmd = "for f in $(grep -RIl"
299 loop_cmd += " -e "+ key
300 loop_cmd += ' INSTALL); do\n sed -i "\n'
302 loop_cmd += " s?" + key + "?$(pwd)/" + d_sub[key] + "?g\n"
303 loop_cmd += ' " $f\ndone'
306 d["BINARIES_DIR"] = binaries_dir_name
307 d["SUBSTITUTION_LOOP"]=loop_cmd
309 # substitute the template and write it in file
310 content=src.template.substitute(installbin_template_path, d)
311 installbin_file.write(content)
312 # change the rights in order to make the file executable for everybody
324 def product_appli_creation_script(config,
328 '''Create a script that can produce an application (EDF style) in the binary
331 :param config Config: The global configuration.
332 :param logger Logger: the logging instance
333 :param file_dir str: the directory where to put the file
334 :param binaries_dir_name str: the name of the repository where the binaries
336 :return: the path of the produced script file
339 template_name = "create_appli.py.for_bin_packages.template"
340 template_path = os.path.join(config.VARS.internal_dir, template_name)
341 text_to_fill = open(template_path, "r").read()
342 text_to_fill = text_to_fill.replace("TO BE FILLED 1",
343 '"' + binaries_dir_name + '"')
346 for product_name in get_SALOME_modules(config):
347 product_info = src.product.get_product_config(config, product_name)
349 if src.product.product_is_smesh_plugin(product_info):
352 if 'install_dir' in product_info and bool(product_info.install_dir):
353 if src.product.product_is_cpp(product_info):
355 for cpp_name in src.product.get_product_components(product_info):
356 line_to_add = ("<module name=\"" +
358 "\" gui=\"yes\" path=\"''' + "
359 "os.path.join(dir_bin_name, \"" +
360 cpp_name + "\") + '''\"/>")
363 line_to_add = ("<module name=\"" +
365 "\" gui=\"yes\" path=\"''' + "
366 "os.path.join(dir_bin_name, \"" +
367 product_name + "\") + '''\"/>")
368 text_to_add += line_to_add + "\n"
370 filled_text = text_to_fill.replace("TO BE FILLED 2", text_to_add)
372 tmp_file_path = os.path.join(file_dir, "create_appli.py")
373 ff = open(tmp_file_path, "w")
374 ff.write(filled_text)
377 # change the rights in order to make the file executable for everybody
378 os.chmod(tmp_file_path,
389 def binary_package(config, logger, options, tmp_working_dir):
390 '''Prepare a dictionary that stores all the needed directories and files to
391 add in a binary package.
393 :param config Config: The global configuration.
394 :param logger Logger: the logging instance
395 :param options OptResult: the options of the launched command
396 :param tmp_working_dir str: The temporary local directory containing some
397 specific directories or files needed in the
399 :return: the dictionary that stores all the needed directories and files to
400 add in a binary package.
401 {label : (path_on_local_machine, path_in_archive)}
405 # Get the list of product installation to add to the archive
406 l_products_name = config.APPLICATION.products.keys()
407 l_product_info = src.product.get_products_infos(l_products_name,
412 l_sources_not_present = []
413 for prod_name, prod_info in l_product_info:
415 # Add the sources of the products that have the property
416 # sources_in_package : "yes"
417 if src.get_property_in_product_cfg(prod_info,
418 "sources_in_package") == "yes":
419 if os.path.exists(prod_info.source_dir):
420 l_source_dir.append((prod_name, prod_info.source_dir))
422 l_sources_not_present.append(prod_name)
424 # ignore the native and fixed products for install directories
425 if (src.product.product_is_native(prod_info)
426 or src.product.product_is_fixed(prod_info)
427 or not src.product.product_compiles(prod_info)):
429 if src.product.check_installation(prod_info):
430 l_install_dir.append((prod_name, prod_info.install_dir))
432 l_not_installed.append(prod_name)
434 # Add also the cpp generated modules (if any)
435 if src.product.product_is_cpp(prod_info):
437 for name_cpp in src.product.get_product_components(prod_info):
438 install_dir = os.path.join(config.APPLICATION.workdir,
440 if os.path.exists(install_dir):
441 l_install_dir.append((name_cpp, install_dir))
443 l_not_installed.append(name_cpp)
445 # Print warning or error if there are some missing products
446 if len(l_not_installed) > 0:
447 text_missing_prods = ""
448 for p_name in l_not_installed:
449 text_missing_prods += "-" + p_name + "\n"
450 if not options.force_creation:
451 msg = _("ERROR: there are missing products installations:")
452 logger.write("%s\n%s" % (src.printcolors.printcError(msg),
457 msg = _("WARNING: there are missing products installations:")
458 logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
462 # Do the same for sources
463 if len(l_sources_not_present) > 0:
464 text_missing_prods = ""
465 for p_name in l_sources_not_present:
466 text_missing_prods += "-" + p_name + "\n"
467 if not options.force_creation:
468 msg = _("ERROR: there are missing products sources:")
469 logger.write("%s\n%s" % (src.printcolors.printcError(msg),
474 msg = _("WARNING: there are missing products sources:")
475 logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
479 # construct the name of the directory that will contain the binaries
480 binaries_dir_name = "BINARIES-" + config.VARS.dist
482 # construct the correlation table between the product names, there
483 # actual install directories and there install directory in archive
485 for prod_name, install_dir in l_install_dir:
486 path_in_archive = os.path.join(binaries_dir_name, prod_name)
487 d_products[prod_name + " (bin)"] = (install_dir, path_in_archive)
489 for prod_name, source_dir in l_source_dir:
490 path_in_archive = os.path.join("SOURCES", prod_name)
491 d_products[prod_name + " (sources)"] = (source_dir, path_in_archive)
493 # create the relative launcher and add it to the files to add
494 if ("profile" in config.APPLICATION and
495 "product" in config.APPLICATION.profile):
496 launcher_name = config.APPLICATION.profile.launcher_name
497 launcher_package = produce_relative_launcher(config,
502 not(options.without_commercial))
504 d_products["launcher"] = (launcher_package, launcher_name)
506 # if we mix binaries and sources, we add a copy of the launcher,
507 # prefixed with "bin",in order to avoid clashes
508 d_products["launcher (copy)"] = (launcher_package, "bin"+launcher_name)
510 # Provide a script for the creation of an application EDF style
511 appli_script = product_appli_creation_script(config,
516 d_products["appli script"] = (appli_script, "create_appli.py")
518 # Put also the environment file
519 env_file = produce_relative_env_files(config,
524 d_products["environment file"] = (env_file, "env_launch.sh")
528 def source_package(sat, config, logger, options, tmp_working_dir):
529 '''Prepare a dictionary that stores all the needed directories and files to
530 add in a source package.
532 :param config Config: The global configuration.
533 :param logger Logger: the logging instance
534 :param options OptResult: the options of the launched command
535 :param tmp_working_dir str: The temporary local directory containing some
536 specific directories or files needed in the
538 :return: the dictionary that stores all the needed directories and files to
539 add in a source package.
540 {label : (path_on_local_machine, path_in_archive)}
544 # Get all the products that are prepared using an archive
545 logger.write("Find archive products ... ")
546 d_archives, l_pinfo_vcs = get_archives(config, logger)
547 logger.write("Done\n")
549 if not options.with_vcs and len(l_pinfo_vcs) > 0:
550 # Make archives with the products that are not prepared using an archive
551 # (git, cvs, svn, etc)
552 logger.write("Construct archives for vcs products ... ")
553 d_archives_vcs = get_archives_vcs(l_pinfo_vcs,
558 logger.write("Done\n")
561 logger.write("Create the project ... ")
562 d_project = create_project_for_src_package(config,
565 logger.write("Done\n")
568 tmp_sat = add_salomeTools(config, tmp_working_dir)
569 d_sat = {"salomeTools" : (tmp_sat, "salomeTools")}
571 # Add a sat symbolic link if not win
572 if not src.architecture.is_windows():
573 tmp_satlink_path = os.path.join(tmp_working_dir, 'sat')
577 # In the jobs, os.getcwd() can fail
578 t = config.USER.workdir
579 os.chdir(tmp_working_dir)
580 if os.path.lexists(tmp_satlink_path):
581 os.remove(tmp_satlink_path)
582 os.symlink(os.path.join('salomeTools', 'sat'), 'sat')
585 d_sat["sat link"] = (tmp_satlink_path, "sat")
587 d_source = src.merge_dicts(d_archives, d_archives_vcs, d_project, d_sat)
590 def get_archives(config, logger):
591 '''Find all the products that are get using an archive and all the products
592 that are get using a vcs (git, cvs, svn) repository.
594 :param config Config: The global configuration.
595 :param logger Logger: the logging instance
596 :return: the dictionary {name_product :
597 (local path of its archive, path in the package of its archive )}
598 and the list of specific configuration corresponding to the vcs
602 # Get the list of product informations
603 l_products_name = config.APPLICATION.products.keys()
604 l_product_info = src.product.get_products_infos(l_products_name,
608 for p_name, p_info in l_product_info:
609 # ignore the native and fixed products
610 if (src.product.product_is_native(p_info)
611 or src.product.product_is_fixed(p_info)):
613 if p_info.get_source == "archive":
614 archive_path = p_info.archive_info.archive_name
615 archive_name = os.path.basename(archive_path)
617 l_pinfo_vcs.append((p_name, p_info))
619 d_archives[p_name] = (archive_path,
620 os.path.join(ARCHIVE_DIR, archive_name))
621 return d_archives, l_pinfo_vcs
623 def add_salomeTools(config, tmp_working_dir):
624 '''Prepare a version of salomeTools that has a specific site.pyconf file
625 configured for a source package.
627 :param config Config: The global configuration.
628 :param tmp_working_dir str: The temporary local directory containing some
629 specific directories or files needed in the
631 :return: The path to the local salomeTools directory to add in the package
634 # Copy sat in the temporary working directory
635 sat_tmp_path = src.Path(os.path.join(tmp_working_dir, "salomeTools"))
636 sat_running_path = src.Path(config.VARS.salometoolsway)
637 sat_running_path.copy(sat_tmp_path)
639 # Update the site.pyconf file that contains the path to the project
640 site_pyconf_name = "site.pyconf"
641 site_pyconf_dir = os.path.join(tmp_working_dir, "salomeTools", "data")
642 site_pyconf_file = os.path.join(site_pyconf_dir, site_pyconf_name)
643 ff = open(site_pyconf_file, "w")
644 ff.write(SITE_TEMPLATE)
647 return sat_tmp_path.path
649 def get_archives_vcs(l_pinfo_vcs, sat, config, logger, tmp_working_dir):
650 '''For sources package that require that all products are get using an
651 archive, one has to create some archive for the vcs products.
652 So this method calls the clean and source command of sat and then create
655 :param l_pinfo_vcs List: The list of specific configuration corresponding to
657 :param sat Sat: The Sat instance that can be called to clean and source the
659 :param config Config: The global configuration.
660 :param logger Logger: the logging instance
661 :param tmp_working_dir str: The temporary local directory containing some
662 specific directories or files needed in the
664 :return: the dictionary that stores all the archives to add in the source
665 package. {label : (path_on_local_machine, path_in_archive)}
668 # clean the source directory of all the vcs products, then use the source
669 # command and thus construct an archive that will not contain the patches
670 l_prod_names = [pn for pn, __ in l_pinfo_vcs]
672 logger.write(_("clean sources\n"))
673 args_clean = config.VARS.application
674 args_clean += " --sources --products "
675 args_clean += ",".join(l_prod_names)
676 sat.clean(args_clean, batch=True, verbose=0, logger_add_link = logger)
678 logger.write(_("get sources"))
679 args_source = config.VARS.application
680 args_source += " --products "
681 args_source += ",".join(l_prod_names)
682 sat.source(args_source, batch=True, verbose=0, logger_add_link = logger)
684 # make the new archives
686 for pn, pinfo in l_pinfo_vcs:
687 path_archive = make_archive(pn, pinfo, tmp_working_dir)
688 d_archives_vcs[pn] = (path_archive,
689 os.path.join(ARCHIVE_DIR, pn + ".tgz"))
690 return d_archives_vcs
692 def make_archive(prod_name, prod_info, where):
693 '''Create an archive of a product by searching its source directory.
695 :param prod_name str: The name of the product.
696 :param prod_info Config: The specific configuration corresponding to the
698 :param where str: The path of the repository where to put the resulting
700 :return: The path of the resulting archive
703 path_targz_prod = os.path.join(where, prod_name + ".tgz")
704 tar_prod = tarfile.open(path_targz_prod, mode='w:gz')
705 local_path = prod_info.source_dir
706 tar_prod.add(local_path, arcname=prod_name)
708 return path_targz_prod
710 def create_project_for_src_package(config, tmp_working_dir, with_vcs):
711 '''Create a specific project for a source package.
713 :param config Config: The global configuration.
714 :param tmp_working_dir str: The temporary local directory containing some
715 specific directories or files needed in the
717 :param with_vcs boolean: True if the package is with vcs products (not
718 transformed into archive products)
719 :return: The dictionary
720 {"project" : (produced project, project path in the archive)}
724 # Create in the working temporary directory the full project tree
725 project_tmp_dir = os.path.join(tmp_working_dir, PROJECT_DIR)
726 products_pyconf_tmp_dir = os.path.join(project_tmp_dir,
728 compil_scripts_tmp_dir = os.path.join(project_tmp_dir,
731 env_scripts_tmp_dir = os.path.join(project_tmp_dir,
734 patches_tmp_dir = os.path.join(project_tmp_dir,
737 application_tmp_dir = os.path.join(project_tmp_dir,
739 for directory in [project_tmp_dir,
740 compil_scripts_tmp_dir,
743 application_tmp_dir]:
744 src.ensure_path_exists(directory)
746 # Create the pyconf that contains the information of the project
747 project_pyconf_name = "project.pyconf"
748 project_pyconf_file = os.path.join(project_tmp_dir, project_pyconf_name)
749 ff = open(project_pyconf_file, "w")
750 ff.write(PROJECT_TEMPLATE)
753 # Loop over the products to get there pyconf and all the scripts
754 # (compilation, environment, patches)
755 # and create the pyconf file to add to the project
756 lproducts_name = config.APPLICATION.products.keys()
757 l_products = src.product.get_products_infos(lproducts_name, config)
758 for p_name, p_info in l_products:
759 find_product_scripts_and_pyconf(p_name,
763 compil_scripts_tmp_dir,
766 products_pyconf_tmp_dir)
768 find_application_pyconf(config, application_tmp_dir)
770 d_project = {"project" : (project_tmp_dir, PROJECT_DIR )}
773 def find_product_scripts_and_pyconf(p_name,
777 compil_scripts_tmp_dir,
780 products_pyconf_tmp_dir):
781 '''Create a specific pyconf file for a given product. Get its environment
782 script, its compilation script and patches and put it in the temporary
783 working directory. This method is used in the source package in order to
784 construct the specific project.
786 :param p_name str: The name of the product.
787 :param p_info Config: The specific configuration corresponding to the
789 :param config Config: The global configuration.
790 :param with_vcs boolean: True if the package is with vcs products (not
791 transformed into archive products)
792 :param compil_scripts_tmp_dir str: The path to the temporary compilation
793 scripts directory of the project.
794 :param env_scripts_tmp_dir str: The path to the temporary environment script
795 directory of the project.
796 :param patches_tmp_dir str: The path to the temporary patch scripts
797 directory of the project.
798 :param products_pyconf_tmp_dir str: The path to the temporary product
799 scripts directory of the project.
802 # read the pyconf of the product
803 product_pyconf_path = src.find_file_in_lpath(p_name + ".pyconf",
804 config.PATHS.PRODUCTPATH)
805 product_pyconf_cfg = src.pyconf.Config(product_pyconf_path)
807 # find the compilation script if any
808 if src.product.product_has_script(p_info):
809 compil_script_path = src.Path(p_info.compil_script)
810 compil_script_path.copy(compil_scripts_tmp_dir)
811 product_pyconf_cfg[p_info.section].compil_script = os.path.basename(
812 p_info.compil_script)
813 # find the environment script if any
814 if src.product.product_has_env_script(p_info):
815 env_script_path = src.Path(p_info.environ.env_script)
816 env_script_path.copy(env_scripts_tmp_dir)
817 product_pyconf_cfg[p_info.section].environ.env_script = os.path.basename(
818 p_info.environ.env_script)
819 # find the patches if any
820 if src.product.product_has_patches(p_info):
821 patches = src.pyconf.Sequence()
822 for patch_path in p_info.patches:
823 p_path = src.Path(patch_path)
824 p_path.copy(patches_tmp_dir)
825 patches.append(os.path.basename(patch_path), "")
827 product_pyconf_cfg[p_info.section].patches = patches
830 # put in the pyconf file the resolved values
831 for info in ["git_info", "cvs_info", "svn_info"]:
833 for key in p_info[info]:
834 product_pyconf_cfg[p_info.section][info][key] = p_info[
837 # if the product is not archive, then make it become archive.
838 if src.product.product_is_vcs(p_info):
839 product_pyconf_cfg[p_info.section].get_source = "archive"
840 if not "archive_info" in product_pyconf_cfg[p_info.section]:
841 product_pyconf_cfg[p_info.section].addMapping("archive_info",
842 src.pyconf.Mapping(product_pyconf_cfg),
844 product_pyconf_cfg[p_info.section
845 ].archive_info.archive_name = p_info.name + ".tgz"
847 # write the pyconf file to the temporary project location
848 product_tmp_pyconf_path = os.path.join(products_pyconf_tmp_dir,
850 ff = open(product_tmp_pyconf_path, 'w')
851 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
852 product_pyconf_cfg.__save__(ff, 1)
855 def find_application_pyconf(config, application_tmp_dir):
856 '''Find the application pyconf file and put it in the specific temporary
857 directory containing the specific project of a source package.
859 :param config Config: The global configuration.
860 :param application_tmp_dir str: The path to the temporary application
861 scripts directory of the project.
863 # read the pyconf of the application
864 application_name = config.VARS.application
865 application_pyconf_path = src.find_file_in_lpath(
866 application_name + ".pyconf",
867 config.PATHS.APPLICATIONPATH)
868 application_pyconf_cfg = src.pyconf.Config(application_pyconf_path)
871 application_pyconf_cfg.APPLICATION.workdir = src.pyconf.Reference(
872 application_pyconf_cfg,
874 'VARS.salometoolsway + $VARS.sep + ".."')
876 # Prevent from compilation in base
877 application_pyconf_cfg.APPLICATION.no_base = "yes"
879 # write the pyconf file to the temporary application location
880 application_tmp_pyconf_path = os.path.join(application_tmp_dir,
881 application_name + ".pyconf")
882 ff = open(application_tmp_pyconf_path, 'w')
883 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
884 application_pyconf_cfg.__save__(ff, 1)
887 def project_package(project_file_path, tmp_working_dir):
888 '''Prepare a dictionary that stores all the needed directories and files to
889 add in a project package.
891 :param project_file_path str: The path to the local project.
892 :param tmp_working_dir str: The temporary local directory containing some
893 specific directories or files needed in the
895 :return: the dictionary that stores all the needed directories and files to
896 add in a project package.
897 {label : (path_on_local_machine, path_in_archive)}
901 # Read the project file and get the directories to add to the package
902 project_pyconf_cfg = src.pyconf.Config(project_file_path)
903 paths = {"ARCHIVEPATH" : "archives",
904 "APPLICATIONPATH" : "applications",
905 "PRODUCTPATH" : "products",
907 "MACHINEPATH" : "machines"}
908 # Loop over the project paths and add it
910 if path not in project_pyconf_cfg:
912 # Add the directory to the files to add in the package
913 d_project[path] = (project_pyconf_cfg[path], paths[path])
914 # Modify the value of the path in the package
915 project_pyconf_cfg[path] = src.pyconf.Reference(
918 'project_path + "/' + paths[path] + '"')
921 if "project_path" not in project_pyconf_cfg:
922 project_pyconf_cfg.addMapping("project_path",
923 src.pyconf.Mapping(project_pyconf_cfg),
925 project_pyconf_cfg.project_path = src.pyconf.Reference(project_pyconf_cfg,
929 # Write the project pyconf file
930 project_file_name = os.path.basename(project_file_path)
931 project_pyconf_tmp_path = os.path.join(tmp_working_dir, project_file_name)
932 ff = open(project_pyconf_tmp_path, 'w')
933 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
934 project_pyconf_cfg.__save__(ff, 1)
936 d_project["Project hat file"] = (project_pyconf_tmp_path, project_file_name)
940 def add_readme(config, options, where):
941 readme_path = os.path.join(where, "README")
942 with codecs.open(readme_path, "w", 'utf-8') as f:
944 # templates for building the header
946 # This package was generated with sat $version
949 # Distribution : $dist
951 In the following, $$ROOT represents the directory where you have installed
952 SALOME (the directory where this file is located).
955 readme_compilation_with_binaries="""
957 compilation based on the binaries used as prerequisites
958 =======================================================
960 If you fail to compile the the complete application (for example because
961 you are not root on your system and cannot install missing packages), you
962 may try a partial compilation based on the binaries.
963 For that it is necessary to copy the binaries from BINARIES to INSTALL,
964 and do some substitutions on cmake and .la files (replace the build directories
966 The procedure to do it is:
967 1) Remove or rename INSTALL directory if it exists
968 2) Execute the shell script bin_install.sh:
971 3) Use SalomeTool (as explained in Sources section) and compile only the
972 modules you need to (with -p option)
975 readme_header_tpl=string.Template(readme_header)
976 readme_template_path_bin_prof = os.path.join(config.VARS.internal_dir,
977 "README_BIN.template")
978 readme_template_path_bin_noprof = os.path.join(config.VARS.internal_dir,
979 "README_BIN_NO_PROFILE.template")
980 readme_template_path_src = os.path.join(config.VARS.internal_dir,
981 "README_SRC.template")
982 readme_template_path_pro = os.path.join(config.VARS.internal_dir,
983 "README_PROJECT.template")
984 readme_template_path_sat = os.path.join(config.VARS.internal_dir,
985 "README_SAT.template")
987 # prepare substitution dictionary
989 d['user'] = config.VARS.user
990 d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
991 d['version'] = config.INTERNAL.sat_version
992 d['dist'] = config.VARS.dist
993 f.write(readme_header_tpl.substitute(d)) # write the general header (common)
995 if options.binaries or options.sources:
996 d['application'] = config.VARS.application
997 f.write("# Application: " + d['application'])
998 if 'profile' in config.APPLICATION:
999 d['launcher'] = config.APPLICATION.profile.launcher_name
1000 d['launcher'] = config.APPLICATION.profile.launcher_name
1002 d['env_file'] = 'env_launch.sh'
1004 # write the specific sections
1005 if options.binaries:
1007 f.write(src.template.substitute(readme_template_path_bin_noprof, d))
1009 f.write(src.template.substitute(readme_template_path_bin_prof, d))
1012 f.write(src.template.substitute(readme_template_path_src, d))
1014 if options.binaries and options.sources:
1015 f.write(readme_compilation_with_binaries)
1018 f.write(src.template.substitute(readme_template_path_pro, d))
1021 f.write(src.template.substitute(readme_template_path_sat, d))
1025 def update_config(config, prop, value):
1026 '''Remove from config.APPLICATION.products the products that have the property given as input.
1028 :param config Config: The global config.
1029 :param prop str: The property to filter
1030 :param value str: The value of the property to filter
1032 src.check_config_has_application(config)
1033 l_product_to_remove = []
1034 for product_name in config.APPLICATION.products.keys():
1035 prod_cfg = src.product.get_product_config(config, product_name)
1036 if src.get_property_in_product_cfg(prod_cfg, prop) == value:
1037 l_product_to_remove.append(product_name)
1038 for product_name in l_product_to_remove:
1039 config.APPLICATION.products.__delitem__(product_name)
1042 '''method that is called when salomeTools is called with --help option.
1044 :return: The text to display for the package command description.
1047 return _("The package command creates an archive.\nThere are 4 kinds of "
1048 "archive, which can be mixed:\n 1- The binary archive. It contains all the product "
1049 "installation directories and a launcher,\n 2- The sources archive."
1050 " It contains the products archives, a project corresponding to "
1051 "the application and salomeTools,\n 3- The project archive. It "
1052 "contains a project (give the project file path as argument),\n 4-"
1053 " The salomeTools archive. It contains salomeTools.\n\nexample:"
1054 "\nsat package SALOME-master --bineries --sources")
1056 def run(args, runner, logger):
1057 '''method that is called when salomeTools is called with package parameter.
1061 (options, args) = parser.parse_args(args)
1063 # Check that a type of package is called, and only one
1064 all_option_types = (options.binaries,
1066 options.project not in ["", None],
1069 # Check if no option for package type
1070 if all_option_types.count(True) == 0:
1071 msg = _("Error: Precise a type for the package\nUse one of the "
1072 "following options: --binaries, --sources, --project or"
1074 logger.write(src.printcolors.printcError(msg), 1)
1075 logger.write("\n", 1)
1078 # The repository where to put the package if not Binary or Source
1079 package_default_path = runner.cfg.USER.workdir
1081 # if the package contains binaries or sources:
1082 if options.binaries or options.sources:
1083 # Check that the command has been called with an application
1084 src.check_config_has_application(runner.cfg)
1086 # Display information
1087 logger.write(_("Packaging application %s\n") % src.printcolors.printcLabel(
1088 runner.cfg.VARS.application), 1)
1090 # Get the default directory where to put the packages
1091 package_default_path = os.path.join(runner.cfg.APPLICATION.workdir,
1093 src.ensure_path_exists(package_default_path)
1095 # if the package contains a project:
1097 # check that the project is visible by SAT
1098 if options.project not in runner.cfg.PROJECTS.project_file_paths:
1099 site_path = os.path.join(runner.cfg.VARS.salometoolsway,
1102 msg = _("ERROR: the project %(proj)s is not visible by salomeTools."
1103 "\nPlease add it in the %(site)s file." % {
1104 "proj" : options.project, "site" : site_path})
1105 logger.write(src.printcolors.printcError(msg), 1)
1106 logger.write("\n", 1)
1109 # Remove the products that are filtered by the --without_property option
1110 if options.without_property:
1111 [prop, value] = options.without_property.split(":")
1112 update_config(runner.cfg, prop, value)
1114 # get the name of the archive or build it
1116 if os.path.basename(options.name) == options.name:
1117 # only a name (not a path)
1118 archive_name = options.name
1119 dir_name = package_default_path
1121 archive_name = os.path.basename(options.name)
1122 dir_name = os.path.dirname(options.name)
1124 # suppress extension
1125 if archive_name[-len(".tgz"):] == ".tgz":
1126 archive_name = archive_name[:-len(".tgz")]
1127 if archive_name[-len(".tar.gz"):] == ".tar.gz":
1128 archive_name = archive_name[:-len(".tar.gz")]
1132 dir_name = package_default_path
1133 if options.binaries or options.sources:
1134 archive_name = runner.cfg.APPLICATION.name
1136 if options.binaries:
1137 archive_name += "_"+runner.cfg.VARS.dist
1140 archive_name += "_SRC"
1141 if options.with_vcs:
1142 archive_name += "_VCS"
1145 project_name, __ = os.path.splitext(
1146 os.path.basename(options.project))
1147 archive_name += ("PROJECT_" + project_name)
1150 archive_name += ("salomeTools_" + runner.cfg.INTERNAL.sat_version)
1151 if len(archive_name)==0: # no option worked
1152 msg = _("Error: Cannot name the archive\n"
1153 " check if at least one of the following options was "
1154 "selected : --binaries, --sources, --project or"
1156 logger.write(src.printcolors.printcError(msg), 1)
1157 logger.write("\n", 1)
1160 path_targz = os.path.join(dir_name, archive_name + ".tgz")
1162 src.printcolors.print_value(logger, "Package path", path_targz, 2)
1164 # Create a working directory for all files that are produced during the
1165 # package creation and that will be removed at the end of the command
1166 tmp_working_dir = os.path.join(runner.cfg.VARS.tmp_root,
1167 runner.cfg.VARS.datehour)
1168 src.ensure_path_exists(tmp_working_dir)
1169 logger.write("\n", 5)
1170 logger.write(_("The temporary working directory: %s\n" % tmp_working_dir),5)
1172 logger.write("\n", 3)
1174 msg = _("Preparation of files to add to the archive")
1175 logger.write(src.printcolors.printcLabel(msg), 2)
1176 logger.write("\n", 2)
1178 d_files_to_add={} # content of the archive
1180 # a dict to hold paths that will need to be substitute for users recompilations
1181 d_paths_to_substitute={}
1183 if options.binaries:
1184 d_bin_files_to_add = binary_package(runner.cfg,
1188 # for all binaries dir, store the substitution that will be required
1189 # for extra compilations
1190 for key in d_bin_files_to_add:
1191 if key.endswith("(bin)"):
1192 source_dir = d_bin_files_to_add[key][0]
1193 path_in_archive = d_bin_files_to_add[key][1].replace("BINARIES-" + runner.cfg.VARS.dist,"INSTALL")
1194 if os.path.basename(source_dir)==os.path.basename(path_in_archive):
1195 # if basename is the same we will just substitute the dirname
1196 d_paths_to_substitute[os.path.dirname(source_dir)]=\
1197 os.path.dirname(path_in_archive)
1199 d_paths_to_substitute[source_dir]=path_in_archive
1201 d_files_to_add.update(d_bin_files_to_add)
1204 d_files_to_add.update(source_package(runner,
1209 if options.binaries:
1210 # for archives with bin and sources we provide a shell script able to
1211 # install binaries for compilation
1212 file_install_bin=produce_install_bin_file(runner.cfg,logger,
1214 d_paths_to_substitute,
1216 d_files_to_add.update({"install_bin" : (file_install_bin, "install_bin.sh")})
1217 logger.write("substitutions that need to be done later : \n", 5)
1218 logger.write(str(d_paths_to_substitute), 5)
1219 logger.write("\n", 5)
1221 # --salomeTool option is not considered when --sources is selected, as this option
1222 # already brings salomeTool!
1224 d_files_to_add.update({"salomeTools" : (runner.cfg.VARS.salometoolsway, "")})
1228 d_files_to_add.update(project_package(options.project, tmp_working_dir))
1230 if not(d_files_to_add):
1231 msg = _("Error: Empty dictionnary to build the archive!\n")
1232 logger.write(src.printcolors.printcError(msg), 1)
1233 logger.write("\n", 1)
1236 # Add the README file in the package
1237 local_readme_tmp_path = add_readme(runner.cfg,
1240 d_files_to_add["README"] = (local_readme_tmp_path, "README")
1242 # Add the additional files of option add_files
1243 if options.add_files:
1244 for file_path in options.add_files:
1245 if not os.path.exists(file_path):
1246 msg = _("WARNING: the file %s is not accessible.\n" % file_path)
1248 file_name = os.path.basename(file_path)
1249 d_files_to_add[file_name] = (file_path, file_name)
1251 logger.write("\n", 2)
1253 logger.write(src.printcolors.printcLabel(_("Actually do the package")), 2)
1254 logger.write("\n", 2)
1257 # Creating the object tarfile
1258 tar = tarfile.open(path_targz, mode='w:gz')
1260 # get the filtering function if needed
1261 filter_function = None
1262 filter_function = exclude_VCS_and_extensions
1264 # Add the files to the tarfile object
1265 res = add_files(tar, archive_name, d_files_to_add, logger, f_exclude=filter_function)
1267 except KeyboardInterrupt:
1268 logger.write(src.printcolors.printcError("\nERROR: forced interruption\n"), 1)
1269 logger.write(_("Removing the temporary working directory ... "), 1)
1270 # remove the working directory
1271 shutil.rmtree(tmp_working_dir)
1272 logger.write(_("OK"), 1)
1273 logger.write(_("\n"), 1)
1276 # remove the working directory
1277 shutil.rmtree(tmp_working_dir)
1279 # Print again the path of the package
1280 logger.write("\n", 2)
1281 src.printcolors.print_value(logger, "Package path", path_targz, 2)