"""
Builder.py, module definition of Builder class.
This is a class designed for controlling the building phase.
"""

# Copyright (C) 2015  Stefano Zaghi
#
# This file is part of FoBiS.py.
#
# FoBiS.py is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# FoBiS.py is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with FoBiS.py. If not, see <http://www.gnu.org/licenses/>.
# from builtins import *
try:
    from multiprocessing import Pool

    __parallel__ = True
except ImportError:
    print("Module 'multiprocessing' not found: parallel compilation disabled")
    __parallel__ = False
import operator
import os
from collections.abc import Callable
from typing import Any, Optional

from .Compiler import Compiler
from .utils import check_results, print_fake, safe_mkdir, syswork, syswork_steps


class Builder:
    """Builder is an object that handles the building system, its attributes and methods."""

    def __init__(
        self,
        cliargs: Any,
        print_n: Optional[Callable[..., None]] = None,
        print_w: Optional[Callable[..., None]] = None,
    ) -> None:
        """
        Parameters
        ----------
        cliargs : argparse object
        print_n : {None}
          function for printing normal message
        print_w : {None}
          function for printing emphized warning message

        Attributes
        ----------
        compiler : Compiler object
          compiler used
        src_dir = {"./"}
          directory into which search source files
        build_dir : {"./"}
          directory containing built files
        obj_dir : {"./"}
          directory containing compiled object files
        mod_dir : {"./"}
          directory containing .mod files
        lib_dir : {None}
          list of directories searched for libraries
        dinc : {None}
          list of directories for searching included files
        libs : {None}
          list of external libraries that are not into the path: must be passed with full paths
        vlibs : {None}
          list of external libraries that are not into the path and that are volatile (can changed thus triggering re-building): must be passed with full paths
        ext_libs : {None}
          list of external libraries that are into the path: are linked as '-llibrary.[a|so]' and searched into '-Ldir'
        ext_vlibs : {None}
          list of external libraries that are into the path and that are volatile (can changed thus triggering re-building): are linked as '-llibrary.[a|so]' and searched into '-Ldir'
        preprocessor : {None}
          activate preprocessor
        preprocessor_dir : {None}
          directory containing sources processed by preprocessor (if any); by default are removed after used
        preprocessor_ext : {None}
          list of file extensions to be processed by preprocessor; by default all files are preprocessed if preprocessor is used
        print_n : {None}
          function for printing normal message
        print_w : {None}
          function for printing emphized warning message
        jobs : {1}
          concurrent compiling jobs
        """

        if print_n is None:
            self.print_n = print_fake
        else:
            self.print_n = print_n

        if print_w is None:
            self.print_w = print_fake
        else:
            self.print_w = print_w

        self.compiler = Compiler(cliargs=cliargs, print_w=self.print_w)

        self.jobs = cliargs.jobs
        self._sanitize_dirs(
            src_dir=cliargs.src,
            build_dir=cliargs.build_dir,
            obj_dir=cliargs.obj_dir,
            mod_dir=cliargs.mod_dir,
            lib_dir=cliargs.lib_dir,
            dinc=cliargs.include,
        )
        self._sanitize_files(
            libs=cliargs.libs, vlibs=cliargs.vlibs, ext_libs=cliargs.ext_libs, ext_vlibs=cliargs.ext_vlibs
        )
        self._set_preprocessor(
            preprocessor=cliargs.preprocessor,
            preprocessor_dir=cliargs.preprocessor_dir,
            preprocessor_ext=cliargs.preprocessor_ext,
            preprocessor_no_o=cliargs.preprocessor_no_o,
            preprocessor_args=cliargs.preprocessor_args,
        )

        self.ar = cliargs.ar
        self.arflags = cliargs.arflags
        self.ranlib = cliargs.ranlib

        self.cmd_comp = self.compiler.compile_cmd(mod_dir=self.mod_dir)
        self.cmd_link = self.compiler.link_cmd(mod_dir=self.mod_dir)
        return

    @staticmethod
    def get_fc(cliargs: Any) -> str:
        """
        Method for getting the compiler command built accordingly to the cli arguments.

        Parameters
        ----------
        cliargs : argparse object
        """
        return Compiler(cliargs=cliargs).fcs

    @staticmethod
    def get_cflags(cliargs: Any) -> str:
        """
        Method for getting the compiling flags built accordingly to the cli arguments.

        Parameters
        ----------
        cliargs : argparse object
        """
        return Compiler(cliargs=cliargs).cflags

    @staticmethod
    def get_lflags(cliargs: Any) -> str:
        """
        Method for getting the linking flags built accordingly to the cli arguments.

        Parameters
        ----------
        cliargs : argparse object
        """
        return Compiler(cliargs=cliargs).lflags

    @staticmethod
    def get_modsw(cliargs: Any) -> str:
        """
        Method for getting the compiler modules switch built accordingly to the cli arguments.

        Parameters
        ----------
        cliargs : argparse object
        """
        # modsw = cliargs.modsw
        # if cliargs.compiler.lower() in Builder.s_compilers and modsw == '':
        #   modsw = Builder.s_compilers[cliargs.compiler.lower()][2]
        # return modsw
        return Compiler(compiler=cliargs.compiler, modsw=cliargs.modsw).modsw

    def _sanitize_dirs(self, src_dir, build_dir, obj_dir, mod_dir, lib_dir, dinc):
        """
        Method for sanitizing directory paths.

        Parameters
        ----------
        src_dir : list
          directory into which search source files
        build_dir : str
          directory containing built files
        obj_dir : str
          directory containing compiled object files
        mod_dir : str
          directory containing .mod files
        lib_dir : list
          list of directories searched for libraries
        dinc : list
          list of directories for searching included files
        """
        self.src_dir = " ".join(src_dir)

        self.build_dir = os.path.normpath(build_dir)
        safe_mkdir(directory=self.build_dir, print_w=self.print_w)

        self.obj_dir = os.path.normpath(os.path.join(build_dir, obj_dir))
        safe_mkdir(directory=self.obj_dir, print_w=self.print_w)

        self.mod_dir = os.path.normpath(os.path.join(build_dir, mod_dir))
        safe_mkdir(directory=self.mod_dir, print_w=self.print_w)

        if lib_dir is None:
            self.lib_dir = []
        else:
            self.lib_dir = lib_dir
            for directory in self.lib_dir:
                directory = os.path.normpath(directory)
        if dinc is None:
            self.dinc = []
        else:
            self.dinc = dinc
            for directory in self.dinc:
                directory = os.path.normpath(directory)
        return

    def _sanitize_files(self, libs, vlibs, ext_libs, ext_vlibs):
        """
        Method for sanitizing files paths.

        Parameters
        ----------
        libs : list
          list of external libraries that are not into the path: must be passed with full paths
        vlibs : list
          list of external libraries that are not into the path and that are volatile (can changed thus triggering re-building): must be passed with full paths
        ext_libs : list
          list of external libraries that are into the path: are linked as '-llibrary.[a|so]' and searched into '-Ldir'
        ext_vlibs : list
          list of external libraries that are into the path and that are volatile (can changed thus triggering re-building): are linked as '-llibrary.[a|so]' and searched into '-Ldir'
        """
        if libs is None:
            self.libs = []
        else:
            self.libs = libs
            for lib in self.libs:
                lib = os.path.normpath(lib)
        if vlibs is None:
            self.vlibs = []
        else:
            self.vlibs = vlibs
            for lib in self.vlibs:
                lib = os.path.normpath(lib)
        if ext_libs is None:
            self.ext_libs = []
        else:
            self.ext_libs = ext_libs
            for lib in self.ext_libs:
                lib = os.path.normpath(lib)
        if ext_vlibs is None:
            self.ext_vlibs = []
        else:
            self.ext_vlibs = ext_vlibs
            for lib in self.ext_vlibs:
                lib = os.path.normpath(lib)
        return

    def _set_preprocessor(self, preprocessor, preprocessor_dir, preprocessor_ext, preprocessor_no_o, preprocessor_args):
        """
        Set preprocessor data.

        Parameters
        ----------
        preprocessor : bool
          activate preprocessor
        preprocessor_dir : str
          directory containing sources processed by preprocessor; by default are removed after used
        preprocessor_ext : list
          list of file extensions to be processed by preprocessor; by default all files are preprocessed if preprocessor is used
        """
        self.preprocessor = preprocessor
        if preprocessor_ext is None:
            self.preprocessor_ext = []
        else:
            self.preprocessor_ext = preprocessor_ext
        self.preprocessor_dir = preprocessor_dir
        if self.preprocessor_dir:
            self.preprocessor_dir = os.path.normpath(os.path.join(self.build_dir, self.preprocessor_dir))
            if not os.path.exists(self.preprocessor_dir):
                os.makedirs(self.preprocessor_dir)
        self.preprocessor_no_o = preprocessor_no_o
        self.preprocessor_args = preprocessor_args
        if self.preprocessor:
            preprocessor_exist = False
            for path in os.environ["PATH"].split(os.pathsep):
                preprocessor_exist = os.path.exists(os.path.join(path, self.preprocessor))
                if preprocessor_exist:
                    break
            if not preprocessor_exist:
                self.print_w(
                    "Error: " + self.preprocessor + " is not in your PATH! You cannot use -preprocessor switch."
                )
        return

    def _compile_preprocessor(self, file_to_compile):
        """
        Create compile command with preprocessor usage.

        Parameters
        ----------
        file_to_compile : ParsedFile object
          file to be compiled

        Returns
        -------
        str
          string containing the preprocessor command (empty if no preprocessing)
        str
          string containing the output file name of preprocessor
        bool
          True if the preprocessor output file should be removed after compilation
        """
        preprocessor_cmd = ""
        preprocessor_output = ""
        preprocessor_remove = False
        to_preprocess = False
        if self.preprocessor:
            if len(self.preprocessor_ext) > 0:
                if file_to_compile.extension in self.preprocessor_ext:
                    to_preprocess = True
            else:
                to_preprocess = True
            if to_preprocess and self.preprocessor_dir:
                preprocessor_dir = self.preprocessor_dir
                preprocessor_store = True
            else:
                preprocessor_dir = ""
                preprocessor_store = False
            if to_preprocess:
                output_prepend = " -o "
                if self.preprocessor_no_o:
                    output_prepend = " "
                preprocessor_cmd = (
                    self.preprocessor
                    + " "
                    + self.preprocessor_args
                    + " "
                    + file_to_compile.name
                    + output_prepend
                    + os.path.join(preprocessor_dir, file_to_compile.basename + ".pp.f90")
                )
                preprocessor_output = os.path.join(preprocessor_dir, file_to_compile.basename + ".pp.f90")
                if not preprocessor_store:
                    preprocessor_remove = True
        return preprocessor_cmd, preprocessor_output, preprocessor_remove

    def _compile_include(self):
        """
        Method for creating compile command with inluded paths.

        Returns
        -------
        str
          string containing the include command
        """
        include_cmd = ""
        if len(self.dinc) > 0:
            include_cmd = "".join(["-I" + s + " " for s in self.dinc])
        return include_cmd

    def _compile_command(self, file_to_compile):
        """
        Method for returning the OS commands for compiling file_to_compile.

        Parameters
        ----------
        file_to_compile : ParsedFile object
          file to be compiled

        Returns
        -------
        list
          list of (cmd_string, cleanup_path_or_None) tuples to execute in order;
          cleanup_path is set on the last step when a temporary preprocessor output must be removed
        """
        preprocessor_cmd, preprocessor_output, preprocessor_remove = self._compile_preprocessor(file_to_compile)
        include_cmd = self._compile_include()

        if preprocessor_cmd != "":
            compile_cmd = (
                self.cmd_comp
                + " "
                + include_cmd
                + preprocessor_output
                + " -o "
                + os.path.join(self.obj_dir, file_to_compile.basename + ".o")
            )
            cleanup = preprocessor_output if preprocessor_remove else None
            return [(preprocessor_cmd, None), (compile_cmd, cleanup)]
        else:
            comp_cmd = (
                self.cmd_comp
                + " "
                + include_cmd
                + file_to_compile.name
                + " -o "
                + os.path.join(self.obj_dir, file_to_compile.basename + ".o")
            )
            return [(comp_cmd, None)]

    def _link_command(self, file_to_build, output=None, nomodlibs=None, submodules=None, mklib=None):
        """
        Return the OS command for linkng file_to_build.

        Parameters
        ----------
        file_to_build : ParsedFile object
          file to be built
        output : str
          output name
        nomodlibs : {None}
          list of old-Fortran style libraries objects
        submodules : {None}
          list of submodules objects
        mklib : {None}
          activate building library mode

        Returns
        -------
        str
          string containing the link command
        """
        exe = self.get_output_name(file_to_build=file_to_build, output=output)
        link_cmd = (
            self.cmd_link
            + " "
            + self._get_libs_link_command(
                file_to_build=file_to_build, nomodlibs=nomodlibs, submodules=submodules, mklib=mklib
            )
            + " -o "
            + exe
        )
        return link_cmd, exe

    def _mklib_command(self, file_to_build, output=None, nomodlibs=None, submodules=None, mklib=None):
        """
        Return the OS command for linkng file_to_build as a library.

        Parameters
        ----------
        file_to_build : ParsedFile object
          file to be built
        output : str
          output name
        nomodlibs : {None}
          list of old-Fortran style libraries objects
        submodules : {None}
          list of submodules objects
        mklib : {None}
          activate building library mode

        Returns
        -------
        str
          string containing the link command
        """
        lib = self.get_output_name(file_to_build=file_to_build, output=output, mklib=mklib)
        link_cmd = self._get_libs_link_command(
            file_to_build=file_to_build, exclude_programs=True, nomodlibs=nomodlibs, submodules=submodules, mklib=mklib
        )
        ranlib_cmd = None
        if mklib is not None:
            if mklib.lower() == "shared":
                link_cmd = self.cmd_link + " " + link_cmd + " -o " + lib
            elif mklib.lower() == "static":
                link_cmd = self.ar + " " + self.arflags + " " + lib + " " + link_cmd
                if self.ranlib:
                    ranlib_cmd = self.ranlib + " " + lib
        return link_cmd, ranlib_cmd, lib

    def _get_libs_link_command(
        self, file_to_build, exclude_programs=False, nomodlibs=None, submodules=None, mklib=None
    ):
        """
        Return the libraries link command

        Parameters
        ----------
        file_to_build : ParsedFile object
          file to be built
        exclude_programs : {False}
          flag for excluding programs obj from the list
        nomodlibs : {None}
          list of old-Fortran style libraries objects
        submodules : {None}
          list of submodules objects
        mklib : {None}
          activate building library mode

        Returns
        -------
        str
          string containing the link command
        """
        objs = []
        if nomodlibs is not None:
            objs = objs + nomodlibs
        if submodules is not None:
            objs = objs + submodules
        objs = objs + file_to_build.obj_dependencies(exclude_programs=exclude_programs)
        objs = list(set(objs))  # remove duplicated objs
        link_cmd = (
            "".join([os.path.join(self.obj_dir, s + " ") for s in objs])
            + "".join([s + " " for s in self.libs])
            + "".join([s + " " for s in self.vlibs])
        )
        if mklib is None or mklib.lower() == "shared":
            if len(self.ext_libs) > 0:
                link_cmd += " " + "".join(["-l" + s + " " for s in self.ext_libs])
            if len(self.ext_vlibs) > 0:
                link_cmd += " " + "".join(["-l" + s + " " for s in self.ext_vlibs])
            if len(self.lib_dir) > 0:
                link_cmd += " " + "".join(["-L" + s + " " for s in self.lib_dir])
        return link_cmd

    def _get_hierarchy(self, file_to_build):
        """
        Create a hierarchy of compiling commands accordingly to the dependencies order.

        Parameters
        ----------
        file_to_build : ParsedFile
        """
        order_max = (
            max(
                [p for p in file_to_build.pfile_dep_all if not p.include and p.to_compile],
                key=operator.attrgetter("order"),
            ).order
            + 1
        )
        hierarchy = [[] for _ in range(order_max)]
        for dep in file_to_build.pfile_dep_all:
            if dep.to_compile and not dep.include:
                hierarchy[dep.order].append([dep.name, self._compile_command(file_to_compile=dep)])
                dep.to_compile = False
        hierarchy = [h for h in hierarchy if len(h) > 0]
        return hierarchy

    def _compile(self, file_to_build, verbose=False, log=False, quiet=False):
        """
        Compile file.

        Parameters
        ----------
        file_to_build : ParsedFile
        verbose : {False}
          bool for activate extreme verbose outputs
        log : {False}
          bool for activate errors log saving

        Returns
        -------
        build_ok : bool
          flag for checking the building is completed;
          it is used for purging out failed non-module-library in order to try to build the target anywhere;
        """
        build_ok = True
        hierarchy = self._get_hierarchy(file_to_build)
        for deps in reversed(hierarchy):
            files_to_compile = ""
            steps_per_file = []
            for dep in deps:
                files_to_compile = files_to_compile + " " + dep[0]
                steps_per_file.append(dep[1])
            if len(deps) > 1 and self.jobs > 1 and __parallel__:
                jobs = min(len(deps), self.jobs)
                if not quiet:
                    self.print_n("Compiling" + files_to_compile + " using " + str(jobs) + " concurrent processes")
                    if verbose:
                        self.print_n("Executing: " + str(steps_per_file))
                pool = Pool(processes=jobs)
                results = pool.map(syswork_steps, steps_per_file)
                pool.close()
                pool.join()
            else:
                if not quiet:
                    self.print_n("Compiling" + files_to_compile + " serially")
                results = []
                for steps in steps_per_file:
                    if not quiet:
                        if verbose:
                            self.print_n("Executing: " + str(steps))
                    result = syswork_steps(steps)
                    results.append(result)
            if not file_to_build.nomodlib:
                if log:
                    check_results(results=results, log="building-errors.log", print_w=self.print_w)
                else:
                    check_results(results=results, print_w=self.print_w)
        if file_to_build.nomodlib:
            if results[0][0] != 0:
                build_ok = False
                if not quiet:
                    self.print_w(
                        "Warning: compiling file "
                        + file_to_build.name
                        + " fails! Removing from the list of non-module libraries linked into the target!"
                    )
        return build_ok

    def gnu_make(self) -> str:
        """
        Return the builder options formated as GNU Make variables

        Returns
        -------
        str
          string containing the builder options in GNU Make format
        """
        string = []
        string.append("DOBJ    = " + os.path.normpath(self.obj_dir) + "/\n")
        string.append("DMOD    = " + os.path.normpath(self.mod_dir) + "/\n")
        string.append("DEXE    = " + os.path.normpath(self.build_dir) + "/\n")
        libs = []
        if len(self.libs) > 0:
            libs += self.libs
        if len(self.vlibs) > 0:
            libs += self.vlibs
        if len(self.ext_libs) > 0:
            libs += ["-l" + l + " " for l in self.ext_libs]
        if len(self.ext_vlibs) > 0:
            libs += ["-l" + l + " " for l in self.ext_vlibs]
        if len(self.lib_dir) > 0:
            libs += ["-L" + l + " " for l in self.lib_dir]
        if len(libs) > 0:
            string.append("LIBS    = " + "".join(" " + l for l in libs) + "\n")
        else:
            string.append("LIBS    =\n")
        string.append("FC      = " + self.compiler.fcs + "\n")
        string.append("OPTSC   = " + self.compiler.cflags + " " + self.compiler.modsw + self.mod_dir + "\n")
        string.append("OPTSL   = " + self.compiler.lflags + " " + self.compiler.modsw + self.mod_dir + "\n")
        return "".join(string)

    def build(
        self,
        file_to_build: Any,
        output: Optional[str] = None,
        nomodlibs: Optional[list[str]] = None,
        submodules: Optional[list[str]] = None,
        mklib: Optional[str] = None,
        verbose: bool = False,
        log: bool = False,
        quiet: bool = False,
        track: bool = False,
    ) -> bool:
        """
        Build a file.

        Parameters
        ----------
        file_to_build : ParsedFile
        output : str
          output name
        nomodlibs : {None}
          list of old-Fortran style libraries objects
        submodules : {None}
          list of submodules objects
        mklib : {None}
          bool for activate building library mode
        verbose : {False}
          bool for activate extreme verbose outputs
        log : {False}
          bool for activate errors log saving
        quiet : {False}
          bool for activate quiet mode
        track : {False}
          bool for activate build traking mode for subsequent install command

        Returns
        -------
        build_ok : bool
          flag for checking the building is completed;
          it is used for purging out failed non-module-library in order to try to build the target anywhere;
        """
        build_ok = True
        if not quiet:
            self.print_n("Building " + file_to_build.name)
        # sort dependencies accordingly to indirect dependency
        file_to_build.sort_dependencies()
        # creating a hierarchy list of compiling commands accordingly to the order of all dependencies
        if len([p for p in file_to_build.pfile_dep_all if not p.include and p.to_compile]) > 0:
            build_ok = self._compile(file_to_build=file_to_build, verbose=verbose, log=log, quiet=quiet)
        else:
            if not quiet:
                self.print_n("Nothing to compile, all objects are up-to-date")
        if file_to_build.program:
            link_cmd, exe = self._link_command(
                file_to_build=file_to_build, output=output, nomodlibs=nomodlibs, submodules=submodules, mklib=mklib
            )
            if not quiet:
                self.print_n("Linking " + exe)
                if verbose:
                    self.print_n("Executing: " + str(link_cmd))
            result = syswork(link_cmd)
            if log:
                check_results(results=[result], log="building-errors.log", print_w=self.print_w)
            else:
                check_results(results=[result], print_w=self.print_w)
            if not quiet:
                self.print_n("Target " + file_to_build.name + " has been successfully built")
        elif mklib:
            link_cmd, ranlib_cmd, lib = self._mklib_command(
                file_to_build=file_to_build, output=output, nomodlibs=nomodlibs, submodules=submodules, mklib=mklib
            )
            if not quiet:
                self.print_n("Linking " + lib)
                if verbose:
                    self.print_n("Executing: " + str(link_cmd))
            result = syswork(link_cmd)
            if log:
                check_results(results=[result], log="building-errors.log", print_w=self.print_w)
            else:
                check_results(results=[result], print_w=self.print_w)
            if ranlib_cmd and result[0] == 0:
                result = syswork(ranlib_cmd)
                if log:
                    check_results(results=[result], log="building-errors.log", print_w=self.print_w)
                else:
                    check_results(results=[result], print_w=self.print_w)
            if not quiet:
                self.print_n("Target " + file_to_build.name + " has been successfully built")
        if track:
            print("Track building of " + file_to_build.name)
            track_file_name = self.get_track_build_file(file_to_build)
            with open(track_file_name, "w") as track_file:
                track_file.writelines("[build]\n")
                track_file.writelines(
                    "output = " + self.get_output_name(file_to_build=file_to_build, output=output) + "\n"
                )
                if file_to_build.program:
                    track_file.writelines("program = True\n")
                elif mklib:
                    track_file.writelines("library = True\n")
                    track_file.writelines(
                        "mod_file = " + os.path.join(self.mod_dir, file_to_build.basename + ".mod") + "\n"
                    )
        return build_ok

    def get_output_name(self, file_to_build: Any, output: Optional[str] = None, mklib: Optional[str] = None) -> str:
        """
        Return the output build file name.

        Parameters
        ----------
        file_to_build : ParsedFile
        output : str
          output build file name
        mklib : {None}
          activate building library mode

        Returns
        -------
        output : str
          output build file name
        """
        if output:
            build_name = os.path.join(self.build_dir, output)
        else:
            if mklib is not None:
                if mklib.lower() == "shared":
                    build_name = os.path.join(self.build_dir, file_to_build.basename + ".so")
                elif mklib.lower() == "static":
                    build_name = os.path.join(self.build_dir, file_to_build.basename + ".a")
            else:
                build_name = os.path.join(self.build_dir, file_to_build.basename)
        return build_name

    def get_track_build_file(self, file_to_build: Any) -> str:
        """
        Return the file name of the 'track build' file.

        Parameters
        ----------
        file_to_build : ParsedFile

        Returns
        -------
        track_file_name : str
          file name of 'track build' file
        """
        return os.path.join(self.build_dir, "." + os.path.basename(file_to_build.name) + ".track_build")

    def verbose(self, quiet: bool = False) -> str:
        """
        The method verbose returns a verbose message containing builder infos.

        Parameters
        ----------
        quiet : {False}
          flag for making less verbose outputs
        """
        message = ""
        if not quiet:
            message = "Builder options" + "\n"
            message += "  Directories\n"
            message += '    Root of source files directory: "' + self.src_dir + '"\n'
            message += '    Building directory: "' + self.build_dir + '"\n'
            message += '    Compiled-objects .o   directory: "' + self.obj_dir + '"\n'
            message += '    Compiled-objects .mod directory: "' + self.mod_dir + '"\n'
            if len(self.lib_dir) > 0:
                message += "  External libraries directories: " + "".join([s + " " for s in self.lib_dir]) + "\n"
            if len(self.dinc) > 0:
                message += "  Included paths: " + "".join([s + " " for s in self.dinc]) + "\n"
            if len(self.libs) > 0:
                message += "  Linked libraries with full path: " + "".join([s + " " for s in self.libs]) + "\n"
            if len(self.vlibs) > 0:
                message += (
                    "  Linked volatile libraries with full path: " + "".join([s + " " for s in self.vlibs]) + "\n"
                )
            if len(self.ext_libs) > 0:
                message += "  Linked libraries in path: " + "".join([s + " " for s in self.ext_libs]) + "\n"
            if len(self.ext_vlibs) > 0:
                message += "  Linked volatile libraries in path: " + "".join([s + " " for s in self.ext_vlibs]) + "\n"
            message += self.compiler.pprint(prefix="  ")
            message += "  Archiver: " + self.ar + " " + self.arflags + "\n"
            if self.ranlib:
                message += "  Ranlib: " + self.ranlib + "\n"
            message += "  Preprocessor used: " + str(self.preprocessor) + "\n"
            message += "  Preprocessor args: " + str(self.preprocessor_args) + "\n"
            message += "  Preprocessor output directory: " + str(self.preprocessor_dir) + "\n"
            message += "  Preprocessor extensions processed: " + str(self.preprocessor_ext) + "\n"
        return message
