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
22 import src.debug as DBG
24 # Define all possible option for the compile command : sat compile <options>
25 parser = src.options.Options()
31 _("Optional: products to compile. This option accepts a comma separated list."),
38 "Optional: force the compilation of product, even if it is already installed. The BUILD directory is cleaned before compilation.",
45 "Optional: update mode, compile only products which sources has changed, including the dependencies.",
53 "Optional: build all necessary products to the given product (KERNEL is "
54 "build before building GUI)."
64 "Optional: build all products using the given product (all SMESH plugins"
65 " are build after SMESH)."
74 _("Optional: clean BUILD dir and INSTALL dir before building product."),
82 _("Optional: clean INSTALL dir before building product."),
90 _("Optional: add extra options to the 'make' command."),
97 _("Optional: DO NOT COMPILE just show if products are installed or not."),
105 _("Optional: Stops the command at first product compilation" " fail."),
113 _("Optional: execute the unit tests after compilation"),
122 _("Optional: remove the build directory after successful compilation"),
127 # from sat product infos, represent the product dependencies in a simple python graph
128 # keys are nodes, the list of dependencies are values
129 def get_dependencies_graph(p_infos, compile_time=True):
131 for (p_name, p_info) in p_infos:
133 for d in p_info.depend:
135 if compile_time and "build_depend" in p_info:
136 for d in p_info.build_depend:
138 graph[p_name] = depprod
142 # this recursive function calculates all the dependencies of node start
143 def depth_search_graph(graph, start, visited=[]):
144 visited = visited + [start]
145 for node in graph[start]: # for all nodes in start dependencies
146 if node not in visited:
147 visited = depth_search_graph(graph, node, visited)
151 # find a path from start node to end (a group of nodes)
152 def find_path_graph(graph, start, end, path=[]):
153 path = path + [start]
156 if start not in graph:
158 for node in graph[start]:
160 newpath = find_path_graph(graph, node, end, path)
166 # Topological sorting algo
167 # return in sorted_nodes the list of sorted nodes
168 def depth_first_topo_graph(graph, start, visited=[], sorted_nodes=[]):
169 visited = visited + [start]
170 if start not in graph:
171 # get more explicit error
172 where = [k for k in graph if start in graph[k]]
173 raise src.SatException(
174 "Error in product dependencies : %s product is referenced in products dependencies, but is not present in the application, from %s"
177 # may be in debug mode, continue loop to get all problems, (if comment raise)
178 # print("WARNING : %s product is referenced in products dependencies but is not present in the application, from %s" % (start, where))
179 # sorted_nodes = sorted_nodes + [start]
180 # return visited, sorted_nodes
181 for node in graph[start]:
182 if node not in visited:
183 visited, sorted_nodes = depth_first_topo_graph(
184 graph, node, visited, sorted_nodes
187 if node not in sorted_nodes:
188 raise src.SatException(
189 "Error in product dependencies : cycle detection for node %s and %s"
193 sorted_nodes = sorted_nodes + [start]
194 return visited, sorted_nodes
197 # check for p_name that all dependencies are installed
198 def check_dependencies(config, p_name_p_info, all_products_dict):
199 l_depends_not_installed = []
200 for prod in p_name_p_info[1]["depend_all"]:
201 # for each dependency, check the install
202 prod_name, prod_info = all_products_dict[prod]
203 if not (src.product.check_installation(config, prod_info)):
204 l_depends_not_installed.append(prod_name)
205 return l_depends_not_installed # non installed deps
208 def log_step(logger, header, step):
209 logger.write("\r%s%s" % (header, " " * 30), 3)
210 logger.write("\r%s%s" % (header, step), 3)
214 def log_res_step(logger, res):
216 logger.write("%s \n" % src.printcolors.printcSuccess("OK"), 4)
219 logger.write("%s \n" % src.printcolors.printcError("KO"), 4)
223 def compile_all_products(
224 sat, config, options, products_infos, all_products_dict, all_products_graph, logger
226 """Execute the proper configuration commands
227 in each product build directory.
229 :param config Config: The global configuration
230 :param products_info list: List of
231 (str, Config) => (product_name, product_info)
232 :param all_products_dict: Dict of all products
233 :param all_products_graph: graph of all products
234 :param logger Logger: The logger instance to use for the display and logging
235 :return: the number of failing commands.
238 # first loop for the cleaning
239 check_salome_configuration = False
240 updated_products = []
241 for p_name_info in products_infos:
243 p_name, p_info = p_name_info
244 if src.product.product_is_salome(p_info):
245 check_salome_configuration = True
247 # nothing to clean for native or fixed products
249 (not src.product.product_compiles(p_info))
250 or src.product.product_is_native(p_info)
251 or src.product.product_is_fixed(p_info)
255 # Clean the build and the install directories
256 # if the corresponding options was called
257 if options.clean_all:
259 config.VARS.application
262 + " --build --install",
265 logger_add_link=logger,
269 # Clean the the install directory
270 # if the corresponding option was called
271 if options.clean_install:
273 config.VARS.application + " --products " + p_name + " --install",
276 logger_add_link=logger,
279 # Clean the the install directory
280 # if the corresponding option was called
283 config.VARS.application + " --products " + p_name + " --build",
286 logger_add_link=logger,
289 if options.update and src.product.product_is_vcs(p_info):
290 # only VCS products are concerned by update option
293 if len(updated_products) > 0:
294 # if other products where updated, check that the current product is a child
295 # in this case it will be also updated
297 all_products_graph, p_name, updated_products
299 logger.write("\nUpdate product %s (child)" % p_name, 5)
303 and os.path.isdir(p_info.source_dir)
304 and os.path.isdir(p_info.install_dir)
306 source_time = os.path.getmtime(p_info.source_dir)
307 install_time = os.path.getmtime(p_info.install_dir)
308 if install_time < source_time:
309 logger.write("\nupdate product %s" % p_name, 5)
312 updated_products.append(p_name)
314 config.VARS.application
317 + " --build --install",
320 logger_add_link=logger,
325 if check_salome_configuration:
326 # For salome applications, we check if the sources of configuration modules are present
327 # configuration modules have the property "configure_dependency"
328 # they are implicit prerequisites of the compilation.
331 # get the list of all modules in application
332 all_products_infos = src.product.get_products_infos(
333 config.APPLICATION.products, config
336 # for configuration modules, check if sources are present
337 for prod in all_products_dict:
338 product_name, product_info = all_products_dict[prod]
339 if src.product.product_is_configuration(product_info):
340 check_source = check_source and src.product.check_source(product_info)
344 "\nERROR : SOURCES of %s not found! It is required for"
345 " the configuration\n" % product_name
350 " Get it with the command : sat prepare %s -p %s \n"
351 % (config.APPLICATION.name, product_name)
356 return res # error configure dependency : we stop the compilation
358 # second loop to compile
360 for p_name_info in products_infos:
362 p_name, p_info = p_name_info
366 header = _("Compilation of %s") % src.printcolors.printcLabel(p_name)
367 header += " %s " % ("." * (len_end_line - len(p_name)))
368 logger.write(header, 3)
371 # Do nothing if the product is not compilable
372 if not src.product.product_compiles(p_info):
373 log_step(logger, header, "ignored")
374 logger.write("\n", 3, False)
377 # Do nothing if the product is native
378 if src.product.product_is_native(p_info):
379 log_step(logger, header, "native")
380 logger.write("\n", 3, False)
383 # Do nothing if the product is fixed (already compiled by third party)
384 if src.product.product_is_fixed(p_info):
385 log_step(logger, header, "native")
386 logger.write("\n", 3, False)
389 # Recompute the product information to get the right install_dir
390 # (it could change if there is a clean of the install directory)
391 p_info = src.product.get_product_config(config, p_name)
393 # Check if sources was already successfully installed
394 check_source = src.product.check_source(p_info)
395 is_pip = src.appli_test_property(
397 ) and src.product.product_test_property(p_info, "pip", "yes")
398 # don't check sources with option --show
399 # or for products managed by pip (there sources are in wheels stored in LOCAL.ARCHIVE
400 if not (options.no_compile or is_pip):
403 _("Sources of product not found (try 'sat -h prepare') \n")
405 res += 1 # one more error
408 # if we don't force compilation, check if the was already successfully installed.
409 # we don't compile in this case.
410 if (not options.force) and src.product.check_installation(config, p_info):
411 logger.write(_("Already installed"))
412 logger.write(_(" in %s" % p_info.install_dir), 4)
413 logger.write(_("\n"))
416 # If the show option was called, do not launch the compilation
417 if options.no_compile:
418 logger.write(_("Not installed in %s\n" % p_info.install_dir))
421 # Check if the dependencies are installed
422 l_depends_not_installed = check_dependencies(
423 config, p_name_info, all_products_dict
425 if len(l_depends_not_installed) > 0:
426 log_step(logger, header, "")
428 src.printcolors.printcError(
430 "ERROR : the following mandatory product(s) is(are) not installed: "
434 for prod_name in l_depends_not_installed:
435 logger.write(src.printcolors.printcError(prod_name + " "))
439 # Call the function to compile the product
440 res_prod, len_end_line, error_step = compile_product(
441 sat, p_name_info, config, options, logger, header, len_end_line
446 # there was an error, we clean install dir, unless :
447 # - the error step is "check", or
448 # - the product is managed by pip and installed in python dir
449 do_not_clean_install = False
450 is_single_dir = src.appli_test_property(
451 config, "single_install_dir", "yes"
452 ) and src.product.product_test_property(p_info, "single_install_dir", "yes")
455 (error_step == "CHECK")
458 and src.appli_test_property(config, "pip_install_dir", "python")
462 # cases for which we do not want to remove install dir
463 # for is_single_dir and is_pip, the test to determine if the product is already
464 # compiled is based on configuration file, not the directory
465 do_not_clean_install = True
467 if not do_not_clean_install:
468 # Clean the install directory if there is any
469 logger.write(_("Cleaning the install directory if there is any\n"), 5)
471 config.VARS.application + " --products " + p_name + " --install",
474 logger_add_link=logger,
477 # Clean the build directory if the compilation and tests succeed
478 if options.clean_build_after:
479 log_step(logger, header, "CLEAN BUILD")
481 config.VARS.application + " --products " + p_name + " --build",
484 logger_add_link=logger,
489 logger.write("\r%s%s" % (header, " " * len_end_line), 3)
491 "\r" + header + src.printcolors.printcError("KO ") + error_step
494 "\n==== %(KO)s in compile of %(name)s \n"
495 % {"name": p_name, "KO": src.printcolors.printcInfo("ERROR")},
498 if error_step == "CHECK":
501 "\nINSTALL directory = %s"
502 % src.printcolors.printcInfo(p_info.install_dir)
508 logger.write("\r%s%s" % (header, " " * len_end_line), 3)
509 logger.write("\r" + header + src.printcolors.printcSuccess("OK"))
512 "\nINSTALL directory = %s"
513 % src.printcolors.printcInfo(p_info.install_dir)
517 logger.write("\n==== %s \n" % src.printcolors.printcInfo("OK"), 4)
519 "\n==== Compilation of %(name)s %(OK)s \n"
520 % {"name": p_name, "OK": src.printcolors.printcInfo("OK")},
524 logger.write("\n", 3, False)
526 if res_prod != 0 and options.stop_first_fail:
532 def compile_product(sat, p_name_info, config, options, logger, header, len_end):
533 """Execute the proper configuration command(s)
534 in the product build directory.
536 :param p_name_info tuple: (str, Config) => (product_name, product_info)
537 :param config Config: The global configuration
538 :param logger Logger: The logger instance to use for the display
540 :param header Str: the header to display when logging
541 :param len_end Int: the lenght of the the end of line (used in display)
542 :return: 1 if it fails, else 0.
546 p_name, p_info = p_name_info
548 # Get the build procedure from the product configuration.
550 # build_sources : autotools -> build_configure, configure, make, make install
551 # build_sources : cmake -> cmake, make, make install
552 # build_sources : script -> script executions
555 # check if pip should be used : the application and product have pip property
556 if src.appli_test_property(
558 ) and src.product.product_test_property(p_info, "pip", "yes"):
559 res, len_end_line, error_step = compile_product_pip(
560 sat, p_name_info, config, options, logger, header, len_end
563 if src.product.product_is_autotools(p_info) or src.product.product_is_cmake(
566 res, len_end_line, error_step = compile_product_cmake_autotools(
567 sat, p_name_info, config, options, logger, header, len_end
569 if src.product.product_has_script(p_info):
570 res, len_end_line, error_step = compile_product_script(
571 sat, p_name_info, config, options, logger, header, len_end
574 # Check that the install directory exists
575 if res == 0 and not (os.path.exists(p_info.install_dir)):
577 error_step = "NO INSTALL DIR"
579 "Error: despite the fact that all the steps ended successfully,"
580 " no install directory was found !"
582 logger.write(src.printcolors.printcError(msg), 4)
583 logger.write("\n", 4)
584 return res, len_end, error_step
586 # Add the config file corresponding to the dependencies/versions of the
587 # product that have been successfully compiled
589 logger.write(_("Add the config file in installation directory\n"), 5)
590 # for git bases : add the description of git tag
591 src_sha1 = src.system.git_describe(p_info.source_dir)
593 p_info.git_tag_description = src_sha1
594 src.product.add_compile_config_file(p_info, config)
597 # Do the unit tests (call the check command)
598 log_step(logger, header, "CHECK")
599 res_check = sat.check(
600 config.VARS.application + " --products " + p_name,
602 logger_add_link=logger,
609 return res, len_end_line, error_step
612 def compile_product_pip(sat, p_name_info, config, options, logger, header, len_end):
613 """Execute the proper build procedure for pip products
614 :param p_name_info tuple: (str, Config) => (product_name, product_info)
615 :param config Config: The global configuration
616 :param logger Logger: The logger instance to use for the display
618 :param header Str: the header to display when logging
619 :param len_end Int: the lenght of the the end of line (used in display)
620 :return: 1 if it fails, else 0.
623 # pip needs openssl-dev. If openssl is declared in the application, we check it!
624 if "openssl" in config.APPLICATION.products:
625 openssl_cfg = src.product.get_product_config(config, "openssl")
626 if not src.product.check_installation(config, openssl_cfg):
627 raise src.SatException(
629 "please install system openssl development package, it is required for products managed by pip."
633 p_name, p_info = p_name_info
636 pip_install_in_python = False
637 pip_wheels_dir = os.path.join(config.LOCAL.archive_dir, "wheels")
639 config.INTERNAL.command.pip_install
640 ) # parametrized in src/internal
642 # b) get the build environment (useful to get the installed python & pip3)
643 build_environ = src.environment.SalomeEnviron(
644 config, src.environment.Environ(dict(os.environ)), True
646 environ_info = src.product.get_product_dependencies(config, p_name, p_info)
647 build_environ.silent = config.USER.output_verbose_level < 5
648 build_environ.set_full_environ(logger, environ_info)
650 # c- download : check/get pip wheel in pip_wheels_dir
652 config.INTERNAL.command.pip_download
653 + " --destination-directory %s --no-deps %s==%s "
654 % (pip_wheels_dir, p_info.name, p_info.version)
656 logger.write("\n" + pip_download_cmd + "\n", 4, False)
661 cwd=config.LOCAL.workdir,
662 env=build_environ.environ.environ,
663 stdout=logger.logTxtFile,
664 stderr=subprocess.STDOUT,
668 # error is not managed at the stage. error will be handled by pip install
669 # here we just print a message
671 logger.write("Error in pip download\n", 4, False)
673 pip_version_cmd = 'python -c "import pip;print(pip.__version__)"'
674 res_pip_version = subprocess.check_output(
677 cwd=config.LOCAL.workdir,
678 env=build_environ.environ.environ,
679 stderr=subprocess.STDOUT,
681 pip_build_options = res_pip_version.split(".")[0] < 21
683 pip_build_options = True
684 # d- install (in python or in separate product directory)
685 if src.appli_test_property(config, "pip_install_dir", "python"):
686 # pip will install product in python directory"
687 if pip_build_options:
688 pip_install_cmd += " --find-links=%s --build %s %s==%s" % (
695 pip_install_cmd += " --find-links=%s --cache-dir %s %s==%s" % (
701 pip_install_in_python = True
703 # pip will install product in product install_dir
704 pip_install_dir = os.path.join(
705 p_info.install_dir, "lib", "python${PYTHON}", "site-packages"
707 if pip_build_options:
708 pip_install_cmd += " --find-links=%s --build %s --target %s %s==%s" % (
716 pip_install_cmd += " --find-links=%s --cache-dir %s --target %s %s==%s" % (
723 log_step(logger, header, "PIP")
724 logger.write("\n" + pip_install_cmd + "\n", 4)
725 len_end_line = len_end + 3
732 cwd=config.LOCAL.workdir,
733 env=build_environ.environ.environ,
734 stdout=logger.logTxtFile,
735 stderr=subprocess.STDOUT,
742 # log_res_step(logger, res)
746 "\nError in pip command, please consult details with sat log command's internal traces\n",
750 return res, len_end_line, error_step
753 def compile_product_cmake_autotools(
754 sat, p_name_info, config, options, logger, header, len_end
756 """Execute the proper build procedure for autotools or cmake
757 in the product build directory.
759 :param p_name_info tuple: (str, Config) => (product_name, product_info)
760 :param config Config: The global configuration
761 :param logger Logger: The logger instance to use for the display
763 :param header Str: the header to display when logging
764 :param len_end Int: the lenght of the the end of line (used in display)
765 :return: 1 if it fails, else 0.
768 p_name, p_info = p_name_info
770 # Execute "sat configure", "sat make" and "sat install"
774 # Logging and sat command call for configure step
775 len_end_line = len_end
776 log_step(logger, header, "CONFIGURE")
777 res_c = sat.configure(
778 config.VARS.application + " --products " + p_name,
780 logger_add_link=logger,
782 log_res_step(logger, res_c)
786 error_step = "CONFIGURE"
788 # Logging and sat command call for make step
789 # Logging take account of the fact that the product has a compilation
791 if src.product.product_has_script(p_info):
792 # if the product has a compilation script,
793 # it is executed during make step
794 scrit_path_display = src.printcolors.printcLabel(p_info.compil_script)
795 log_step(logger, header, "SCRIPT " + scrit_path_display)
796 len_end_line = len(scrit_path_display)
798 log_step(logger, header, "MAKE")
799 make_arguments = config.VARS.application + " --products " + p_name
800 # Get the make_flags option if there is any
801 if options.makeflags:
802 make_arguments += " --option -j" + options.makeflags
803 res_m = sat.make(make_arguments, verbose=0, logger_add_link=logger)
804 log_res_step(logger, res_m)
810 # Logging and sat command call for make install step
811 log_step(logger, header, "MAKE INSTALL")
812 res_mi = sat.makeinstall(
813 config.VARS.application + " --products " + p_name,
815 logger_add_link=logger,
818 log_res_step(logger, res_mi)
822 error_step = "MAKE INSTALL"
824 return res, len_end_line, error_step
827 def compile_product_script(sat, p_name_info, config, options, logger, header, len_end):
828 """Execute the script build procedure in the product build directory.
830 :param p_name_info tuple: (str, Config) => (product_name, product_info)
831 :param config Config: The global configuration
832 :param logger Logger: The logger instance to use for the display
834 :param header Str: the header to display when logging
835 :param len_end Int: the lenght of the the end of line (used in display)
836 :return: 1 if it fails, else 0.
839 p_name, p_info = p_name_info
841 # Execute "sat configure", "sat make" and "sat install"
844 # Logging and sat command call for the script step
845 scrit_path_display = src.printcolors.printcLabel(p_info.compil_script)
846 log_step(logger, header, "SCRIPT " + scrit_path_display)
847 len_end_line = len_end + len(scrit_path_display)
849 config.VARS.application + " --products " + p_name,
851 logger_add_link=logger,
853 log_res_step(logger, res)
855 return res, len_end_line, error_step
859 """method that is called when salomeTools is called with --help option.
861 :return: The text to display for the compile command description.
865 "The compile command constructs the products of the application"
866 "\n\nexample:\nsat compile SALOME-master --products KERNEL,GUI,"
867 "MEDCOUPLING --clean_all"
871 def run(args, runner, logger):
872 """method that is called when salomeTools is called with compile parameter."""
874 (options, args) = parser.parse_args(args)
876 # Warn the user if he invoked the clean_all option
877 # without --products option
878 if options.clean_all and options.products is None and not runner.options.batch:
881 "You used --clean_all without specifying a product"
882 " are you sure you want to continue? [Yes/No] "
885 if rep.upper() != _("YES").upper():
888 if options.update and (options.clean_all or options.force or options.clean_install):
889 options.update = False # update is useless in this case
891 # check that the command has been called with an application
892 src.check_config_has_application(runner.cfg)
894 # write warning if platform is not declared as supported
895 src.check_platform_is_supported(runner.cfg, logger)
897 # Print some informations
900 "Executing the compile commands in the build "
901 "directories of the products of "
902 "the application %s\n"
904 % src.printcolors.printcLabel(runner.cfg.VARS.application),
910 _("SOURCE directory"),
911 os.path.join(runner.cfg.APPLICATION.workdir, "SOURCES"),
913 (_("BUILD directory"), os.path.join(runner.cfg.APPLICATION.workdir, "BUILD")),
915 src.print_info(logger, info)
917 # Get the list of all application products, and create its dependency graph
918 all_products_infos = src.product.get_products_infos(
919 runner.cfg.APPLICATION.products, runner.cfg
921 all_products_graph = get_dependencies_graph(all_products_infos)
922 # logger.write("Dependency graph of all application products : %s\n" % all_products_graph, 6)
923 DBG.write("Dependency graph of all application products : ", all_products_graph)
925 # Get the list of products we have to compile
926 products_infos = src.product.get_products_list(options, runner.cfg, logger)
927 products_list = [pi[0] for pi in products_infos]
930 "Product we have to compile (as specified by user) : %s\n" % products_list, 5
933 # Extend the list with all recursive dependencies of the given products
935 for p_name in products_list:
936 visited = depth_search_graph(all_products_graph, p_name, visited)
937 products_list = visited
939 logger.write("Product list to compile with fathers : %s\n" % products_list, 5)
941 # Extend the list with all products that depends upon the given products
943 for n in all_products_graph:
944 # for all products (that are not in products_list):
945 # if we we find a path from the product to the product list,
946 # then we product is a child and we add it to the children list
947 if (n not in children) and (n not in products_list):
948 if find_path_graph(all_products_graph, n, products_list):
949 children = children + [n]
950 # complete products_list (the products we have to compile) with the list of children
951 products_list = products_list + children
952 logger.write("Product list to compile with children : %s\n" % products_list, 5)
954 # Sort the list of all products (topological sort).
955 # the products listed first do not depend upon products listed after
958 for n in all_products_graph:
959 if n not in visited_nodes:
960 visited_nodes, sorted_nodes = depth_first_topo_graph(
961 all_products_graph, n, visited_nodes, sorted_nodes
964 "Complete dependency graph topological search (sorting): %s\n" % sorted_nodes, 6
967 # Create a dict of all products to facilitate products_infos sorting
968 all_products_dict = {}
969 for (pname, pinfo) in all_products_infos:
970 all_products_dict[pname] = (pname, pinfo)
972 # Use the sorted list of all products to sort the list of products we have to compile
973 sorted_product_list = []
974 product_list_runtime = []
975 product_list_compiletime = []
977 # store at beginning compile time products, we need to compile them before!
978 for n in sorted_nodes:
979 if n in products_list:
980 sorted_product_list.append(n)
981 logger.write("Sorted list of products to compile : %s\n" % sorted_product_list, 5)
983 # from the sorted list of products to compile, build a sorted list of products infos
985 for product in sorted_product_list:
986 products_infos.append(all_products_dict[product])
988 # for all products to compile, store in "depend_all" field the complete dependencies (recursive)
989 # (will be used by check_dependencies function)
990 for pi in products_infos:
992 dep_prod = depth_search_graph(all_products_graph, pi[0], dep_prod)
993 pi[1]["depend_all"] = dep_prod[1:]
995 # Call the function that will loop over all the products and execute
996 # the right command(s)
997 res = compile_all_products(
1007 # Print the final state
1008 nb_products = len(products_infos)
1015 _("\nCompilation: %(status)s (%(valid_result)d/%(nb_products)d)\n")
1017 "status": src.printcolors.printc(final_status),
1018 "valid_result": nb_products - res,
1019 "nb_products": nb_products,