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 LOCAL_TEMPLATE = ("""#!/usr/bin/env python
76 project_file_paths : [$VARS.salometoolsway + $VARS.sep + \"..\" + $VARS.sep"""
77 """ + \"""" + PROJECT_DIR + """\" + $VARS.sep + "project.pyconf"]
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.'),
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 parser.add_option('', 'without_property', 'string', 'without_property',
105 _('Optional: Filter the products by their properties.\n\tSyntax: '
106 '--without_property <property>:<value>'))
109 def add_files(tar, name_archive, d_content, logger, f_exclude=None):
110 '''Create an archive containing all directories and files that are given in
111 the d_content argument.
113 :param tar tarfile: The tarfile instance used to make the archive.
114 :param name_archive str: The name of the archive to make.
115 :param d_content dict: The dictionary that contain all directories and files
116 to add in the archive.
118 (path_on_local_machine, path_in_archive)
119 :param logger Logger: the logging instance
120 :param f_exclude Function: the function that filters
121 :return: 0 if success, 1 if not.
124 # get the max length of the messages in order to make the display
125 max_len = len(max(d_content.keys(), key=len))
128 # loop over each directory or file stored in the d_content dictionary
129 for name in d_content.keys():
130 # display information
131 len_points = max_len - len(name)
132 logger.write(name + " " + len_points * "." + " ", 3)
133 # Get the local path and the path in archive
134 # of the directory or file to add
135 local_path, archive_path = d_content[name]
136 in_archive = os.path.join(name_archive, archive_path)
137 # Add it in the archive
139 tar.add(local_path, arcname=in_archive, exclude=f_exclude)
140 logger.write(src.printcolors.printcSuccess(_("OK")), 3)
141 except Exception as e:
142 logger.write(src.printcolors.printcError(_("KO ")), 3)
143 logger.write(str(e), 3)
145 logger.write("\n", 3)
148 def exclude_VCS_and_extensions(filename):
149 ''' The function that is used to exclude from package the link to the
150 VCS repositories (like .git)
152 :param filename Str: The filname to exclude (or not).
153 :return: True if the file has to be exclude
156 for dir_name in IGNORED_DIRS:
157 if dir_name in filename:
159 for extension in IGNORED_EXTENSIONS:
160 if filename.endswith(extension):
164 def produce_relative_launcher(config,
169 with_commercial=True):
170 '''Create a specific SALOME launcher for the binary package. This launcher
173 :param config Config: The global configuration.
174 :param logger Logger: the logging instance
175 :param file_dir str: the directory where to put the launcher
176 :param file_name str: The launcher name
177 :param binaries_dir_name str: the name of the repository where the binaries
179 :return: the path of the produced launcher
183 # get KERNEL installation path
184 kernel_root_dir = os.path.join(binaries_dir_name, "KERNEL")
186 # set kernel bin dir (considering fhs property)
187 kernel_cfg = src.product.get_product_config(config, "KERNEL")
188 if src.get_property_in_product_cfg(kernel_cfg, "fhs"):
189 bin_kernel_install_dir = os.path.join(kernel_root_dir,"bin")
191 bin_kernel_install_dir = os.path.join(kernel_root_dir,"bin","salome")
193 # Get the launcher template and do substitutions
194 withProfile = src.fileEnviron.withProfile
196 withProfile = withProfile.replace(
197 "ABSOLUTE_APPLI_PATH'] = 'KERNEL_INSTALL_DIR'",
198 "ABSOLUTE_APPLI_PATH'] = out_dir_Path + '" + config.VARS.sep + kernel_root_dir + "'")
199 withProfile = withProfile.replace(
200 " 'BIN_KERNEL_INSTALL_DIR'",
201 " out_dir_Path + '" + config.VARS.sep + bin_kernel_install_dir + "'")
203 before, after = withProfile.split(
204 "# here your local standalone environment\n")
206 # create an environment file writer
207 writer = src.environment.FileEnvWriter(config,
212 filepath = os.path.join(file_dir, file_name)
213 # open the file and write into it
214 launch_file = open(filepath, "w")
215 launch_file.write(before)
217 writer.write_cfgForPy_file(launch_file,
218 for_package = binaries_dir_name,
219 with_commercial=with_commercial)
220 launch_file.write(after)
223 # Little hack to put out_dir_Path outside the strings
224 src.replace_in_file(filepath, 'r"out_dir_Path', 'out_dir_Path + r"' )
226 # A hack to put a call to a file for distene licence.
227 # It does nothing to an application that has no distene product
228 hack_for_distene_licence(filepath)
230 # change the rights in order to make the file executable for everybody
242 def hack_for_distene_licence(filepath):
243 '''Replace the distene licence env variable by a call to a file.
245 :param filepath Str: The path to the launcher to modify.
247 shutil.move(filepath, filepath + "_old")
249 filein = filepath + "_old"
250 fin = open(filein, "r")
251 fout = open(fileout, "w")
252 text = fin.readlines()
253 # Find the Distene section
255 for i,line in enumerate(text):
256 if "# Set DISTENE License" in line:
260 # No distene product, there is nothing to do
266 del text[num_line +1]
267 del text[num_line +1]
268 text_to_insert =""" import imp
270 distene = imp.load_source('distene_licence', '/data/tmpsalome/salome/prerequis/install/LICENSE/dlim8.var.py')
271 distene.set_distene_variables(context)
274 text.insert(num_line + 1, text_to_insert)
281 def produce_relative_env_files(config,
285 '''Create some specific environment files for the binary package. These
286 files use relative paths.
288 :param config Config: The global configuration.
289 :param logger Logger: the logging instance
290 :param file_dir str: the directory where to put the files
291 :param binaries_dir_name str: the name of the repository where the binaries
293 :return: the list of path of the produced environment files
296 # create an environment file writer
297 writer = src.environment.FileEnvWriter(config,
303 filepath = writer.write_env_file("env_launch.sh",
306 for_package = binaries_dir_name)
308 # Little hack to put out_dir_Path as environment variable
309 src.replace_in_file(filepath, '"out_dir_Path', '"${out_dir_Path}' )
311 # change the rights in order to make the file executable for everybody
323 def produce_install_bin_file(config,
328 '''Create a bash shell script which do substitutions in BIRARIES dir
329 in order to use it for extra compilations.
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 files
334 :param d_sub, dict: the dictionnary that contains the substitutions to be done
335 :param file_name str: the name of the install script file
336 :return: the produced file
340 filepath = os.path.join(file_dir, file_name)
341 # open the file and write into it
342 # use codec utf-8 as sat variables are in unicode
343 with codecs.open(filepath, "w", 'utf-8') as installbin_file:
344 installbin_template_path = os.path.join(config.VARS.internal_dir,
345 "INSTALL_BIN.template")
347 # build the name of the directory that will contain the binaries
348 binaries_dir_name = "BINARIES-" + config.VARS.dist
349 # build the substitution loop
350 loop_cmd = "for f in $(grep -RIl"
352 loop_cmd += " -e "+ key
353 loop_cmd += ' INSTALL); do\n sed -i "\n'
355 loop_cmd += " s?" + key + "?$(pwd)/" + d_sub[key] + "?g\n"
356 loop_cmd += ' " $f\ndone'
359 d["BINARIES_DIR"] = binaries_dir_name
360 d["SUBSTITUTION_LOOP"]=loop_cmd
362 # substitute the template and write it in file
363 content=src.template.substitute(installbin_template_path, d)
364 installbin_file.write(content)
365 # change the rights in order to make the file executable for everybody
377 def product_appli_creation_script(config,
381 '''Create a script that can produce an application (EDF style) in the binary
384 :param config Config: The global configuration.
385 :param logger Logger: the logging instance
386 :param file_dir str: the directory where to put the file
387 :param binaries_dir_name str: the name of the repository where the binaries
389 :return: the path of the produced script file
392 template_name = "create_appli.py.for_bin_packages.template"
393 template_path = os.path.join(config.VARS.internal_dir, template_name)
394 text_to_fill = open(template_path, "r").read()
395 text_to_fill = text_to_fill.replace("TO BE FILLED 1",
396 '"' + binaries_dir_name + '"')
399 for product_name in get_SALOME_modules(config):
400 product_info = src.product.get_product_config(config, product_name)
402 if src.product.product_is_smesh_plugin(product_info):
405 if 'install_dir' in product_info and bool(product_info.install_dir):
406 if src.product.product_is_cpp(product_info):
408 for cpp_name in src.product.get_product_components(product_info):
409 line_to_add = ("<module name=\"" +
411 "\" gui=\"yes\" path=\"''' + "
412 "os.path.join(dir_bin_name, \"" +
413 cpp_name + "\") + '''\"/>")
416 line_to_add = ("<module name=\"" +
418 "\" gui=\"yes\" path=\"''' + "
419 "os.path.join(dir_bin_name, \"" +
420 product_name + "\") + '''\"/>")
421 text_to_add += line_to_add + "\n"
423 filled_text = text_to_fill.replace("TO BE FILLED 2", text_to_add)
425 tmp_file_path = os.path.join(file_dir, "create_appli.py")
426 ff = open(tmp_file_path, "w")
427 ff.write(filled_text)
430 # change the rights in order to make the file executable for everybody
431 os.chmod(tmp_file_path,
442 def binary_package(config, logger, options, tmp_working_dir):
443 '''Prepare a dictionary that stores all the needed directories and files to
444 add in a binary package.
446 :param config Config: The global configuration.
447 :param logger Logger: the logging instance
448 :param options OptResult: the options of the launched command
449 :param tmp_working_dir str: The temporary local directory containing some
450 specific directories or files needed in the
452 :return: the dictionary that stores all the needed directories and files to
453 add in a binary package.
454 {label : (path_on_local_machine, path_in_archive)}
458 # Get the list of product installation to add to the archive
459 l_products_name = config.APPLICATION.products.keys()
460 l_product_info = src.product.get_products_infos(l_products_name,
465 l_sources_not_present = []
466 for prod_name, prod_info in l_product_info:
468 # Add the sources of the products that have the property
469 # sources_in_package : "yes"
470 if src.get_property_in_product_cfg(prod_info,
471 "sources_in_package") == "yes":
472 if os.path.exists(prod_info.source_dir):
473 l_source_dir.append((prod_name, prod_info.source_dir))
475 l_sources_not_present.append(prod_name)
477 # ignore the native and fixed products for install directories
478 if (src.product.product_is_native(prod_info)
479 or src.product.product_is_fixed(prod_info)
480 or not src.product.product_compiles(prod_info)):
482 if src.product.check_installation(prod_info):
483 l_install_dir.append((prod_name, prod_info.install_dir))
485 l_not_installed.append(prod_name)
487 # Add also the cpp generated modules (if any)
488 if src.product.product_is_cpp(prod_info):
490 for name_cpp in src.product.get_product_components(prod_info):
491 install_dir = os.path.join(config.APPLICATION.workdir,
493 if os.path.exists(install_dir):
494 l_install_dir.append((name_cpp, install_dir))
496 l_not_installed.append(name_cpp)
498 # Print warning or error if there are some missing products
499 if len(l_not_installed) > 0:
500 text_missing_prods = ""
501 for p_name in l_not_installed:
502 text_missing_prods += "-" + p_name + "\n"
503 if not options.force_creation:
504 msg = _("ERROR: there are missing products installations:")
505 logger.write("%s\n%s" % (src.printcolors.printcError(msg),
510 msg = _("WARNING: there are missing products installations:")
511 logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
515 # Do the same for sources
516 if len(l_sources_not_present) > 0:
517 text_missing_prods = ""
518 for p_name in l_sources_not_present:
519 text_missing_prods += "-" + p_name + "\n"
520 if not options.force_creation:
521 msg = _("ERROR: there are missing products sources:")
522 logger.write("%s\n%s" % (src.printcolors.printcError(msg),
527 msg = _("WARNING: there are missing products sources:")
528 logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
532 # construct the name of the directory that will contain the binaries
533 binaries_dir_name = "BINARIES-" + config.VARS.dist
535 # construct the correlation table between the product names, there
536 # actual install directories and there install directory in archive
538 for prod_name, install_dir in l_install_dir:
539 path_in_archive = os.path.join(binaries_dir_name, prod_name)
540 d_products[prod_name + " (bin)"] = (install_dir, path_in_archive)
542 for prod_name, source_dir in l_source_dir:
543 path_in_archive = os.path.join("SOURCES", prod_name)
544 d_products[prod_name + " (sources)"] = (source_dir, path_in_archive)
546 # for packages of SALOME applications including KERNEL,
547 # we produce a salome launcher or a virtual application (depending on salome version)
548 if 'KERNEL' in config.APPLICATION.products:
549 VersionSalome = src.get_salome_version(config)
550 # Case where SALOME has the launcher that uses the SalomeContext API
551 if VersionSalome >= 730:
552 # create the relative launcher and add it to the files to add
553 launcher_name = src.get_launcher_name(config)
554 launcher_package = produce_relative_launcher(config,
559 not(options.without_commercial))
561 d_products["launcher"] = (launcher_package, launcher_name)
563 # if we mix binaries and sources, we add a copy of the launcher,
564 # prefixed with "bin",in order to avoid clashes
565 d_products["launcher (copy)"] = (launcher_package, "bin"+launcher_name)
567 # Provide a script for the creation of an application EDF style
568 appli_script = product_appli_creation_script(config,
573 d_products["appli script"] = (appli_script, "create_appli.py")
575 # Put also the environment file
576 env_file = produce_relative_env_files(config,
581 d_products["environment file"] = (env_file, "env_launch.sh")
585 def source_package(sat, config, logger, options, tmp_working_dir):
586 '''Prepare a dictionary that stores all the needed directories and files to
587 add in a source package.
589 :param config Config: The global configuration.
590 :param logger Logger: the logging instance
591 :param options OptResult: the options of the launched command
592 :param tmp_working_dir str: The temporary local directory containing some
593 specific directories or files needed in the
595 :return: the dictionary that stores all the needed directories and files to
596 add in a source package.
597 {label : (path_on_local_machine, path_in_archive)}
601 # Get all the products that are prepared using an archive
602 logger.write("Find archive products ... ")
603 d_archives, l_pinfo_vcs = get_archives(config, logger)
604 logger.write("Done\n")
606 if not options.with_vcs and len(l_pinfo_vcs) > 0:
607 # Make archives with the products that are not prepared using an archive
608 # (git, cvs, svn, etc)
609 logger.write("Construct archives for vcs products ... ")
610 d_archives_vcs = get_archives_vcs(l_pinfo_vcs,
615 logger.write("Done\n")
618 logger.write("Create the project ... ")
619 d_project = create_project_for_src_package(config,
622 logger.write("Done\n")
625 tmp_sat = add_salomeTools(config, tmp_working_dir)
626 d_sat = {"salomeTools" : (tmp_sat, "salomeTools")}
628 # Add a sat symbolic link if not win
629 if not src.architecture.is_windows():
630 tmp_satlink_path = os.path.join(tmp_working_dir, 'sat')
634 # In the jobs, os.getcwd() can fail
635 t = config.LOCAL.workdir
636 os.chdir(tmp_working_dir)
637 if os.path.lexists(tmp_satlink_path):
638 os.remove(tmp_satlink_path)
639 os.symlink(os.path.join('salomeTools', 'sat'), 'sat')
642 d_sat["sat link"] = (tmp_satlink_path, "sat")
644 d_source = src.merge_dicts(d_archives, d_archives_vcs, d_project, d_sat)
647 def get_archives(config, logger):
648 '''Find all the products that are get using an archive and all the products
649 that are get using a vcs (git, cvs, svn) repository.
651 :param config Config: The global configuration.
652 :param logger Logger: the logging instance
653 :return: the dictionary {name_product :
654 (local path of its archive, path in the package of its archive )}
655 and the list of specific configuration corresponding to the vcs
659 # Get the list of product informations
660 l_products_name = config.APPLICATION.products.keys()
661 l_product_info = src.product.get_products_infos(l_products_name,
665 for p_name, p_info in l_product_info:
666 # ignore the native and fixed products
667 if (src.product.product_is_native(p_info)
668 or src.product.product_is_fixed(p_info)):
670 if p_info.get_source == "archive":
671 archive_path = p_info.archive_info.archive_name
672 archive_name = os.path.basename(archive_path)
674 l_pinfo_vcs.append((p_name, p_info))
676 d_archives[p_name] = (archive_path,
677 os.path.join(ARCHIVE_DIR, archive_name))
678 return d_archives, l_pinfo_vcs
680 def add_salomeTools(config, tmp_working_dir):
681 '''Prepare a version of salomeTools that has a specific local.pyconf file
682 configured for a source package.
684 :param config Config: The global configuration.
685 :param tmp_working_dir str: The temporary local directory containing some
686 specific directories or files needed in the
688 :return: The path to the local salomeTools directory to add in the package
691 # Copy sat in the temporary working directory
692 sat_tmp_path = src.Path(os.path.join(tmp_working_dir, "salomeTools"))
693 sat_running_path = src.Path(config.VARS.salometoolsway)
694 sat_running_path.copy(sat_tmp_path)
696 # Update the local.pyconf file that contains the path to the project
697 local_pyconf_name = "local.pyconf"
698 local_pyconf_dir = os.path.join(tmp_working_dir, "salomeTools", "data")
699 local_pyconf_file = os.path.join(local_pyconf_dir, local_pyconf_name)
700 # Remove the .pyconf file in the root directory of salomeTools if there is
701 # any. (For example when launching jobs, a pyconf file describing the jobs
702 # can be here and is not useful)
703 files_or_dir_SAT = os.listdir(os.path.join(tmp_working_dir, "salomeTools"))
704 for file_or_dir in files_or_dir_SAT:
705 if file_or_dir.endswith(".pyconf") or file_or_dir.endswith(".txt"):
706 file_path = os.path.join(tmp_working_dir,
711 ff = open(local_pyconf_file, "w")
712 ff.write(LOCAL_TEMPLATE)
715 return sat_tmp_path.path
717 def get_archives_vcs(l_pinfo_vcs, sat, config, logger, tmp_working_dir):
718 '''For sources package that require that all products are get using an
719 archive, one has to create some archive for the vcs products.
720 So this method calls the clean and source command of sat and then create
723 :param l_pinfo_vcs List: The list of specific configuration corresponding to
725 :param sat Sat: The Sat instance that can be called to clean and source the
727 :param config Config: The global configuration.
728 :param logger Logger: the logging instance
729 :param tmp_working_dir str: The temporary local directory containing some
730 specific directories or files needed in the
732 :return: the dictionary that stores all the archives to add in the source
733 package. {label : (path_on_local_machine, path_in_archive)}
736 # clean the source directory of all the vcs products, then use the source
737 # command and thus construct an archive that will not contain the patches
738 l_prod_names = [pn for pn, __ in l_pinfo_vcs]
740 logger.write(_("clean sources\n"))
741 args_clean = config.VARS.application
742 args_clean += " --sources --products "
743 args_clean += ",".join(l_prod_names)
744 sat.clean(args_clean, batch=True, verbose=0, logger_add_link = logger)
746 logger.write(_("get sources"))
747 args_source = config.VARS.application
748 args_source += " --products "
749 args_source += ",".join(l_prod_names)
750 sat.source(args_source, batch=True, verbose=0, logger_add_link = logger)
752 # make the new archives
754 for pn, pinfo in l_pinfo_vcs:
755 path_archive = make_archive(pn, pinfo, tmp_working_dir)
756 d_archives_vcs[pn] = (path_archive,
757 os.path.join(ARCHIVE_DIR, pn + ".tgz"))
758 return d_archives_vcs
760 def make_archive(prod_name, prod_info, where):
761 '''Create an archive of a product by searching its source directory.
763 :param prod_name str: The name of the product.
764 :param prod_info Config: The specific configuration corresponding to the
766 :param where str: The path of the repository where to put the resulting
768 :return: The path of the resulting archive
771 path_targz_prod = os.path.join(where, prod_name + ".tgz")
772 tar_prod = tarfile.open(path_targz_prod, mode='w:gz')
773 local_path = prod_info.source_dir
774 tar_prod.add(local_path,
776 exclude=exclude_VCS_and_extensions)
778 return path_targz_prod
780 def create_project_for_src_package(config, tmp_working_dir, with_vcs):
781 '''Create a specific project for a source package.
783 :param config Config: The global configuration.
784 :param tmp_working_dir str: The temporary local directory containing some
785 specific directories or files needed in the
787 :param with_vcs boolean: True if the package is with vcs products (not
788 transformed into archive products)
789 :return: The dictionary
790 {"project" : (produced project, project path in the archive)}
794 # Create in the working temporary directory the full project tree
795 project_tmp_dir = os.path.join(tmp_working_dir, PROJECT_DIR)
796 products_pyconf_tmp_dir = os.path.join(project_tmp_dir,
798 compil_scripts_tmp_dir = os.path.join(project_tmp_dir,
801 env_scripts_tmp_dir = os.path.join(project_tmp_dir,
804 patches_tmp_dir = os.path.join(project_tmp_dir,
807 application_tmp_dir = os.path.join(project_tmp_dir,
809 for directory in [project_tmp_dir,
810 compil_scripts_tmp_dir,
813 application_tmp_dir]:
814 src.ensure_path_exists(directory)
816 # Create the pyconf that contains the information of the project
817 project_pyconf_name = "project.pyconf"
818 project_pyconf_file = os.path.join(project_tmp_dir, project_pyconf_name)
819 ff = open(project_pyconf_file, "w")
820 ff.write(PROJECT_TEMPLATE)
823 # Loop over the products to get there pyconf and all the scripts
824 # (compilation, environment, patches)
825 # and create the pyconf file to add to the project
826 lproducts_name = config.APPLICATION.products.keys()
827 l_products = src.product.get_products_infos(lproducts_name, config)
828 for p_name, p_info in l_products:
829 find_product_scripts_and_pyconf(p_name,
833 compil_scripts_tmp_dir,
836 products_pyconf_tmp_dir)
838 find_application_pyconf(config, application_tmp_dir)
840 d_project = {"project" : (project_tmp_dir, PROJECT_DIR )}
843 def find_product_scripts_and_pyconf(p_name,
847 compil_scripts_tmp_dir,
850 products_pyconf_tmp_dir):
851 '''Create a specific pyconf file for a given product. Get its environment
852 script, its compilation script and patches and put it in the temporary
853 working directory. This method is used in the source package in order to
854 construct the specific project.
856 :param p_name str: The name of the product.
857 :param p_info Config: The specific configuration corresponding to the
859 :param config Config: The global configuration.
860 :param with_vcs boolean: True if the package is with vcs products (not
861 transformed into archive products)
862 :param compil_scripts_tmp_dir str: The path to the temporary compilation
863 scripts directory of the project.
864 :param env_scripts_tmp_dir str: The path to the temporary environment script
865 directory of the project.
866 :param patches_tmp_dir str: The path to the temporary patch scripts
867 directory of the project.
868 :param products_pyconf_tmp_dir str: The path to the temporary product
869 scripts directory of the project.
872 # read the pyconf of the product
873 product_pyconf_path = src.find_file_in_lpath(p_name + ".pyconf",
874 config.PATHS.PRODUCTPATH)
875 product_pyconf_cfg = src.pyconf.Config(product_pyconf_path)
877 # find the compilation script if any
878 if src.product.product_has_script(p_info):
879 compil_script_path = src.Path(p_info.compil_script)
880 compil_script_path.copy(compil_scripts_tmp_dir)
881 product_pyconf_cfg[p_info.section].compil_script = os.path.basename(
882 p_info.compil_script)
883 # find the environment script if any
884 if src.product.product_has_env_script(p_info):
885 env_script_path = src.Path(p_info.environ.env_script)
886 env_script_path.copy(env_scripts_tmp_dir)
887 product_pyconf_cfg[p_info.section].environ.env_script = os.path.basename(
888 p_info.environ.env_script)
889 # find the patches if any
890 if src.product.product_has_patches(p_info):
891 patches = src.pyconf.Sequence()
892 for patch_path in p_info.patches:
893 p_path = src.Path(patch_path)
894 p_path.copy(patches_tmp_dir)
895 patches.append(os.path.basename(patch_path), "")
897 product_pyconf_cfg[p_info.section].patches = patches
900 # put in the pyconf file the resolved values
901 for info in ["git_info", "cvs_info", "svn_info"]:
903 for key in p_info[info]:
904 product_pyconf_cfg[p_info.section][info][key] = p_info[
907 # if the product is not archive, then make it become archive.
908 if src.product.product_is_vcs(p_info):
909 product_pyconf_cfg[p_info.section].get_source = "archive"
910 if not "archive_info" in product_pyconf_cfg[p_info.section]:
911 product_pyconf_cfg[p_info.section].addMapping("archive_info",
912 src.pyconf.Mapping(product_pyconf_cfg),
914 product_pyconf_cfg[p_info.section
915 ].archive_info.archive_name = p_info.name + ".tgz"
917 # write the pyconf file to the temporary project location
918 product_tmp_pyconf_path = os.path.join(products_pyconf_tmp_dir,
920 ff = open(product_tmp_pyconf_path, 'w')
921 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
922 product_pyconf_cfg.__save__(ff, 1)
925 def find_application_pyconf(config, application_tmp_dir):
926 '''Find the application pyconf file and put it in the specific temporary
927 directory containing the specific project of a source package.
929 :param config Config: The global configuration.
930 :param application_tmp_dir str: The path to the temporary application
931 scripts directory of the project.
933 # read the pyconf of the application
934 application_name = config.VARS.application
935 application_pyconf_path = src.find_file_in_lpath(
936 application_name + ".pyconf",
937 config.PATHS.APPLICATIONPATH)
938 application_pyconf_cfg = src.pyconf.Config(application_pyconf_path)
941 application_pyconf_cfg.APPLICATION.workdir = src.pyconf.Reference(
942 application_pyconf_cfg,
944 'VARS.salometoolsway + $VARS.sep + ".."')
946 # Prevent from compilation in base
947 application_pyconf_cfg.APPLICATION.no_base = "yes"
949 # write the pyconf file to the temporary application location
950 application_tmp_pyconf_path = os.path.join(application_tmp_dir,
951 application_name + ".pyconf")
952 ff = open(application_tmp_pyconf_path, 'w')
953 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
954 application_pyconf_cfg.__save__(ff, 1)
957 def project_package(project_file_path, tmp_working_dir):
958 '''Prepare a dictionary that stores all the needed directories and files to
959 add in a project package.
961 :param project_file_path str: The path to the local project.
962 :param tmp_working_dir str: The temporary local directory containing some
963 specific directories or files needed in the
965 :return: the dictionary that stores all the needed directories and files to
966 add in a project package.
967 {label : (path_on_local_machine, path_in_archive)}
971 # Read the project file and get the directories to add to the package
972 project_pyconf_cfg = src.pyconf.Config(project_file_path)
973 paths = {"ARCHIVEPATH" : "archives",
974 "APPLICATIONPATH" : "applications",
975 "PRODUCTPATH" : "products",
977 "MACHINEPATH" : "machines"}
978 # Loop over the project paths and add it
980 if path not in project_pyconf_cfg:
982 # Add the directory to the files to add in the package
983 d_project[path] = (project_pyconf_cfg[path], paths[path])
984 # Modify the value of the path in the package
985 project_pyconf_cfg[path] = src.pyconf.Reference(
988 'project_path + "/' + paths[path] + '"')
991 if "project_path" not in project_pyconf_cfg:
992 project_pyconf_cfg.addMapping("project_path",
993 src.pyconf.Mapping(project_pyconf_cfg),
995 project_pyconf_cfg.project_path = src.pyconf.Reference(project_pyconf_cfg,
999 # Write the project pyconf file
1000 project_file_name = os.path.basename(project_file_path)
1001 project_pyconf_tmp_path = os.path.join(tmp_working_dir, project_file_name)
1002 ff = open(project_pyconf_tmp_path, 'w')
1003 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
1004 project_pyconf_cfg.__save__(ff, 1)
1006 d_project["Project hat file"] = (project_pyconf_tmp_path, project_file_name)
1010 def add_readme(config, options, where):
1011 readme_path = os.path.join(where, "README")
1012 with codecs.open(readme_path, "w", 'utf-8') as f:
1014 # templates for building the header
1016 # This package was generated with sat $version
1019 # Distribution : $dist
1021 In the following, $$ROOT represents the directory where you have installed
1022 SALOME (the directory where this file is located).
1025 readme_compilation_with_binaries="""
1027 compilation based on the binaries used as prerequisites
1028 =======================================================
1030 If you fail to compile the complete application (for example because
1031 you are not root on your system and cannot install missing packages), you
1032 may try a partial compilation based on the binaries.
1033 For that it is necessary to copy the binaries from BINARIES to INSTALL,
1034 and do some substitutions on cmake and .la files (replace the build directories
1036 The procedure to do it is:
1037 1) Remove or rename INSTALL directory if it exists
1038 2) Execute the shell script install_bin.sh:
1041 3) Use SalomeTool (as explained in Sources section) and compile only the
1042 modules you need to (with -p option)
1045 readme_header_tpl=string.Template(readme_header)
1046 readme_template_path_bin = os.path.join(config.VARS.internal_dir,
1047 "README_BIN.template")
1048 readme_template_path_bin_launcher = os.path.join(config.VARS.internal_dir,
1049 "README_LAUNCHER.template")
1050 readme_template_path_bin_virtapp = os.path.join(config.VARS.internal_dir,
1051 "README_BIN_VIRTUAL_APP.template")
1052 readme_template_path_src = os.path.join(config.VARS.internal_dir,
1053 "README_SRC.template")
1054 readme_template_path_pro = os.path.join(config.VARS.internal_dir,
1055 "README_PROJECT.template")
1056 readme_template_path_sat = os.path.join(config.VARS.internal_dir,
1057 "README_SAT.template")
1059 # prepare substitution dictionary
1061 d['user'] = config.VARS.user
1062 d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
1063 d['version'] = config.INTERNAL.sat_version
1064 d['dist'] = config.VARS.dist
1065 f.write(readme_header_tpl.substitute(d)) # write the general header (common)
1067 if options.binaries or options.sources:
1068 d['application'] = config.VARS.application
1069 f.write("# Application: " + d['application'] + "\n")
1070 if 'KERNEL' in config.APPLICATION.products:
1071 VersionSalome = src.get_salome_version(config)
1072 # Case where SALOME has the launcher that uses the SalomeContext API
1073 if VersionSalome >= 730:
1074 d['launcher'] = config.APPLICATION.profile.launcher_name
1076 d['virtual_app'] = 'runAppli' # this info is not used now)
1078 # write the specific sections
1079 if options.binaries:
1080 f.write(src.template.substitute(readme_template_path_bin, d))
1081 if "virtual_app" in d:
1082 f.write(src.template.substitute(readme_template_path_bin_virtapp, d))
1084 f.write(src.template.substitute(readme_template_path_bin_launcher, d))
1087 f.write(src.template.substitute(readme_template_path_src, d))
1089 if options.binaries and options.sources:
1090 f.write(readme_compilation_with_binaries)
1093 f.write(src.template.substitute(readme_template_path_pro, d))
1096 f.write(src.template.substitute(readme_template_path_sat, d))
1100 def update_config(config, prop, value):
1101 '''Remove from config.APPLICATION.products the products that have the property given as input.
1103 :param config Config: The global config.
1104 :param prop str: The property to filter
1105 :param value str: The value of the property to filter
1107 src.check_config_has_application(config)
1108 l_product_to_remove = []
1109 for product_name in config.APPLICATION.products.keys():
1110 prod_cfg = src.product.get_product_config(config, product_name)
1111 if src.get_property_in_product_cfg(prod_cfg, prop) == value:
1112 l_product_to_remove.append(product_name)
1113 for product_name in l_product_to_remove:
1114 config.APPLICATION.products.__delitem__(product_name)
1117 '''method that is called when salomeTools is called with --help option.
1119 :return: The text to display for the package command description.
1122 return _("The package command creates an archive.\nThere are 4 kinds of "
1123 "archive, which can be mixed:\n 1- The binary archive. It contains all the product "
1124 "installation directories and a launcher,\n 2- The sources archive."
1125 " It contains the products archives, a project corresponding to "
1126 "the application and salomeTools,\n 3- The project archive. It "
1127 "contains a project (give the project file path as argument),\n 4-"
1128 " The salomeTools archive. It contains salomeTools.\n\nexample:"
1129 "\nsat package SALOME-master --bineries --sources")
1131 def run(args, runner, logger):
1132 '''method that is called when salomeTools is called with package parameter.
1136 (options, args) = parser.parse_args(args)
1138 # Check that a type of package is called, and only one
1139 all_option_types = (options.binaries,
1141 options.project not in ["", None],
1144 # Check if no option for package type
1145 if all_option_types.count(True) == 0:
1146 msg = _("Error: Precise a type for the package\nUse one of the "
1147 "following options: --binaries, --sources, --project or"
1149 logger.write(src.printcolors.printcError(msg), 1)
1150 logger.write("\n", 1)
1153 # The repository where to put the package if not Binary or Source
1154 package_default_path = runner.cfg.LOCAL.workdir
1156 # if the package contains binaries or sources:
1157 if options.binaries or options.sources:
1158 # Check that the command has been called with an application
1159 src.check_config_has_application(runner.cfg)
1161 # Display information
1162 logger.write(_("Packaging application %s\n") % src.printcolors.printcLabel(
1163 runner.cfg.VARS.application), 1)
1165 # Get the default directory where to put the packages
1166 package_default_path = os.path.join(runner.cfg.APPLICATION.workdir,
1168 src.ensure_path_exists(package_default_path)
1170 # if the package contains a project:
1172 # check that the project is visible by SAT
1173 if options.project not in runner.cfg.PROJECTS.project_file_paths:
1174 local_path = os.path.join(runner.cfg.VARS.salometoolsway,
1177 msg = _("ERROR: the project %(proj)s is not visible by salomeTools."
1178 "\nPlease add it in the %(local)s file." % {
1179 "proj" : options.project, "local" : local_path})
1180 logger.write(src.printcolors.printcError(msg), 1)
1181 logger.write("\n", 1)
1184 # Remove the products that are filtered by the --without_property option
1185 if options.without_property:
1186 [prop, value] = options.without_property.split(":")
1187 update_config(runner.cfg, prop, value)
1189 # get the name of the archive or build it
1191 if os.path.basename(options.name) == options.name:
1192 # only a name (not a path)
1193 archive_name = options.name
1194 dir_name = package_default_path
1196 archive_name = os.path.basename(options.name)
1197 dir_name = os.path.dirname(options.name)
1199 # suppress extension
1200 if archive_name[-len(".tgz"):] == ".tgz":
1201 archive_name = archive_name[:-len(".tgz")]
1202 if archive_name[-len(".tar.gz"):] == ".tar.gz":
1203 archive_name = archive_name[:-len(".tar.gz")]
1207 dir_name = package_default_path
1208 if options.binaries or options.sources:
1209 archive_name = runner.cfg.APPLICATION.name
1211 if options.binaries:
1212 archive_name += "-"+runner.cfg.VARS.dist
1215 archive_name += "-SRC"
1216 if options.with_vcs:
1217 archive_name += "-VCS"
1220 project_name, __ = os.path.splitext(
1221 os.path.basename(options.project))
1222 archive_name += ("PROJECT-" + project_name)
1225 archive_name += ("salomeTools_" + runner.cfg.INTERNAL.sat_version)
1226 if len(archive_name)==0: # no option worked
1227 msg = _("Error: Cannot name the archive\n"
1228 " check if at least one of the following options was "
1229 "selected : --binaries, --sources, --project or"
1231 logger.write(src.printcolors.printcError(msg), 1)
1232 logger.write("\n", 1)
1235 path_targz = os.path.join(dir_name, archive_name + ".tgz")
1237 src.printcolors.print_value(logger, "Package path", path_targz, 2)
1239 # Create a working directory for all files that are produced during the
1240 # package creation and that will be removed at the end of the command
1241 tmp_working_dir = os.path.join(runner.cfg.VARS.tmp_root,
1242 runner.cfg.VARS.datehour)
1243 src.ensure_path_exists(tmp_working_dir)
1244 logger.write("\n", 5)
1245 logger.write(_("The temporary working directory: %s\n" % tmp_working_dir),5)
1247 logger.write("\n", 3)
1249 msg = _("Preparation of files to add to the archive")
1250 logger.write(src.printcolors.printcLabel(msg), 2)
1251 logger.write("\n", 2)
1253 d_files_to_add={} # content of the archive
1255 # a dict to hold paths that will need to be substitute for users recompilations
1256 d_paths_to_substitute={}
1258 if options.binaries:
1259 d_bin_files_to_add = binary_package(runner.cfg,
1263 # for all binaries dir, store the substitution that will be required
1264 # for extra compilations
1265 for key in d_bin_files_to_add:
1266 if key.endswith("(bin)"):
1267 source_dir = d_bin_files_to_add[key][0]
1268 path_in_archive = d_bin_files_to_add[key][1].replace("BINARIES-" + runner.cfg.VARS.dist,"INSTALL")
1269 if os.path.basename(source_dir)==os.path.basename(path_in_archive):
1270 # if basename is the same we will just substitute the dirname
1271 d_paths_to_substitute[os.path.dirname(source_dir)]=\
1272 os.path.dirname(path_in_archive)
1274 d_paths_to_substitute[source_dir]=path_in_archive
1276 d_files_to_add.update(d_bin_files_to_add)
1279 d_files_to_add.update(source_package(runner,
1284 if options.binaries:
1285 # for archives with bin and sources we provide a shell script able to
1286 # install binaries for compilation
1287 file_install_bin=produce_install_bin_file(runner.cfg,logger,
1289 d_paths_to_substitute,
1291 d_files_to_add.update({"install_bin" : (file_install_bin, "install_bin.sh")})
1292 logger.write("substitutions that need to be done later : \n", 5)
1293 logger.write(str(d_paths_to_substitute), 5)
1294 logger.write("\n", 5)
1296 # --salomeTool option is not considered when --sources is selected, as this option
1297 # already brings salomeTool!
1299 d_files_to_add.update({"salomeTools" : (runner.cfg.VARS.salometoolsway, "")})
1303 d_files_to_add.update(project_package(options.project, tmp_working_dir))
1305 if not(d_files_to_add):
1306 msg = _("Error: Empty dictionnary to build the archive!\n")
1307 logger.write(src.printcolors.printcError(msg), 1)
1308 logger.write("\n", 1)
1311 # Add the README file in the package
1312 local_readme_tmp_path = add_readme(runner.cfg,
1315 d_files_to_add["README"] = (local_readme_tmp_path, "README")
1317 # Add the additional files of option add_files
1318 if options.add_files:
1319 for file_path in options.add_files:
1320 if not os.path.exists(file_path):
1321 msg = _("WARNING: the file %s is not accessible.\n" % file_path)
1323 file_name = os.path.basename(file_path)
1324 d_files_to_add[file_name] = (file_path, file_name)
1326 logger.write("\n", 2)
1328 logger.write(src.printcolors.printcLabel(_("Actually do the package")), 2)
1329 logger.write("\n", 2)
1332 # Creating the object tarfile
1333 tar = tarfile.open(path_targz, mode='w:gz')
1335 # get the filtering function if needed
1336 filter_function = exclude_VCS_and_extensions
1338 # Add the files to the tarfile object
1339 res = add_files(tar, archive_name, d_files_to_add, logger, f_exclude=filter_function)
1341 except KeyboardInterrupt:
1342 logger.write(src.printcolors.printcError("\nERROR: forced interruption\n"), 1)
1343 logger.write(_("Removing the temporary working directory ... "), 1)
1344 # remove the working directory
1345 shutil.rmtree(tmp_working_dir)
1346 logger.write(_("OK"), 1)
1347 logger.write(_("\n"), 1)
1350 # remove the working directory
1351 shutil.rmtree(tmp_working_dir)
1353 # Print again the path of the package
1354 logger.write("\n", 2)
1355 src.printcolors.print_value(logger, "Package path", path_targz, 2)