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