Salome HOME
style: black format
[tools/sat.git] / commands / install.py
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2012  CEA/DEN
4 #
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.
9 #
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.
14 #
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
18
19 import os
20 import shutil
21 import re
22 import subprocess
23
24 import src
25 import prepare
26 import src.debug as DBG
27
28 PACKAGE_EXT = ".tar.gz"  # the extension we use for the packages
29
30 # Define all possible option for patch command :  sat patch <options>
31 parser = src.options.Options()
32 parser.add_option(
33     "p",
34     "products",
35     "list2",
36     "products",
37     _(
38         "Optional: products from which to get the sources. This option accepts a comma separated list."
39     ),
40 )
41
42
43 def get_binary_from_archive(config, product_name, product_info, install_dir, logger):
44     """The method get the binary of the product from an archive
45
46     :param config Config: The global configuration
47     :param product_name : The name of the product
48     :param product_info Config: The configuration specific to
49                                the product to be prepared
50     :param install_dir Path: The Path instance corresponding to the
51                             directory where to put the sources
52     :param logger Logger: The logger instance to use for the display and logging
53     :return: True if it succeed, else False
54     :rtype: boolean
55     """
56
57     # check archive exists
58
59     # the expected name of the bin archive, as produced by sat package --bin_products
60     archive_name = (
61         product_name + "-" + product_info.version + "-" + config.VARS.dist + PACKAGE_EXT
62     )
63     # we search this archive in bin directory
64     bin_arch_name = os.path.join("bin", archive_name)
65     # search in the config.PATHS.ARCHIVEPATH
66     arch_path = src.find_file_in_lpath(archive_name, config.PATHS.ARCHIVEPATH, "bin")
67     if not arch_path:
68         # bin archive was not found locally in ARCHIVEPATH
69         # search on ftp site
70         logger.write(
71             "\n   The bin archive is not found on local file system, we try ftp\n", 3
72         )
73         ret = src.find_file_in_ftppath(
74             archive_name,
75             config.PATHS.ARCHIVEFTP,
76             config.LOCAL.archive_dir,
77             logger,
78             "bin",
79         )
80
81         if ret:
82             # archive was found on ftp and stored in ret
83             arch_path = ret
84         else:
85             logger.write("%s  " % src.printcolors.printc(src.OK_STATUS), 3, False)
86             msg = (
87                 _("Archive not found in ARCHIVEPATH, nor on ARCHIVEFTP: '%s'")
88                 % bin_arch_name
89             )
90             logger.write(msg, 3)
91             return 1
92
93     logger.write("arc:%s ... " % src.printcolors.printcInfo(archive_name), 3, False)
94     logger.flush()
95     # Call the system function that do the extraction in archive mode
96     retcode, NameExtractedDirectory = src.system.archive_extract(
97         arch_path, install_dir.dir(), logger
98     )
99
100     # Rename the source directory if
101     # it does not match with product_info.source_dir
102     if NameExtractedDirectory.replace("/", "") != os.path.basename(
103         product_info.install_dir
104     ):
105         shutil.move(
106             os.path.join(
107                 os.path.dirname(product_info.install_dir), NameExtractedDirectory
108             ),
109             product_info.install_dir,
110         )
111
112     return retcode
113
114
115 def get_all_product_binaries(config, products, logger):
116     """Get all the product sources.
117
118     :param config Config: The global configuration
119     :param products List: The list of tuples (product name, product informations)
120     :param logger Logger: The logger instance to be used for the logging
121     :return: the tuple (number of success, dictionary product_name/success_fail)
122     :rtype: (int,dict)
123     """
124
125     # Initialize the variables that will count the fails and success
126     results = dict()
127     good_result = 0
128
129     # Get the maximum name length in order to format the terminal display
130     max_product_name_len = 1
131     if len(products) > 0:
132         max_product_name_len = max(map(lambda l: len(l), products[0])) + 4
133
134     # The loop on all the products from which to get the binaries
135     for product_name, product_info in products:
136         # display and log
137         logger.write("%s: " % src.printcolors.printcLabel(product_name), 3)
138         logger.write(" " * (max_product_name_len - len(product_name)), 3, False)
139         logger.write("\n", 4, False)
140         #
141         do_install_prod = True
142         # check if there is something to do!
143         if src.product.product_is_fixed(product_info):
144             do_install_prod = False
145             msg = (
146                 _("INFO : Not doing anything because the products %s is fixed\n")
147                 % product_name
148             )
149         elif src.product.product_is_native(product_info):
150             do_install_prod = False
151             msg = (
152                 _("INFO : Not doing anything because the products %s is native\n")
153                 % product_name
154             )
155         elif src.appli_test_property(
156             config, "pip", "yes"
157         ) and src.product.product_test_property(product_info, "pip", "yes"):
158             do_install_prod = False
159             msg = (
160                 _(
161                     "INFO : Not doing anything because the products %s is managed by pip\n"
162                 )
163                 % product_name
164             )
165         else:
166             install_dir = src.Path(product_info.install_dir)
167             if install_dir.exists():
168                 do_install_prod = False
169                 msg = (
170                     _(
171                         "INFO : Not doing anything because the install directory already exists:\n    %s\n"
172                     )
173                     % install_dir
174                 )
175
176         if not do_install_prod:
177             logger.write("%s  " % src.printcolors.printc(src.OK_STATUS), 3, False)
178             logger.write(msg, 3)
179             good_result = good_result + 1
180             # Do not get the binaries and go to next product
181             continue
182
183         # we neeed to install binaries for the product
184         retcode = get_binary_from_archive(
185             config, product_name, product_info, install_dir, logger
186         )
187
188         # Check that the sources are correctly get using the files to be tested
189         # in product information
190         if retcode:
191             pass
192             # CNC TODO check md5sum
193             # check_OK, wrong_path = check_sources(product_info, logger)
194             # if not check_OK:
195             #    # Print the missing file path
196             #    msg = _("The required file %s does not exists. " % wrong_path)
197             #    logger.write(src.printcolors.printcError("\nERROR: ") + msg, 3)
198             #    retcode = False
199         # does post install substitutions
200         # for f in $(grep -RIl -e /volatile/salome/jenkins/workspace/Salome_master_CO7/SALOME-9.7.0-CO7/INSTALL INSTALL); do
201         #     sed -i "
202         #        s?/volatile/salome/jenkins/workspace/Salome_master_CO7/SALOME-9.7.0-CO7/INSTALL?$(pwd)/INSTALL?g
203         #            " $f
204         # done
205
206         # show results
207         results[product_name] = retcode
208         if retcode:
209             # The case where it succeed
210             res = src.OK_STATUS
211             good_result = good_result + 1
212         else:
213             # The case where it failed
214             res = src.KO_STATUS
215
216         # print the result
217         if do_install_prod:
218             logger.write("%s\n" % src.printcolors.printc(res), 3, False)
219
220     return good_result, results
221
222
223 def check_sources(product_info, logger):
224     """Check that the sources are correctly get, using the files to be tested
225        in product information
226
227     :param product_info Config: The configuration specific to
228                                 the product to be prepared
229     :return: True if the files exists (or no files to test is provided).
230     :rtype: boolean
231     """
232     # Get the files to test if there is any
233     if "present_files" in product_info and "source" in product_info.present_files:
234         l_files_to_be_tested = product_info.present_files.source
235         for file_path in l_files_to_be_tested:
236             # The path to test is the source directory
237             # of the product joined the file path provided
238             path_to_test = os.path.join(product_info.source_dir, file_path)
239             logger.write(_("\nTesting existence of file: \n"), 5)
240             logger.write(path_to_test, 5)
241             if not os.path.exists(path_to_test):
242                 return False, path_to_test
243             logger.write(src.printcolors.printcSuccess(" OK\n"), 5)
244     return True, ""
245
246
247 def description():
248     """method that is called when salomeTools is called with --help option.
249
250     :return: The text to display for the source command description.
251     :rtype: str
252     """
253     return _(
254         "The install command gets the binaries of the application products "
255         "from local (ARCHIVEPATH) or ftp server.\n\nexample:"
256         "\nsat install SALOME-master --products GEOM,SMESH"
257     )
258
259
260 def run(args, runner, logger):
261     """method that is called when salomeTools is called with install parameter."""
262     DBG.write("install.run()", args)
263     # Parse the options
264     (options, args) = parser.parse_args(args)
265
266     # check that the command has been called with an application
267     src.check_config_has_application(runner.cfg)
268
269     # Print some informations
270     logger.write(
271         _("Getting binaries of the application %s\n")
272         % src.printcolors.printcLabel(runner.cfg.VARS.application),
273         1,
274     )
275     src.printcolors.print_value(logger, "workdir", runner.cfg.APPLICATION.workdir, 2)
276     logger.write("\n", 2, False)
277
278     # Get the list of all application products, and create its dependency graph
279     all_products_infos = src.product.get_products_infos(
280         runner.cfg.APPLICATION.products, runner.cfg
281     )
282     from compile import get_dependencies_graph, depth_search_graph
283
284     all_products_graph = get_dependencies_graph(all_products_infos)
285     # logger.write("Dependency graph of all application products : %s\n" % all_products_graph, 6)
286     DBG.write("Dependency graph of all application products : ", all_products_graph)
287
288     products_infos = []
289     if options.products is None:
290         # implicit selection of all products
291         products_infos = all_products_infos
292     else:
293         # a list of products is specified
294         products_list = options.products
295         # we evaluate the complete list including dependencies (~ to the --with-fathers of sat compile)
296
297         # Extend the list with all recursive dependencies of the given products
298         visited = []
299         for p_name in products_list:
300             visited = depth_search_graph(all_products_graph, p_name, visited)
301         products_list = visited
302         logger.write(
303             "Product we have to compile (as specified by user) : %s\n" % products_list,
304             5,
305         )
306
307         #  Create a dict of all products to facilitate products_infos sorting
308         all_products_dict = {}
309         for (pname, pinfo) in all_products_infos:
310             all_products_dict[pname] = (pname, pinfo)
311
312         # build products_infos for the products we have to install
313         for product in products_list:
314             products_infos.append(all_products_dict[product])
315
316     # Call to the function that gets all the sources
317     good_result, results = get_all_product_binaries(runner.cfg, products_infos, logger)
318
319     # Display the results (how much passed, how much failed, etc...)
320     status = src.OK_STATUS
321     details = []
322
323     logger.write("\n", 2, False)
324     if good_result == len(products_infos):
325         res_count = "%d / %d" % (good_result, good_result)
326     else:
327         status = src.KO_STATUS
328         res_count = "%d / %d" % (good_result, len(products_infos))
329
330         for product in results:
331             if results[product] == 0 or results[product] is None:
332                 details.append(product)
333
334     result = len(products_infos) - good_result
335
336     # write results
337     logger.write(_("Getting binaries of the application:"), 1)
338     logger.write(" " + src.printcolors.printc(status), 1, False)
339     logger.write(" (%s)\n" % res_count, 1, False)
340
341     if len(details) > 0:
342         logger.write(_("Following binaries haven't been get:\n"), 2)
343         logger.write(" ".join(details), 2)
344         logger.write("\n", 2, False)
345
346     return result