import os import textwrap import subprocess import socket import ctypes import random import uuid from binarycpython.utils.functions import temp_dir, remove_file def autogen_C_logging_code(logging_dict, verbose=0): """ Function that autogenerates PRINTF statements for binaryc. Input is a dictionary where the key is the header of that logging line and items which are lists of parameters that will be put in that logging line Example:: {'MY_STELLAR_DATA': [ 'model.time', 'star[0].mass', 'model.probability', 'model.dt' ] } """ # Check if the input is of the correct form if not type(logging_dict) == dict: print("Error: please use a dictionary as input") return None code = "" # Loop over dict keys for key in logging_dict: if verbose > 0: print( "Generating Print statement for custom logging code with {} as a header".format( key ) ) logging_dict_entry = logging_dict[key] # Check if item is of correct type: if type(logging_dict_entry) == list: # Construct print statement code += 'Printf("{}'.format(key) code += " {}".format("%g " * len(logging_dict_entry)) code = code.strip() code += '\\n"' # Add format keys for param in logging_dict_entry: code += ",((double)stardata->{})".format(param) code += ");\n" else: print( "Error: please use a list for the list of parameters that you want to have logged" ) code = code.strip() return code #################################################################################### def binary_c_log_code(code, verbose=0): """ Function to construct the code to construct the custom logging function """ if verbose > 0: print("Creating the code for the shared library for the custom logging") # Create code custom_logging_function_string = """\ #pragma push_macro(\"MAX\") #pragma push_macro(\"MIN\") #undef MAX #undef MIN #include \"binary_c.h\" #include \"RLOF/RLOF_prototypes.h\" // add visibility __attribute__ ((visibility ("default"))) to it void binary_c_API_function custom_output_function(struct stardata_t * stardata); void binary_c_API_function custom_output_function(struct stardata_t * stardata) {{ // struct stardata_t * stardata = (struct stardata_t *)x; {}; }} #undef MAX #undef MIN #pragma pop_macro(\"MIN\") #pragma pop_macro(\"MAX\")\ """.format( code ) # print(repr(textwrap.dedent(custom_logging_function_string))) return textwrap.dedent(custom_logging_function_string) def binary_c_write_log_code(code, filename, verbose=0): """ Function to write the generated logging code to a file """ cwd = os.getcwd() filePath = os.path.join(cwd, filename) # Remove if it exists remove_file(filePath, verbose) if verbose > 0: print("Writing the custom logging code to {}".format(filePath)) # Write again with open(filePath, "w") as f: f.write(code) def from_binary_c_config(config_file, flag): """ Function to run the binaryc_config command with flags """ res = subprocess.check_output( "{config_file} {flag}".format(config_file=config_file, flag=flag), shell=True, stderr=subprocess.STDOUT, ) # convert and chop off newline res = res.decode("utf").rstrip() return res def return_compilation_dict(verbose=0): """ Function to build the compile command for the shared library inspired by binary_c_inline_config command in perl TODO: this function still has some cleaning up to do wrt default values for the compile command # https://developers.redhat.com/blog/2018/03/21/compiler-and-linker-flags-gcc/ returns: - string containing the command to build the shared library """ if verbose > 0: print( "Calling the binary_c config code to get the info to build the shared library" ) # use binary_c-config to get necessary flags BINARY_C_DIR = os.getenv("BINARY_C") if BINARY_C_DIR: BINARY_C_CONFIG = os.path.join(BINARY_C_DIR, "binary_c-config") BINARY_C_SRC_DIR = os.path.join(BINARY_C_DIR, "src") # TODO: build in check to see whether the file exists else: raise NameError("Envvar BINARY_C doesnt exist") return None # TODO: make more options for the compiling cc = from_binary_c_config(BINARY_C_CONFIG, "cc") # Check for binary_c BINARY_C_EXE = os.path.join(BINARY_C_DIR, "binary_c") if not os.path.isfile(BINARY_C_EXE): print("We require binary_c executable; have you built binary_c?") raise NameError("BINARY_C executable doesnt exist") # TODO: debug libbinary_c = "-lbinary_c" binclibs = from_binary_c_config(BINARY_C_CONFIG, "libs") libdirs = "{} -L{}".format( from_binary_c_config(BINARY_C_CONFIG, "libdirs"), BINARY_C_SRC_DIR ) bincflags = from_binary_c_config(BINARY_C_CONFIG, "cflags") bincincdirs = from_binary_c_config(BINARY_C_CONFIG, "incdirs") # combine binclibs = " {} {} {}".format(libdirs, libbinary_c, binclibs) # setup defaults: defaults = { "cc": "gcc", # default compiler "ccflags": bincflags, "ld": "ld", # 'ld': $Config{ld}, # default linker "debug": 0, "inc": "{} -I{}".format(bincincdirs, BINARY_C_SRC_DIR), # inc => ' '.($Config{inc}//' ').' '.$bincincdirs." -I$srcdir ", # include the defaults plus # GSL and binary_c # 'libname': libname, # libname is usually just binary_c corresponding to libbinary_c.so "libs": binclibs, } # set values with defaults. TODO: make other input possile. ld = defaults["ld"] debug = defaults["debug"] inc = defaults[ "inc" ] # = ($ENV{BINARY_GRID2_INC} // $defaults{inc}).' '.($ENV{BINARY_GRID2_EXTRAINC} // ''); libs = defaults[ "libs" ] # = ($ENV{BINARY_GRID2_LIBS} // $defaults{libs}).' '.($ENV{BINARY_GRID2_EXTRALIBS}//''); ccflags = defaults[ "ccflags" ] # = $ENV{BINARY_GRID2_CCFLAGS} // ($defaults{ccflags}) . ($ENV{BINARY_GRID2_EXTRACCFLAGS} // ''); # you must define _SEARCH_H to prevent it being loaded twice ccflags += " -shared -D_SEARCH_H" # remove the visibility=hidden for this compilation ccflags = ccflags.replace("-fvisibility=hidden", "") # ensure library paths to the front of the libs: libs_content = libs.split(" ") library_paths = [el for el in libs_content if el.startswith("-L")] non_library_paths = [ el for el in libs_content if (not el.startswith("-L") and not el == "") ] libs = "{} {}".format(" ".join(library_paths), " ".join(non_library_paths)) if verbose > 0: print( "Got options to compile:\n\tcc = {cc}\n\tccflags = {ccflags}\n\tld = {ld}\n\tlibs = {libs}\n\tinc = {inc}\n\n".format( cc=cc, ccflags=ccflags, ld=ld, libs=libs, inc=inc ) ) return {"cc": cc, "ld": ld, "ccflags": ccflags, "libs": libs, "inc": inc} def compile_shared_lib(code, sourcefile_name, outfile_name, verbose=0): """ Function to write the custom logging code to a file and then compile it. TODO: nicely put in the -fPIC """ # Write code to file binary_c_write_log_code(code, sourcefile_name, verbose) # Remove the library if present: remove_file(outfile_name, verbose) # create compilation command compilation_dict = return_compilation_dict(verbose) # Construct full command command = "{cc} -fPIC {ccflags} {libs} -o {outfile_name} {sourcefile_name} {inc}".format( cc=compilation_dict["cc"], ccflags=compilation_dict["ccflags"], libs=compilation_dict["libs"], outfile_name=outfile_name, sourcefile_name=sourcefile_name, inc=compilation_dict["inc"], ) # remove extra whitespaces: command = " ".join(command.split()) # Execute compilation and create the library if verbose > 0: # BINARY_C_DIR = os.getenv("BINARY_C") # BINARY_C_SRC_DIR = os.path.join(BINARY_C_DIR, "src") print( "Building shared library for custom logging with (binary_c.h) on {}\n".format( socket.gethostname() ) ) print( "Executing following command to compile the shared library:\n{command}".format( command=command ) ) res = subprocess.check_output("{command}".format(command=command), shell=True) if verbose > 0: if res: print("Output of compilation command:\n{}".format(res)) def create_and_load_logging_function(custom_logging_code, verbose=0): """ Function to automatically compile the shared library with the given custom logging code and load it with ctypes returns: memory adress of the custom logging function in a int type. """ # library_name = os.path.join( temp_dir(), "libcustom_logging_{}.so".format(uuid.uuid4().hex) ) compile_shared_lib( custom_logging_code, sourcefile_name=os.path.join(temp_dir(), "custom_logging.c"), outfile_name=library_name, verbose=verbose, ) if verbose > 0: print("loading shared library for custom logging") # Loading library _ = ctypes.CDLL("libgslcblas.so", mode=ctypes.RTLD_GLOBAL) _ = ctypes.CDLL("libgsl.so", mode=ctypes.RTLD_GLOBAL) _ = ctypes.CDLL("libbinary_c.so", mode=ctypes.RTLD_GLOBAL) libcustom_logging = ctypes.CDLL( library_name, mode=ctypes.RTLD_GLOBAL, ) # loads the shared library # Get memory adress of function. mimicking a pointer func_memaddr = ctypes.cast( libcustom_logging.custom_output_function, ctypes.c_void_p ).value if verbose > 0: print( "loaded shared library for custom logging. custom_output_function is loaded in memory at {}".format( func_memaddr ) ) return func_memaddr, library_name