diff --git a/include/lgi/common/LgiDefs.h b/include/lgi/common/LgiDefs.h --- a/include/lgi/common/LgiDefs.h +++ b/include/lgi/common/LgiDefs.h @@ -1,589 +1,590 @@ /** \file \author Matthew Allen \date 24/9/1999 \brief Defines and types Copyright (C) 1999-2004, Matthew Allen */ #ifndef _LGIDEFS_H_ #define _LGIDEFS_H_ #ifdef HAIKU #include #endif #include "LgiInc.h" #if defined(WIN32) && defined(__GNUC__) #define PLATFORM_MINGW #endif #include // Unsafe typedefs, for backward compatibility typedef unsigned char uchar; typedef unsigned short ushort; typedef unsigned int uint; typedef unsigned long ulong; // Length safe typedesf, use these in new code #ifdef _MSC_VER /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef signed __int64 int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef unsigned __int64 uint64; #ifndef _WCHAR_T_DEFINED #include #endif #pragma warning(error:4263) #ifdef _WIN64 typedef signed __int64 ssize_t; #else typedef signed int ssize_t; #endif #elif defined(LINUX) /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef int64_t int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef uint64_t uint64; #elif !defined(HAIKU) /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef signed long long int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef unsigned long long uint64; #endif #ifndef __cplusplus #include #if defined(_MSC_VER) && _MSC_VER<1800 typedef unsigned char bool; #define true 1 #define false 0 #else #include #endif #endif /// \brief Wide unicode char /// /// This is 16 bits on Win32 and Mac, but 32 bits on unix platforms. There are a number /// of wide character string function available for manipulating wide char strings. /// /// Firstly to convert to and from utf-8 there is: ///
    ///
  • Utf8ToWide() ///
  • WideToUtf8() ///
/// /// Wide versions of standard library functions are available: ///
    ///
  • StrchrW() ///
  • StrrchrW() ///
  • StrnchrW() ///
  • StrstrW() ///
  • StristrW() ///
  • StrnstrW() ///
  • StrnistrW() ///
  • StrcmpW() ///
  • StricmpW() ///
  • StrncmpW() ///
  • StrnicmpW() ///
  • StrcpyW() ///
  • StrncpyW() ///
  • StrlenW() ///
  • StrcatW() ///
  • HtoiW() ///
  • NewStrW() ///
  • TrimStrW() ///
  • ValidStrW() ///
  • MatchStrW() ///
#include typedef wchar_t char16; #if !WINNATIVE #ifdef UNICODE typedef char16 TCHAR; #ifndef _T #define _T(arg) L##arg #endif #else typedef char TCHAR; #ifndef _T #define _T(arg) arg #endif #endif #endif #if defined(_MSC_VER) #if _MSC_VER >= 1400 #ifdef _WIN64 typedef __int64 NativeInt; typedef unsigned __int64 UNativeInt; #else typedef _W64 int NativeInt; typedef _W64 unsigned int UNativeInt; #endif #else typedef int NativeInt; typedef unsigned int UNativeInt; #endif #else #if __LP64__ typedef int64 NativeInt; typedef uint64 UNativeInt; #else typedef int NativeInt; typedef unsigned int UNativeInt; #endif #endif /// Generic pointer to any base type. Used when addressing continuous data of /// different types. typedef union { int8_t *s8; uint8_t *u8; int16_t *s16; uint16_t *u16; int32_t *s32; uint32_t *u32; int64 *s64; uint64 *u64; NativeInt *ni; UNativeInt *uni; char *c; char16 *w; float *f; double *d; #ifdef __cplusplus bool *b; #else unsigned char *b; #endif void **vp; int i; } LPointer; // Basic macros #define limit(i,l,u) (((i)<(l)) ? (l) : (((i)>(u)) ? (u) : (i))) // #define makelong(a, b) ((a)<<16 | (b&0xFFFF)) // #define loword(a) (a&0xFFFF) // #define hiword(a) (a>>16) #undef ABS #ifdef __cplusplus template inline T ABS(T v) { if (v < 0) return -v; return v; } #else #define ABS(v) ((v) < 0 ? -(v) : (v)) #endif /// Returns true if 'c' is an ascii character #define IsAlpha(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) /// Returns true if 'c' is a digit (number) #define IsDigit(c) ((c) >= '0' && (c) <= '9') /// Returns true if 'c' is a hexadecimal digit #define IsHexDigit(c) ( \ ((c) >= '0' && (c) <= '9') || \ ((c) >= 'a' && (c) <= 'f') || \ ((c) >= 'A' && (c) <= 'F') \ ) // Byte swapping #define LgiSwap16(a) ( (((a) & 0xff00) >> 8) | \ (((a) & 0x00ff) << 8) ) #define LgiSwap32(a) ( (((a) & 0xff000000) >> 24) | \ (((a) & 0x00ff0000) >> 8) | \ (((a) & 0x0000ff00) << 8) | \ (((a) & 0x000000ff) << 24) ) #ifdef __GNUC__ #define LgiSwap64(a) ( (((a) & 0xff00000000000000LLU) >> 56) | \ (((a) & 0x00ff000000000000LLU) >> 40) | \ (((a) & 0x0000ff0000000000LLU) >> 24) | \ (((a) & 0x000000ff00000000LLU) >> 8) | \ (((a) & 0x00000000ff000000LLU) << 8) | \ (((a) & 0x0000000000ff0000LLU) << 24) | \ (((a) & 0x000000000000ff00LLU) << 40) | \ (((a) & 0x00000000000000ffLLU) << 56) ) #else #define LgiSwap64(a) ( (((a) & 0xff00000000000000) >> 56) | \ (((a) & 0x00ff000000000000) >> 40) | \ (((a) & 0x0000ff0000000000) >> 24) | \ (((a) & 0x000000ff00000000) >> 8) | \ (((a) & 0x00000000ff000000) << 8) | \ (((a) & 0x0000000000ff0000) << 24) | \ (((a) & 0x000000000000ff00) << 40) | \ (((a) & 0x00000000000000ff) << 56) ) #endif #ifdef __cplusplus // Define a way of creating a 4 character code string literal as an int. constexpr uint32_t Lgi4CC( char const p[5] ) { return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; } #endif // Asserts +#ifndef LAssert LgiFunc void _lgi_assert(bool b, const char *test, const char *file, int line); #ifdef _DEBUG #define LAssert(b) _lgi_assert(b, #b, __FILE__, __LINE__) #else #define LAssert(b) ((void)0) #endif - +#endif // Good ol NULLy #ifndef NULL #define NULL 0 #endif // Slashes and quotes #define IsSlash(c) (((c)=='/')||((c)=='\\')) #define IsQuote(c) (((c)=='\"')||((c)=='\'')) // Some objectish ones #define ZeroObj(obj) memset(&obj, 0, sizeof(obj)) #ifndef CountOf #define CountOf(array) (sizeof(array)/sizeof(array[0])) #endif #ifdef __cplusplus template void LSwap(T &a, T &b) { T tmp = a; a = b; b = tmp; } #endif #ifndef MEMORY_DEBUG #define DeleteObj(obj) if (obj) { delete obj; obj = 0; } #define DeleteArray(obj) if (obj) { delete [] obj; obj = 0; } #endif // Flags #define SetFlag(i, f) (i) |= (f) #define ClearFlag(i, f) (i) &= ~(f) #define TestFlag(i, f) (((i) & (f)) != 0) // Defines /// Enum of all the operating systems we might be running on. enum LgiOs { /// \brief Unknown OS /// \sa LGetOs LGI_OS_UNKNOWN = 0, /// \brief Windows 95, 98[se] or ME. (Not supported) /// \sa LGetOs LGI_OS_WIN9X, /// \brief Windows 10+ 32bit. (Not supported) /// \sa LGetOs LGI_OS_WIN32, /// \brief Windows 10+ 64bit. (Supported) /// \sa LGetOs LGI_OS_WIN64, /// \brief BeOS/Haiku. (Somewhat supported) /// \sa LGetOs LGI_OS_HAIKU, /// \brief Linux. (Kernels v2.4 and up supported) /// \sa LGetOs LGI_OS_LINUX, /// \brief Mac OS X 10.15 or later. (Supported) /// \sa LGetOs LGI_OS_MAC_OS_X, /// One higher than the maximum OS define LGI_OS_MAX, }; // Edge types enum LEdge { EdgeNone, EdgeXpSunken, EdgeXpRaised, EdgeXpChisel, EdgeXpFlat, EdgeWin7FocusSunken, EdgeWin7Sunken, }; #define DefaultSunkenEdge EdgeWin7Sunken #define DefaultRaisedEdge EdgeXpRaised // Cursors enum LCursor { /// Blank/invisible cursor LCUR_Blank, /// Normal arrow LCUR_Normal, /// Upwards arrow LCUR_UpArrow, /// Downwards arrow LCUR_DownArrow, /// Left arrow LCUR_LeftArrow, /// Right arrow LCUR_RightArrow, /// Crosshair LCUR_Cross, /// Hourglass/watch LCUR_Wait, /// Ibeam/text entry LCUR_Ibeam, /// Vertical resize (|) LCUR_SizeVer, /// Horizontal resize (-) LCUR_SizeHor, /// Diagonal resize (/) LCUR_SizeBDiag, /// Diagonal resize (\) LCUR_SizeFDiag, /// All directions resize LCUR_SizeAll, /// Vertical splitting LCUR_SplitV, /// Horziontal splitting LCUR_SplitH, /// A pointing hand LCUR_PointingHand, /// A slashed circle LCUR_Forbidden, /// Copy Drop LCUR_DropCopy, /// Copy Move LCUR_DropMove, }; // General Event Flags #define LGI_EF_LCTRL (1 << 0) #define LGI_EF_RCTRL (1 << 1) #define LGI_EF_CTRL (LGI_EF_LCTRL | LGI_EF_RCTRL) #define LGI_EF_LALT (1 << 2) #define LGI_EF_RALT (1 << 3) #define LGI_EF_ALT (LGI_EF_LALT | LGI_EF_RALT) #define LGI_EF_LSHIFT (1 << 4) #define LGI_EF_RSHIFT (1 << 5) #define LGI_EF_SHIFT (LGI_EF_LSHIFT | LGI_EF_RSHIFT) #define LGI_EF_DOWN (1 << 6) #define LGI_EF_DOUBLE (1 << 7) #define LGI_EF_CAPS_LOCK (1 << 8) #define LGI_EF_IS_CHAR (1 << 9) #define LGI_EF_IS_NOT_CHAR (1 << 10) #define LGI_EF_SYSTEM (1 << 11) // Windows key/Apple key etc // Mouse Event Flags #define LGI_EF_LEFT (1 << 16) #define LGI_EF_MIDDLE (1 << 17) #define LGI_EF_RIGHT (1 << 18) #define LGI_EF_MOVE (1 << 19) #define LGI_EF_XBTN1 (1 << 20) #define LGI_EF_XBTN2 (1 << 21) // Emit compiler warnings #define __STR2__(x) #x #define __STR1__(x) __STR2__(x) #define __LOC__ __FILE__ "(" __STR1__(__LINE__) ") : Warning: " // To use just do #pragma message(__LOC__"My warning message") // Simple definition of breakable unicode characters #define LGI_BreakableChar(c) ( \ (c) == '\n' || \ (c) == ' ' || \ (c) == '\t' || \ ( (c) >= 0x3040 && (c) <= 0x30FF ) || \ ( (c) >= 0x3300 && (c) <= 0x9FAF ) \ ) // Os metrics enum LSystemMetric { /// Get the standard window horizontal border size /// \sa LApp::GetMetric() LGI_MET_DECOR_X = 1, /// Get the standard window vertical border size including caption bar. /// \sa LApp::GetMetric() LGI_MET_DECOR_Y, /// Get the standard window caption bar height only. /// \sa LApp::GetMetric() LGI_MET_DECOR_CAPTION, /// Get the height of a single line menu bar /// \sa LApp::GetMetric() LGI_MET_MENU, /// This is non-zero if the system is theme aware LGI_MET_THEME_AWARE, /// Size of a window's shadow LGI_MET_WINDOW_SHADOW, }; /// \brief Types of system paths available for querying /// \sa LgiGetSystemPath enum LSystemPath { LSP_ROOT, /// The location of the operating system folder /// [Win32] = e.g. C:\Windows /// [Mac] = /System /// [Linux] /boot LSP_OS, /// The system library folder /// [Win32] = e.g. C:\Windows\System32 /// [Mac] = /Library /// [Linux] = /usr/lib LSP_OS_LIB, /// A folder for storing temporary files. These files are usually /// deleted automatically later by the system. /// [Win32] = ~\Local Settings\Temp /// [Mac] = ~/Library/Caches/TemporaryItems /// [Linux] = /tmp LSP_TEMP, /// System wide application data /// [Win32] = ~\..\All Users\Application Data /// [Mac] = /System/Library /// [Linux] = /usr LSP_COMMON_APP_DATA, /// User specific application data /// [Win32] = ~\Application Data /// [Mac] = ~/Library /// [Linux] = /usr /// [Haiku] = ~/config (???) LSP_USER_APP_DATA, /// Machine + user specific application data (probably should not use) /// [Win32] = ~\Local Settings\Application Data /// [Mac] = ~/Library /// [Linux] = /usr/local LSP_LOCAL_APP_DATA, /// Desktop dir /// i.e. ~/Desktop LSP_DESKTOP, /// Home dir /// i.e. ~ LSP_HOME, /// Application install folder: /// [Win] c:\Program Files /// [Mac] /Applications /// [Linux] /usr/bin LSP_USER_APPS, /// The running application's path. /// [Mac] This doesn't include the "Contents/MacOS" part of the path inside the bundle. LSP_EXE, /// The system trash folder. LSP_TRASH, /// The app's install folder. /// [Win32] = $ExePath (sans '\Release' or '\Debug') /// [Mac/Linux] = $ExePath /// Where $ExePath is the folder the application is running from LSP_APP_INSTALL, /// The app's root folder (Where config and data should be stored) /// LOptionsFile uses LSP_APP_ROOT as the default location. /// [Win32] = ~\Application Data\Roaming\$AppName /// [Mac] = ~/Library/$AppName /// [Linux] = ~/.$AppName /// Where $AppName = LApp::GetName. /// If the given folder doesn't exist it will be created. LSP_APP_ROOT, /// This is the user's documents folder /// [Win32] ~\My Documents /// [Mac] ~\Documents LSP_USER_DOCUMENTS, /// This is the user's music folder /// [Win32] ~\My Music /// [Mac] ~\Music LSP_USER_MUSIC, /// This is the user's video folder /// [Win32] ~\My Videos /// [Mac] ~\Movies LSP_USER_VIDEO, /// This is the user's download folder /// ~\Downloads LSP_USER_DOWNLOADS, /// This is the user's links folder /// [Win32] = ~\Links /// [Mac] = ??? /// [Linux] = ??? LSP_USER_LINKS, /// User's pictures/photos folder LSP_USER_PICTURES, /// [Win32] = C:\Users\%HOME%\Pictures /// [Mac] = ??? /// [Linux] = ~\Pictures LSP_MOUNT_POINT, }; #ifdef _DEBUG #define DeclDebugArgs , const char *_file, int _line #define PassDebugArgs , __FILE__, __LINE__ #else #define DeclDebugArgs #define PassDebugArgs #endif #define _FL __FILE__, __LINE__ #define CALL_MEMBER_FN(obj, memFn) ((obj).*(memFn)) #include "lgi/common/AutoPtr.h" #endif diff --git a/src/linux/CrashHandler/CrashHandler.cpp b/src/linux/CrashHandler/CrashHandler.cpp --- a/src/linux/CrashHandler/CrashHandler.cpp +++ b/src/linux/CrashHandler/CrashHandler.cpp @@ -1,425 +1,425 @@ #include #include #include #include #include #include #include #define LAssert assert #include "lgi/common/LgiDefs.h" #include "lgi/common/Array.h" #include "lgi/common/StringClass.h" #define _FL __FILE__, __LINE__ enum HandleType { hRead, hWrite, hMax, }; int in[hMax]; // app -> child pipe int out[hMax]; // child -> app pipe bool loop = true; void LSleep(uint32_t i) { struct timespec request = {}, remain = {}; request.tv_sec = i / 1000; request.tv_nsec = (i % 1000) * 1000000; while (nanosleep(&request, &remain) == -1) request = remain; } template T *strnchr(T *str, char ch, size_t len) { if (!str) return NULL; T *end = str + len; while (str < end) { if (*str == ch) return str; str++; } return NULL; } enum AppState { sInit, sFrameSwitch, sLocals, sArgs, sList, sGetThreads, sThreadSwitch, sThreadBt, } state = sInit; LString::Array *output = NULL; int curThread = 1, threads = -1; FILE *crashLog = NULL; void Log(const char *fmt, ...) { va_list args; va_start(args, fmt); char buf[1024]; auto ch = vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); if (ch > 0) { printf("%.*s", (int)ch, buf); if (crashLog) fwrite(buf, ch, 1, crashLog); } } void OpenCrashLog() { if (crashLog) return; time_t now; time(&now); auto cur = localtime(&now); char file[256] = "crash.log"; if (cur) snprintf(file, sizeof(file), - "crash-%i%02.2i%02.2i-%02.2i%02.2i%02.2i.log", + "crash-%i%2.2i%2.2i-%2.2i%2.2i%2.2i.log", cur->tm_year + 1900, cur->tm_mon + 1, cur->tm_mday, cur->tm_hour, cur->tm_min, cur->tm_sec); crashLog = fopen(file, "w"); } void DumpOutput(const char *Label) { Log("%s:\n", Label); for (auto ln: *output) { if (ln.Find("(gdb)") < 0) Log(" %s\n", ln.Get()); } Log("\n"); output->Length(0); } /* Basic state flow: sInit sGetThreads # display all the threads for each thread: sThreadBt # back trace the thread sThreadSwitch # display back trace and check for crashed frame if has crash frame: sFrameSwitch # get to the right frame... sLocals # display the locals sArgs # display any function args sList # list the source code around the crash point */ void AtPrompt() { auto GotoNextThread = [&]() { if (curThread <= threads) { // Check out the next thread... char cmd[256]; auto cmdSz = snprintf(cmd, sizeof(cmd), "thread %i\n", curThread); int wr = write(in[hWrite], cmd, cmdSz); state = sThreadBt; } else loop = false; }; switch (state) { case sInit: { /* First thing is to look through all the threads and find the one that crashed... No point dumping locals and args from the thread that didn't crash. */ auto cmd = "info threads\n"; int wr = write(in[hWrite], cmd, strlen(cmd)); state = sGetThreads; output = new LString::Array; OpenCrashLog(); break; } case sFrameSwitch: { DumpOutput("Frame"); // Then start dumping out the locals and args... auto cmd = "info locals\n"; auto wr = write(in[hWrite], cmd, strlen(cmd)); state = sLocals; break; } case sLocals: { DumpOutput("Locals"); auto cmd = "info args\n"; int wr = write(in[hWrite], cmd, strlen(cmd)); state = sArgs; break; } case sArgs: { DumpOutput("Args"); auto cmd = "list\n"; int wr = write(in[hWrite], cmd, strlen(cmd)); state = sList; break; } case sList: { DumpOutput("List"); // If there are more threads... go back trace them GotoNextThread(); break; } case sGetThreads: { LString last; for (int i=output->Length()-1; i>=0; i--) { last = (*output)[i].Strip(); if (isdigit(last(0))) { threads = (int)last.Int(); break; } } DumpOutput("Threads"); output->Length(0); if (threads > 0) { GotoNextThread(); } else { Log("%s:%i - Error: failed to get thread count.\n", _FL); loop = false; } break; } case sThreadSwitch: { bool hasCrash = false; int crashFrame = -1; if (output->Length() > 0) { char label[64]; snprintf(label, sizeof(label), "Thread %i", curThread++); // Scan through the output and look for some crash indication int curFrame = -1; for (unsigned i=0; iLength(); i++) { auto ln = (*output)[i]; auto s = ln.Strip(); if (s.Length() && s(0) == '#') curFrame = s.SplitDelimit()[0].Strip("#").Int(); if (ln.Find("LgiCrashHandler") >= 0 || ln.Find("signal handler called") >= 0) { hasCrash = true; crashFrame = curFrame + 1; } } DumpOutput(label); } char cmd[256]; if (hasCrash && crashFrame >= 0) { // Switch to the crashing frame here... auto cmdSz = snprintf(cmd, sizeof(cmd), "frame %i\n", crashFrame); int wr = write(in[hWrite], cmd, cmdSz); state = sFrameSwitch; } else { // Check out the next thread... GotoNextThread(); } break; } case sThreadBt: { auto cmd = "bt\n"; int wr = write(in[hWrite], cmd, strlen(cmd)); state = sThreadSwitch; output->Length(0); break; } } } void OnLine(char *str) { // printf("line='%s'\n", str); if (output) output->Add(str); if (!strcasecmp(str, "(gdb) ")) AtPrompt(); } int main(int argCount, char **arg) { int pid = -1; for (int i=1; i 0) { memmove(buf, nl, remaining); used = remaining; // printf("rem=%i used=%i\n", (int)remaining, (int)used); } else { used = 0; // printf("break used=%i\n", (int)used); break; } } if (used > 0) { buf[used] = 0; OnLine(buf); used = 0; } // printf("\n"); } if (crashLog) fclose(crashLog); return 0; } diff --git a/src/linux/CrashHandler/CrashHandlerMakefile.linux b/src/linux/CrashHandler/CrashHandlerMakefile.linux --- a/src/linux/CrashHandler/CrashHandlerMakefile.linux +++ b/src/linux/CrashHandler/CrashHandlerMakefile.linux @@ -1,80 +1,85 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = ./crash-handler ifndef Build Build = Debug endif +MakeDir := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) BuildDir = $(Build) -Flags = -fPIC -w -fno-inline -fpermissive +CFlags = -MMD -MP -g -fPIC -fno-inline +CppFlags = $(CFlags) -fpermissive -std=c++14 ifeq ($(Build),Debug) - Flags += -g -std=c++14 + CFlags += -g + CppFlags += -g Tag = d Defs = -D_DEBUG -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX Libs = Inc = \ `pkg-config --cflags gtk+-3.0` \ -I../../../include/lgi/linux/Gtk \ -I../../../include else - Flags += -s -Os -std=c++14 + CFlags += -s -Os + CppFlags += -s -Os Defs = -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX Libs = Inc = \ `pkg-config --cflags gtk+-3.0` \ -I../../../include/lgi/linux/Gtk \ -I../../../include endif # Dependencies Source = CrashHandler.cpp -SourceLst := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(Source))) +SourceC := $(filter %.c,$(Source)) +ObjectsC := $(SourceC:.c=.o) +SourceCpp := $(filter %.cpp,$(Source)) +ObjectsCpp := $(SourceCpp:.cpp=.o) +Objects := $(notdir $(ObjectsC) $(ObjectsCpp)) +Objects := $(addprefix $(BuildDir)/,$(Objects)) +Deps := $(patsubst %.o,%.d,$(Objects)) -Objects := $(addprefix $(BuildDir)/,$(SourceLst)) +$(BuildDir)/%.o: %.c + mkdir -p $(@D) + echo $(notdir $<) [$(Build)] + $(CC) $(Inc) $(CFlags) $(Defs) -c $< -o $@ + +$(BuildDir)/%.o: %.cpp + mkdir -p $(@D) + echo $(notdir $<) [$(Build)] + $(CPP) $(Inc) $(CppFlags) $(Defs) -c $< -o $@ # Target # Executable target $(Target) : $(Objects) mkdir -p $(BuildDir) @echo Linking $(Target) [$(Build)]... $(CPP) -Wl,-export-dynamic,-R. -o \ $(Target) $(Objects) $(Libs) @echo Done. -.SECONDEXPANSION: -$(Objects): $(BuildDir)/%.o: $$(wildcard %.c*) - mkdir -p $(@D) - @echo $(