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