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"
72 tmp_dir_with_application : '/tmp' + $VARS.sep + $VARS.user + """
73 """$VARS.sep + $APPLICATION.name + $VARS.sep + 'test'
74 tmp_dir : '/tmp' + $VARS.sep + $VARS.user + $VARS.sep + 'test'
81 project_file_paths : [$VARS.salometoolsway + $VARS.sep + \"..\" + $VARS.sep"""
82 """ + \"""" + PROJECT_DIR + """\" + $VARS.sep + "project.pyconf"]
86 # Define all possible option for the package command : sat package <options>
87 parser = src.options.Options()
88 parser.add_option('b', 'binaries', 'boolean', 'binaries',
89 _('Optional: Produce a binary package.'), False)
90 parser.add_option('f', 'force_creation', 'boolean', 'force_creation',
91 _('Optional: Only binary package: produce the archive even if '
92 'there are some missing products.'), False)
93 parser.add_option('s', 'sources', 'boolean', 'sources',
94 _('Optional: Produce a compilable archive of the sources of the '
95 'application.'), False)
96 parser.add_option('', 'with_vcs', 'boolean', 'with_vcs',
97 _('Optional: Only source package: do not make archive of vcs products.'),
99 parser.add_option('p', 'project', 'string', 'project',
100 _('Optional: Produce an archive that contains a project.'), "")
101 parser.add_option('t', 'salometools', 'boolean', 'sat',
102 _('Optional: Produce an archive that contains salomeTools.'), False)
103 parser.add_option('n', 'name', 'string', 'name',
104 _('Optional: The name or full path of the archive.'), None)
105 parser.add_option('', 'add_files', 'list2', 'add_files',
106 _('Optional: The list of additional files to add to the archive.'), [])
107 parser.add_option('', 'without_commercial', 'boolean', 'without_commercial',
108 _('Optional: do not add commercial licence.'), False)
109 parser.add_option('', 'without_property', 'string', 'without_property',
110 _('Optional: Filter the products by their properties.\n\tSyntax: '
111 '--without_property <property>:<value>'))
114 def add_files(tar, name_archive, d_content, logger, f_exclude=None):
115 '''Create an archive containing all directories and files that are given in
116 the d_content argument.
118 :param tar tarfile: The tarfile instance used to make the archive.
119 :param name_archive str: The name of the archive to make.
120 :param d_content dict: The dictionary that contain all directories and files
121 to add in the archive.
123 (path_on_local_machine, path_in_archive)
124 :param logger Logger: the logging instance
125 :param f_exclude Function: the function that filters
126 :return: 0 if success, 1 if not.
129 # get the max length of the messages in order to make the display
130 max_len = len(max(d_content.keys(), key=len))
133 # loop over each directory or file stored in the d_content dictionary
134 for name in d_content.keys():
135 # display information
136 len_points = max_len - len(name)
137 logger.write(name + " " + len_points * "." + " ", 3)
138 # Get the local path and the path in archive
139 # of the directory or file to add
140 local_path, archive_path = d_content[name]
141 in_archive = os.path.join(name_archive, archive_path)
142 # Add it in the archive
144 tar.add(local_path, arcname=in_archive, exclude=f_exclude)
145 logger.write(src.printcolors.printcSuccess(_("OK")), 3)
146 except Exception as e:
147 logger.write(src.printcolors.printcError(_("KO ")), 3)
148 logger.write(str(e), 3)
150 logger.write("\n", 3)
153 def exclude_VCS_and_extensions(filename):
154 ''' The function that is used to exclude from package the link to the
155 VCS repositories (like .git)
157 :param filename Str: The filname to exclude (or not).
158 :return: True if the file has to be exclude
161 for dir_name in IGNORED_DIRS:
162 if dir_name in filename:
164 for extension in IGNORED_EXTENSIONS:
165 if filename.endswith(extension):
169 def produce_relative_launcher(config,
174 with_commercial=True):
175 '''Create a specific SALOME launcher for the binary package. This launcher
178 :param config Config: The global configuration.
179 :param logger Logger: the logging instance
180 :param file_dir str: the directory where to put the launcher
181 :param file_name str: The launcher name
182 :param binaries_dir_name str: the name of the repository where the binaries
184 :return: the path of the produced launcher
188 # Get the launcher template
189 profile_install_dir = os.path.join(binaries_dir_name,
190 config.APPLICATION.profile.product)
191 withProfile = src.fileEnviron.withProfile
192 withProfile = withProfile.replace(
193 "ABSOLUTE_APPLI_PATH'] = 'PROFILE_INSTALL_DIR'",
194 "ABSOLUTE_APPLI_PATH'] = out_dir_Path + '" + config.VARS.sep + profile_install_dir + "'")
195 withProfile = withProfile.replace(
196 "os.path.join( 'PROFILE_INSTALL_DIR'",
197 "os.path.join( out_dir_Path, '" + profile_install_dir + "'")
199 before, after = withProfile.split(
200 "# here your local standalone environment\n")
202 # create an environment file writer
203 writer = src.environment.FileEnvWriter(config,
208 filepath = os.path.join(file_dir, file_name)
209 # open the file and write into it
210 launch_file = open(filepath, "w")
211 launch_file.write(before)
213 writer.write_cfgForPy_file(launch_file,
214 for_package = binaries_dir_name,
215 with_commercial=with_commercial)
216 launch_file.write(after)
219 # Little hack to put out_dir_Path outside the strings
220 src.replace_in_file(filepath, 'r"out_dir_Path', 'out_dir_Path + r"' )
222 # change the rights in order to make the file executable for everybody
234 def produce_relative_env_files(config,
238 '''Create some specific environment files for the binary package. These
239 files use relative paths.
241 :param config Config: The global configuration.
242 :param logger Logger: the logging instance
243 :param file_dir str: the directory where to put the files
244 :param binaries_dir_name str: the name of the repository where the binaries
246 :return: the list of path of the produced environment files
249 # create an environment file writer
250 writer = src.environment.FileEnvWriter(config,
256 filepath = writer.write_env_file("env_launch.sh",
259 for_package = binaries_dir_name)
261 # Little hack to put out_dir_Path as environment variable
262 src.replace_in_file(filepath, '"out_dir_Path', '"${out_dir_Path}' )
264 # change the rights in order to make the file executable for everybody
276 def produce_install_bin_file(config,
281 '''Create a bash shell script which do substitutions in BIRARIES dir
282 in order to use it for extra compilations.
284 :param config Config: The global configuration.
285 :param logger Logger: the logging instance
286 :param file_dir str: the directory where to put the files
287 :param d_sub, dict: the dictionnary that contains the substitutions to be done
288 :param file_name str: the name of the install script file
289 :return: the produced file
293 filepath = os.path.join(file_dir, file_name)
294 # open the file and write into it
295 # use codec utf-8 as sat variables are in unicode
296 with codecs.open(filepath, "w", 'utf-8') as installbin_file:
297 installbin_template_path = os.path.join(config.VARS.internal_dir,
298 "INSTALL_BIN.template")
300 # build the name of the directory that will contain the binaries
301 binaries_dir_name = "BINARIES-" + config.VARS.dist
302 # build the substitution loop
303 loop_cmd = "for f in $(grep -RIl"
305 loop_cmd += " -e "+ key
306 loop_cmd += ' INSTALL); do\n sed -i "\n'
308 loop_cmd += " s?" + key + "?$(pwd)/" + d_sub[key] + "?g\n"
309 loop_cmd += ' " $f\ndone'
312 d["BINARIES_DIR"] = binaries_dir_name
313 d["SUBSTITUTION_LOOP"]=loop_cmd
315 # substitute the template and write it in file
316 content=src.template.substitute(installbin_template_path, d)
317 installbin_file.write(content)
318 # change the rights in order to make the file executable for everybody
330 def product_appli_creation_script(config,
334 '''Create a script that can produce an application (EDF style) in the binary
337 :param config Config: The global configuration.
338 :param logger Logger: the logging instance
339 :param file_dir str: the directory where to put the file
340 :param binaries_dir_name str: the name of the repository where the binaries
342 :return: the path of the produced script file
345 template_name = "create_appli.py.for_bin_packages.template"
346 template_path = os.path.join(config.VARS.internal_dir, template_name)
347 text_to_fill = open(template_path, "r").read()
348 text_to_fill = text_to_fill.replace("TO BE FILLED 1",
349 '"' + binaries_dir_name + '"')
352 for product_name in get_SALOME_modules(config):
353 product_info = src.product.get_product_config(config, product_name)
355 if src.product.product_is_smesh_plugin(product_info):
358 if 'install_dir' in product_info and bool(product_info.install_dir):
359 if src.product.product_is_cpp(product_info):
361 for cpp_name in src.product.get_product_components(product_info):
362 line_to_add = ("<module name=\"" +
364 "\" gui=\"yes\" path=\"''' + "
365 "os.path.join(dir_bin_name, \"" +
366 cpp_name + "\") + '''\"/>")
369 line_to_add = ("<module name=\"" +
371 "\" gui=\"yes\" path=\"''' + "
372 "os.path.join(dir_bin_name, \"" +
373 product_name + "\") + '''\"/>")
374 text_to_add += line_to_add + "\n"
376 filled_text = text_to_fill.replace("TO BE FILLED 2", text_to_add)
378 tmp_file_path = os.path.join(file_dir, "create_appli.py")
379 ff = open(tmp_file_path, "w")
380 ff.write(filled_text)
383 # change the rights in order to make the file executable for everybody
384 os.chmod(tmp_file_path,
395 def binary_package(config, logger, options, tmp_working_dir):
396 '''Prepare a dictionary that stores all the needed directories and files to
397 add in a binary package.
399 :param config Config: The global configuration.
400 :param logger Logger: the logging instance
401 :param options OptResult: the options of the launched command
402 :param tmp_working_dir str: The temporary local directory containing some
403 specific directories or files needed in the
405 :return: the dictionary that stores all the needed directories and files to
406 add in a binary package.
407 {label : (path_on_local_machine, path_in_archive)}
411 # Get the list of product installation to add to the archive
412 l_products_name = config.APPLICATION.products.keys()
413 l_product_info = src.product.get_products_infos(l_products_name,
418 l_sources_not_present = []
419 for prod_name, prod_info in l_product_info:
421 # Add the sources of the products that have the property
422 # sources_in_package : "yes"
423 if src.get_property_in_product_cfg(prod_info,
424 "sources_in_package") == "yes":
425 if os.path.exists(prod_info.source_dir):
426 l_source_dir.append((prod_name, prod_info.source_dir))
428 l_sources_not_present.append(prod_name)
430 # ignore the native and fixed products for install directories
431 if (src.product.product_is_native(prod_info)
432 or src.product.product_is_fixed(prod_info)
433 or not src.product.product_compiles(prod_info)):
435 if src.product.check_installation(prod_info):
436 l_install_dir.append((prod_name, prod_info.install_dir))
438 l_not_installed.append(prod_name)
440 # Add also the cpp generated modules (if any)
441 if src.product.product_is_cpp(prod_info):
443 for name_cpp in src.product.get_product_components(prod_info):
444 install_dir = os.path.join(config.APPLICATION.workdir,
446 if os.path.exists(install_dir):
447 l_install_dir.append((name_cpp, install_dir))
449 l_not_installed.append(name_cpp)
451 # Print warning or error if there are some missing products
452 if len(l_not_installed) > 0:
453 text_missing_prods = ""
454 for p_name in l_not_installed:
455 text_missing_prods += "-" + p_name + "\n"
456 if not options.force_creation:
457 msg = _("ERROR: there are missing products installations:")
458 logger.write("%s\n%s" % (src.printcolors.printcError(msg),
463 msg = _("WARNING: there are missing products installations:")
464 logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
468 # Do the same for sources
469 if len(l_sources_not_present) > 0:
470 text_missing_prods = ""
471 for p_name in l_sources_not_present:
472 text_missing_prods += "-" + p_name + "\n"
473 if not options.force_creation:
474 msg = _("ERROR: there are missing products sources:")
475 logger.write("%s\n%s" % (src.printcolors.printcError(msg),
480 msg = _("WARNING: there are missing products sources:")
481 logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
485 # construct the name of the directory that will contain the binaries
486 binaries_dir_name = "BINARIES-" + config.VARS.dist
488 # construct the correlation table between the product names, there
489 # actual install directories and there install directory in archive
491 for prod_name, install_dir in l_install_dir:
492 path_in_archive = os.path.join(binaries_dir_name, prod_name)
493 d_products[prod_name + " (bin)"] = (install_dir, path_in_archive)
495 for prod_name, source_dir in l_source_dir:
496 path_in_archive = os.path.join("SOURCES", prod_name)
497 d_products[prod_name + " (sources)"] = (source_dir, path_in_archive)
499 # create the relative launcher and add it to the files to add
500 if ("profile" in config.APPLICATION and
501 "product" in config.APPLICATION.profile):
502 launcher_name = config.APPLICATION.profile.launcher_name
503 launcher_package = produce_relative_launcher(config,
508 not(options.without_commercial))
510 d_products["launcher"] = (launcher_package, launcher_name)
512 # if we mix binaries and sources, we add a copy of the launcher,
513 # prefixed with "bin",in order to avoid clashes
514 d_products["launcher (copy)"] = (launcher_package, "bin"+launcher_name)
516 # Provide a script for the creation of an application EDF style
517 appli_script = product_appli_creation_script(config,
522 d_products["appli script"] = (appli_script, "create_appli.py")
524 # Put also the environment file
525 env_file = produce_relative_env_files(config,
530 d_products["environment file"] = (env_file, "env_launch.sh")
534 def source_package(sat, config, logger, options, tmp_working_dir):
535 '''Prepare a dictionary that stores all the needed directories and files to
536 add in a source package.
538 :param config Config: The global configuration.
539 :param logger Logger: the logging instance
540 :param options OptResult: the options of the launched command
541 :param tmp_working_dir str: The temporary local directory containing some
542 specific directories or files needed in the
544 :return: the dictionary that stores all the needed directories and files to
545 add in a source package.
546 {label : (path_on_local_machine, path_in_archive)}
550 # Get all the products that are prepared using an archive
551 logger.write("Find archive products ... ")
552 d_archives, l_pinfo_vcs = get_archives(config, logger)
553 logger.write("Done\n")
555 if not options.with_vcs and len(l_pinfo_vcs) > 0:
556 # Make archives with the products that are not prepared using an archive
557 # (git, cvs, svn, etc)
558 logger.write("Construct archives for vcs products ... ")
559 d_archives_vcs = get_archives_vcs(l_pinfo_vcs,
564 logger.write("Done\n")
567 logger.write("Create the project ... ")
568 d_project = create_project_for_src_package(config,
571 logger.write("Done\n")
574 tmp_sat = add_salomeTools(config, tmp_working_dir)
575 d_sat = {"salomeTools" : (tmp_sat, "salomeTools")}
577 # Add a sat symbolic link if not win
578 if not src.architecture.is_windows():
579 tmp_satlink_path = os.path.join(tmp_working_dir, 'sat')
583 # In the jobs, os.getcwd() can fail
584 t = config.USER.workdir
585 os.chdir(tmp_working_dir)
586 if os.path.lexists(tmp_satlink_path):
587 os.remove(tmp_satlink_path)
588 os.symlink(os.path.join('salomeTools', 'sat'), 'sat')
591 d_sat["sat link"] = (tmp_satlink_path, "sat")
593 d_source = src.merge_dicts(d_archives, d_archives_vcs, d_project, d_sat)
596 def get_archives(config, logger):
597 '''Find all the products that are get using an archive and all the products
598 that are get using a vcs (git, cvs, svn) repository.
600 :param config Config: The global configuration.
601 :param logger Logger: the logging instance
602 :return: the dictionary {name_product :
603 (local path of its archive, path in the package of its archive )}
604 and the list of specific configuration corresponding to the vcs
608 # Get the list of product informations
609 l_products_name = config.APPLICATION.products.keys()
610 l_product_info = src.product.get_products_infos(l_products_name,
614 for p_name, p_info in l_product_info:
615 # ignore the native and fixed products
616 if (src.product.product_is_native(p_info)
617 or src.product.product_is_fixed(p_info)):
619 if p_info.get_source == "archive":
620 archive_path = p_info.archive_info.archive_name
621 archive_name = os.path.basename(archive_path)
623 l_pinfo_vcs.append((p_name, p_info))
625 d_archives[p_name] = (archive_path,
626 os.path.join(ARCHIVE_DIR, archive_name))
627 return d_archives, l_pinfo_vcs
629 def add_salomeTools(config, tmp_working_dir):
630 '''Prepare a version of salomeTools that has a specific site.pyconf file
631 configured for a source package.
633 :param config Config: The global configuration.
634 :param tmp_working_dir str: The temporary local directory containing some
635 specific directories or files needed in the
637 :return: The path to the local salomeTools directory to add in the package
640 # Copy sat in the temporary working directory
641 sat_tmp_path = src.Path(os.path.join(tmp_working_dir, "salomeTools"))
642 sat_running_path = src.Path(config.VARS.salometoolsway)
643 sat_running_path.copy(sat_tmp_path)
645 # Update the site.pyconf file that contains the path to the project
646 site_pyconf_name = "site.pyconf"
647 site_pyconf_dir = os.path.join(tmp_working_dir, "salomeTools", "data")
648 site_pyconf_file = os.path.join(site_pyconf_dir, site_pyconf_name)
649 ff = open(site_pyconf_file, "w")
650 ff.write(SITE_TEMPLATE)
653 return sat_tmp_path.path
655 def get_archives_vcs(l_pinfo_vcs, sat, config, logger, tmp_working_dir):
656 '''For sources package that require that all products are get using an
657 archive, one has to create some archive for the vcs products.
658 So this method calls the clean and source command of sat and then create
661 :param l_pinfo_vcs List: The list of specific configuration corresponding to
663 :param sat Sat: The Sat instance that can be called to clean and source the
665 :param config Config: The global configuration.
666 :param logger Logger: the logging instance
667 :param tmp_working_dir str: The temporary local directory containing some
668 specific directories or files needed in the
670 :return: the dictionary that stores all the archives to add in the source
671 package. {label : (path_on_local_machine, path_in_archive)}
674 # clean the source directory of all the vcs products, then use the source
675 # command and thus construct an archive that will not contain the patches
676 l_prod_names = [pn for pn, __ in l_pinfo_vcs]
678 logger.write(_("clean sources\n"))
679 args_clean = config.VARS.application
680 args_clean += " --sources --products "
681 args_clean += ",".join(l_prod_names)
682 sat.clean(args_clean, batch=True, verbose=0, logger_add_link = logger)
684 logger.write(_("get sources"))
685 args_source = config.VARS.application
686 args_source += " --products "
687 args_source += ",".join(l_prod_names)
688 sat.source(args_source, batch=True, verbose=0, logger_add_link = logger)
690 # make the new archives
692 for pn, pinfo in l_pinfo_vcs:
693 path_archive = make_archive(pn, pinfo, tmp_working_dir)
694 d_archives_vcs[pn] = (path_archive,
695 os.path.join(ARCHIVE_DIR, pn + ".tgz"))
696 return d_archives_vcs
698 def make_archive(prod_name, prod_info, where):
699 '''Create an archive of a product by searching its source directory.
701 :param prod_name str: The name of the product.
702 :param prod_info Config: The specific configuration corresponding to the
704 :param where str: The path of the repository where to put the resulting
706 :return: The path of the resulting archive
709 path_targz_prod = os.path.join(where, prod_name + ".tgz")
710 tar_prod = tarfile.open(path_targz_prod, mode='w:gz')
711 local_path = prod_info.source_dir
712 tar_prod.add(local_path, arcname=prod_name)
714 return path_targz_prod
716 def create_project_for_src_package(config, tmp_working_dir, with_vcs):
717 '''Create a specific project for a source package.
719 :param config Config: The global configuration.
720 :param tmp_working_dir str: The temporary local directory containing some
721 specific directories or files needed in the
723 :param with_vcs boolean: True if the package is with vcs products (not
724 transformed into archive products)
725 :return: The dictionary
726 {"project" : (produced project, project path in the archive)}
730 # Create in the working temporary directory the full project tree
731 project_tmp_dir = os.path.join(tmp_working_dir, PROJECT_DIR)
732 products_pyconf_tmp_dir = os.path.join(project_tmp_dir,
734 compil_scripts_tmp_dir = os.path.join(project_tmp_dir,
737 env_scripts_tmp_dir = os.path.join(project_tmp_dir,
740 patches_tmp_dir = os.path.join(project_tmp_dir,
743 application_tmp_dir = os.path.join(project_tmp_dir,
745 for directory in [project_tmp_dir,
746 compil_scripts_tmp_dir,
749 application_tmp_dir]:
750 src.ensure_path_exists(directory)
752 # Create the pyconf that contains the information of the project
753 project_pyconf_name = "project.pyconf"
754 project_pyconf_file = os.path.join(project_tmp_dir, project_pyconf_name)
755 ff = open(project_pyconf_file, "w")
756 ff.write(PROJECT_TEMPLATE)
759 # Loop over the products to get there pyconf and all the scripts
760 # (compilation, environment, patches)
761 # and create the pyconf file to add to the project
762 lproducts_name = config.APPLICATION.products.keys()
763 l_products = src.product.get_products_infos(lproducts_name, config)
764 for p_name, p_info in l_products:
765 # ignore native and fixed products
766 if src.product.product_is_native(p_info):
768 find_product_scripts_and_pyconf(p_name,
772 compil_scripts_tmp_dir,
775 products_pyconf_tmp_dir)
777 find_application_pyconf(config, application_tmp_dir)
779 d_project = {"project" : (project_tmp_dir, PROJECT_DIR )}
782 def find_product_scripts_and_pyconf(p_name,
786 compil_scripts_tmp_dir,
789 products_pyconf_tmp_dir):
790 '''Create a specific pyconf file for a given product. Get its environment
791 script, its compilation script and patches and put it in the temporary
792 working directory. This method is used in the source package in order to
793 construct the specific project.
795 :param p_name str: The name of the product.
796 :param p_info Config: The specific configuration corresponding to the
798 :param config Config: The global configuration.
799 :param with_vcs boolean: True if the package is with vcs products (not
800 transformed into archive products)
801 :param compil_scripts_tmp_dir str: The path to the temporary compilation
802 scripts directory of the project.
803 :param env_scripts_tmp_dir str: The path to the temporary environment script
804 directory of the project.
805 :param patches_tmp_dir str: The path to the temporary patch scripts
806 directory of the project.
807 :param products_pyconf_tmp_dir str: The path to the temporary product
808 scripts directory of the project.
811 # read the pyconf of the product
812 product_pyconf_path = src.find_file_in_lpath(p_name + ".pyconf",
813 config.PATHS.PRODUCTPATH)
814 product_pyconf_cfg = src.pyconf.Config(product_pyconf_path)
816 # find the compilation script if any
817 if src.product.product_has_script(p_info):
818 compil_script_path = src.Path(p_info.compil_script)
819 compil_script_path.copy(compil_scripts_tmp_dir)
820 product_pyconf_cfg[p_info.section].compil_script = os.path.basename(
821 p_info.compil_script)
822 # find the environment script if any
823 if src.product.product_has_env_script(p_info):
824 env_script_path = src.Path(p_info.environ.env_script)
825 env_script_path.copy(env_scripts_tmp_dir)
826 product_pyconf_cfg[p_info.section].environ.env_script = os.path.basename(
827 p_info.environ.env_script)
828 # find the patches if any
829 if src.product.product_has_patches(p_info):
830 patches = src.pyconf.Sequence()
831 for patch_path in p_info.patches:
832 p_path = src.Path(patch_path)
833 p_path.copy(patches_tmp_dir)
834 patches.append(os.path.basename(patch_path), "")
836 product_pyconf_cfg[p_info.section].patches = patches
839 # put in the pyconf file the resolved values
840 for info in ["git_info", "cvs_info", "svn_info"]:
842 for key in p_info[info]:
843 product_pyconf_cfg[p_info.section][info][key] = p_info[
846 # if the product is not archive, then make it become archive.
847 if src.product.product_is_vcs(p_info):
848 product_pyconf_cfg[p_info.section].get_source = "archive"
849 if not "archive_info" in product_pyconf_cfg[p_info.section]:
850 product_pyconf_cfg[p_info.section].addMapping("archive_info",
851 src.pyconf.Mapping(product_pyconf_cfg),
853 product_pyconf_cfg[p_info.section
854 ].archive_info.archive_name = p_info.name + ".tgz"
856 # write the pyconf file to the temporary project location
857 product_tmp_pyconf_path = os.path.join(products_pyconf_tmp_dir,
859 ff = open(product_tmp_pyconf_path, 'w')
860 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
861 product_pyconf_cfg.__save__(ff, 1)
864 def find_application_pyconf(config, application_tmp_dir):
865 '''Find the application pyconf file and put it in the specific temporary
866 directory containing the specific project of a source package.
868 :param config Config: The global configuration.
869 :param application_tmp_dir str: The path to the temporary application
870 scripts directory of the project.
872 # read the pyconf of the application
873 application_name = config.VARS.application
874 application_pyconf_path = src.find_file_in_lpath(
875 application_name + ".pyconf",
876 config.PATHS.APPLICATIONPATH)
877 application_pyconf_cfg = src.pyconf.Config(application_pyconf_path)
880 application_pyconf_cfg.APPLICATION.workdir = src.pyconf.Reference(
881 application_pyconf_cfg,
883 'VARS.salometoolsway + $VARS.sep + ".."')
885 # Prevent from compilation in base
886 application_pyconf_cfg.APPLICATION.no_base = "yes"
888 # write the pyconf file to the temporary application location
889 application_tmp_pyconf_path = os.path.join(application_tmp_dir,
890 application_name + ".pyconf")
891 ff = open(application_tmp_pyconf_path, 'w')
892 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
893 application_pyconf_cfg.__save__(ff, 1)
896 def project_package(project_file_path, tmp_working_dir):
897 '''Prepare a dictionary that stores all the needed directories and files to
898 add in a project package.
900 :param project_file_path str: The path to the local project.
901 :param tmp_working_dir str: The temporary local directory containing some
902 specific directories or files needed in the
904 :return: the dictionary that stores all the needed directories and files to
905 add in a project package.
906 {label : (path_on_local_machine, path_in_archive)}
910 # Read the project file and get the directories to add to the package
911 project_pyconf_cfg = src.pyconf.Config(project_file_path)
912 paths = {"ARCHIVEPATH" : "archives",
913 "APPLICATIONPATH" : "applications",
914 "PRODUCTPATH" : "products",
916 "MACHINEPATH" : "machines"}
917 # Loop over the project paths and add it
919 if path not in project_pyconf_cfg:
921 # Add the directory to the files to add in the package
922 d_project[path] = (project_pyconf_cfg[path], paths[path])
923 # Modify the value of the path in the package
924 project_pyconf_cfg[path] = src.pyconf.Reference(
927 'project_path + "/' + paths[path] + '"')
930 if "project_path" not in project_pyconf_cfg:
931 project_pyconf_cfg.addMapping("project_path",
932 src.pyconf.Mapping(project_pyconf_cfg),
934 project_pyconf_cfg.project_path = src.pyconf.Reference(project_pyconf_cfg,
938 # Write the project pyconf file
939 project_file_name = os.path.basename(project_file_path)
940 project_pyconf_tmp_path = os.path.join(tmp_working_dir, project_file_name)
941 ff = open(project_pyconf_tmp_path, 'w')
942 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
943 project_pyconf_cfg.__save__(ff, 1)
945 d_project["Project hat file"] = (project_pyconf_tmp_path, project_file_name)
949 def add_readme(config, options, where):
950 readme_path = os.path.join(where, "README")
951 with codecs.open(readme_path, "w", 'utf-8') as f:
953 # templates for building the header
955 # This package was generated with sat $version
958 # Distribution : $dist
960 In the following, $$ROOT represents the directory where you have installed
961 SALOME (the directory where this file is located).
964 readme_compilation_with_binaries="""
966 compilation based on the binaries used as prerequisites
967 =======================================================
969 If you fail to compile the the complete application (for example because
970 you are not root on your system and cannot install missing packages), you
971 may try a partial compilation based on the binaries.
972 For that it is necessary to copy the binaries from BINARIES to INSTALL,
973 and do some substitutions on cmake and .la files (replace the build directories
975 The procedure to do it is:
976 1) Remove or rename INSTALL directory if it exists
977 2) Execute the shell script bin_install.sh:
980 3) Use SalomeTool (as explained in Sources section) and compile only the
981 modules you need to (with -p option)
984 readme_header_tpl=string.Template(readme_header)
985 readme_template_path_bin_prof = os.path.join(config.VARS.internal_dir,
986 "README_BIN.template")
987 readme_template_path_bin_noprof = os.path.join(config.VARS.internal_dir,
988 "README_BIN_NO_PROFILE.template")
989 readme_template_path_src = os.path.join(config.VARS.internal_dir,
990 "README_SRC.template")
991 readme_template_path_pro = os.path.join(config.VARS.internal_dir,
992 "README_PROJECT.template")
993 readme_template_path_sat = os.path.join(config.VARS.internal_dir,
994 "README_SAT.template")
996 # prepare substitution dictionary
998 d['user'] = config.VARS.user
999 d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
1000 d['version'] = config.INTERNAL.sat_version
1001 d['dist'] = config.VARS.dist
1002 f.write(readme_header_tpl.substitute(d)) # write the general header (common)
1004 if options.binaries or options.sources:
1005 d['application'] = config.VARS.application
1006 f.write("# Application: " + d['application'])
1007 if 'profile' in config.APPLICATION:
1008 d['launcher'] = config.APPLICATION.profile.launcher_name
1009 d['launcher'] = config.APPLICATION.profile.launcher_name
1011 d['env_file'] = 'env_launch.sh'
1013 # write the specific sections
1014 if options.binaries:
1016 f.write(src.template.substitute(readme_template_path_bin_noprof, d))
1018 f.write(src.template.substitute(readme_template_path_bin_prof, d))
1021 f.write(src.template.substitute(readme_template_path_src, d))
1023 if options.binaries and options.sources:
1024 f.write(readme_compilation_with_binaries)
1027 f.write(src.template.substitute(readme_template_path_pro, d))
1030 f.write(src.template.substitute(readme_template_path_sat, d))
1034 def update_config(config, prop, value):
1035 '''Remove from config.APPLICATION.products the products that have the property given as input.
1037 :param config Config: The global config.
1038 :param prop str: The property to filter
1039 :param value str: The value of the property to filter
1041 src.check_config_has_application(config)
1042 l_product_to_remove = []
1043 for product_name in config.APPLICATION.products.keys():
1044 prod_cfg = src.product.get_product_config(config, product_name)
1045 if src.get_property_in_product_cfg(prod_cfg, prop) == value:
1046 l_product_to_remove.append(product_name)
1047 for product_name in l_product_to_remove:
1048 config.APPLICATION.products.__delitem__(product_name)
1051 '''method that is called when salomeTools is called with --help option.
1053 :return: The text to display for the package command description.
1056 return _("The package command creates an archive.\nThere are 4 kinds of "
1057 "archive:\n 1- The binary archive. It contains all the product "
1058 "installation directories and a launcher,\n 2- The sources archive."
1059 " It contains the products archives, a project corresponding to "
1060 "the application and salomeTools,\n 3- The project archive. It "
1061 "contains a project (give the project file path as argument),\n 4-"
1062 " The salomeTools archive. It contains salomeTools.\n\nexample:"
1063 "\nsat package SALOME-master --sources")
1065 def run(args, runner, logger):
1066 '''method that is called when salomeTools is called with package parameter.
1070 (options, args) = parser.parse_args(args)
1072 # Check that a type of package is called, and only one
1073 all_option_types = (options.binaries,
1075 options.project not in ["", None],
1078 # Check if no option for package type
1079 if all_option_types.count(True) == 0:
1080 msg = _("Error: Precise a type for the package\nUse one of the "
1081 "following options: --binaries, --sources, --project or"
1083 logger.write(src.printcolors.printcError(msg), 1)
1084 logger.write("\n", 1)
1087 # The repository where to put the package if not Binary or Source
1088 package_default_path = runner.cfg.USER.workdir
1090 # if the package contains binaries or sources:
1091 if options.binaries or options.sources:
1092 # Check that the command has been called with an application
1093 src.check_config_has_application(runner.cfg)
1095 # Display information
1096 logger.write(_("Packaging application %s\n") % src.printcolors.printcLabel(
1097 runner.cfg.VARS.application), 1)
1099 # Get the default directory where to put the packages
1100 package_default_path = os.path.join(runner.cfg.APPLICATION.workdir,
1102 src.ensure_path_exists(package_default_path)
1104 # if the package contains a project:
1106 # check that the project is visible by SAT
1107 if options.project not in runner.cfg.PROJECTS.project_file_paths:
1108 site_path = os.path.join(runner.cfg.VARS.salometoolsway,
1111 msg = _("ERROR: the project %(proj)s is not visible by salomeTools."
1112 "\nPlease add it in the %(site)s file." % {
1113 "proj" : options.project, "site" : site_path})
1114 logger.write(src.printcolors.printcError(msg), 1)
1115 logger.write("\n", 1)
1118 # Remove the products that are filtered by the --without_property option
1119 if options.without_property:
1120 [prop, value] = options.without_property.split(":")
1121 update_config(runner.cfg, prop, value)
1123 # get the name of the archive or build it
1125 if os.path.basename(options.name) == options.name:
1126 # only a name (not a path)
1127 archive_name = options.name
1128 dir_name = package_default_path
1130 archive_name = os.path.basename(options.name)
1131 dir_name = os.path.dirname(options.name)
1133 # suppress extension
1134 if archive_name[-len(".tgz"):] == ".tgz":
1135 archive_name = archive_name[:-len(".tgz")]
1136 if archive_name[-len(".tar.gz"):] == ".tar.gz":
1137 archive_name = archive_name[:-len(".tar.gz")]
1140 dir_name = package_default_path
1141 if options.binaries:
1142 archive_name = (runner.cfg.APPLICATION.name +
1144 runner.cfg.VARS.dist)
1146 elif options.sources:
1147 archive_name = (runner.cfg.APPLICATION.name +
1150 if options.with_vcs:
1151 archive_name = (runner.cfg.APPLICATION.name +
1157 elif options.project:
1158 project_name, __ = os.path.splitext(
1159 os.path.basename(options.project))
1160 archive_name = ("PROJECT" +
1165 archive_name = ("salomeTools" +
1167 runner.cfg.INTERNAL.sat_version)
1169 msg = _("Error: Cannot name the archive\n"
1170 " check if at least one of the following options was "
1171 "selected : --binaries, --sources, --project or"
1173 logger.write(src.printcolors.printcError(msg), 1)
1174 logger.write("\n", 1)
1177 path_targz = os.path.join(dir_name, archive_name + ".tgz")
1179 src.printcolors.print_value(logger, "Package path", path_targz, 2)
1181 # Create a working directory for all files that are produced during the
1182 # package creation and that will be removed at the end of the command
1183 tmp_working_dir = os.path.join(runner.cfg.VARS.tmp_root,
1184 runner.cfg.VARS.datehour)
1185 src.ensure_path_exists(tmp_working_dir)
1186 logger.write("\n", 5)
1187 logger.write(_("The temporary working directory: %s\n" % tmp_working_dir),5)
1189 logger.write("\n", 3)
1191 msg = _("Preparation of files to add to the archive")
1192 logger.write(src.printcolors.printcLabel(msg), 2)
1193 logger.write("\n", 2)
1195 d_files_to_add={} # content of the archive
1197 # a dict to hold paths that will need to be substitute for users recompilations
1198 d_paths_to_substitute={}
1200 if options.binaries:
1201 d_bin_files_to_add = binary_package(runner.cfg,
1205 # for all binaries dir, store the substitution that will be required
1206 # for extra compilations
1207 for key in d_bin_files_to_add:
1208 if key.endswith("(bin)"):
1209 source_dir = d_bin_files_to_add[key][0]
1210 path_in_archive = d_bin_files_to_add[key][1].replace("BINARIES-" + runner.cfg.VARS.dist,"INSTALL")
1211 if os.path.basename(source_dir)==os.path.basename(path_in_archive):
1212 # if basename is the same we will just substitute the dirname
1213 d_paths_to_substitute[os.path.dirname(source_dir)]=\
1214 os.path.dirname(path_in_archive)
1216 d_paths_to_substitute[source_dir]=path_in_archive
1218 d_files_to_add.update(d_bin_files_to_add)
1221 d_files_to_add.update(source_package(runner,
1226 if options.binaries:
1227 # for archives with bin and sources we provide a shell script able to
1228 # install binaries for compilation
1229 file_install_bin=produce_install_bin_file(runner.cfg,logger,
1231 d_paths_to_substitute,
1233 d_files_to_add.update({"install_bin" : (file_install_bin, "install_bin.sh")})
1234 logger.write("substitutions that need to be done later : \n", 5)
1235 logger.write(d_paths_to_substitute, 5)
1236 logger.write("\n", 5)
1238 # --salomeTool option is not considered when --sources is selected, as this option
1239 # already brings salomeTool!
1241 d_files_to_add.update({"salomeTools" : (runner.cfg.VARS.salometoolsway, "")})
1245 d_files_to_add.update(project_package(options.project, tmp_working_dir))
1247 if not(d_files_to_add):
1248 msg = _("Error: Empty dictionnary to build the archive!\n")
1249 logger.write(src.printcolors.printcError(msg), 1)
1250 logger.write("\n", 1)
1253 # Add the README file in the package
1254 local_readme_tmp_path = add_readme(runner.cfg,
1257 d_files_to_add["README"] = (local_readme_tmp_path, "README")
1259 # Add the additional files of option add_files
1260 if options.add_files:
1261 for file_path in options.add_files:
1262 if not os.path.exists(file_path):
1263 msg = _("WARNING: the file %s is not accessible.\n" % file_path)
1265 file_name = os.path.basename(file_path)
1266 d_files_to_add[file_name] = (file_path, file_name)
1268 logger.write("\n", 2)
1270 logger.write(src.printcolors.printcLabel(_("Actually do the package")), 2)
1271 logger.write("\n", 2)
1274 # Creating the object tarfile
1275 tar = tarfile.open(path_targz, mode='w:gz')
1277 # get the filtering function if needed
1278 filter_function = None
1279 filter_function = exclude_VCS_and_extensions
1281 # Add the files to the tarfile object
1282 res = add_files(tar, archive_name, d_files_to_add, logger, f_exclude=filter_function)
1284 except KeyboardInterrupt:
1285 logger.write(src.printcolors.printcError("\nERROR: forced interruption\n"), 1)
1286 logger.write(_("Removing the temporary working directory ... "), 1)
1287 # remove the working directory
1288 shutil.rmtree(tmp_working_dir)
1289 logger.write(_("OK"), 1)
1290 logger.write(_("\n"), 1)
1293 # remove the working directory
1294 shutil.rmtree(tmp_working_dir)
1296 # Print again the path of the package
1297 logger.write("\n", 2)
1298 src.printcolors.print_value(logger, "Package path", path_targz, 2)