Salome HOME
style: black format
[tools/sat.git] / commands / update.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 src
21
22
23 # Define all possible option for update command :  sat update <options>
24 parser = src.options.Options()
25 parser.add_option(
26     "p",
27     "products",
28     "list2",
29     "products",
30     _("Optional: products to update. This option accepts a comma separated list."),
31 )
32 parser.add_option(
33     "f",
34     "force",
35     "boolean",
36     "force",
37     _("Optional: force to update the products in development mode."),
38 )
39 parser.add_option(
40     "",
41     "force_patch",
42     "boolean",
43     "force_patch",
44     _("Optional: force to apply patch to the products in development mode."),
45 )
46 # parser.add_option('c', 'complete', 'boolean', 'complete',
47 #     _("Optional: completion mode, only update products not present in SOURCES dir."),
48 #     False)
49
50
51 def find_products_already_prepared(l_products):
52     """function that returns the list of products that have an existing source
53        directory.
54
55     :param l_products List: The list of products to check
56     :return: The list of product configurations that have an existing source
57              directory.
58     :rtype: List
59     """
60     l_res = []
61     for p_name_p_cfg in l_products:
62         _, prod_cfg = p_name_p_cfg
63         if "source_dir" in prod_cfg and os.path.exists(prod_cfg.source_dir):
64             l_res.append(p_name_p_cfg)
65     return l_res
66
67
68 def find_git_products(l_products):
69     """
70     function that returns the list of products that have an existing source
71     directory and a git configuration. Those products will be updated using :
72
73     git checkout TARGET_TAG
74     git pull origin TARGET_TAG --ff-only
75
76     Not committed dev or conflict with origin during pull will trigger an error.
77
78     :param l_products List: The list of products to check
79     :return: The list of product configurations that have an existing source
80              directory and a git history.
81     :rtype: List
82     """
83     l_res = []
84     for p_name_p_cfg in l_products:
85         _, prod_cfg = p_name_p_cfg
86         if "source_dir" in prod_cfg and os.path.exists(prod_cfg.source_dir):
87             if prod_cfg.get_source == "git":
88                 l_res.append(p_name_p_cfg)
89     return l_res
90
91
92 def find_products_with_patchs(l_products):
93     """function that returns the list of products that have one or more patches.
94
95     :param l_products List: The list of products to check
96     :return: The list of product configurations that have one or more patches.
97     :rtype: List
98     """
99     l_res = []
100     for p_name_p_cfg in l_products:
101         _, prod_cfg = p_name_p_cfg
102         l_patchs = src.get_cfg_param(prod_cfg, "patches", [])
103         if len(l_patchs) > 0:
104             l_res.append(p_name_p_cfg)
105     return l_res
106
107
108 def description():
109     """method that is called when salomeTools is called with --help option.
110
111     :return: The text to display for the update command description.
112     :rtype: str
113     """
114     return _(
115         "The update command updates the sources under git and gets the sources of "
116         "the application products and apply the patches if there is any."
117         "\n\nexample:\nsat update SALOME-master --products KERNEL,GUI"
118     )
119
120
121 class UpdateOp:
122     """
123     This is an operation class. It is prepared though the init and launched
124     with the launch method.
125
126     This operation updates the products, meaning it get the missing ones, and
127     pull the TARGET_TAG for the already present ones. It prevents from erasing
128     everything, especially .git/ files.
129
130     In case you have uncommited work, the operation will stop.
131
132     In case the remote tracking branch can't be pulled fast-forward (after a
133     checkout to the matching local branch), the operation will stop.
134     """
135
136     def __init__(self, args, runner, logger):
137         """
138         Initialisation of the UpdateOp. The runner and the plateform are
139         checked.
140
141         :args: arguments passed to sat
142         :runner: Sat class instance
143         :logger: Current logger
144         """
145         # check that the command has been called with an application
146         src.check_config_has_application(runner.cfg)
147
148         # write warning if platform is not declared as supported
149         src.check_platform_is_supported(runner.cfg, logger)
150
151         # Parse the options
152         (options, args) = parser.parse_args(args)
153         self._list_of_products = options.products
154         self._force_patch = options.force_patch
155         self._force = options.force
156
157         self.runner = runner
158         self.logger = logger
159         self.products_infos = src.product.get_products_list(
160             options, self.runner.cfg, self.logger
161         )
162
163         # Construct the arguments to pass to the clean, source and patch commands
164         self.args_appli = runner.cfg.VARS.application + " "  # useful whitespace
165
166     @property
167     def products(self):
168         if self._list_of_products:
169             return list(self._list_of_products)
170         return [name for name, _ in self.products_infos]
171
172     def getProductsToPrepare(self):
173         """
174         Remove products that are already prepared and under git tracking so
175         that only new products (and not tracked ones) are prepared.
176         """
177         pi_already_prepared = find_git_products(self.products_infos)
178         l_already_prepared = [i for i, _ in pi_already_prepared]
179         newList, removedList = removeInList(self.products, l_already_prepared)
180         if len(newList) == 0 and len(removedList) > 0:
181             msg = "\nAll the products are already installed, do nothing!\n"
182             self.logger.write(src.printcolors.printcWarning(msg), 1)
183             return 0
184         if len(removedList) > 0:
185             msg = (
186                 "\nList of already prepared products that are skipped : %s\n"
187                 % ",".join(removedList)
188             )
189             self.logger.write(msg, 3)
190         return newList
191
192     def getProductsToUpdate(self):
193         pi_already_prepared = find_git_products(self.products_infos)
194         productsToUpdate = [i for i, _ in pi_already_prepared]
195         return productsToUpdate
196
197     def getProductsToClean(self, listProdToPrepare):
198         ldev_products = [
199             p for p in self.products_infos if src.product.product_is_dev(p[1])
200         ]
201         productsToClean = listProdToPrepare  # default
202         if len(ldev_products) > 0:
203             l_products_not_getted = find_products_already_prepared(ldev_products)
204             listNot = [i for i, _ in l_products_not_getted]
205             productsToClean, removedList = removeInList(listProdToPrepare, listNot)
206             if len(removedList) > 0:
207                 msg = _(
208                     """
209                     Do not get the source of the following products in
210                     development mode.
211                     """
212                 )
213                 msg += "\n%s\n" % ",".join(removedList)
214                 self.logger.write(src.printcolors.printcWarning(msg), 1)
215         return productsToClean
216
217     def getProductsToPatch(self, listProdToPrepare):
218         productsToPatch = listProdToPrepare  # default
219         ldev_products = [
220             p for p in self.products_infos if src.product.product_is_dev(p[1])
221         ]
222         if not self._force_patch and len(ldev_products) > 0:
223             l_products_with_patchs = find_products_with_patchs(ldev_products)
224             listNot = [i for i, _ in l_products_with_patchs]
225             productsToPatch, removedList = removeInList(listProdToPrepare, listNot)
226             if len(removedList) > 0:
227                 msg = _(
228                     """
229                     Do not patch the following products in development mode.
230                     Use the --force_patch option to overwrite it.
231                     """
232                 )
233                 msg += "\n%s\n" % ",".join(removedList)
234                 self.logger.write(src.printcolors.printcWarning(msg), 1)
235         return productsToPatch
236
237     def launch(self):
238         productsToPrepare = self.getProductsToPrepare()
239         args_product_to_prepare_opt = "--products " + ",".join(productsToPrepare)
240
241         productsToUpdate = self.getProductsToUpdate()
242         args_product_to_update_opt = "--products " + ",".join(productsToUpdate)
243
244         productsToClean = self.getProductsToClean(productsToPrepare)
245         args_product_opt_clean = "--products " + ",".join(productsToClean)
246
247         productsToPatch = self.getProductsToPatch(productsToPrepare)
248         args_product_opt_patch = "--products " + ",".join(productsToPatch)
249
250         # Initialize the results to a running status
251         res_clean = 0
252         res_source = 0
253         res_patch = 0
254
255         # Call the commands using the API
256         if self._force:
257             if len(productsToClean) > 0:
258                 msg = _("Clean the source directories ...")
259                 self.logger.write(msg, 3)
260                 self.logger.flush()
261                 args_clean = self.args_appli + args_product_opt_clean + " --sources"
262                 res_clean = self.runner.clean(
263                     args_clean, batch=True, verbose=0, logger_add_link=self.logger
264                 )
265                 if res_clean == 0:
266                     self.logger.write("%s\n" % src.printcolors.printc(src.OK_STATUS), 3)
267                 else:
268                     self.logger.write("%s\n" % src.printcolors.printc(src.KO_STATUS), 3)
269             if len(productsToPrepare) > 0:
270                 msg = _("Get the sources of the products ...")
271                 self.logger.write(msg, 5)
272                 args_source = self.args_appli + args_product_to_prepare_opt
273                 res_source = self.runner.source(
274                     args_source, logger_add_link=self.logger
275                 )
276                 if res_source == 0:
277                     self.logger.write("%s\n" % src.printcolors.printc(src.OK_STATUS), 5)
278                 else:
279                     self.logger.write("%s\n" % src.printcolors.printc(src.KO_STATUS), 5)
280             if len(productsToPatch) > 0:
281                 msg = _("Patch the product sources (if any) ...")
282                 self.logger.write(msg, 5)
283                 args_patch = self.args_appli + args_product_opt_patch
284                 res_patch = self.runner.patch(args_patch, logger_add_link=self.logger)
285                 if res_patch == 0:
286                     self.logger.write("%s\n" % src.printcolors.printc(src.OK_STATUS), 5)
287                 else:
288                     self.logger.write("%s\n" % src.printcolors.printc(src.KO_STATUS), 5)
289         if len(productsToUpdate) > 0:
290             msg = _("Update the sources of the products ...")
291             self.logger.write(msg, 5)
292             args_source = self.args_appli + args_product_to_update_opt
293             res_source = self.runner.source_update(
294                 args_source, logger_add_link=self.logger
295             )
296             if res_source == 0:
297                 self.logger.write("%s\n" % src.printcolors.printc(src.OK_STATUS), 5)
298             else:
299                 self.logger.write("%s\n" % src.printcolors.printc(src.KO_STATUS), 5)
300         return res_clean + res_source + res_patch
301
302
303 def run(args, runner, logger):
304     """method that is called when salomeTools is called with update parameter."""
305     updateOp = UpdateOp(args, runner, logger)
306     res = updateOp.launch()
307     return res
308
309
310 def removeInList(aList, removeList):
311     """Removes elements of removeList list from aList
312
313     :param aList: (list) The list from which to remove elements
314     :param removeList: (list) The list which contains elements to remove
315     :return: (list, list) (list with elements removed, list of elements removed)
316     """
317     res1 = [i for i in aList if i not in removeList]
318     res2 = [i for i in aList if i in removeList]
319     return (res1, res2)