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
23 import src.debug as DBG
25 # Compatibility python 2/3 for input function
26 # input stays input for python 3 and input = raw_input for python 2
33 # Define all possible option for the upgrade command : sat upgrade <options>
34 parser = src.options.Options()
40 _("Optional: products to upgrade. This option accepts a comma separated list."),
48 "Optional: build all necessary products to the given product (KERNEL is "
49 "build before building GUI)."
59 "Optional: build all products using the given product (all SMESH plugins"
60 " are build after SMESH)."
69 _("Optional: add extra options to the 'make' command."),
76 _("Optional: DO NOT COMPILE just show if products are installed or not."),
84 _("Optional: Stops the command at first product compilation" " fail."),
92 _("Optional: execute the unit tests after compilation"),
101 _("Optional: remove the build directory after successful compilation"),
106 # from sat product infos, represent the product dependencies in a simple python graph
107 # keys are nodes, the list of dependencies are values
108 def get_dependencies_graph(p_infos, compile_time=True):
110 for p_name, p_info in p_infos:
112 for d in p_info.depend:
114 if compile_time and "build_depend" in p_info:
115 for d in p_info.build_depend:
117 graph[p_name] = depprod
121 # this recursive function calculates all the dependencies of node start
122 def depth_search_graph(graph, start, visited=[]):
123 visited = visited + [start]
124 for node in graph[start]: # for all nodes in start dependencies
125 if node not in visited:
126 visited = depth_search_graph(graph, node, visited)
130 # find a path from start node to end (a group of nodes)
131 def find_path_graph(graph, start, end, path=[]):
132 path = path + [start]
135 if start not in graph:
137 for node in graph[start]:
139 newpath = find_path_graph(graph, node, end, path)
145 # Topological sorting algo
146 # return in sorted_nodes the list of sorted nodes
147 def depth_first_topo_graph(graph, start, visited=[], sorted_nodes=[]):
148 visited = visited + [start]
149 if start not in graph:
150 # get more explicit error
151 where = [k for k in graph if start in graph[k]]
152 raise src.SatException(
153 "Error in product dependencies : %s product is referenced in products dependencies, but is not present in the application, from %s"
156 # may be in debug mode, continue loop to get all problems, (if comment raise)
157 # print("WARNING : %s product is referenced in products dependencies but is not present in the application, from %s" % (start, where))
158 # sorted_nodes = sorted_nodes + [start]
159 # return visited, sorted_nodes
160 for node in graph[start]:
161 if node not in visited:
162 visited, sorted_nodes = depth_first_topo_graph(
163 graph, node, visited, sorted_nodes
166 if node not in sorted_nodes:
167 raise src.SatException(
168 "Error in product dependencies : cycle detection for node %s and %s"
172 sorted_nodes = sorted_nodes + [start]
173 return visited, sorted_nodes
176 # check for p_name that all dependencies are installed
177 def check_dependencies(config, p_name_p_info, all_products_dict):
178 l_depends_not_installed = []
179 for prod in p_name_p_info[1]["depend_all"]:
180 # for each dependency, check the install
181 prod_name, prod_info = all_products_dict[prod]
182 if not (src.product.check_installation(config, prod_info)):
183 l_depends_not_installed.append(prod_name)
184 return l_depends_not_installed # non installed deps
187 def log_step(logger, header, step):
188 logger.write("\r%s%s" % (header, " " * 30), 3)
189 logger.write("\r%s%s" % (header, step), 3)
193 def log_res_step(logger, res):
195 logger.write("%s \n" % src.printcolors.printcSuccess("OK"), 4)
198 logger.write("%s \n" % src.printcolors.printcError("KO"), 4)
202 def clean_before_build(
203 sat, config, options, products_infos, all_products_dict, all_products_graph, logger
205 check_salome_configuration = False
206 updated_products = []
207 for p_name_info in products_infos:
208 p_name, p_info = p_name_info
209 if src.product.product_is_salome(p_info):
210 check_salome_configuration = True
212 # nothing to clean for native or fixed products
214 (not src.product.product_compiles(p_info))
215 or src.product.product_is_native(p_info)
216 or src.product.product_is_fixed(p_info)
220 if src.product.product_is_vcs(p_info):
223 if len(updated_products) > 0:
224 # if other products where updated, check that the current product is a child
225 # in this case it will be also updated
226 if find_path_graph(all_products_graph, p_name, updated_products):
227 logger.write("\nUpdate product %s (child)" % p_name, 5)
231 and os.path.isdir(p_info.source_dir)
232 and os.path.isdir(p_info.install_dir)
234 source_time = os.path.getmtime(p_info.source_dir)
235 install_time = os.path.getmtime(p_info.install_dir)
236 if install_time < source_time:
237 logger.write("\nupdate product %s" % p_name, 5)
240 updated_products.append(p_name)
242 config.VARS.application
248 logger_add_link=logger,
253 if check_salome_configuration:
254 # For salome applications, we check if the sources of configuration modules are present
255 # configuration modules have the property "configure_dependency"
256 # they are implicit prerequisites of the compilation.
259 # get the list of all modules in application
260 all_products_infos = src.product.get_products_infos(
261 config.APPLICATION.products, config
264 # for configuration modules, check if sources are present
265 for prod in all_products_dict:
266 product_name, product_info = all_products_dict[prod]
267 if src.product.product_is_configuration(product_info):
268 check_source = check_source and src.product.check_source(product_info)
272 "\nERROR : SOURCES of %s not found! It is required for"
273 " the configuration\n" % product_name
278 " Get it with the command : sat prepare %s -p %s \n"
279 % (config.APPLICATION.name, product_name)
284 return res # error configure dependency : we stop the compilation
288 def upgrade_all_products(
289 sat, config, options, products_infos, all_products_dict, all_products_graph, logger
291 """Execute the proper configuration commands
292 in each product build directory.
294 :param config Config: The global configuration
295 :param products_info list: List of
296 (str, Config) => (product_name, product_info)
297 :param all_products_dict: Dict of all products
298 :param all_products_graph: graph of all products
299 :param logger Logger: The logger instance to use for the display and logging
300 :return: the number of failing commands.
303 # first loop for the cleaning
314 # second loop to upgrade
316 for p_name_info in products_infos:
317 p_name, p_info = p_name_info
321 header = _("Compilation of %s") % src.printcolors.printcLabel(p_name)
322 header += " %s " % ("." * (len_end_line - len(p_name)))
323 logger.write(header, 3)
326 # Do nothing if the product is not compilable
327 if not src.product.product_compiles(p_info):
328 log_step(logger, header, "ignored")
329 logger.write("\n", 3, False)
332 # Do nothing if the product is native
333 if src.product.product_is_native(p_info):
334 log_step(logger, header, "native")
335 logger.write("\n", 3, False)
338 # Do nothing if the product is fixed (already upgraded by third party)
339 if src.product.product_is_fixed(p_info):
340 log_step(logger, header, "native")
341 logger.write("\n", 3, False)
344 # if not src.product.product_is_vcs(p_info):
345 # log_step(logger, header, "not vcs")
346 # logger.write("\n", 3, False)
349 # Recompute the product information to get the right install_dir
350 # (it could change if there is a clean of the install directory)
351 p_info = src.product.get_product_config(config, p_name)
353 # Check if sources was already successfully installed
354 check_source = src.product.check_source(p_info)
355 is_pip = src.appli_test_property(
357 ) and src.product.product_test_property(p_info, "pip", "yes")
358 # don't check sources with option --show
359 # or for products managed by pip (there sources are in wheels stored in LOCAL.ARCHIVE
360 if not (options.no_compile or is_pip):
363 _("Sources of product not found (try 'sat -h prepare') \n")
365 res += 1 # one more error
368 # if we don't force compilation, check if the was already successfully installed.
369 # we don't compile in this case.
370 if src.product.check_installation(config, p_info):
371 logger.write(_("Already installed"))
372 logger.write(_(" in %s" % p_info.install_dir), 4)
373 logger.write(_("\n"))
376 # If the show option was called, do not launch the compilation
377 if options.no_compile:
378 logger.write(_("Not installed in %s\n" % p_info.install_dir))
381 # Check if the dependencies are installed
382 l_depends_not_installed = check_dependencies(
383 config, p_name_info, all_products_dict
385 if len(l_depends_not_installed) > 0:
386 log_step(logger, header, "")
388 src.printcolors.printcError(
390 "ERROR : the following mandatory product(s) is(are) not installed: "
394 for prod_name in l_depends_not_installed:
395 logger.write(src.printcolors.printcError(prod_name + " "))
399 # Call the function to upgrade the product
400 res_prod, len_end_line, error_step = upgrade_product(
401 sat, p_name_info, config, options, logger, header, len_end_line
405 src.printcolors.printcError(
407 "\nERROR : the product does not compile, retrying after clean it."
412 config.VARS.application
419 logger_add_link=logger,
421 res_prod, len_end_line, error_step = upgrade_product(
422 sat, p_name_info, config, options, logger, header, len_end_line
427 # there was an error, we clean install dir, unless :
428 # - the error step is "check", or
429 # - the product is managed by pip and installed in python dir
430 do_not_clean_install = False
431 is_single_dir = src.appli_test_property(
432 config, "single_install_dir", "yes"
433 ) and src.product.product_test_property(p_info, "single_install_dir", "yes")
436 (error_step == "CHECK")
439 and src.appli_test_property(config, "pip_install_dir", "python")
443 # cases for which we do not want to remove install dir
444 # for is_single_dir and is_pip, the test to determine if the product is already
445 # upgraded is based on configuration file, not the directory
446 do_not_clean_install = True
448 if not do_not_clean_install:
449 # Clean the install directory if there is any
450 logger.write(_("Cleaning the install directory if there is any\n"), 5)
452 config.VARS.application + " --products " + p_name + " --install",
455 logger_add_link=logger,
458 # Clean the build directory if the compilation and tests succeed
459 if options.clean_build_after:
460 log_step(logger, header, "CLEAN BUILD")
462 config.VARS.application + " --products " + p_name + " --build",
465 logger_add_link=logger,
470 logger.write("\r%s%s" % (header, " " * len_end_line), 3)
472 "\r" + header + src.printcolors.printcError("KO ") + error_step
475 "\n==== %(KO)s in compile of %(name)s \n"
476 % {"name": p_name, "KO": src.printcolors.printcInfo("ERROR")},
479 if error_step == "CHECK":
482 "\nINSTALL directory = %s"
483 % src.printcolors.printcInfo(p_info.install_dir)
489 logger.write("\r%s%s" % (header, " " * len_end_line), 3)
490 logger.write("\r" + header + src.printcolors.printcSuccess("OK"))
493 "\nINSTALL directory = %s"
494 % src.printcolors.printcInfo(p_info.install_dir)
498 logger.write("\n==== %s \n" % src.printcolors.printcInfo("OK"), 4)
500 "\n==== Compilation of %(name)s %(OK)s \n"
501 % {"name": p_name, "OK": src.printcolors.printcInfo("OK")},
505 logger.write("\n", 3, False)
507 if res_prod != 0 and options.stop_first_fail:
513 def upgrade_product(sat, p_name_info, config, options, logger, header, len_end):
514 """Execute the proper configuration command(s)
515 in the product build directory.
517 :param p_name_info tuple: (str, Config) => (product_name, product_info)
518 :param config Config: The global configuration
519 :param logger Logger: The logger instance to use for the display
521 :param header Str: the header to display when logging
522 :param len_end Int: the lenght of the the end of line (used in display)
523 :return: 1 if it fails, else 0.
527 p_name, p_info = p_name_info
529 # Get the build procedure from the product configuration.
531 # build_sources : autotools -> build_configure, configure, make, make install
532 # build_sources : cmake -> cmake, make, make install
533 # build_sources : script -> script executions
536 # check if pip should be used : the application and product have pip property
537 if src.appli_test_property(
539 ) and src.product.product_test_property(p_info, "pip", "yes"):
540 res, len_end_line, error_step = upgrade_product_pip(
541 sat, p_name_info, config, options, logger, header, len_end
544 if src.product.product_is_autotools(p_info) or src.product.product_is_cmake(
547 res, len_end_line, error_step = upgrade_product_cmake_autotools(
548 sat, p_name_info, config, options, logger, header, len_end
550 if src.product.product_has_script(p_info):
551 res, len_end_line, error_step = upgrade_product_script(
552 sat, p_name_info, config, options, logger, header, len_end
555 # Check that the install directory exists
556 if res == 0 and not (os.path.exists(p_info.install_dir)):
558 error_step = "NO INSTALL DIR"
560 "Error: despite the fact that all the steps ended successfully,"
561 " no install directory was found !"
563 logger.write(src.printcolors.printcError(msg), 4)
564 logger.write("\n", 4)
565 return res, len_end, error_step
567 # Add the config file corresponding to the dependencies/versions of the
568 # product that have been successfully compiled
570 logger.write(_("Add the config file in installation directory\n"), 5)
571 # for git bases : add the description of git tag
572 src_sha1 = src.system.git_describe(p_info.source_dir)
574 p_info.git_tag_description = src_sha1
575 src.product.add_compile_config_file(p_info, config)
578 # Do the unit tests (call the check command)
579 log_step(logger, header, "CHECK")
580 res_check = sat.check(
581 config.VARS.application + " --products " + p_name,
583 logger_add_link=logger,
590 return res, len_end_line, error_step
593 def upgrade_product_pip(sat, p_name_info, config, options, logger, header, len_end):
594 """Execute the proper build procedure for pip products
595 :param p_name_info tuple: (str, Config) => (product_name, product_info)
596 :param config Config: The global configuration
597 :param logger Logger: The logger instance to use for the display
599 :param header Str: the header to display when logging
600 :param len_end Int: the lenght of the the end of line (used in display)
601 :return: 1 if it fails, else 0.
604 # pip needs openssl-dev. If openssl is declared in the application, we check it!
605 if "openssl" in config.APPLICATION.products:
606 openssl_cfg = src.product.get_product_config(config, "openssl")
607 if not src.product.check_installation(config, openssl_cfg):
608 raise src.SatException(
610 "please install system openssl development package, it is required for products managed by pip."
614 p_name, p_info = p_name_info
617 pip_install_in_python = False
618 pip_wheels_dir = os.path.join(config.LOCAL.archive_dir, "wheels")
620 config.INTERNAL.command.pip_install
621 ) # parametrized in src/internal
623 # b) get the build environment (useful to get the installed python & pip3)
624 build_environ = src.environment.SalomeEnviron(
625 config, src.environment.Environ(dict(os.environ)), True
627 environ_info = src.product.get_product_dependencies(config, p_name, p_info)
628 build_environ.silent = config.USER.output_verbose_level < 5
629 build_environ.set_full_environ(logger, environ_info)
631 # c- download : check/get pip wheel in pip_wheels_dir
633 config.INTERNAL.command.pip_download
634 + " --destination-directory %s --no-deps %s==%s "
635 % (pip_wheels_dir, p_info.name, p_info.version)
637 logger.write("\n" + pip_download_cmd + "\n", 4, False)
642 cwd=config.LOCAL.workdir,
643 env=build_environ.environ.environ,
644 stdout=logger.logTxtFile,
645 stderr=subprocess.STDOUT,
649 # error is not managed at the stage. error will be handled by pip install
650 # here we just print a message
652 logger.write("Error in pip download\n", 4, False)
654 pip_version_cmd = 'python -c "import pip;print(pip.__version__)"'
655 res_pip_version = subprocess.check_output(
658 cwd=config.LOCAL.workdir,
659 env=build_environ.environ.environ,
660 stderr=subprocess.STDOUT,
662 pip_build_options = res_pip_version.split(".")[0] < 21
664 pip_build_options = True
665 # d- install (in python or in separate product directory)
666 if src.appli_test_property(config, "pip_install_dir", "python"):
667 # pip will install product in python directory"
668 if pip_build_options:
669 pip_install_cmd += " --find-links=%s --build %s %s==%s" % (
676 pip_install_cmd += " --find-links=%s --cache-dir %s %s==%s" % (
682 pip_install_in_python = True
684 # pip will install product in product install_dir
685 pip_install_dir = os.path.join(
686 p_info.install_dir, "lib", "python${PYTHON}", "site-packages"
688 if pip_build_options:
689 pip_install_cmd += " --find-links=%s --build %s --target %s %s==%s" % (
697 pip_install_cmd += " --find-links=%s --cache-dir %s --target %s %s==%s" % (
704 log_step(logger, header, "PIP")
705 logger.write("\n" + pip_install_cmd + "\n", 4)
706 len_end_line = len_end + 3
713 cwd=config.LOCAL.workdir,
714 env=build_environ.environ.environ,
715 stdout=logger.logTxtFile,
716 stderr=subprocess.STDOUT,
723 # log_res_step(logger, res)
727 "\nError in pip command, please consult details with sat log command's internal traces\n",
731 return res, len_end_line, error_step
734 def upgrade_product_cmake_autotools(
735 sat, p_name_info, config, options, logger, header, len_end
737 """Execute the proper build procedure for autotools or cmake
738 in the product build directory.
740 :param p_name_info tuple: (str, Config) => (product_name, product_info)
741 :param config Config: The global configuration
742 :param logger Logger: The logger instance to use for the display
744 :param header Str: the header to display when logging
745 :param len_end Int: the lenght of the the end of line (used in display)
746 :return: 1 if it fails, else 0.
749 p_name, p_info = p_name_info
751 # Execute "sat configure", "sat make" and "sat install"
755 # Logging and sat command call for configure step
756 len_end_line = len_end
757 log_step(logger, header, "CONFIGURE")
758 res_c = sat.configure(
759 config.VARS.application + " --products " + p_name,
761 logger_add_link=logger,
763 log_res_step(logger, res_c)
767 error_step = "CONFIGURE"
769 # Logging and sat command call for make step
770 # Logging take account of the fact that the product has a compilation
772 if src.product.product_has_script(p_info):
773 # if the product has a compilation script,
774 # it is executed during make step
775 scrit_path_display = src.printcolors.printcLabel(p_info.compil_script)
776 log_step(logger, header, "SCRIPT " + scrit_path_display)
777 len_end_line = len(scrit_path_display)
779 log_step(logger, header, "MAKE")
780 make_arguments = config.VARS.application + " --products " + p_name
781 # Get the make_flags option if there is any
782 if options.makeflags:
783 make_arguments += " --option -j" + options.makeflags
784 res_m = sat.make(make_arguments, verbose=0, logger_add_link=logger)
785 log_res_step(logger, res_m)
791 # Logging and sat command call for make install step
792 log_step(logger, header, "MAKE INSTALL")
793 res_mi = sat.makeinstall(
794 config.VARS.application + " --products " + p_name,
796 logger_add_link=logger,
799 log_res_step(logger, res_mi)
803 error_step = "MAKE INSTALL"
805 return res, len_end_line, error_step
808 def upgrade_product_script(sat, p_name_info, config, options, logger, header, len_end):
809 """Execute the script build procedure in the product build directory.
811 :param p_name_info tuple: (str, Config) => (product_name, product_info)
812 :param config Config: The global configuration
813 :param logger Logger: The logger instance to use for the display
815 :param header Str: the header to display when logging
816 :param len_end Int: the lenght of the the end of line (used in display)
817 :return: 1 if it fails, else 0.
820 p_name, p_info = p_name_info
822 # Execute "sat configure", "sat make" and "sat install"
825 # Logging and sat command call for the script step
826 scrit_path_display = src.printcolors.printcLabel(p_info.compil_script)
827 log_step(logger, header, "SCRIPT " + scrit_path_display)
828 len_end_line = len_end + len(scrit_path_display)
830 config.VARS.application + " --products " + p_name,
832 logger_add_link=logger,
834 log_res_step(logger, res)
836 return res, len_end_line, error_step
840 """method that is called when salomeTools is called with --help option.
842 :return: The text to display for the upgrade command description.
846 "The upgrade command constructs the products of the application"
847 "\n\nexample:\nsat upgrade SALOME-master --products KERNEL,GUI,"
848 "MEDCOUPLING --clean_all"
852 def run(args, runner, logger):
853 """method that is called when salomeTools is called with upgrade parameter."""
855 (options, args) = parser.parse_args(args)
857 # check that the command has been called with an application
858 src.check_config_has_application(runner.cfg)
860 # write warning if platform is not declared as supported
861 src.check_platform_is_supported(runner.cfg, logger)
863 # Print some informations
866 "Executing the upgrade commands in the build "
867 "directories of the products of "
868 "the application %s\n"
870 % src.printcolors.printcLabel(runner.cfg.VARS.application),
876 _("SOURCE directory"),
877 os.path.join(runner.cfg.APPLICATION.workdir, "SOURCES"),
879 (_("BUILD directory"), os.path.join(runner.cfg.APPLICATION.workdir, "BUILD")),
881 src.print_info(logger, info)
883 # Get the list of all application products, and create its dependency graph
884 all_products_infos = src.product.get_products_infos(
885 runner.cfg.APPLICATION.products, runner.cfg
887 all_products_graph = get_dependencies_graph(all_products_infos)
889 "Dependency graph of all application products : %s\n" % all_products_graph, 6
891 # DBG.write("Dependency graph of all application products : ", all_products_graph)
893 # Get the list of products we have to upgrade
894 products_infos = src.product.get_products_list(options, runner.cfg, logger)
895 products_list = [pi[0] for pi in products_infos]
898 "Product we have to upgrade (as specified by user) : %s\n" % products_list, 5
901 # Extend the list with all recursive dependencies of the given products
903 for p_name in products_list:
904 visited = depth_search_graph(all_products_graph, p_name, visited)
905 products_list = visited
907 logger.write("Product list to upgrade with fathers : %s\n" % products_list, 5)
909 # Extend the list with all products that depends upon the given products
911 for n in all_products_graph:
912 # for all products (that are not in products_list):
913 # if we we find a path from the product to the product list,
914 # then we product is a child and we add it to the children list
915 if (n not in children) and (n not in products_list):
916 if find_path_graph(all_products_graph, n, products_list):
917 children = children + [n]
918 # complete products_list (the products we have to upgrade) with the list of children
919 products_list = products_list + children
920 logger.write("Product list to upgrade with children : %s\n" % products_list, 5)
922 # Sort the list of all products (topological sort).
923 # the products listed first do not depend upon products listed after
926 for n in all_products_graph:
927 if n not in visited_nodes:
928 visited_nodes, sorted_nodes = depth_first_topo_graph(
929 all_products_graph, n, visited_nodes, sorted_nodes
932 "Complete dependency graph topological search (sorting): %s\n" % sorted_nodes, 6
935 # Create a dict of all products to facilitate products_infos sorting
936 all_products_dict = {}
937 for pname, pinfo in all_products_infos:
938 all_products_dict[pname] = (pname, pinfo)
940 # Use the sorted list of all products to sort the list of products we have to upgrade
941 sorted_product_list = []
942 product_list_runtime = []
943 product_list_compiletime = []
945 # store at beginning compile time products, we need to compile them before!
946 for n in sorted_nodes:
947 if n in products_list:
948 sorted_product_list.append(n)
949 logger.write("Sorted list of products to upgrade : %s\n" % sorted_product_list, 5)
951 # from the sorted list of products to upgrade, build a sorted list of products infos
953 for product in sorted_product_list:
954 products_infos.append(all_products_dict[product])
956 # for all products to upgrade, store in "depend_all" field the complete dependencies (recursive)
957 # (will be used by check_dependencies function)
958 for pi in products_infos:
960 dep_prod = depth_search_graph(all_products_graph, pi[0], dep_prod)
961 pi[1]["depend_all"] = dep_prod[1:]
963 # Call the function that will loop over all the products and execute
964 # the right command(s)
965 res = upgrade_all_products(
975 # Print the final state
976 nb_products = len(products_infos)
983 _("\nCompilation: %(status)s (%(valid_result)d/%(nb_products)d)\n")
985 "status": src.printcolors.printc(final_status),
986 "valid_result": nb_products - res,
987 "nb_products": nb_products,