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