configure_cmake.py 17.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"""
Prepare the CMake command to configure the project: a pre-cache file is read but may be partly superseded in command line. In the process, an update pre-cache file is generated; this file will be handy to configure models that relies upon MoReFEM.
"""

import argparse
import os
import subprocess


class configure_cmake(object):
    """
    Prepare the CMake command to configure the project: a pre-cache file is read but may be partly superseded in command line. In the process, an update pre-cache file is generated; this file will be handy to configure models that relies upon MoReFEM.
    """
    
    def __init__(self):
        
        parser = argparse.ArgumentParser(
            description='Generate the CMake build of the project.\n\
            \n\
There are two reasons to use this script instead of the direct cmake invocation:\n\n\
    - There are some options that are often changed (type of library, compiler, whether there is one or more libraries for MoReFEM) and this script allows to limit the number of PreCache files to maintain (one for macOS and one for Linux is enough).\n\
22
    - Keeping the PreCache file with the options actually used is very handy for external models: we may use it directly when creating the cmake build.\n\
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
            \n\
If you need to change a value which is not provided in this script input, you may either introduce it in cmake_args with the -D syntax or just use the --no-run-command flag and edit the generated PreCacheFile.cmake before running the command.',
            formatter_class=argparse.RawDescriptionHelpFormatter)

        self.__command_line_args = self._interpret_command_line(parser)
        self._parse_pre_cache_file()
        self._generate_cache_file()
        self._cmake_command()


    def _interpret_command_line(self, parser):
        """Interpret the content of the command line.
    
        \param[in] The argparse object.
    
        \return The arguments of the command line.
        """
        parser.add_argument(
            '--cache_file',
42
            required=True,
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
            help=
            'The pre-cache file which contains default values for few build options. This argument is the only one that is mandatory; the other ones are optional but may supersede the values written in pre-cache file.'
        )
    
    
        parser.add_argument(
            '--root_directory',
            default="..",
            help=
            'The path to the root directory of the project (in which the base CMakeLists.txt should be). Default is "..".'
        )
    
        parser.add_argument(
            '--third_party_directory',
            help=
            "If you want to supersede pre-cache_file cache value, the path of the directory in which all third libraries are installed."
        )
60
61
62
63
64
65


        def check_mode(value):
            if value not in('debug', 'release'):
                raise argparse.ArgumentTypeError("Mode should be either 'debug' or 'release'; you chose \'{}\'.".format(value))                
            return value.title() # Uppercase the first letter.
66
67
        
        parser.add_argument(
68
69
70
71
72
            '--mode',
            type=check_mode,
            help=
            "Either 'debug' or 'release'; if not specified debug mode is chosen."
        )
73
74
75
76
77
78
79

        parser.add_argument(
            '--install_directory',
            help=
            "If you want to supersede pre-cache_file cache value, the directory into which executables and libraries will be installed (in subdirectories)"
        )

80
81
82
83
84
85
86
87
88
89
        # parser.add_argument(
        #     '--use_sanitizer',
        #     help=
        #     "If you want to supersede pre-cache_file cache value, specify 'True' or 'False' here."
        # )
        
        def check_library_type(value):
            if value not in('shared', 'static'):
                raise argparse.ArgumentTypeError("Library type should be either 'shared' or 'static'; you chose \'{}\'.".format(value))                
            return value.upper()
90
91
92

        parser.add_argument(
            '--library_type',
93
            type=check_library_type,
94
95
96
97
            help=
            "If you want to supersede pre-cache_file cache value, specify 'shared' or 'static' here."
        )

98
99
100
101
102
103
104
105
        def check_boolean(value):
            value = value.lower()
            
            if value not in('true', 'false'):
                raise argparse.ArgumentTypeError("Value should be 'true' or 'false'")
            
            return value == 'true' and True or False

106
107
        parser.add_argument(
            '--morefem_as_single_library',
108
            type=check_boolean,
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
            help=
            "If you want to supersede pre-cache_file cache value, specify 'True' if MoReFEM is to be built as a single library or 'False' if you want several libraries."
        )

        parser.add_argument(
            '--cmake_args',
            help=
            "Arguments you would like to give to cmake command, e.g. '-G Ninja'. Please use quotes or double quotes if there are several components. You might also override here some other CMake default values I haven't bother to make known in this Python Script."
        )

        parser.add_argument(
            '--no_run_command',
            action='store_true',
            help=
            "If this flag is set, the cmake command is just printed on screen, not run."
        )

        args = parser.parse_args()

        if not os.path.isfile(args.cache_file):
            raise Exception("The cache file '{}' does not exist!".format(
                args.cache_file))

        return args


    def _parse_pre_cache_file(self):
        """Parse the content of the pre-cache file and store it inside a dictionary.
    
        \param[in] file The pre-cache file to be parsed.
    
        In output, self.__pre_cache_dict is filled.
        """
        possible_keys = ('CMAKE_INSTALL_PREFIX',
143
                         'MOREFEM_THIRD_PARTY_LIBRARIES_DIR',
144
                         'CMAKE_C_COMPILER', 'CMAKE_CXX_COMPILER',
145
146
147
148
149
150
151
152
153
154
                         'CMAKE_CXX_STANDARD', 'CMAKE_CXX_STANDARD_REQUIRED',
                         'CMAKE_CXX_EXTENSIONS', 'LIBRARY_TYPE',
                         'BUILD_MOREFEM_UNIQUE_LIBRARY',
                         'MOREFEM_CHECK_UPDATE_GHOSTS_CALL_RELEVANCE',
                         'MOREFEM_EXTENDED_TIME_KEEP',
                         'MOREFEM_NO_TRAP_SNES_EXCEPTION', 'BLAS_CUSTOM_LINKER',
                         'BLAS_LIB', 'PHILLIPS_DIR', 'BLAS_LIB_DIR', 
                         'OPEN_MPI_INCL_DIR', 'OPEN_MPI_LIB_DIR',
                         'PETSC_INCL_DIR', 'PETSC_LIB_DIR',
                         'BOOST_INCL_DIR', 'BOOST_LIB_DIR',
155
                         'XTENSOR_INCL_DIR',
156
157
                         'LIBMESH_INCL_DIR', 'LIBMESH_LIB_DIR', 
                         'LIBMESH_MAJOR_VERSION',
158
159
                         'LUA_INCL_DIR', 'LUA_LIB_DIR',
                         'PARMETIS_INCL_DIR', 'PARMETIS_LIB_DIR',
160
                         'CMAKE_BUILD_TYPE', 'MOREFEM_ASAN'
161
                         )
162
163
                         

164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186

        with open(self.__command_line_args.cache_file) as stream:

            self.__pre_cache_dict = {}

            for line in stream:
                splitted = line.split("set(")

                if len(splitted) <= 1:
                    continue

                line = splitted[1]
                splitted = line.split("CACHE")

                key_value = splitted[0]

                splitted = key_value.split()

                key = splitted[0]

                if key not in possible_keys:
                    raise Exception(
                        "Key {} found in file does not belong to the list of expected keys!"
187
                        .format(key))
188
189
190
191

                value = " ".join(splitted[1:])

                self.__pre_cache_dict[key] = value
192
                
193
194
195
196
197
198
199
200
201
202
            
    def _cache_entry(self, entry_name, cmake_type, cmake_comment, command_line_entry_value = None):
        """
        Prepare the line corresponding to an entry in the PreCache file.
        
        \param[in] entry_name Name of the CMake entry, e.g. CMAKE_INSTALL_PREFIX or BUILD_MOREFEM_UNIQUE_LIBRARY.
        \param[in] cmake_type Type of the item in CMake; e.g. STRING, PATH or BOOL
        \param[in] cmake_comment Comment of the entry in CMake, e.g.  "C++ standard; at least 17 is expected."
        \param[in] command_line_entry_value: The value read in the command line if any, None otherwise (in which case the one from the pre-cache file is kept).
        """
203
        if command_line_entry_value is not None:
204
205
            value = command_line_entry_value
        else:
206
207
208
209
210
211
            try:
                value = self.__pre_cache_dict[entry_name]
            except KeyError:
                raise Exception("Entry name {} was not provided in PreCache file {}".format(entry_name, \
                    self.__command_line_args.cache_file))

212
213
            
        entry = 'set({0} {1} CACHE {2} "{3}")'.format(entry_name, value, cmake_type, cmake_comment)
GILLES Sebastien's avatar
GILLES Sebastien committed
214

215
216
217
218
219
220
221
222
223
224
225
226
227
228
        self.__output_file.write("{}\n\n".format(entry))
        

    def _generate_cache_file(self):
        """Generate the actual cache file, with the values on command line in place of the pre-cache one if some were superseded.
    
        A file named PreCacheFile.cmake will be generated.
        """
        args = self.__command_line_args

        self.__output_file = open("PreCacheFile.cmake", "w")
        output_file = self.__output_file
        pre_cache_dict = self.__pre_cache_dict
        
229
        
230
231
232
233
234
235
236
237
238
239
        self._cache_entry("CMAKE_INSTALL_PREFIX", \
                          "PATH",
                          "Installation directory for executables and libraries. A MoReFEM folder will be created there when install is invoked. This value should be overridden in command line!",
                          args.install_directory)
                          
        self._cache_entry("MOREFEM_THIRD_PARTY_LIBRARIES_DIR", \
                          "STRING",
                          "Path to third party directory",
                          args.third_party_directory)
                          
240
241
242
243
244
245
246
247
248
        self._cache_entry("CMAKE_C_COMPILER", \
                          "STRING", 
                          "C compiler. Prefer to use an openmpi wrapper.")
                          

        self._cache_entry("CMAKE_CXX_COMPILER", \
                          "PATH", 
                          "C++ compiler. Prefer to use an openmpi wrapper.")  
                          
249
250
251
252
        # self._cache_entry("MOREFEM_ASAN", \
#                           "BOOL",
#                           "If true address sanitizer is enabled.",
#                           args.use_sanitizer)
253
254
255
256
                          
        self._cache_entry("CMAKE_BUILD_TYPE", \
                          "STRING",
                          "Either 'Debug' or 'Release'",
257
                          args.mode)
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272

        self._cache_entry("CMAKE_CXX_STANDARD",
                          "STRING",
                          "C++ standard; at least 17 is required.")

        self._cache_entry("CMAKE_CXX_STANDARD_REQUIRED",
                          "STRING",
                          "Leave this one active.")
                          
        self._cache_entry("CMAKE_CXX_EXTENSIONS",
                          "STRING",
                          "If ON you might be using gnu++17; with OFF you will use c++17.")                      

        self._cache_entry("LIBRARY_TYPE",
                          "BOOL",
273
274
                          "Choose either STATIC or SHARED.",
                          args.library_type)      
275
276
277
                          
        self._cache_entry("BUILD_MOREFEM_UNIQUE_LIBRARY",
                          "BOOL",
278
279
                          "Whether a unique library is built for MoReFEM core libraries or on the contrary if it is splitted in modules.",
                          args.morefem_as_single_library)
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
                          
        self._cache_entry("MOREFEM_CHECK_UPDATE_GHOSTS_CALL_RELEVANCE",
                          "BOOL",
                          "If true, add a (costly) method that gives an hint whether an UpdateGhost() call was relevant or not.")   
                          
        self._cache_entry("MOREFEM_EXTENDED_TIME_KEEP",
                          "BOOL",
                          "If true, TimeKeep gains the ability to track times between each call of PrintTimeElapsed\(\). If not, PrintTimeElapsed\(\) is flatly ignored. False is the best choice in production!")                              
        self._cache_entry("MOREFEM_NO_TRAP_SNES_EXCEPTION",
                          "BOOL",
                          "If true, exceptions aren\'t caught in the three SNES functions I have to define for a Petsc Newton \(at least the default ones; if you define your own it\'s up to you to introduce the macro in your code\). If not caught, an eventual exception will be written properly but the exception is not guaranteed to be caught and it might result in a rather messy output. I therefore advise not to set it to True in debug mode; in release mode it is ok to do so as such exceptions are rare.")                                               

        self._cache_entry("BLAS_CUSTOM_LINKER",
                          "BOOL",
                          "If BLAS_CUSTOM_LINKER is true, BLAS_LIB field must give the command use to link with Blas. For instance on macOS it is usually \'-framework Accelerate\' \(Beware: Without the quotes CMake will mute this into -framework -Accelerate\(\). If False, FindLibrary is used to find the Blas library to be used, as for the other libraries in this file. The difference is that the name of the .a, .so or .dylib is not known, so it must be given in BLAS_LIB_NAME field. For instance openblas to find libopenblas.a in BLAS_LIB_DIR.")
        
        self._cache_entry("BLAS_LIB",
                          "STRING",
                          "Name of the Blas lib (e.g. openblas) or command to pass if custom linker is used; see BLAS_CUSTOM_LINKER.")


        self._cache_entry("BLAS_LIB_DIR",
                          "STRING",
                          "None or path to the lib directory of Blas (see BLAS_CUSTOM_LINKER).")
                                  

        self._cache_entry("OPEN_MPI_INCL_DIR",
                          "PATH",
GILLES Sebastien's avatar
GILLES Sebastien committed
308
                          "Path to the directory that contains the Openmpi header files.")
309
310
311

        self._cache_entry("OPEN_MPI_LIB_DIR",
                          "PATH",
GILLES Sebastien's avatar
GILLES Sebastien committed
312
                          "Path to the directory that contains the Openmpi library files.")
313
314
315

        self._cache_entry("PETSC_INCL_DIR",
                          "PATH",
GILLES Sebastien's avatar
GILLES Sebastien committed
316
                          "Path to the directory that contains the PETSc header files.")
317
318
319

        self._cache_entry("PETSC_LIB_DIR",
                          "PATH",
GILLES Sebastien's avatar
GILLES Sebastien committed
320
                          "Path to the directory that contains the PETSc library files.")
321
322
323
                          
        self._cache_entry("PARMETIS_INCL_DIR",
                          "PATH",
GILLES Sebastien's avatar
GILLES Sebastien committed
324
                          "Path to the directory that contains the Parmetis header files.")
325
326
327

        self._cache_entry("PARMETIS_LIB_DIR",
                          "PATH",
GILLES Sebastien's avatar
GILLES Sebastien committed
328
                          "Path to the directory that contains the Parmetis library files.")    
329
330
331
                          
        self._cache_entry("LUA_INCL_DIR",
                          "PATH",
GILLES Sebastien's avatar
GILLES Sebastien committed
332
                          "Path to the directory that contains the Lua header files.")
333
334
335

        self._cache_entry("LUA_LIB_DIR",
                          "PATH",
GILLES Sebastien's avatar
GILLES Sebastien committed
336
                          "Path to the directory that contains the Lua library files.")                                                      
337
338
        self._cache_entry("BOOST_INCL_DIR",
                          "PATH",
GILLES Sebastien's avatar
GILLES Sebastien committed
339
                          "Path to the directory that contains the Boost header files.")
340
341
342

        self._cache_entry("BOOST_LIB_DIR",
                          "PATH",
343
344
                          "Path to the directory that contains the Boost library files.") 
                          
345
346
347
348
349
        self._cache_entry("XTENSOR_INCL_DIR",
                          "PATH",
                          "Path to the directory that contains the Xtensor include files.") 
        
                          
350
351
352
353
354
355
356
        self._cache_entry("LIBMESH_INCL_DIR",
                          "PATH",
                          "Path to the directory that contains the Libmesh include files.") 
                          
        self._cache_entry("LIBMESH_LIB_DIR",
                          "PATH",
                          "Path to the directory that contains the Libmesh library files.")                                                             
357
358
359
360
361
                          
        self._cache_entry("PHILLIPS_DIR",
                          "BOOL",
                          "If you want to couple Morefem with Phillips library. False in most of the cases! Beware: it is not put in MOREFEM_COMMON_DEP; if you need it you must add it in your add_executable command.")                      

362
        self.__output_file.close()
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381

    def _cmake_command(self):
        """Generates the cmake command, and either run it or print it depending on no_run_command flag.

        """
        args = self.__command_line_args
        cmd = "cmake -C PreCacheFile.cmake {} {}".format(args.cmake_args, args.root_directory)

        if args.no_run_command:
            print("The generated CMake command is:\n\n\t{}".format(cmd))
        else:
            subprocess.Popen(cmd, shell=True).communicate()


if __name__ == "__main__":
    
    configure_cmake()