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
255 del text[num_line +1]
256 del text[num_line +1]
257 text_to_insert =""" import imp
259 distene = imp.load_source('distene_licence', '/data/tmpsalome/salome/prerequis/install/LICENSE/dlim8.var.py')
260 distene.set_distene_variables(context)
263 text.insert(num_line + 1, text_to_insert)
270 def produce_relative_env_files(config,
274 '''Create some specific environment files for the binary package. These
275 files use relative paths.
277 :param config Config: The global configuration.
278 :param logger Logger: the logging instance
279 :param file_dir str: the directory where to put the files
280 :param binaries_dir_name str: the name of the repository where the binaries
282 :return: the list of path of the produced environment files
285 # create an environment file writer
286 writer = src.environment.FileEnvWriter(config,
292 filepath = writer.write_env_file("env_launch.sh",
295 for_package = binaries_dir_name)
297 # Little hack to put out_dir_Path as environment variable
298 src.replace_in_file(filepath, '"out_dir_Path', '"${out_dir_Path}' )
300 # change the rights in order to make the file executable for everybody
312 def produce_install_bin_file(config,
317 '''Create a bash shell script which do substitutions in BIRARIES dir
318 in order to use it for extra compilations.
320 :param config Config: The global configuration.
321 :param logger Logger: the logging instance
322 :param file_dir str: the directory where to put the files
323 :param d_sub, dict: the dictionnary that contains the substitutions to be done
324 :param file_name str: the name of the install script file
325 :return: the produced file
329 filepath = os.path.join(file_dir, file_name)
330 # open the file and write into it
331 # use codec utf-8 as sat variables are in unicode
332 with codecs.open(filepath, "w", 'utf-8') as installbin_file:
333 installbin_template_path = os.path.join(config.VARS.internal_dir,
334 "INSTALL_BIN.template")
336 # build the name of the directory that will contain the binaries
337 binaries_dir_name = "BINARIES-" + config.VARS.dist
338 # build the substitution loop
339 loop_cmd = "for f in $(grep -RIl"
341 loop_cmd += " -e "+ key
342 loop_cmd += ' INSTALL); do\n sed -i "\n'
344 loop_cmd += " s?" + key + "?$(pwd)/" + d_sub[key] + "?g\n"
345 loop_cmd += ' " $f\ndone'
348 d["BINARIES_DIR"] = binaries_dir_name
349 d["SUBSTITUTION_LOOP"]=loop_cmd
351 # substitute the template and write it in file
352 content=src.template.substitute(installbin_template_path, d)
353 installbin_file.write(content)
354 # change the rights in order to make the file executable for everybody
366 def product_appli_creation_script(config,
370 '''Create a script that can produce an application (EDF style) in the binary
373 :param config Config: The global configuration.
374 :param logger Logger: the logging instance
375 :param file_dir str: the directory where to put the file
376 :param binaries_dir_name str: the name of the repository where the binaries
378 :return: the path of the produced script file
381 template_name = "create_appli.py.for_bin_packages.template"
382 template_path = os.path.join(config.VARS.internal_dir, template_name)
383 text_to_fill = open(template_path, "r").read()
384 text_to_fill = text_to_fill.replace("TO BE FILLED 1",
385 '"' + binaries_dir_name + '"')
388 for product_name in get_SALOME_modules(config):
389 product_info = src.product.get_product_config(config, product_name)
391 if src.product.product_is_smesh_plugin(product_info):
394 if 'install_dir' in product_info and bool(product_info.install_dir):
395 if src.product.product_is_cpp(product_info):
397 for cpp_name in src.product.get_product_components(product_info):
398 line_to_add = ("<module name=\"" +
400 "\" gui=\"yes\" path=\"''' + "
401 "os.path.join(dir_bin_name, \"" +
402 cpp_name + "\") + '''\"/>")
405 line_to_add = ("<module name=\"" +
407 "\" gui=\"yes\" path=\"''' + "
408 "os.path.join(dir_bin_name, \"" +
409 product_name + "\") + '''\"/>")
410 text_to_add += line_to_add + "\n"
412 filled_text = text_to_fill.replace("TO BE FILLED 2", text_to_add)
414 tmp_file_path = os.path.join(file_dir, "create_appli.py")
415 ff = open(tmp_file_path, "w")
416 ff.write(filled_text)
419 # change the rights in order to make the file executable for everybody
420 os.chmod(tmp_file_path,
431 def binary_package(config, logger, options, tmp_working_dir):
432 '''Prepare a dictionary that stores all the needed directories and files to
433 add in a binary package.
435 :param config Config: The global configuration.
436 :param logger Logger: the logging instance
437 :param options OptResult: the options of the launched command
438 :param tmp_working_dir str: The temporary local directory containing some
439 specific directories or files needed in the
441 :return: the dictionary that stores all the needed directories and files to
442 add in a binary package.
443 {label : (path_on_local_machine, path_in_archive)}
447 # Get the list of product installation to add to the archive
448 l_products_name = config.APPLICATION.products.keys()
449 l_product_info = src.product.get_products_infos(l_products_name,
454 l_sources_not_present = []
455 for prod_name, prod_info in l_product_info:
457 # Add the sources of the products that have the property
458 # sources_in_package : "yes"
459 if src.get_property_in_product_cfg(prod_info,
460 "sources_in_package") == "yes":
461 if os.path.exists(prod_info.source_dir):
462 l_source_dir.append((prod_name, prod_info.source_dir))
464 l_sources_not_present.append(prod_name)
466 # ignore the native and fixed products for install directories
467 if (src.product.product_is_native(prod_info)
468 or src.product.product_is_fixed(prod_info)
469 or not src.product.product_compiles(prod_info)):
471 if src.product.check_installation(prod_info):
472 l_install_dir.append((prod_name, prod_info.install_dir))
474 l_not_installed.append(prod_name)
476 # Add also the cpp generated modules (if any)
477 if src.product.product_is_cpp(prod_info):
479 for name_cpp in src.product.get_product_components(prod_info):
480 install_dir = os.path.join(config.APPLICATION.workdir,
482 if os.path.exists(install_dir):
483 l_install_dir.append((name_cpp, install_dir))
485 l_not_installed.append(name_cpp)
487 # Print warning or error if there are some missing products
488 if len(l_not_installed) > 0:
489 text_missing_prods = ""
490 for p_name in l_not_installed:
491 text_missing_prods += "-" + p_name + "\n"
492 if not options.force_creation:
493 msg = _("ERROR: there are missing products installations:")
494 logger.write("%s\n%s" % (src.printcolors.printcError(msg),
499 msg = _("WARNING: there are missing products installations:")
500 logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
504 # Do the same for sources
505 if len(l_sources_not_present) > 0:
506 text_missing_prods = ""
507 for p_name in l_sources_not_present:
508 text_missing_prods += "-" + p_name + "\n"
509 if not options.force_creation:
510 msg = _("ERROR: there are missing products sources:")
511 logger.write("%s\n%s" % (src.printcolors.printcError(msg),
516 msg = _("WARNING: there are missing products sources:")
517 logger.write("%s\n%s" % (src.printcolors.printcWarning(msg),
521 # construct the name of the directory that will contain the binaries
522 binaries_dir_name = "BINARIES-" + config.VARS.dist
524 # construct the correlation table between the product names, there
525 # actual install directories and there install directory in archive
527 for prod_name, install_dir in l_install_dir:
528 path_in_archive = os.path.join(binaries_dir_name, prod_name)
529 d_products[prod_name + " (bin)"] = (install_dir, path_in_archive)
531 for prod_name, source_dir in l_source_dir:
532 path_in_archive = os.path.join("SOURCES", prod_name)
533 d_products[prod_name + " (sources)"] = (source_dir, path_in_archive)
535 # create the relative launcher and add it to the files to add
536 if ("profile" in config.APPLICATION and
537 "product" in config.APPLICATION.profile):
538 launcher_name = config.APPLICATION.profile.launcher_name
539 launcher_package = produce_relative_launcher(config,
544 not(options.without_commercial))
546 d_products["launcher"] = (launcher_package, launcher_name)
548 # if we mix binaries and sources, we add a copy of the launcher,
549 # prefixed with "bin",in order to avoid clashes
550 d_products["launcher (copy)"] = (launcher_package, "bin"+launcher_name)
552 # Provide a script for the creation of an application EDF style
553 appli_script = product_appli_creation_script(config,
558 d_products["appli script"] = (appli_script, "create_appli.py")
560 # Put also the environment file
561 env_file = produce_relative_env_files(config,
566 d_products["environment file"] = (env_file, "env_launch.sh")
570 def source_package(sat, config, logger, options, tmp_working_dir):
571 '''Prepare a dictionary that stores all the needed directories and files to
572 add in a source package.
574 :param config Config: The global configuration.
575 :param logger Logger: the logging instance
576 :param options OptResult: the options of the launched command
577 :param tmp_working_dir str: The temporary local directory containing some
578 specific directories or files needed in the
580 :return: the dictionary that stores all the needed directories and files to
581 add in a source package.
582 {label : (path_on_local_machine, path_in_archive)}
586 # Get all the products that are prepared using an archive
587 logger.write("Find archive products ... ")
588 d_archives, l_pinfo_vcs = get_archives(config, logger)
589 logger.write("Done\n")
591 if not options.with_vcs and len(l_pinfo_vcs) > 0:
592 # Make archives with the products that are not prepared using an archive
593 # (git, cvs, svn, etc)
594 logger.write("Construct archives for vcs products ... ")
595 d_archives_vcs = get_archives_vcs(l_pinfo_vcs,
600 logger.write("Done\n")
603 logger.write("Create the project ... ")
604 d_project = create_project_for_src_package(config,
607 logger.write("Done\n")
610 tmp_sat = add_salomeTools(config, tmp_working_dir)
611 d_sat = {"salomeTools" : (tmp_sat, "salomeTools")}
613 # Add a sat symbolic link if not win
614 if not src.architecture.is_windows():
615 tmp_satlink_path = os.path.join(tmp_working_dir, 'sat')
619 # In the jobs, os.getcwd() can fail
620 t = config.USER.workdir
621 os.chdir(tmp_working_dir)
622 if os.path.lexists(tmp_satlink_path):
623 os.remove(tmp_satlink_path)
624 os.symlink(os.path.join('salomeTools', 'sat'), 'sat')
627 d_sat["sat link"] = (tmp_satlink_path, "sat")
629 d_source = src.merge_dicts(d_archives, d_archives_vcs, d_project, d_sat)
632 def get_archives(config, logger):
633 '''Find all the products that are get using an archive and all the products
634 that are get using a vcs (git, cvs, svn) repository.
636 :param config Config: The global configuration.
637 :param logger Logger: the logging instance
638 :return: the dictionary {name_product :
639 (local path of its archive, path in the package of its archive )}
640 and the list of specific configuration corresponding to the vcs
644 # Get the list of product informations
645 l_products_name = config.APPLICATION.products.keys()
646 l_product_info = src.product.get_products_infos(l_products_name,
650 for p_name, p_info in l_product_info:
651 # ignore the native and fixed products
652 if (src.product.product_is_native(p_info)
653 or src.product.product_is_fixed(p_info)):
655 if p_info.get_source == "archive":
656 archive_path = p_info.archive_info.archive_name
657 archive_name = os.path.basename(archive_path)
659 l_pinfo_vcs.append((p_name, p_info))
661 d_archives[p_name] = (archive_path,
662 os.path.join(ARCHIVE_DIR, archive_name))
663 return d_archives, l_pinfo_vcs
665 def add_salomeTools(config, tmp_working_dir):
666 '''Prepare a version of salomeTools that has a specific local.pyconf file
667 configured for a source package.
669 :param config Config: The global configuration.
670 :param tmp_working_dir str: The temporary local directory containing some
671 specific directories or files needed in the
673 :return: The path to the local salomeTools directory to add in the package
676 # Copy sat in the temporary working directory
677 sat_tmp_path = src.Path(os.path.join(tmp_working_dir, "salomeTools"))
678 sat_running_path = src.Path(config.VARS.salometoolsway)
679 sat_running_path.copy(sat_tmp_path)
681 # Update the local.pyconf file that contains the path to the project
682 local_pyconf_name = "local.pyconf"
683 local_pyconf_dir = os.path.join(tmp_working_dir, "salomeTools", "data")
684 local_pyconf_file = os.path.join(local_pyconf_dir, local_pyconf_name)
685 ff = open(local_pyconf_file, "w")
686 ff.write(LOCAL_TEMPLATE)
689 return sat_tmp_path.path
691 def get_archives_vcs(l_pinfo_vcs, sat, config, logger, tmp_working_dir):
692 '''For sources package that require that all products are get using an
693 archive, one has to create some archive for the vcs products.
694 So this method calls the clean and source command of sat and then create
697 :param l_pinfo_vcs List: The list of specific configuration corresponding to
699 :param sat Sat: The Sat instance that can be called to clean and source the
701 :param config Config: The global configuration.
702 :param logger Logger: the logging instance
703 :param tmp_working_dir str: The temporary local directory containing some
704 specific directories or files needed in the
706 :return: the dictionary that stores all the archives to add in the source
707 package. {label : (path_on_local_machine, path_in_archive)}
710 # clean the source directory of all the vcs products, then use the source
711 # command and thus construct an archive that will not contain the patches
712 l_prod_names = [pn for pn, __ in l_pinfo_vcs]
714 logger.write(_("clean sources\n"))
715 args_clean = config.VARS.application
716 args_clean += " --sources --products "
717 args_clean += ",".join(l_prod_names)
718 sat.clean(args_clean, batch=True, verbose=0, logger_add_link = logger)
720 logger.write(_("get sources"))
721 args_source = config.VARS.application
722 args_source += " --products "
723 args_source += ",".join(l_prod_names)
724 sat.source(args_source, batch=True, verbose=0, logger_add_link = logger)
726 # make the new archives
728 for pn, pinfo in l_pinfo_vcs:
729 path_archive = make_archive(pn, pinfo, tmp_working_dir)
730 d_archives_vcs[pn] = (path_archive,
731 os.path.join(ARCHIVE_DIR, pn + ".tgz"))
732 return d_archives_vcs
734 def make_archive(prod_name, prod_info, where):
735 '''Create an archive of a product by searching its source directory.
737 :param prod_name str: The name of the product.
738 :param prod_info Config: The specific configuration corresponding to the
740 :param where str: The path of the repository where to put the resulting
742 :return: The path of the resulting archive
745 path_targz_prod = os.path.join(where, prod_name + ".tgz")
746 tar_prod = tarfile.open(path_targz_prod, mode='w:gz')
747 local_path = prod_info.source_dir
748 tar_prod.add(local_path, arcname=prod_name)
750 return path_targz_prod
752 def create_project_for_src_package(config, tmp_working_dir, with_vcs):
753 '''Create a specific project for a source package.
755 :param config Config: The global configuration.
756 :param tmp_working_dir str: The temporary local directory containing some
757 specific directories or files needed in the
759 :param with_vcs boolean: True if the package is with vcs products (not
760 transformed into archive products)
761 :return: The dictionary
762 {"project" : (produced project, project path in the archive)}
766 # Create in the working temporary directory the full project tree
767 project_tmp_dir = os.path.join(tmp_working_dir, PROJECT_DIR)
768 products_pyconf_tmp_dir = os.path.join(project_tmp_dir,
770 compil_scripts_tmp_dir = os.path.join(project_tmp_dir,
773 env_scripts_tmp_dir = os.path.join(project_tmp_dir,
776 patches_tmp_dir = os.path.join(project_tmp_dir,
779 application_tmp_dir = os.path.join(project_tmp_dir,
781 for directory in [project_tmp_dir,
782 compil_scripts_tmp_dir,
785 application_tmp_dir]:
786 src.ensure_path_exists(directory)
788 # Create the pyconf that contains the information of the project
789 project_pyconf_name = "project.pyconf"
790 project_pyconf_file = os.path.join(project_tmp_dir, project_pyconf_name)
791 ff = open(project_pyconf_file, "w")
792 ff.write(PROJECT_TEMPLATE)
795 # Loop over the products to get there pyconf and all the scripts
796 # (compilation, environment, patches)
797 # and create the pyconf file to add to the project
798 lproducts_name = config.APPLICATION.products.keys()
799 l_products = src.product.get_products_infos(lproducts_name, config)
800 for p_name, p_info in l_products:
801 find_product_scripts_and_pyconf(p_name,
805 compil_scripts_tmp_dir,
808 products_pyconf_tmp_dir)
810 find_application_pyconf(config, application_tmp_dir)
812 d_project = {"project" : (project_tmp_dir, PROJECT_DIR )}
815 def find_product_scripts_and_pyconf(p_name,
819 compil_scripts_tmp_dir,
822 products_pyconf_tmp_dir):
823 '''Create a specific pyconf file for a given product. Get its environment
824 script, its compilation script and patches and put it in the temporary
825 working directory. This method is used in the source package in order to
826 construct the specific project.
828 :param p_name str: The name of the product.
829 :param p_info Config: The specific configuration corresponding to the
831 :param config Config: The global configuration.
832 :param with_vcs boolean: True if the package is with vcs products (not
833 transformed into archive products)
834 :param compil_scripts_tmp_dir str: The path to the temporary compilation
835 scripts directory of the project.
836 :param env_scripts_tmp_dir str: The path to the temporary environment script
837 directory of the project.
838 :param patches_tmp_dir str: The path to the temporary patch scripts
839 directory of the project.
840 :param products_pyconf_tmp_dir str: The path to the temporary product
841 scripts directory of the project.
844 # read the pyconf of the product
845 product_pyconf_path = src.find_file_in_lpath(p_name + ".pyconf",
846 config.PATHS.PRODUCTPATH)
847 product_pyconf_cfg = src.pyconf.Config(product_pyconf_path)
849 # find the compilation script if any
850 if src.product.product_has_script(p_info):
851 compil_script_path = src.Path(p_info.compil_script)
852 compil_script_path.copy(compil_scripts_tmp_dir)
853 product_pyconf_cfg[p_info.section].compil_script = os.path.basename(
854 p_info.compil_script)
855 # find the environment script if any
856 if src.product.product_has_env_script(p_info):
857 env_script_path = src.Path(p_info.environ.env_script)
858 env_script_path.copy(env_scripts_tmp_dir)
859 product_pyconf_cfg[p_info.section].environ.env_script = os.path.basename(
860 p_info.environ.env_script)
861 # find the patches if any
862 if src.product.product_has_patches(p_info):
863 patches = src.pyconf.Sequence()
864 for patch_path in p_info.patches:
865 p_path = src.Path(patch_path)
866 p_path.copy(patches_tmp_dir)
867 patches.append(os.path.basename(patch_path), "")
869 product_pyconf_cfg[p_info.section].patches = patches
872 # put in the pyconf file the resolved values
873 for info in ["git_info", "cvs_info", "svn_info"]:
875 for key in p_info[info]:
876 product_pyconf_cfg[p_info.section][info][key] = p_info[
879 # if the product is not archive, then make it become archive.
880 if src.product.product_is_vcs(p_info):
881 product_pyconf_cfg[p_info.section].get_source = "archive"
882 if not "archive_info" in product_pyconf_cfg[p_info.section]:
883 product_pyconf_cfg[p_info.section].addMapping("archive_info",
884 src.pyconf.Mapping(product_pyconf_cfg),
886 product_pyconf_cfg[p_info.section
887 ].archive_info.archive_name = p_info.name + ".tgz"
889 # write the pyconf file to the temporary project location
890 product_tmp_pyconf_path = os.path.join(products_pyconf_tmp_dir,
892 ff = open(product_tmp_pyconf_path, 'w')
893 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
894 product_pyconf_cfg.__save__(ff, 1)
897 def find_application_pyconf(config, application_tmp_dir):
898 '''Find the application pyconf file and put it in the specific temporary
899 directory containing the specific project of a source package.
901 :param config Config: The global configuration.
902 :param application_tmp_dir str: The path to the temporary application
903 scripts directory of the project.
905 # read the pyconf of the application
906 application_name = config.VARS.application
907 application_pyconf_path = src.find_file_in_lpath(
908 application_name + ".pyconf",
909 config.PATHS.APPLICATIONPATH)
910 application_pyconf_cfg = src.pyconf.Config(application_pyconf_path)
913 application_pyconf_cfg.APPLICATION.workdir = src.pyconf.Reference(
914 application_pyconf_cfg,
916 'VARS.salometoolsway + $VARS.sep + ".."')
918 # Prevent from compilation in base
919 application_pyconf_cfg.APPLICATION.no_base = "yes"
921 # write the pyconf file to the temporary application location
922 application_tmp_pyconf_path = os.path.join(application_tmp_dir,
923 application_name + ".pyconf")
924 ff = open(application_tmp_pyconf_path, 'w')
925 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
926 application_pyconf_cfg.__save__(ff, 1)
929 def project_package(project_file_path, tmp_working_dir):
930 '''Prepare a dictionary that stores all the needed directories and files to
931 add in a project package.
933 :param project_file_path str: The path to the local project.
934 :param tmp_working_dir str: The temporary local directory containing some
935 specific directories or files needed in the
937 :return: the dictionary that stores all the needed directories and files to
938 add in a project package.
939 {label : (path_on_local_machine, path_in_archive)}
943 # Read the project file and get the directories to add to the package
944 project_pyconf_cfg = src.pyconf.Config(project_file_path)
945 paths = {"ARCHIVEPATH" : "archives",
946 "APPLICATIONPATH" : "applications",
947 "PRODUCTPATH" : "products",
949 "MACHINEPATH" : "machines"}
950 # Loop over the project paths and add it
952 if path not in project_pyconf_cfg:
954 # Add the directory to the files to add in the package
955 d_project[path] = (project_pyconf_cfg[path], paths[path])
956 # Modify the value of the path in the package
957 project_pyconf_cfg[path] = src.pyconf.Reference(
960 'project_path + "/' + paths[path] + '"')
963 if "project_path" not in project_pyconf_cfg:
964 project_pyconf_cfg.addMapping("project_path",
965 src.pyconf.Mapping(project_pyconf_cfg),
967 project_pyconf_cfg.project_path = src.pyconf.Reference(project_pyconf_cfg,
971 # Write the project pyconf file
972 project_file_name = os.path.basename(project_file_path)
973 project_pyconf_tmp_path = os.path.join(tmp_working_dir, project_file_name)
974 ff = open(project_pyconf_tmp_path, 'w')
975 ff.write("#!/usr/bin/env python\n#-*- coding:utf-8 -*-\n\n")
976 project_pyconf_cfg.__save__(ff, 1)
978 d_project["Project hat file"] = (project_pyconf_tmp_path, project_file_name)
982 def add_readme(config, options, where):
983 readme_path = os.path.join(where, "README")
984 with codecs.open(readme_path, "w", 'utf-8') as f:
986 # templates for building the header
988 # This package was generated with sat $version
991 # Distribution : $dist
993 In the following, $$ROOT represents the directory where you have installed
994 SALOME (the directory where this file is located).
997 readme_compilation_with_binaries="""
999 compilation based on the binaries used as prerequisites
1000 =======================================================
1002 If you fail to compile the the complete application (for example because
1003 you are not root on your system and cannot install missing packages), you
1004 may try a partial compilation based on the binaries.
1005 For that it is necessary to copy the binaries from BINARIES to INSTALL,
1006 and do some substitutions on cmake and .la files (replace the build directories
1008 The procedure to do it is:
1009 1) Remove or rename INSTALL directory if it exists
1010 2) Execute the shell script bin_install.sh:
1013 3) Use SalomeTool (as explained in Sources section) and compile only the
1014 modules you need to (with -p option)
1017 readme_header_tpl=string.Template(readme_header)
1018 readme_template_path_bin_prof = os.path.join(config.VARS.internal_dir,
1019 "README_BIN.template")
1020 readme_template_path_bin_noprof = os.path.join(config.VARS.internal_dir,
1021 "README_BIN_NO_PROFILE.template")
1022 readme_template_path_src = os.path.join(config.VARS.internal_dir,
1023 "README_SRC.template")
1024 readme_template_path_pro = os.path.join(config.VARS.internal_dir,
1025 "README_PROJECT.template")
1026 readme_template_path_sat = os.path.join(config.VARS.internal_dir,
1027 "README_SAT.template")
1029 # prepare substitution dictionary
1031 d['user'] = config.VARS.user
1032 d['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
1033 d['version'] = config.INTERNAL.sat_version
1034 d['dist'] = config.VARS.dist
1035 f.write(readme_header_tpl.substitute(d)) # write the general header (common)
1037 if options.binaries or options.sources:
1038 d['application'] = config.VARS.application
1039 f.write("# Application: " + d['application'])
1040 if 'profile' in config.APPLICATION:
1041 d['launcher'] = config.APPLICATION.profile.launcher_name
1042 d['launcher'] = config.APPLICATION.profile.launcher_name
1044 d['env_file'] = 'env_launch.sh'
1046 # write the specific sections
1047 if options.binaries:
1049 f.write(src.template.substitute(readme_template_path_bin_noprof, d))
1051 f.write(src.template.substitute(readme_template_path_bin_prof, d))
1054 f.write(src.template.substitute(readme_template_path_src, d))
1056 if options.binaries and options.sources:
1057 f.write(readme_compilation_with_binaries)
1060 f.write(src.template.substitute(readme_template_path_pro, d))
1063 f.write(src.template.substitute(readme_template_path_sat, d))
1067 def update_config(config, prop, value):
1068 '''Remove from config.APPLICATION.products the products that have the property given as input.
1070 :param config Config: The global config.
1071 :param prop str: The property to filter
1072 :param value str: The value of the property to filter
1074 src.check_config_has_application(config)
1075 l_product_to_remove = []
1076 for product_name in config.APPLICATION.products.keys():
1077 prod_cfg = src.product.get_product_config(config, product_name)
1078 if src.get_property_in_product_cfg(prod_cfg, prop) == value:
1079 l_product_to_remove.append(product_name)
1080 for product_name in l_product_to_remove:
1081 config.APPLICATION.products.__delitem__(product_name)
1084 '''method that is called when salomeTools is called with --help option.
1086 :return: The text to display for the package command description.
1089 return _("The package command creates an archive.\nThere are 4 kinds of "
1090 "archive, which can be mixed:\n 1- The binary archive. It contains all the product "
1091 "installation directories and a launcher,\n 2- The sources archive."
1092 " It contains the products archives, a project corresponding to "
1093 "the application and salomeTools,\n 3- The project archive. It "
1094 "contains a project (give the project file path as argument),\n 4-"
1095 " The salomeTools archive. It contains salomeTools.\n\nexample:"
1096 "\nsat package SALOME-master --bineries --sources")
1098 def run(args, runner, logger):
1099 '''method that is called when salomeTools is called with package parameter.
1103 (options, args) = parser.parse_args(args)
1105 # Check that a type of package is called, and only one
1106 all_option_types = (options.binaries,
1108 options.project not in ["", None],
1111 # Check if no option for package type
1112 if all_option_types.count(True) == 0:
1113 msg = _("Error: Precise a type for the package\nUse one of the "
1114 "following options: --binaries, --sources, --project or"
1116 logger.write(src.printcolors.printcError(msg), 1)
1117 logger.write("\n", 1)
1120 # The repository where to put the package if not Binary or Source
1121 package_default_path = runner.cfg.USER.workdir
1123 # if the package contains binaries or sources:
1124 if options.binaries or options.sources:
1125 # Check that the command has been called with an application
1126 src.check_config_has_application(runner.cfg)
1128 # Display information
1129 logger.write(_("Packaging application %s\n") % src.printcolors.printcLabel(
1130 runner.cfg.VARS.application), 1)
1132 # Get the default directory where to put the packages
1133 package_default_path = os.path.join(runner.cfg.APPLICATION.workdir,
1135 src.ensure_path_exists(package_default_path)
1137 # if the package contains a project:
1139 # check that the project is visible by SAT
1140 if options.project not in runner.cfg.PROJECTS.project_file_paths:
1141 local_path = os.path.join(runner.cfg.VARS.salometoolsway,
1144 msg = _("ERROR: the project %(proj)s is not visible by salomeTools."
1145 "\nPlease add it in the %(local)s file." % {
1146 "proj" : options.project, "local" : local_path})
1147 logger.write(src.printcolors.printcError(msg), 1)
1148 logger.write("\n", 1)
1151 # Remove the products that are filtered by the --without_property option
1152 if options.without_property:
1153 [prop, value] = options.without_property.split(":")
1154 update_config(runner.cfg, prop, value)
1156 # get the name of the archive or build it
1158 if os.path.basename(options.name) == options.name:
1159 # only a name (not a path)
1160 archive_name = options.name
1161 dir_name = package_default_path
1163 archive_name = os.path.basename(options.name)
1164 dir_name = os.path.dirname(options.name)
1166 # suppress extension
1167 if archive_name[-len(".tgz"):] == ".tgz":
1168 archive_name = archive_name[:-len(".tgz")]
1169 if archive_name[-len(".tar.gz"):] == ".tar.gz":
1170 archive_name = archive_name[:-len(".tar.gz")]
1174 dir_name = package_default_path
1175 if options.binaries or options.sources:
1176 archive_name = runner.cfg.APPLICATION.name
1178 if options.binaries:
1179 archive_name += "_"+runner.cfg.VARS.dist
1182 archive_name += "_SRC"
1183 if options.with_vcs:
1184 archive_name += "_VCS"
1187 project_name, __ = os.path.splitext(
1188 os.path.basename(options.project))
1189 archive_name += ("PROJECT_" + project_name)
1192 archive_name += ("salomeTools_" + runner.cfg.INTERNAL.sat_version)
1193 if len(archive_name)==0: # no option worked
1194 msg = _("Error: Cannot name the archive\n"
1195 " check if at least one of the following options was "
1196 "selected : --binaries, --sources, --project or"
1198 logger.write(src.printcolors.printcError(msg), 1)
1199 logger.write("\n", 1)
1202 path_targz = os.path.join(dir_name, archive_name + ".tgz")
1204 src.printcolors.print_value(logger, "Package path", path_targz, 2)
1206 # Create a working directory for all files that are produced during the
1207 # package creation and that will be removed at the end of the command
1208 tmp_working_dir = os.path.join(runner.cfg.VARS.tmp_root,
1209 runner.cfg.VARS.datehour)
1210 src.ensure_path_exists(tmp_working_dir)
1211 logger.write("\n", 5)
1212 logger.write(_("The temporary working directory: %s\n" % tmp_working_dir),5)
1214 logger.write("\n", 3)
1216 msg = _("Preparation of files to add to the archive")
1217 logger.write(src.printcolors.printcLabel(msg), 2)
1218 logger.write("\n", 2)
1220 d_files_to_add={} # content of the archive
1222 # a dict to hold paths that will need to be substitute for users recompilations
1223 d_paths_to_substitute={}
1225 if options.binaries:
1226 d_bin_files_to_add = binary_package(runner.cfg,
1230 # for all binaries dir, store the substitution that will be required
1231 # for extra compilations
1232 for key in d_bin_files_to_add:
1233 if key.endswith("(bin)"):
1234 source_dir = d_bin_files_to_add[key][0]
1235 path_in_archive = d_bin_files_to_add[key][1].replace("BINARIES-" + runner.cfg.VARS.dist,"INSTALL")
1236 if os.path.basename(source_dir)==os.path.basename(path_in_archive):
1237 # if basename is the same we will just substitute the dirname
1238 d_paths_to_substitute[os.path.dirname(source_dir)]=\
1239 os.path.dirname(path_in_archive)
1241 d_paths_to_substitute[source_dir]=path_in_archive
1243 d_files_to_add.update(d_bin_files_to_add)
1246 d_files_to_add.update(source_package(runner,
1251 if options.binaries:
1252 # for archives with bin and sources we provide a shell script able to
1253 # install binaries for compilation
1254 file_install_bin=produce_install_bin_file(runner.cfg,logger,
1256 d_paths_to_substitute,
1258 d_files_to_add.update({"install_bin" : (file_install_bin, "install_bin.sh")})
1259 logger.write("substitutions that need to be done later : \n", 5)
1260 logger.write(str(d_paths_to_substitute), 5)
1261 logger.write("\n", 5)
1263 # --salomeTool option is not considered when --sources is selected, as this option
1264 # already brings salomeTool!
1266 d_files_to_add.update({"salomeTools" : (runner.cfg.VARS.salometoolsway, "")})
1270 d_files_to_add.update(project_package(options.project, tmp_working_dir))
1272 if not(d_files_to_add):
1273 msg = _("Error: Empty dictionnary to build the archive!\n")
1274 logger.write(src.printcolors.printcError(msg), 1)
1275 logger.write("\n", 1)
1278 # Add the README file in the package
1279 local_readme_tmp_path = add_readme(runner.cfg,
1282 d_files_to_add["README"] = (local_readme_tmp_path, "README")
1284 # Add the additional files of option add_files
1285 if options.add_files:
1286 for file_path in options.add_files:
1287 if not os.path.exists(file_path):
1288 msg = _("WARNING: the file %s is not accessible.\n" % file_path)
1290 file_name = os.path.basename(file_path)
1291 d_files_to_add[file_name] = (file_path, file_name)
1293 logger.write("\n", 2)
1295 logger.write(src.printcolors.printcLabel(_("Actually do the package")), 2)
1296 logger.write("\n", 2)
1299 # Creating the object tarfile
1300 tar = tarfile.open(path_targz, mode='w:gz')
1302 # get the filtering function if needed
1303 filter_function = None
1304 filter_function = exclude_VCS_and_extensions
1306 # Add the files to the tarfile object
1307 res = add_files(tar, archive_name, d_files_to_add, logger, f_exclude=filter_function)
1309 except KeyboardInterrupt:
1310 logger.write(src.printcolors.printcError("\nERROR: forced interruption\n"), 1)
1311 logger.write(_("Removing the temporary working directory ... "), 1)
1312 # remove the working directory
1313 shutil.rmtree(tmp_working_dir)
1314 logger.write(_("OK"), 1)
1315 logger.write(_("\n"), 1)
1318 # remove the working directory
1319 shutil.rmtree(tmp_working_dir)
1321 # Print again the path of the package
1322 logger.write("\n", 2)
1323 src.printcolors.print_value(logger, "Package path", path_targz, 2)