Peano
Loading...
Searching...
No Matches
Makefile.py
Go to the documentation of this file.
1# This file is part of the Peano project. For conditions of distribution and
2# use, please see the copyright notice at www.peano-framework.org
3import os
4import re
5import sys
6import subprocess
7import jinja2
8
9from .Helper import write_file
10
11from .CompileMode import CompileMode
12
13
14class Makefile(object):
15 """Represents the created Makefile of a Peano 4 project"""
16
17 default_overwrite = True
18
19 def __init__(self):
20 self.clear()
21
22 @property
23 def readme_entry(self):
24 result_template = jinja2.Template(
25 """
26## Makefile settings
27
28~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29CXX = {{CXX}}
30CXXFLAGS = {{CXXFLAGS}}
31FC = {{FC}}
32FCFLAGS = {{FCFLAGS}}
33LDFLAGS = {{LDFLAGS}}
34LIBS = {{LIBS}}
35DIM = {{DIM}}
36CONFIGUREPATH = {{CONFIGUREPATH}}
37EXECUTABLENAME = {{EXECUTABLENAME}}
38FORTRAN_MODULES = {{FORTRAN_MODULES}}
39MODE = {{MODE}}
40~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
41
42## Compiler version
43
44~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45{{VERSION}}
46~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
47
48"""
49 )
50 try:
51 version_string = subprocess.run(
52 [self.d["CXX"], "--version"], stdout=subprocess.PIPE
53 )
54 except Exception:
55 version_string = subprocess.run(
56 ["echo", "Unknown"], shell=True, stdout=subprocess.PIPE
57 )
58 self.d["VERSION"] = version_string.stdout.decode("utf-8")
59 return result_template.render(**self.d) + self.configure_call
60
61 @property
63 return jinja2.Template(
64 """
65{% if Parallel %}
66
67### MPI parallelisation
68
69Peano's distributed memory parallelisation is based upon plain space-filling
70curves which are used first to split the cells among the ranks, and, after
71that, to split the cells into chunks and deploy them onto cores (if shared
72memory is used). There is no particularly novel contribution/paper on this
73aspect of the code. The paper that is (algorithmically) most interesting is
74an old EuroPar publication:
75
76 @InProceedings{Bungartz:2006:Parallelisation,
77 author={Bungartz, Hans-Joachim and Mehl, Miriam and Weinzierl, Tobias},
78 editor={Nagel, Wolfgang E. and Walter, Wolfgang V. and Lehner, Wolfgang},
79 title={A Parallel Adaptive Cartesian PDE Solver Using Space--Filling Curves},
80 booktitle={Euro-Par 2006 Parallel Processing},
81 year={2006},
82 publisher={Springer Berlin Heidelberg},
83 address={Berlin, Heidelberg},
84 pages={1064--1074},
85 abstract={In this paper, we present a parallel multigrid PDE solver working on adaptive hierarchical cartesian grids. The presentation is restricted to the linear elliptic operator of second order, but extensions are possible and have already been realised as prototypes. Within the solver the handling of the vertices and the degrees of freedom associated to them is implemented solely using stacks and iterates of a Peano space--filling curve. Thus, due to the structuredness of the grid, two administrative bits per vertex are sufficient to store both geometry and grid refinement information. The implementation and parallel extension, using a space--filling curve to obtain a load balanced domain decomposition, will be formalised. In view of the fact that we are using a multigrid solver of linear complexity {\\$}{\\backslash}mathcal{\\{}O{\\}}(n){\\$}, it has to be ensured that communication cost and, hence, the parallel algorithm's overall complexity do not exceed this linear behaviour.},
86 isbn={978-3-540-37784-9}
87 }
88
89{% endif %}
90{% if SharedOMP %}
91
92### OpenMP parallelisation
93
94Peano 4 run uses a wrapper around OpenMP to obtain a high task efficiency.
95The wrapper can be read as user-level threading implemented on top of OpenMP's
96tasking mechanism. It is described in
97
98 @article{Schulz:2021:Tasking,
99 title = {Task inefficiency patterns for a wave equation solver},
100 journal = {IWOMP},"
101 year = {2021},
102 author = {Holger Schulz and Gonzalo Brito Gadeschi and Oleksandr Rudyy and Tobias Weinzierl},
103 }
104
105{% endif %}
106
107{% if SharedOMP %}
108
109Peano relies on a mixture of classic domain decomposition and task-based
110parallelism. The domain decomposition provides the baseline performance,
111and the tasking adds the big flexibility and scalability gain on top. The
112key publication discussing the overall idea and algorithmic ingredients is
113the SISC paper
114
115 @article{Charrier:2020:Enclave,
116 author = {Charrier, Dominic Etienne and Hazelwood, Benjamin and Weinzierl, Tobias},
117 title = {Enclave Tasking for DG Methods on Dynamically Adaptive Meshes},
118 journal = {SIAM Journal on Scientific Computing},
119 volume = {42},
120 number = {3},
121 pages = {C69-C96},
122 year = {2020},
123 doi = {10.1137/19M1276194},
124 URL = {https://doi.org/10.1137/19M1276194},
125 eprint = {https://doi.org/10.1137/19M1276194}
126 }
127
128{% endif %}
129
130"""
131 ).render(**self.d)
132
134 return self.d["EXECUTABLENAME"]
135
136 def clear(self):
138 self.d = {}
139 self.d["CXX"] = ""
140 self.d["CXXFLAGS"] = ""
141 self.d["FC"] = ""
142 self.d["FCFLAGS"] = ""
143 self.d["LDFLAGS"] = ""
144 self.d["LIBS"] = "-lomp"
145 self.d["DIM"] = "2"
146 self.d["CONFIGUREPATH"] = "."
147 self.d["EXECUTABLENAME"] = ""
148 self.d["CMAKE_BUILD_DIR"] = ""
149 self.d["FORTRAN_MODULES"] = []
150 self.d["APP_SUBDIRECTORIES"] = []
151 self.set_mode(CompileMode.DEBUG)
152 self.clear_files()
153
154 def set_executable_name(self, fname):
155 self.d["EXECUTABLENAME"] = fname
156
157 def clear_files(self):
158 self.hfiles = []
159 self.cppfiles = []
160 self.fortranfiles = []
164
165 def set_dimension(self, dimension):
166 self.d["DIM"] = str(dimension)
167
169 """
170 Returns the directory where the ./configure script is located
171 """
172 return self.d["CONFIGUREPATH"]
173
175 """
176 Returns the directory where the ./configure script is located
177 """
178 return self.get_configure_path() + "/src"
179
180 def add_header_search_path(self, path):
181 """
182 Add the header search path to both the C++ and the Fortran
183 call command.
184 """
185 self.d["CXXFLAGS"] += " -I" + path
186 self.d["FCFLAGS"] += " -I" + path
187
188 def add_library(self, library_name, library_path=""):
189 """
190 If you want to link against a library from Peano, feel free to use
191 get_Peano4_source_directory() and hand in a concatenation of this
192 path plus a subpath. Otherwise, specify the absolute path where to
193 search for. By default, Peano's src directory is in the search
194 path.
195
196 A popular invocation including one of Peano's toolboxes is
197
198 project.output.makefile.add_library("ToolboxFiniteElements2d_trace", project.output.makefile.get_source_path() + "/toolbox/finiteelements")
199 """
200 if library_path != "":
201 self.d["LIBS"] = "-L" + library_path + " " + self.d["LIBS"]
202 self.d["LIBS"] += library_name + " "
203
204 def set_mode(self, mode):
205 """
206 mode should be of type CompileMode. Pass in
207
208 peano4.api.output.CompileMode.DEBUG
209
210 for example. Debug is the default.
211 """
212 if mode == CompileMode.DEBUG:
213 self.d["MODE"] = "DEBUG"
214 elif mode == CompileMode.ASSERTS:
215 self.d["MODE"] = "ASSERTS"
216 elif mode == CompileMode.STATS:
217 self.d["MODE"] = "STATS"
218 elif mode == CompileMode.TRACE:
219 self.d["MODE"] = "TRACE"
220 elif mode == CompileMode.RELEASE:
221 self.d["MODE"] = "RELEASE"
222 else:
223 assert False
224
225 def set_CXX_compiler(self, value):
226 self.d["CXX"] = value
227
228 def set_CXX_flags(self, value):
229 self.d["CXXFLAGS"] = value
230
231 def add_CXX_flag(self, value, force=False):
232 if value in self.d["CXXFLAGS"] and not force:
233 print(
234 "CXXFLAG "
235 + value
236 + " is already in list of flags. Ignored as force attribute is not set"
237 )
238 else:
239 self.d["CXXFLAGS"] += " " + value
240
241 def set_Fortran_compiler(self, value):
242 self.d["FC"] = value
243
244 def set_Fortran_flags(self, value):
245 self.d["FCFLAGS"] = value
246
247 def add_Fortran_flag(self, value, force=False):
248 if value in self.d["FCFLAGS"] and not force:
249 print(
250 "FCFLAGS "
251 + value
252 + " is already in list of flags. Ignored as force attribute is not set"
253 )
254 else:
255 self.d["FCFLAGS"] += " " + value
256
257 def add_linker_flag(self, value):
258 self.d["LDFLAGS"] += " " + value
259
260 def set_linker_flags(self, value):
261 self.d["LDFLAGS"] = value + " "
262
263 def parse_configure_script_outcome(self, directory):
264 """
265 directory should point to the directory which holds the ./configure script.
266 It furthermore has to be invoked after configure has passed successfully.
267 This script does not accept relative paths. We then search for the subdirectory
268 src and parse the Makefile there.
269 """
270 if 'PEANO_SRC_ROOT_DIR' in os.environ:
271 directory = os.environ['PEANO_SRC_ROOT_DIR']
272
273 self.d["CONFIGUREPATH"] = directory
274
275 if 'PEANO_CMAKE_BUILD_DIR' in os.environ: # If this environment flag has been set, favour CMake
276 self.d["CMAKE_BUILD_DIR"] = os.environ['PEANO_CMAKE_BUILD_DIR']
277 else: # If not, and an Automake configuration is present, favour this instead
278 try:
279 input_file = directory + "/config.log"
280 input = open(input_file, "r")
281 self.configure_call = ""
282 print("parse configure outcome " + input_file + " to extract configure settings")
283 MakefileDefined = [
284 "Parallel",
285 "SharedOMP",
286 "SharedSYCL",
287 "SharedCPP",
288 ]
289
290 for line in input:
291 if "./configure" in line and self.configure_call == "":
292 print("found the configure call info " + line)
293 self.configure_call = line.strip()
294 for define in MakefileDefined:
295 if re.search("#define " + define, line):
296 self.d[define] = "1"
297
298 input_file = directory + "/src/Makefile"
299 input = open(input_file, "r")
300 print("parse configure outcome " + input_file + " to extract compile settings")
301 MakefileConstants = [
302 "CXXFLAGS_PEANO_2D_RELEASE",
303 "CXXFLAGS_PEANO_2D_STATS",
304 "CXXFLAGS_PEANO_2D_ASSERTS",
305 "CXXFLAGS_PEANO_2D_TRACE",
306 "CXXFLAGS_PEANO_2D_DEBUG",
307 "CXXFLAGS_PEANO_3D_RELEASE",
308 "CXXFLAGS_PEANO_3D_STATS",
309 "CXXFLAGS_PEANO_3D_ASSERTS",
310 "CXXFLAGS_PEANO_3D_TRACE",
311 "CXXFLAGS_PEANO_3D_DEBUG",
312 "LDFLAGS_PEANO_RELEASE",
313 "LDFLAGS_PEANO_STATS",
314 "LDFLAGS_PEANO_ASSERTS",
315 "LDFLAGS_PEANO_TRACE",
316 "LDFLAGS_PEANO_DEBUG",
317 "LDADD_PEANO_2D_RELEASE",
318 "LDADD_PEANO_2D_STATS",
319 "LDADD_PEANO_2D_ASSERTS",
320 "LDADD_PEANO_2D_TRACE",
321 "LDADD_PEANO_2D_DEBUG",
322 "LDADD_PEANO_3D_RELEASE",
323 "LDADD_PEANO_3D_STATS",
324 "LDADD_PEANO_3D_ASSERTS",
325 "LDADD_PEANO_3D_TRACE",
326 "LDADD_PEANO_3D_DEBUG",
327 "CXX",
328 "FC",
329 "CXXFLAGS",
330 "LDFLAGS",
331 "LIBS",
332 "GPU_VENDOR",
333 "GPU_ARCH"
334 ]
335
336 for line in input:
337 for constant in MakefileConstants:
338 if re.search(constant + " *=", line) and line.startswith(constant):
339 try:
340 flags = line.split("=", 1)[1].strip()
341 #print("add " + constant + "=" + flags)
342 self.d[constant] = flags
343 except:
344 print("Error in " + line + " for token " + constant)
345 return
346 except IOError:
347 self.d["CMAKE_BUILD_DIR"] = next((path for path in (os.path.join(directory, subdir) for subdir in os.listdir(directory)) if os.path.isfile(os.path.join(path, "PeanoTargets.cmake"))), "")
348 pass # If there is no Automake configuration we try CMake as last attempt
349
350 if self.d["CMAKE_BUILD_DIR"] != "":
351 cmake_cache_file = self.d["CMAKE_BUILD_DIR"] + "/CMakeCache.txt"
352 if os.path.isfile(cmake_cache_file):
353 with open(cmake_cache_file, 'r') as input:
354 for line in input:
355 key_value = line.split("=", 1)
356 if len(key_value) == 2:
357 key, value = key_value
358 key = key.split(":")[0].strip()
359 if key == "CMAKE_CXX_COMPILER":
360 self.d["CXX"] = value.strip()
361 if key == "CMAKE_Fortran_COMPILER":
362 self.d["FC"] = value.strip()
363 else: # Nothing has been configured so far
364 raise Exception("""
365 Error: if you call parse_configure_script_outcome(), please hand over directory where
366 ./configure had been called. You passed """ + directory
367 )
368
369 def add_h_file(self, filename, generated=False):
370 """
371 Add a new header filename.
372 This is actually not needed for compilation,
373 but is useful to have for IDEs where the header files can be displayed
374 in the project.
375
376 filename: String
377 Filename of a C/C++ header file. They usually should have the .h/.hpp extension.
378 generated: Bool
379 Use this flag for generated files which can be tracked
380 by the cleaning routines 'distclean' or 'maintainer-clean' of the Makefile.
381 """
382 if generated:
383 if (
384 self.generated_hfiles.count(filename) == 0
385 and self.hfiles.count(filename) == 0
386 ):
387 self.generated_hfiles.append(filename)
388 else:
389 if self.hfiles.count(filename) == 0:
390 self.hfiles.append(filename)
391 if self.generated_hfiles.count(filename) > 0:
392 self.generated_hfiles.remove(filename)
393
394 def remove_h_file(self, filename):
395 if filename in self.generated_hfiles:
396 self.generated_hfiles.remove(filename)
397 if filename in self.hfiles:
398 self.hfiles.remove(filename)
399
400 def add_cpp_file(self, filename, generated=False):
401 """
402 Add a new cpp filename. This is basically a set implementation, i.e., you can
403 add files multiple times, but they are not inserted multiple times. This
404 is important, as the steps add the cpp files. Multiple steps can hold the
405 same action, so this action would be created multiple times.
406
407 All the standard Peano 4 routines rely on this function to add their
408 generated files to the build environment. Nothing stops you however to
409 add more files yourself.
410
411 filename: String
412 Filename of a C++ file. They usually should have the .cpp/.cxx extension.
413 generated: Bool
414 Use this flag for generated files which can be tracked
415 by the cleaning routines 'distclean' or 'maintainer-clean' of the Makefile.
416 """
417 if generated:
418 # Non-generated file takes precedence over generated files.
419 # We only add a generated file if has not been added yet as a non-generated file instead.
420 if (
421 self.generated_cppfiles.count(filename) == 0
422 and self.cppfiles.count(filename) == 0
423 ):
424 self.generated_cppfiles.append(filename)
425 else:
426 # If a file is a non-generated file we definitely want to add it.
427 if self.cppfiles.count(filename) == 0:
428 self.cppfiles.append(filename)
429 # We ensure that we only add a .cpp file once.
430 # Otherwise we would get duplicated symbols errors.
431 # But we never delete a non-generated file.
432 if self.generated_cppfiles.count(filename) > 0:
433 self.generated_cppfiles.remove(filename)
434
435 def remove_cpp_file(self, filename):
436 if filename in self.generated_cppfiles:
437 self.generated_cppfiles.remove(filename)
438 if filename in self.cppfiles:
439 self.cppfiles.remove(filename)
440
441 def add_Fortran_file(self, filename, generated=False):
442 """
443 Add a new Fortran file.
444
445 All the standard Peano 4 routines rely on this function to add their
446 generated files to the build environment. Nothing stops you however to
447 add more files yourself. Don't add a file multiple times. This might
448 break the compiler.
449
450 Fortran is really picky about the translation order. So you have to add
451 the stuff in the right order. Otherwise, Fortran might complain. This is
452 your responsibility.
453
454 If your file defines a module, please do not use this routine, but use
455 add_Fortran_module() instead.
456
457 filename: String
458 Filename of a Fortran file. They usually should have the .f90 extension.
459 generated: Bool
460 Use this flag for generated files which can be tracked
461 by the cleaning routines 'distclean' or 'maintainer-clean' of the Makefile.
462 """
463 if generated:
464 if (
465 self.generated_fortranfiles.count(filename) == 0
466 and self.fortranfiles.count(filename) == 0
467 ):
468 self.generated_fortranfiles.append(filename)
469 else:
470 if self.fortranfiles.count(filename) == 0:
471 self.fortranfiles.append(filename)
472 if self.generated_fortranfiles.count(filename) > 0:
473 self.generated_fortranfiles.remove(filename)
474
475 def remove_Fortran_file(self, filename):
476 if filename in self.generated_fortranfiles:
477 self.generated_fortranfiles.remove(filename)
478 if filename in self.fortranfiles:
479 self.fortranfiles.remove(filename)
480
481 def add_Fortran_module(self, module_file, force=False):
482 """
483 Add a Fortran module
484
485 module_file: String
486 Filename of a Fortran source code file which hosts a module. It should
487 have the extension .f90 or similar.
488
489 force: Boolean
490 Enforce that Fortran module is added even though it might already be in
491 the list.
492 """
493 if not module_file.endswith(".f90"):
494 print(
495 "Warning: Fortran module file does not have extension .f90 ("
496 + module_file
497 + ") and translation thus might fail"
498 )
499 if module_file in self.d["FORTRAN_MODULES"] and not force:
500 print(
501 """Fortran module file
502"""
503 + module_file
504 + """
505is already in module file list. Did not add it once more. You can overwrite
506this default behaviour via the force attribute in add_Fortran_module(). If
507you create multiple Peano 4 makefiles in a row (as you change parameters, e.g.)
508then this message can typically be ignored.
509"""
510 )
511 elif module_file in self.d["FORTRAN_MODULES"] and force:
512 print(
513 "Fortran module file "
514 + module_file
515 + " is already in module file list but force flag is set. Add it"
516 )
517 self.d["FORTRAN_MODULES"].append(module_file)
518 else:
519 self.d["FORTRAN_MODULES"].append(module_file)
520
521 def add_Fortran_modules(self, module_files):
522 for i in module_files:
523 self.add_Fortran_module(i)
524
525 def generate(self, overwrite, directory, subdirectories=[]):
526 """
527 Generates build files and other project-related files based on templates.
528
529 :param overwrite: Specifies whether existing files should be overwritten.
530 :type overwrite: bool
531 :param directory: The directory where the generated files will be placed.
532 :type directory: str
533 """
534 self.d["H_HEADERS"] = self.hfiles
535 self.d["CXX_SOURCES"] = self.cppfiles
536 self.d["FORTRAN_SOURCES"] = self.fortranfiles
537 self.d["GENERATED_H_HEADERS"] = self.generated_hfiles
538 self.d["GENERATED_CXX_SOURCES"] = self.generated_cppfiles
539 self.d["GENERATED_FORTRAN_SOURCES"] = self.generated_fortranfiles
540 if subdirectories:
541 for subdirectory in subdirectories:
542 self.d["APP_SUBDIRECTORIES"].append(subdirectory)
543
544 # Encapsulate file generation as a function
545 def generate_output_file(input_file_path, output_file_path):
546 if write_file(overwrite, self.default_overwritedefault_overwrite, output_file_path):
547 print("write " + output_file_path)
548 template_loader = jinja2.FileSystemLoader(searchpath=os.path.split(input_file_path)[0])
549 templateEnv = jinja2.Environment(loader=template_loader)
550 template = templateEnv.get_template(os.path.split(input_file_path)[1])
551 with open(output_file_path, "w") as output:
552 output.write(template.render(self.d))
553
554 file_dir = os.path.dirname(os.path.realpath(__file__))
555
556 generate_output_file(os.path.join(file_dir, "Gitignore.template"), os.path.join(directory, ".gitignore"))
557
558 if self.d["CMAKE_BUILD_DIR"] != "":
559 generate_output_file(os.path.join(file_dir, "CMakeLists.txt.template"), os.path.join(directory, "CMakeLists.txt"))
560 # Shortcut to the CMake build. Used to mirror Autotools.
561 generate_output_file(os.path.join(file_dir, "Makefile.cmake.template"), os.path.join(directory, "Makefile"))
562 else:
563 generate_output_file(os.path.join(file_dir, "Makefile.template"), os.path.join(directory, "Makefile"))
Represents the created Makefile of a Peano 4 project.
Definition Makefile.py:14
get_configure_path(self)
Returns the directory where the .
Definition Makefile.py:168
add_CXX_flag(self, value, force=False)
Definition Makefile.py:231
add_Fortran_module(self, module_file, force=False)
Add a Fortran module.
Definition Makefile.py:481
add_Fortran_file(self, filename, generated=False)
Add a new Fortran file.
Definition Makefile.py:441
add_Fortran_modules(self, module_files)
Definition Makefile.py:521
get_source_path(self)
Returns the directory where the .
Definition Makefile.py:174
add_Fortran_flag(self, value, force=False)
Definition Makefile.py:247
parse_configure_script_outcome(self, directory)
directory should point to the directory which holds the .
Definition Makefile.py:263
add_library(self, library_name, library_path="")
If you want to link against a library from Peano, feel free to use get_Peano4_source_directory() and ...
Definition Makefile.py:188
add_cpp_file(self, filename, generated=False)
Add a new cpp filename.
Definition Makefile.py:400
add_header_search_path(self, path)
Add the header search path to both the C++ and the Fortran call command.
Definition Makefile.py:180
generate(self, overwrite, directory, subdirectories=[])
Generates build files and other project-related files based on templates.
Definition Makefile.py:525
add_h_file(self, filename, generated=False)
Add a new header filename.
Definition Makefile.py:369
set_mode(self, mode)
mode should be of type CompileMode.
Definition Makefile.py:204