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