Salome HOME
sat #32302 pip option --build obsolète : integration du patch fourni par Nabil
[tools/sat.git] / commands / generate.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2013  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 sys
21 import shutil
22 import imp
23 import subprocess
24
25 import src
26 import src.debug as DBG
27
28 parser = src.options.Options()
29 parser.add_option('p', 'products', 'list2', 'products',
30                   _("Optional: the list of products to generate"))
31 parser.add_option('', 'yacsgen', 'string', 'yacsgen',
32                   _("Optional: path to YACSGEN's module_generator package"))
33
34 def generate_component_list(config, product_name, product_info, context, logger):
35     res = "?"
36     logger.write("\n", 3)
37     for compo in src.product.get_product_components(product_info):
38         header = "  %s %s " % (src.printcolors.printcLabel(compo),
39                                "." * (20 - len(compo)))
40         res = generate_component(config,
41                                  compo,
42                                  product_name,
43                                  product_info,
44                                  context,
45                                  header,
46                                  logger)
47         if config.USER.output_verbose_level == 3:
48             logger.write("\r%s%s\r%s" % (header, " " * 20, header), 3)
49         logger.write(src.printcolors.printc(res), 3, False)
50         logger.write("\n", 3, False)
51     return res
52
53 def generate_component(config, compo, product_name, product_info, context, header, logger):
54 #   get from config include file name and librairy name, or take default value
55     if "hxxfile" in product_info:
56         hxxfile = product_info.hxxfile
57     else:
58         hxxfile = compo + ".hxx"
59     if "cpplib" in product_info:
60         cpplib = product_info.cpplib
61     else:
62         cpplib = "lib" + compo + "CXX.so"
63     cpp_path = product_info.install_dir
64
65     logger.write("%s\n" % header, 4, False)
66     src.printcolors.print_value(logger, "hxxfile", hxxfile, 4)
67     src.printcolors.print_value(logger, "cpplib", cpplib, 4)
68     src.printcolors.print_value(logger, "cpp_path", cpp_path, 4)
69
70     # create a product_info at runtime
71     compo_info = src.pyconf.Mapping(config)
72     compo_info.name = compo
73     compo_info.nb_proc = 1
74     generate_dir = os.path.join(config.APPLICATION.workdir, "GENERATED")
75     install_dir = os.path.join(config.APPLICATION.workdir, "INSTALL")
76     build_dir = os.path.join(config.APPLICATION.workdir, "BUILD")
77     compo_info.source_dir = os.path.join(generate_dir, compo + "_SRC")
78     compo_info.install_dir = os.path.join(install_dir, compo)
79     compo_info.build_dir = os.path.join(build_dir, compo)
80     compo_info.depend = product_info.depend
81     compo_info.depend.append(product_info.name, "") # add cpp module
82     compo_info.opt_depend = product_info.opt_depend
83
84     config.PRODUCTS.addMapping(compo, src.pyconf.Mapping(config), "")
85     config.PRODUCTS[compo].default = compo_info
86
87     builder = src.compilation.Builder(config, logger, product_name, compo_info, check_src=False)
88     builder.header = header
89
90     # generate the component
91     # create GENERATE dir if necessary
92     if not os.path.exists(generate_dir):
93         os.mkdir(generate_dir)
94
95     # delete previous generated directory if it already exists
96     if os.path.exists(compo_info.source_dir):
97         logger.write("  delete %s\n" % compo_info.source_dir, 4)
98         shutil.rmtree(compo_info.source_dir)
99
100     # generate generates in the current directory => change for generate dir
101     curdir = os.curdir
102     os.chdir(generate_dir)
103
104     # inline class to override bootstrap method
105     import module_generator
106     class sat_generator(module_generator.Generator):
107         # old bootstrap for automake (used if salome version <= 7.4)
108         def bootstrap(self, source_dir, log_file):
109             # replace call to default bootstrap() by using subprocess call (cleaner)
110             command = "sh autogen.sh"
111             ier = subprocess.call(command, shell=True, cwd=source_dir,
112                                   stdout=log_file, stderr=subprocess.STDOUT)
113             if ier != 0:
114                 raise src.SatException("bootstrap has ended in error")
115
116     
117     # determine salome version
118     VersionSalome = src.get_salome_version(config)
119     if VersionSalome >= 750 :
120         use_autotools=False
121         builder.log('USE CMAKE', 3)
122     else:
123         use_autotools=True
124         builder.log('USE AUTOTOOLS', 3)
125
126     result = "GENERATE"
127     builder.log('GENERATE', 3)
128     
129     prevstdout = sys.stdout
130     prevstderr = sys.stderr
131
132     try:
133         sys.stdout = logger.logTxtFile
134         sys.stderr = logger.logTxtFile
135
136         if src.product.product_is_mpi(product_info):
137             salome_compo = module_generator.HXX2SALOMEParaComponent(hxxfile,
138                                                                     cpplib,
139                                                                     cpp_path)
140         else:
141             salome_compo = module_generator.HXX2SALOMEComponent(hxxfile,
142                                                                 cpplib,
143                                                                 cpp_path)
144
145         if src.product.product_has_salome_gui(product_info):
146             # get files to build a template GUI
147             try: # try new yacsgen api
148                 gui_files = salome_compo.getGUIfilesTemplate(compo)
149             except:  # use old yacsgen api
150                 gui_files = salome_compo.getGUIfilesTemplate()
151         else:
152             gui_files = None
153
154         mg = module_generator.Module(compo, components=[salome_compo],
155                                      prefix=generate_dir, gui=gui_files)
156         g = sat_generator(mg, context)
157         g.generate()
158
159         if use_autotools:
160             result = "BUID_CONFIGURE"
161             builder.log('BUID_CONFIGURE (no bootstrap)', 3)
162             g.bootstrap(compo_info.source_dir, logger.logTxtFile)
163
164         result = src.OK_STATUS
165     finally:
166         sys.stdout = prevstdout
167         sys.stderr = prevstderr
168
169     # go back to previous directory
170     os.chdir(curdir)
171
172     # do the compilation using the builder object
173     if builder.prepare()!= 0: return "Error in prepare"
174     if use_autotools:
175         if builder.configure()!= 0: return "Error in configure"
176     else:
177         if builder.cmake()!= 0: return "Error in cmake"
178
179     if builder.make(config.VARS.nb_proc, "")!=0: return "Error in make"
180     if builder.install()!=0: return "Error in make install"
181
182     # copy specified logo in generated component install directory
183     # rem : logo is not copied in source dir because this would require
184     #       to modify the generated makefile
185     logo_path = src.product.product_has_logo(product_info)
186     if logo_path:
187         destlogo = os.path.join(compo_info.install_dir, "share", "salome",
188             "resources", compo.lower(), compo + ".png")
189         src.Path(logo_path).copyfile(destlogo)
190
191     return result
192
193 def build_context(config, logger):
194     products_list = [ 'KERNEL', 'GUI' ]
195     ctxenv = src.environment.SalomeEnviron(config,
196                                            src.environment.Environ(dict(
197                                                                    os.environ)),
198                                            True)
199     ctxenv.silent = True
200     ctxenv.set_full_environ(logger, config.APPLICATION.products.keys())
201
202     dicdir = {}
203     for p in products_list:
204         prod_env = p + "_ROOT_DIR"
205         val = os.getenv(prod_env)
206         if os.getenv(prod_env) is None:
207             if p not in config.APPLICATION.products:
208                 warn = _("product %(product)s is not defined. Include it in the"
209                          " application or define $%(env)s.") % \
210                     { "product": p, "env": prod_env}
211                 logger.write(src.printcolors.printcWarning(warn), 1)
212                 logger.write("\n", 3, False)
213                 val = ""
214             val = ctxenv.environ.environ[prod_env]
215         dicdir[p] = val
216
217     # the dictionary requires all keys 
218     # but the generation requires only values for KERNEL and GUI
219     context = {
220         "update": 1,
221         "makeflags": "-j2",
222         "kernel": dicdir["KERNEL"],
223         "gui":    dicdir["GUI"],
224         "yacs":   "",
225         "med":    "",
226         "mesh":   "",
227         "visu":   "",
228         "geom":   "",
229     }
230     return context
231
232 def check_module_generator(directory=None):
233     """Check if module_generator is available.
234     
235     :param directory str: The directory of YACSGEN.
236     :return: The YACSGEN path if the module_generator is available, else None
237     :rtype: str
238     """
239     undo = False
240     if directory is not None and directory not in sys.path:
241         sys.path.insert(0, directory)
242         undo = True
243     
244     res = None
245     try:
246         #import module_generator
247         info = imp.find_module("module_generator")
248         res = info[1]
249     except ImportError:
250         if undo:
251             sys.path.remove(directory)
252         res = None
253
254     return res
255
256 def check_yacsgen(config, directory, logger):
257     """Check if YACSGEN is available.
258     
259     :param config Config: The global configuration.
260     :param directory str: The directory given by option --yacsgen
261     :param logger Logger: The logger instance
262     :return: The path to yacsgen directory
263     :rtype: str
264     """
265     # first check for YACSGEN (command option, then product, then environment)
266     yacsgen_dir = None
267     yacs_src = "?"
268     if directory is not None:
269         yacsgen_dir = directory
270         yacs_src = _("Using YACSGEN from command line")
271     elif 'YACSGEN' in config.APPLICATION.products:
272         yacsgen_info = src.product.get_product_config(config, 'YACSGEN')
273         yacsgen_dir = yacsgen_info.install_dir
274         yacs_src = _("Using YACSGEN from application")
275     elif "YACSGEN_ROOT_DIR" in os.environ:
276         yacsgen_dir = os.getenv("YACSGEN_ROOT_DIR")
277         yacs_src = _("Using YACSGEN from environment")
278
279     if yacsgen_dir is None:
280         return (False, _("The generate command requires YACSGEN."))
281     
282     logger.write("  %s\n" % yacs_src, 2, True)
283     logger.write("  %s\n" % yacsgen_dir, 5, True)
284
285     if not os.path.exists(yacsgen_dir):
286         message = _("YACSGEN directory not found: '%s'") % yacsgen_dir
287         return (False, _(message))
288     
289     # load module_generator
290     c = check_module_generator(yacsgen_dir)
291     if c is not None:
292         return c
293     
294     pv = os.getenv("PYTHON_VERSION")
295     if pv is None:
296         python_info = src.product.get_product_config(config, "Python")
297         pv = '.'.join(python_info.version.split('.')[:2])
298     assert pv is not None, "$PYTHON_VERSION not defined"
299     yacsgen_dir = os.path.join(yacsgen_dir, "lib", "python%s" % pv,
300                                "site-packages")
301     c = check_module_generator(yacsgen_dir)
302     if c is not None:
303         return c
304
305     return (False,
306             _("The python module module_generator was not found in YACSGEN"))
307
308
309 def description():
310     '''method that is called when salomeTools is called with --help option.
311     
312     :return: The text to display for the generate command description.
313     :rtype: str
314     '''
315     return _("The generate command generates SALOME modules from 'pure cpp' "
316              "products.\nWARNING this command NEEDS YACSGEN to run!\n\nexample:"
317              "\nsat generate SALOME-master --products FLICACPP")
318
319
320 def run(args, runner, logger):
321     '''method that is called when salomeTools is called with generate parameter.
322     '''
323     
324     # Check that the command has been called with an application
325     src.check_config_has_application(runner.cfg)
326     
327     logger.write(_('Generation of SALOME modules for application %s\n') % \
328         src.printcolors.printcLabel(runner.cfg.VARS.application), 1)
329
330     (options, args) = parser.parse_args(args)
331
332     status = src.KO_STATUS
333
334     # verify that YACSGEN is available
335     yacsgen_dir = check_yacsgen(runner.cfg, options.yacsgen, logger)
336     
337     if isinstance(yacsgen_dir, tuple):
338         # The check failed
339         __, error = yacsgen_dir
340         msg = _("Error: %s" % error)
341         logger.write(src.printcolors.printcError(msg), 1)
342         logger.write("\n", 1)
343         return 1
344     
345     # Make the generator module visible by python
346     sys.path.insert(0, yacsgen_dir)
347
348     src.printcolors.print_value(logger, _("YACSGEN dir"), yacsgen_dir, 3)
349     logger.write("\n", 2)
350     products = runner.cfg.APPLICATION.products
351     if options.products:
352         products = options.products
353
354     details = []
355     nbgen = 0
356
357     context = build_context(runner.cfg, logger)
358     for product in products:
359         header = _("Generating %s") % src.printcolors.printcLabel(product)
360         header += " %s " % ("." * (20 - len(product)))
361         logger.write(header, 3)
362         logger.flush()
363
364         if product not in runner.cfg.PRODUCTS:
365             logger.write(_("Unknown product\n"), 3, False)
366             continue
367
368         pi = src.product.get_product_config(runner.cfg, product)
369         if not src.product.product_is_generated(pi):
370             logger.write(_("not a generated product\n"), 3, False)
371             continue
372
373         logger.write(_("\nCleaning generated directories\n"), 3, False)
374         # clean source, build and install directories of the generated product
375         # no verbosity to avoid warning at the first generation, for which dirs don't exist
376         runner.clean(runner.cfg.VARS.application + 
377                   " --products " + pi.name + 
378                   " --generated",
379                   batch=True,
380                   verbose=0,
381                   logger_add_link = logger)
382         nbgen += 1
383         try:
384             result = generate_component_list(runner.cfg,
385                                              product,
386                                              pi,
387                                              context,
388                                              logger)
389         except Exception as exc:
390             result = str(exc)
391
392         if result != src.OK_STATUS:
393             result = _("ERROR: %s") % result
394             details.append([product, result])
395
396     if len(details) == 0:
397         status = src.OK_STATUS
398     else: #if config.USER.output_level != 3:
399         logger.write("\n", 2, False)
400         logger.write(_("The following modules were not generated correctly:\n"), 2)
401         for d in details:
402             logger.write("  %s: %s\n" % (d[0], d[1]), 2, False)
403     logger.write("\n", 2, False)
404
405     if status == src.OK_STATUS:
406         return 0
407     return len(details)
408