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 the launcher template
184 profile_install_dir = os.path.join(binaries_dir_name,
185 config.APPLICATION.profile.product)
186 withProfile = src.fileEnviron.withProfile
187 withProfile = withProfile.replace(
188 "ABSOLUTE_APPLI_PATH'] = 'PROFILE_INSTALL_DIR'",
189 "ABSOLUTE_APPLI_PATH'] = out_dir_Path + '" + config.VARS.sep + profile_install_dir + "'")
190 withProfile = withProfile.replace(
191 "os.path.join( 'PROFILE_INSTALL_DIR'",
192 "os.path.join( out_dir_Path, '" + profile_install_dir + "'")
194 before, after = withProfile.split(
195 "# here your local standalone environment\n")
197 # create an environment file writer
198 writer = src.environment.FileEnvWriter(config,
203 filepath = os.path.join(file_dir, file_name)
204 # open the file and write into it
205 launch_file = open(filepath, "w")
206 launch_file.write(before)
208 writer.write_cfgForPy_file(launch_file,
209 for_package = binaries_dir_name,
210 with_commercial=with_commercial)
211 launch_file.write(after)
214 # Little hack to put out_dir_Path outside the strings
215 src.replace_in_file(filepath, 'r"out_dir_Path', 'out_dir_Path + r"' )
217 # A hack to put a call to a file for distene licence.
218 # It does nothing to an application that has no distene product
219 hack_for_distene_licence(filepath)
221 # change the rights in order to make the file executable for everybody
233 def hack_for_distene_licence(filepath):
234 '''Replace the distene licence env variable by a call to a file.
236 :param filepath Str: The path to the launcher to modify.
238 shutil.move(filepath, filepath + "_old")
240 filein = filepath + "_old"
241 fin = open(filein, "r")
242 fout = open(fileout, "w")
243 text = fin.readlines()
244 # Find the Distene section
246 for i,line in enumerate(text):
247 if "# Set DISTENE License" in line:
251 # No distene product, there is nothing to do
257 del text[num_line +1]
258 del text[num_line +1]
259 text_to_insert =""" import imp
261 distene = imp.load_source('distene_licence', '/data/tmpsalome/salome/prerequis/install/LICENSE/dlim8.var.py')
262 distene.set_distene_variables(context)
265 text.insert(num_line + 1, text_to_insert)
272 def produce_relative_env_files(config,
276 '''Create some specific environment files for the binary package. These
277 files use relative paths.
279 :param config Config: The global configuration.
280 :param logger Logger: the logging instance
281 :param file_dir str: the directory where to put the files
282 :param binaries_dir_name str: the name of the repository where the binaries
284 :return: the list of path of the produced environment files
287 # create an environment file writer
288 writer = src.environment.FileEnvWriter(config,
294 filepath = writer.write_env_file("env_launch.sh",
297 for_package = binaries_dir_name)
299 # Little hack to put out_dir_Path as environment variable
300 src.replace_in_file(filepath, '"out_dir_Path', '"${out_dir_Path}' )
302 # change the rights in order to make the file executable for everybody
314 def produce_install_bin_file(config,
319 '''Create a bash shell script which do substitutions in BIRARIES dir
320 in order to use it for extra compilations.
322 :param config Config: The global configuration.
323 :param logger Logger: the logging instance
324 :param file_dir str: the directory where to put the files
325 :param d_sub, dict: the dictionnary that contains the substitutions to be done
326 :param file_name str: the name of the install script file
327 :return: the produced file
331 filepath = os.path.join(file_dir, file_name)
332 # open the file and write into it
333 # use codec utf-8 as sat variables are in unicode
334 with codecs.open(filepath, "w", 'utf-8') as installbin_file:
335 installbin_template_path = os.path.join(config.VARS.internal_dir,
336 "INSTALL_BIN.template")
338 # build the name of the directory that will contain the binaries
339 binaries_dir_name = "BINARIES-" + config.VARS.dist
340 # build the substitution loop
341 loop_cmd = "for f in $(grep -RIl"
343 loop_cmd += " -e "+ key
344 loop_cmd += ' INSTALL); do\n sed -i "\n'
346 loop_cmd += " s?" + key + "?$(pwd)/" + d_sub[key] + "?g\n"
347 loop_cmd += ' " $f\ndone'
350 d["BINARIES_DIR"] = binaries_dir_name
351 d["SUBSTITUTION_LOOP"]=loop_cmd
353 # substitute the template and write it in file
354 content=src.template.substitute(installbin_template_path, d)
355 installbin_file.write(content)
356 # change the rights in order to make the file executable for everybody
368 def product_appli_creation_script(config,
372 '''Create a script that can produce an application (EDF style) in the binary
375 :param config Config: The global configuration.
376 :param logger Logger: the logging instance
377 :param file_dir str: the directory where to put the file
378 :param binaries_dir_name str: the name of the repository where the binaries
380 :return: the path of the produced script file
383 template_name = "create_appli.py.for_bin_packages.template"
384 template_path = os.path.join(config.VARS.internal_dir, template_name)
385 text_to_fill = open(template_path, "r").read()
386 text_to_fill = text_to_fill.replace("TO BE FILLED 1",
387 '"' + binaries_dir_name + '"')
390 for product_name in get_SALOME_modules(config):
391 product_info = src.product.get_product_config(config, product_name)
393 if src.product.product_is_smesh_plugin(product_info):
396 if 'install_dir' in product_info and bool(product_info.install_dir):
397 if src.product.product_is_cpp(product_info):
399 for cpp_name in src.product.get_product_components(product_info):
400 line_to_add = ("<module name=\"" +
402 "\" gui=\"yes\" path=\"''' + "
403 "os.path.join(dir_bin_name, \"" +
404 cpp_name + "\") + '''\"/>")
407 line_to_add = ("<module name=\"" +
409 "\" gui=\"yes\" path=\"''' + "
410 "os.path.join(dir_bin_name, \"" +
411 product_name + "\") + '''\"/>")
412 text_to_add += line_to_add + "\n"
414 filled_text = text_to_fill.replace("TO BE FILLED 2", text_to_add)
416 tmp_file_path = os.path.join(file_dir, "create_appli.py")
417 ff = open(tmp_file_path, "w")
418 ff.write(filled_text)
421 # change the rights in order to make the file executable for everybody
422 os.chmod(tmp_file_path,
433 def binary_package(config, logger, options, tmp_working_dir):
434 '''Prepare a dictionary that stores all the needed directories and files to
435 add in a binary package.
437 :param config Config: The global configuration.
438 :param logger Logger: the logging instance
439 :param options OptResult: the options of the launched command
440 :param tmp_working_dir str: The temporary local directory containing some
441 specific directories or files needed in the
443 :return: the dictionary that stores all the needed directories and files to
444 add in a binary package.
445 {label : (path_on_local_machine, path_in_archive)}
449 # Get the list of product installation to add to the archive
450 l_products_name = config.APPLICATION.products.keys()
451 l_product_info = src.product.get_products_infos(l_products_name,
456 l_sources_not_present = []
457 for prod_name, prod_info in l_product_info:
459 # Add the sources of the products that have the property
460 # sources_in_package : "yes"
461 if src.get_property_in_product_cfg(prod_info,
462 "sources_in_package") == "yes":
463 if os.path.exists(prod_info.source_dir):
464 l_source_dir.append((prod_name, prod_info.source_dir))
466 l_sources_not_present.append(prod_name)
468 # ignore the native and fixed products for install directories
469 if (src.product.product_is_native(prod_info)
470 or src.product.product_is_fixed(prod_info)
471 or not src.product.product_compiles(prod_info)):
473 if src.product.check_installation(prod_info):
474 l_install_dir.append((prod_name, prod_info.install_dir))
476 l_not_installed.append(prod_name)
478 # Add also the cpp generated modules (if any)
479 if src.product.product_is_cpp(prod_info):
481 for name_cpp in src.product.get_product_components(prod_info):
482 install_dir = os.path.join(config.APPLICATION.workdir,
484 if os.path.exists(install_dir):
485 l_install_dir.append((name_cpp, install_dir))
487 l_not_installed.append(name_cpp)
489 # Print warning or error if there are some missing products
490 if len(l_not_installed) > 0:
491 text_missing_prods = ""
492 for p_name in l_not_installed:
493 text_missing_prods += "-" + p_name + "\n"
494 if not options.force_creation:
495 msg = _("ERROR: there are missing products installations:")
496 logger.write("%s\n%s" % (src.printcolors.printcError(msg),
501 msg = _("WARNING: there are missing products installations:")
502 logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
506 # Do the same for sources
507 if len(l_sources_not_present) > 0:
508 text_missing_prods = ""
509 for p_name in l_sources_not_present:
510 text_missing_prods += "-" + p_name + "\n"
511 if not options.force_creation:
512 msg = _("ERROR: there are missing products sources:")
513 logger.write("%s\n%s" % (src.printcolors.printcError(msg),
518 msg = _("WARNING: there are missing products sources:")
519 logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
523 # construct the name of the directory that will contain the binaries
524 binaries_dir_name = "BINARIES-" + config.VARS.dist
526 # construct the correlation table between the product names, there
527 # actual install directories and there install directory in archive
529 for prod_name, install_dir in l_install_dir:
530 path_in_archive = os.path.join(binaries_dir_name, prod_name)
531 d_products[prod_name + " (bin)"] = (install_dir, path_in_archive)
533 for prod_name, source_dir in l_source_dir:
534 path_in_archive = os.path.join("SOURCES", prod_name)
535 d_products[prod_name + " (sources)"] = (source_dir, path_in_archive)
537 # create the relative launcher and add it to the files to add
538 if ("profile" in config.APPLICATION and
539 "product" in config.APPLICATION.profile):
540 launcher_name = config.APPLICATION.profile.launcher_name
541 launcher_package = produce_relative_launcher(config,
546 not(options.without_commercial))
548 d_products["launcher"] = (launcher_package, launcher_name)
550 # if we mix binaries and sources, we add a copy of the launcher,
551 # prefixed with "bin",in order to avoid clashes
552 d_products["launcher (copy)"] = (launcher_package, "bin"+launcher_name)
554 # Provide a script for the creation of an application EDF style
555 appli_script = product_appli_creation_script(config,
560 d_products["appli script"] = (appli_script, "create_appli.py")
562 # Put also the environment file
563 env_file = produce_relative_env_files(config,
568 d_products["environment file"] = (env_file, "env_launch.sh")
572 def source_package(sat, config, logger, options, tmp_working_dir):
573 '''Prepare a dictionary that stores all the needed directories and files to
574 add in a source package.
576 :param config Config: The global configuration.
577 :param logger Logger: the logging instance
578 :param options OptResult: the options of the launched command
579 :param tmp_working_dir str: The temporary local directory containing some
580 specific directories or files needed in the
582 :return: the dictionary that stores all the needed directories and files to
583 add in a source package.
584 {label : (path_on_local_machine, path_in_archive)}
588 # Get all the products that are prepared using an archive
589 logger.write("Find archive products ... ")
590 d_archives, l_pinfo_vcs = get_archives(config, logger)
591 logger.write("Done\n")
593 if not options.with_vcs and len(l_pinfo_vcs) > 0:
594 # Make archives with the products that are not prepared using an archive
595 # (git, cvs, svn, etc)
596 logger.write("Construct archives for vcs products ... ")
597 d_archives_vcs = get_archives_vcs(l_pinfo_vcs,
602 logger.write("Done\n")
605 logger.write("Create the project ... ")
606 d_project = create_project_for_src_package(config,
609 logger.write("Done\n")
612 tmp_sat = add_salomeTools(config, tmp_working_dir)
613 d_sat = {"salomeTools" : (tmp_sat, "salomeTools")}
615 # Add a sat symbolic link if not win
616 if not src.architecture.is_windows():
617 tmp_satlink_path = os.path.join(tmp_working_dir, 'sat')
621 # In the jobs, os.getcwd() can fail
622 t = config.USER.workdir
623 os.chdir(tmp_working_dir)
624 if os.path.lexists(tmp_satlink_path):
625 os.remove(tmp_satlink_path)
626 os.symlink(os.path.join('salomeTools', 'sat'), 'sat')
629 d_sat["sat link"] = (tmp_satlink_path, "sat")
631 d_source = src.merge_dicts(d_archives, d_archives_vcs, d_project, d_sat)
634 def get_archives(config, logger):
635 '''Find all the products that are get using an archive and all the products
636 that are get using a vcs (git, cvs, svn) repository.
638 :param config Config: The global configuration.
639 :param logger Logger: the logging instance
640 :return: the dictionary {name_product :
641 (local path of its archive, path in the package of its archive )}
642 and the list of specific configuration corresponding to the vcs
646 # Get the list of product informations
647 l_products_name = config.APPLICATION.products.keys()
648 l_product_info = src.product.get_products_infos(l_products_name,
652 for p_name, p_info in l_product_info:
653 # ignore the native and fixed products
654 if (src.product.product_is_native(p_info)
655 or src.product.product_is_fixed(p_info)):
657 if p_info.get_source == "archive":
658 archive_path = p_info.archive_info.archive_name
659 archive_name = os.path.basename(archive_path)
661 l_pinfo_vcs.append((p_name, p_info))
663 d_archives[p_name] = (archive_path,
664 os.path.join(ARCHIVE_DIR, archive_name))
665 return d_archives, l_pinfo_vcs
667 def add_salomeTools(config, tmp_working_dir):
668 '''Prepare a version of salomeTools that has a specific local.pyconf file
669 configured for a source package.
671 :param config Config: The global configuration.
672 :param tmp_working_dir str: The temporary local directory containing some
673 specific directories or files needed in the
675 :return: The path to the local salomeTools directory to add in the package
678 # Copy sat in the temporary working directory
679 sat_tmp_path = src.Path(os.path.join(tmp_working_dir, "salomeTools"))
680 sat_running_path = src.Path(config.VARS.salometoolsway)
681 sat_running_path.copy(sat_tmp_path)
683 # Update the local.pyconf file that contains the path to the project
684 local_pyconf_name = "local.pyconf"
685 local_pyconf_dir = os.path.join(tmp_working_dir, "salomeTools", "data")
686 local_pyconf_file = os.path.join(local_pyconf_dir, local_pyconf_name)
687 # Remove the .pyconf file in the root directory of salomeTools if there is
688 # any. (For example when launching jobs, a pyconf file describing the jobs
689 # can be here and is not useful)
690 files_or_dir_SAT = os.listdir(os.path.join(tmp_working_dir, "salomeTools"))
691 for file_or_dir in files_or_dir_SAT:
692 if file_or_dir.endswith(".pyconf"):
693 file_path = os.path.join(tmp_working_dir,
698 ff = open(local_pyconf_file, "w")
699 ff.write(LOCAL_TEMPLATE)
702 return sat_tmp_path.path
704 def get_archives_vcs(l_pinfo_vcs, sat, config, logger, tmp_working_dir):
705 '''For sources package that require that all products are get using an
706 archive, one has to create some archive for the vcs products.
707 So this method calls the clean and source command of sat and then create
710 :param l_pinfo_vcs List: The list of specific configuration corresponding to
712 :param sat Sat: The Sat instance that can be called to clean and source the
714 :param config Config: The global configuration.
715 :param logger Logger: the logging instance
716 :param tmp_working_dir str: The temporary local directory containing some
717 specific directories or files needed in the
719 :return: the dictionary that stores all the archives to add in the source
720 package. {label : (path_on_local_machine, path_in_archive)}
723 # clean the source directory of all the vcs products, then use the source
724 # command and thus construct an archive that will not contain the patches
725 l_prod_names = [pn for pn, __ in l_pinfo_vcs]
727 logger.write(_("clean sources\n"))
728 args_clean = config.VARS.application
729 args_clean += " --sources --products "
730 args_clean += ",".join(l_prod_names)
731 sat.clean(args_clean, batch=True, verbose=0, logger_add_link = logger)
733 logger.write(_("get sources"))
734 args_source = config.VARS.application
735 args_source += " --products "
736 args_source += ",".join(l_prod_names)
737 sat.source(args_source, batch=True, verbose=0, logger_add_link = logger)
739 # make the new archives
741 for pn, pinfo in l_pinfo_vcs:
742 path_archive = make_archive(pn, pinfo, tmp_working_dir)
743 d_archives_vcs[pn] = (path_archive,
744 os.path.join(ARCHIVE_DIR, pn + ".tgz"))
745 return d_archives_vcs
747 def make_archive(prod_name, prod_info, where):
748 '''Create an archive of a product by searching its source directory.
750 :param prod_name str: The name of the product.
751 :param prod_info Config: The specific configuration corresponding to the
753 :param where str: The path of the repository where to put the resulting
755 :return: The path of the resulting archive
758 path_targz_prod = os.path.join(where, prod_name + ".tgz")
759 tar_prod = tarfile.open(path_targz_prod, mode='w:gz')
760 local_path = prod_info.source_dir
761 tar_prod.add(local_path,
763 exclude=exclude_VCS_and_extensions)
765 return path_targz_prod
767 def create_project_for_src_package(config, tmp_working_dir, with_vcs):
768 '''Create a specific project for a source package.
770 :param config Config: The global configuration.
771 :param tmp_working_dir str: The temporary local directory containing some
772 specific directories or files needed in the
774 :param with_vcs boolean: True if the package is with vcs products (not
775 transformed into archive products)
776 :return: The dictionary
777 {"project" : (produced project, project path in the archive)}
781 # Create in the working temporary directory the full project tree
782 project_tmp_dir = os.path.join(tmp_working_dir, PROJECT_DIR)
783 products_pyconf_tmp_dir = os.path.join(project_tmp_dir,
785 compil_scripts_tmp_dir = os.path.join(project_tmp_dir,
788 env_scripts_tmp_dir = os.path.join(project_tmp_dir,
791 patches_tmp_dir = os.path.join(project_tmp_dir,
794 application_tmp_dir = os.path.join(project_tmp_dir,
796 for directory in [project_tmp_dir,
797 compil_scripts_tmp_dir,
800 application_tmp_dir]:
801 src.ensure_path_exists(directory)
803 # Create the pyconf that contains the information of the project
804 project_pyconf_name = "project.pyconf"
805 project_pyconf_file = os.path.join(project_tmp_dir, project_pyconf_name)
806 ff = open(project_pyconf_file, "w")
807 ff.write(PROJECT_TEMPLATE)
810 # Loop over the products to get there pyconf and all the scripts
811 # (compilation, environment, patches)
812 # and create the pyconf file to add to the project
813 lproducts_name = config.APPLICATION.products.keys()
814 l_products = src.product.get_products_infos(lproducts_name, config)
815 for p_name, p_info in l_products:
816 find_product_scripts_and_pyconf(p_name,
820 compil_scripts_tmp_dir,
823 products_pyconf_tmp_dir)
825 find_application_pyconf(config, application_tmp_dir)
827 d_project = {"project" : (project_tmp_dir, PROJECT_DIR )}
830 def find_product_scripts_and_pyconf(p_name,
834 compil_scripts_tmp_dir,
837 products_pyconf_tmp_dir):
838 '''Create a specific pyconf file for a given product. Get its environment
839 script, its compilation script and patches and put it in the temporary
840 working directory. This method is used in the source package in order to
841 construct the specific project.
843 :param p_name str: The name of the product.
844 :param p_info Config: The specific configuration corresponding to the
846 :param config Config: The global configuration.
847 :param with_vcs boolean: True if the package is with vcs products (not
848 transformed into archive products)
849 :param compil_scripts_tmp_dir str: The path to the temporary compilation
850 scripts directory of the project.
851 :param env_scripts_tmp_dir str: The path to the temporary environment script
852 directory of the project.
853 :param patches_tmp_dir str: The path to the temporary patch scripts
854 directory of the project.
855 :param products_pyconf_tmp_dir str: The path to the temporary product
856 scripts directory of the project.
859 # read the pyconf of the product
860 product_pyconf_path = src.find_file_in_lpath(p_name + ".pyconf",
861 config.PATHS.PRODUCTPATH)
862 product_pyconf_cfg = src.pyconf.Config(product_pyconf_path)
864 # find the compilation script if any
865 if src.product.product_has_script(p_info):
866 compil_script_path = src.Path(p_info.compil_script)
867 compil_script_path.copy(compil_scripts_tmp_dir)
868 product_pyconf_cfg[p_info.section].compil_script = os.path.basename(
869 p_info.compil_script)
870 # find the environment script if any
871 if src.product.product_has_env_script(p_info):
872 env_script_path = src.Path(p_info.environ.env_script)
873 env_script_path.copy(env_scripts_tmp_dir)
874 product_pyconf_cfg[p_info.section].environ.env_script = os.path.basename(
875 p_info.environ.env_script)
876 # find the patches if any
877 if src.product.product_has_patches(p_info):
878 patches = src.pyconf.Sequence()
879 for patch_path in p_info.patches:
880 p_path = src.Path(patch_path)
881 p_path.copy(patches_tmp_dir)
882 patches.append(os.path.basename(patch_path), "")
884 product_pyconf_cfg[p_info.section].patches = patches
887 # put in the pyconf file the resolved values
888 for info in ["git_info", "cvs_info", "svn_info"]:
890 for key in p_info[info]:
891 product_pyconf_cfg[p_info.section][info][key] = p_info[
894 # if the product is not archive, then make it become archive.
895 if src.product.product_is_vcs(p_info):
896 product_pyconf_cfg[p_info.section].get_source = "archive"
897 if not "archive_info" in product_pyconf_cfg[p_info.section]:
898 product_pyconf_cfg[p_info.section].addMapping("archive_info",
899 src.pyconf.Mapping(product_pyconf_cfg),
901 product_pyconf_cfg[p_info.section
902 ].archive_info.archive_name = p_info.name + ".tgz"
904 # write the pyconf file to the temporary project location
905 product_tmp_pyconf_path = os.path.join(products_pyconf_tmp_dir,
907 ff = open(product_tmp_pyconf_path, 'w')
908 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
909 product_pyconf_cfg.__save__(ff, 1)
912 def find_application_pyconf(config, application_tmp_dir):
913 '''Find the application pyconf file and put it in the specific temporary
914 directory containing the specific project of a source package.
916 :param config Config: The global configuration.
917 :param application_tmp_dir str: The path to the temporary application
918 scripts directory of the project.
920 # read the pyconf of the application
921 application_name = config.VARS.application
922 application_pyconf_path = src.find_file_in_lpath(
923 application_name + ".pyconf",
924 config.PATHS.APPLICATIONPATH)
925 application_pyconf_cfg = src.pyconf.Config(application_pyconf_path)
928 application_pyconf_cfg.APPLICATION.workdir = src.pyconf.Reference(
929 application_pyconf_cfg,
931 'VARS.salometoolsway + $VARS.sep + ".."')
933 # Prevent from compilation in base
934 application_pyconf_cfg.APPLICATION.no_base = "yes"
936 # write the pyconf file to the temporary application location
937 application_tmp_pyconf_path = os.path.join(application_tmp_dir,
938 application_name + ".pyconf")
939 ff = open(application_tmp_pyconf_path, 'w')
940 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
941 application_pyconf_cfg.__save__(ff, 1)
944 def project_package(project_file_path, tmp_working_dir):
945 '''Prepare a dictionary that stores all the needed directories and files to
946 add in a project package.
948 :param project_file_path str: The path to the local project.
949 :param tmp_working_dir str: The temporary local directory containing some
950 specific directories or files needed in the
952 :return: the dictionary that stores all the needed directories and files to
953 add in a project package.
954 {label : (path_on_local_machine, path_in_archive)}
958 # Read the project file and get the directories to add to the package
959 project_pyconf_cfg = src.pyconf.Config(project_file_path)
960 paths = {"ARCHIVEPATH" : "archives",
961 "APPLICATIONPATH" : "applications",
962 "PRODUCTPATH" : "products",
964 "MACHINEPATH" : "machines"}
965 # Loop over the project paths and add it
967 if path not in project_pyconf_cfg:
969 # Add the directory to the files to add in the package
970 d_project[path] = (project_pyconf_cfg[path], paths[path])
971 # Modify the value of the path in the package
972 project_pyconf_cfg[path] = src.pyconf.Reference(
975 'project_path + "/' + paths[path] + '"')
978 if "project_path" not in project_pyconf_cfg:
979 project_pyconf_cfg.addMapping("project_path",
980 src.pyconf.Mapping(project_pyconf_cfg),
982 project_pyconf_cfg.project_path = src.pyconf.Reference(project_pyconf_cfg,
986 # Write the project pyconf file
987 project_file_name = os.path.basename(project_file_path)
988 project_pyconf_tmp_path = os.path.join(tmp_working_dir, project_file_name)
989 ff = open(project_pyconf_tmp_path, 'w')
990 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
991 project_pyconf_cfg.__save__(ff, 1)
993 d_project["Project hat file"] = (project_pyconf_tmp_path, project_file_name)
997 def add_readme(config, options, where):
998 readme_path = os.path.join(where, "README")
999 with codecs.open(readme_path, "w", 'utf-8') as f:
1001 # templates for building the header
1003 # This package was generated with sat $version
1006 # Distribution : $dist
1008 In the following, $$ROOT represents the directory where you have installed
1009 SALOME (the directory where this file is located).
1012 readme_compilation_with_binaries="""
1014 compilation based on the binaries used as prerequisites
1015 =======================================================
1017 If you fail to compile the complete application (for example because
1018 you are not root on your system and cannot install missing packages), you
1019 may try a partial compilation based on the binaries.
1020 For that it is necessary to copy the binaries from BINARIES to INSTALL,
1021 and do some substitutions on cmake and .la files (replace the build directories
1023 The procedure to do it is:
1024 1) Remove or rename INSTALL directory if it exists
1025 2) Execute the shell script install_bin.sh:
1028 3) Use SalomeTool (as explained in Sources section) and compile only the
1029 modules you need to (with -p option)
1032 readme_header_tpl=string.Template(readme_header)
1033 readme_template_path_bin_prof = os.path.join(config.VARS.internal_dir,
1034 "README_BIN.template")
1035 readme_template_path_bin_noprof = os.path.join(config.VARS.internal_dir,
1036 "README_BIN_NO_PROFILE.template")
1037 readme_template_path_src = os.path.join(config.VARS.internal_dir,
1038 "README_SRC.template")
1039 readme_template_path_pro = os.path.join(config.VARS.internal_dir,
1040 "README_PROJECT.template")
1041 readme_template_path_sat = os.path.join(config.VARS.internal_dir,
1042 "README_SAT.template")
1044 # prepare substitution dictionary
1046 d['user'] = config.VARS.user
1047 d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
1048 d['version'] = config.INTERNAL.sat_version
1049 d['dist'] = config.VARS.dist
1050 f.write(readme_header_tpl.substitute(d)) # write the general header (common)
1052 if options.binaries or options.sources:
1053 d['application'] = config.VARS.application
1054 f.write("# Application: " + d['application'])
1055 if 'profile' in config.APPLICATION:
1056 d['launcher'] = config.APPLICATION.profile.launcher_name
1057 d['launcher'] = config.APPLICATION.profile.launcher_name
1059 d['env_file'] = 'env_launch.sh'
1061 # write the specific sections
1062 if options.binaries:
1064 f.write(src.template.substitute(readme_template_path_bin_noprof, d))
1066 f.write(src.template.substitute(readme_template_path_bin_prof, d))
1069 f.write(src.template.substitute(readme_template_path_src, d))
1071 if options.binaries and options.sources:
1072 f.write(readme_compilation_with_binaries)
1075 f.write(src.template.substitute(readme_template_path_pro, d))
1078 f.write(src.template.substitute(readme_template_path_sat, d))
1082 def update_config(config, prop, value):
1083 '''Remove from config.APPLICATION.products the products that have the property given as input.
1085 :param config Config: The global config.
1086 :param prop str: The property to filter
1087 :param value str: The value of the property to filter
1089 src.check_config_has_application(config)
1090 l_product_to_remove = []
1091 for product_name in config.APPLICATION.products.keys():
1092 prod_cfg = src.product.get_product_config(config, product_name)
1093 if src.get_property_in_product_cfg(prod_cfg, prop) == value:
1094 l_product_to_remove.append(product_name)
1095 for product_name in l_product_to_remove:
1096 config.APPLICATION.products.__delitem__(product_name)
1099 '''method that is called when salomeTools is called with --help option.
1101 :return: The text to display for the package command description.
1104 return _("The package command creates an archive.\nThere are 4 kinds of "
1105 "archive, which can be mixed:\n 1- The binary archive. It contains all the product "
1106 "installation directories and a launcher,\n 2- The sources archive."
1107 " It contains the products archives, a project corresponding to "
1108 "the application and salomeTools,\n 3- The project archive. It "
1109 "contains a project (give the project file path as argument),\n 4-"
1110 " The salomeTools archive. It contains salomeTools.\n\nexample:"
1111 "\nsat package SALOME-master --bineries --sources")
1113 def run(args, runner, logger):
1114 '''method that is called when salomeTools is called with package parameter.
1118 (options, args) = parser.parse_args(args)
1120 # Check that a type of package is called, and only one
1121 all_option_types = (options.binaries,
1123 options.project not in ["", None],
1126 # Check if no option for package type
1127 if all_option_types.count(True) == 0:
1128 msg = _("Error: Precise a type for the package\nUse one of the "
1129 "following options: --binaries, --sources, --project or"
1131 logger.write(src.printcolors.printcError(msg), 1)
1132 logger.write("\n", 1)
1135 # The repository where to put the package if not Binary or Source
1136 package_default_path = runner.cfg.USER.workdir
1138 # if the package contains binaries or sources:
1139 if options.binaries or options.sources:
1140 # Check that the command has been called with an application
1141 src.check_config_has_application(runner.cfg)
1143 # Display information
1144 logger.write(_("Packaging application %s\n") % src.printcolors.printcLabel(
1145 runner.cfg.VARS.application), 1)
1147 # Get the default directory where to put the packages
1148 package_default_path = os.path.join(runner.cfg.APPLICATION.workdir,
1150 src.ensure_path_exists(package_default_path)
1152 # if the package contains a project:
1154 # check that the project is visible by SAT
1155 if options.project not in runner.cfg.PROJECTS.project_file_paths:
1156 local_path = os.path.join(runner.cfg.VARS.salometoolsway,
1159 msg = _("ERROR: the project %(proj)s is not visible by salomeTools."
1160 "\nPlease add it in the %(local)s file." % {
1161 "proj" : options.project, "local" : local_path})
1162 logger.write(src.printcolors.printcError(msg), 1)
1163 logger.write("\n", 1)
1166 # Remove the products that are filtered by the --without_property option
1167 if options.without_property:
1168 [prop, value] = options.without_property.split(":")
1169 update_config(runner.cfg, prop, value)
1171 # get the name of the archive or build it
1173 if os.path.basename(options.name) == options.name:
1174 # only a name (not a path)
1175 archive_name = options.name
1176 dir_name = package_default_path
1178 archive_name = os.path.basename(options.name)
1179 dir_name = os.path.dirname(options.name)
1181 # suppress extension
1182 if archive_name[-len(".tgz"):] == ".tgz":
1183 archive_name = archive_name[:-len(".tgz")]
1184 if archive_name[-len(".tar.gz"):] == ".tar.gz":
1185 archive_name = archive_name[:-len(".tar.gz")]
1189 dir_name = package_default_path
1190 if options.binaries or options.sources:
1191 archive_name = runner.cfg.APPLICATION.name
1193 if options.binaries:
1194 archive_name += "_"+runner.cfg.VARS.dist
1197 archive_name += "_SRC"
1198 if options.with_vcs:
1199 archive_name += "_VCS"
1202 project_name, __ = os.path.splitext(
1203 os.path.basename(options.project))
1204 archive_name += ("PROJECT_" + project_name)
1207 archive_name += ("salomeTools_" + runner.cfg.INTERNAL.sat_version)
1208 if len(archive_name)==0: # no option worked
1209 msg = _("Error: Cannot name the archive\n"
1210 " check if at least one of the following options was "
1211 "selected : --binaries, --sources, --project or"
1213 logger.write(src.printcolors.printcError(msg), 1)
1214 logger.write("\n", 1)
1217 path_targz = os.path.join(dir_name, archive_name + ".tgz")
1219 src.printcolors.print_value(logger, "Package path", path_targz, 2)
1221 # Create a working directory for all files that are produced during the
1222 # package creation and that will be removed at the end of the command
1223 tmp_working_dir = os.path.join(runner.cfg.VARS.tmp_root,
1224 runner.cfg.VARS.datehour)
1225 src.ensure_path_exists(tmp_working_dir)
1226 logger.write("\n", 5)
1227 logger.write(_("The temporary working directory: %s\n" % tmp_working_dir),5)
1229 logger.write("\n", 3)
1231 msg = _("Preparation of files to add to the archive")
1232 logger.write(src.printcolors.printcLabel(msg), 2)
1233 logger.write("\n", 2)
1235 d_files_to_add={} # content of the archive
1237 # a dict to hold paths that will need to be substitute for users recompilations
1238 d_paths_to_substitute={}
1240 if options.binaries:
1241 d_bin_files_to_add = binary_package(runner.cfg,
1245 # for all binaries dir, store the substitution that will be required
1246 # for extra compilations
1247 for key in d_bin_files_to_add:
1248 if key.endswith("(bin)"):
1249 source_dir = d_bin_files_to_add[key][0]
1250 path_in_archive = d_bin_files_to_add[key][1].replace("BINARIES-" + runner.cfg.VARS.dist,"INSTALL")
1251 if os.path.basename(source_dir)==os.path.basename(path_in_archive):
1252 # if basename is the same we will just substitute the dirname
1253 d_paths_to_substitute[os.path.dirname(source_dir)]=\
1254 os.path.dirname(path_in_archive)
1256 d_paths_to_substitute[source_dir]=path_in_archive
1258 d_files_to_add.update(d_bin_files_to_add)
1261 d_files_to_add.update(source_package(runner,
1266 if options.binaries:
1267 # for archives with bin and sources we provide a shell script able to
1268 # install binaries for compilation
1269 file_install_bin=produce_install_bin_file(runner.cfg,logger,
1271 d_paths_to_substitute,
1273 d_files_to_add.update({"install_bin" : (file_install_bin, "install_bin.sh")})
1274 logger.write("substitutions that need to be done later : \n", 5)
1275 logger.write(str(d_paths_to_substitute), 5)
1276 logger.write("\n", 5)
1278 # --salomeTool option is not considered when --sources is selected, as this option
1279 # already brings salomeTool!
1281 d_files_to_add.update({"salomeTools" : (runner.cfg.VARS.salometoolsway, "")})
1285 d_files_to_add.update(project_package(options.project, tmp_working_dir))
1287 if not(d_files_to_add):
1288 msg = _("Error: Empty dictionnary to build the archive!\n")
1289 logger.write(src.printcolors.printcError(msg), 1)
1290 logger.write("\n", 1)
1293 # Add the README file in the package
1294 local_readme_tmp_path = add_readme(runner.cfg,
1297 d_files_to_add["README"] = (local_readme_tmp_path, "README")
1299 # Add the additional files of option add_files
1300 if options.add_files:
1301 for file_path in options.add_files:
1302 if not os.path.exists(file_path):
1303 msg = _("WARNING: the file %s is not accessible.\n" % file_path)
1305 file_name = os.path.basename(file_path)
1306 d_files_to_add[file_name] = (file_path, file_name)
1308 logger.write("\n", 2)
1310 logger.write(src.printcolors.printcLabel(_("Actually do the package")), 2)
1311 logger.write("\n", 2)
1314 # Creating the object tarfile
1315 tar = tarfile.open(path_targz, mode='w:gz')
1317 # get the filtering function if needed
1318 filter_function = exclude_VCS_and_extensions
1320 # Add the files to the tarfile object
1321 res = add_files(tar, archive_name, d_files_to_add, logger, f_exclude=filter_function)
1323 except KeyboardInterrupt:
1324 logger.write(src.printcolors.printcError("\nERROR: forced interruption\n"), 1)
1325 logger.write(_("Removing the temporary working directory ... "), 1)
1326 # remove the working directory
1327 shutil.rmtree(tmp_working_dir)
1328 logger.write(_("OK"), 1)
1329 logger.write(_("\n"), 1)
1332 # remove the working directory
1333 shutil.rmtree(tmp_working_dir)
1335 # Print again the path of the package
1336 logger.write("\n", 2)
1337 src.printcolors.print_value(logger, "Package path", path_targz, 2)