diff --git a/Ide/LgiIdeProj.xml b/Ide/LgiIdeProj.xml
--- a/Ide/LgiIdeProj.xml
+++ b/Ide/LgiIdeProj.xml
@@ -1,279 +1,279 @@
.\win\Makefile.windows
./linux/Makefile.linux
./MacCocoa/LgiIde.xcodeproj
Makefile.haiku
./lgiide
./lgiide
LgiIde.exe
LgiIde.exe
WINDOWS
WINNATIVE
WINDOWS
WINNATIVE
POSIX
POSIX
LIBPNG_VERSION=\"1.2\"
LIBPNG_VERSION=\"1.2\"
MingW
/home/matthew/Code/Lgi/trunk/Ide/Code/IdeProjectSettings.cpp
./src
./resources
../include
./src
./resources
../include
..\include\lgi\win
..\include\lgi\win
../include/lgi/linux
../include/lgi/linux/Gtk
../../../../codelib/openssl/include
../include/lgi/linux
../include/lgi/linux/Gtk
../../../../codelib/openssl/include
../include/lgi/haiku
../include/lgi/haiku
../include/lgi/mac/cocoa
../include/lgi/mac/cocoa
imm32
imm32
magic
pthread
`pkg-config --libs gtk+-3.0`
-static-libgcc
magic
pthread
`pkg-config --libs gtk+-3.0`
-static-libgcc
-static-libgcc
gnu
network
be
-static-libgcc
gnu
network
be
Executable
lgiide
lgiide
LgiIde.exe
LgiIde.exe
lgiide
lgiide
`pkg-config --cflags gtk+-3.0`
`pkg-config --cflags gtk+-3.0`
POSIX
_GNU_SOURCE
POSIX
_GNU_SOURCE
4
4
0
1
SOME_TEST=testing
- /home/matthew/code/scribe/trunk_os/linux/ScribeProj.xml
+ /home/matthew/code/i.Hex/trunk/iHexProject.xml
diff --git a/Ide/src/IdeProject.cpp b/Ide/src/IdeProject.cpp
--- a/Ide/src/IdeProject.cpp
+++ b/Ide/src/IdeProject.cpp
@@ -1,4104 +1,4105 @@
#if defined(WIN32)
#include
#else
#include
#endif
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/DragAndDrop.h"
#include "lgi/common/Token.h"
#include "lgi/common/Combo.h"
#include "lgi/common/Net.h"
#include "lgi/common/ListItemCheckBox.h"
#include "lgi/common/ClipBoard.h"
#include "lgi/common/DropFiles.h"
#include "lgi/common/SubProcess.h"
#include "lgi/common/Css.h"
#include "lgi/common/TableLayout.h"
#include "lgi/common/TextLabel.h"
#include "lgi/common/Button.h"
#include "lgi/common/RegKey.h"
#include "lgi/common/FileSelect.h"
#include "lgi/common/Menu.h"
#include "LgiIde.h"
#include "resdefs.h"
#include "FtpThread.h"
#include "ProjectNode.h"
#include "WebFldDlg.h"
extern const char *Untitled;
const char SourcePatterns[] = "*.c;*.h;*.cpp;*.cc;*.java;*.d;*.php;*.html;*.css;*.js";
const char *AddFilesProgress::DefaultExt = "c,cpp,cc,cxx,h,hpp,hxx,html,css,json,js,jsx,txt,png,jpg,jpeg,rc,xml,mk,paths,makefile,py,java,php";
const char *VsBinaries[] = {"devenv.com", "WDExpress.exe"};
#define USE_OPEN_PROGRESS 1
#define STOP_BUILD_TIMEOUT 2000
#ifdef WINDOWS
#define LGI_STATIC_LIBRARY_EXT "lib"
#else
#define LGI_STATIC_LIBRARY_EXT "a"
#endif
const char *PlatformNames[] =
{
"Windows",
"Linux",
"Mac",
"Haiku",
0
};
int PlatformCtrlId[] =
{
IDC_WIN32,
IDC_LINUX,
IDC_MAC,
IDC_HAIKU,
0
};
const char *PlatformDynamicLibraryExt(IdePlatform Platform)
{
if (Platform == PlatformWin)
return "dll";
if (Platform == PlatformMac)
return "dylib";
return "so";
}
const char *PlatformSharedLibraryExt(IdePlatform Platform)
{
if (Platform == PlatformWin)
return "lib";
return "a";
}
const char *PlatformExecutableExt(IdePlatform Platform)
{
if (Platform == PlatformWin)
return ".exe";
return "";
}
char *ToUnixPath(char *s)
{
if (s)
{
char *c;
while ((c = strchr(s, '\\')))
*c = '/';
}
return s;
}
const char *CastEmpty(char *s)
{
return s ? s : "";
}
bool FindInPath(LString &Exe)
{
LString::Array Path = LString(getenv("PATH")).Split(LGI_PATH_SEPARATOR);
for (unsigned i=0; i SubProc;
LString::Array BuildConfigs;
LString::Array PostBuild;
int AppHnd;
enum CompilerType
{
DefaultCompiler,
VisualStudio,
MingW,
Gcc,
CrossCompiler,
PythonScript,
IAR,
Nmake,
Cygwin,
Xcode,
}
Compiler;
enum ArchType
{
DefaultArch,
ArchX32,
ArchX64,
ArchArm6,
ArchArm7,
}
Arch;
public:
BuildThread(IdeProject *proj, char *makefile, bool clean, BuildConfig config, bool all, int wordsize);
~BuildThread();
ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) override;
LString FindExe();
LAutoString WinToMingWPath(const char *path);
int Main() override;
};
class IdeProjectPrivate
{
public:
AppWnd *App;
IdeProject *Project;
bool Dirty, UserFileDirty;
LString FileName;
IdeProject *ParentProject;
IdeProjectSettings Settings;
LAutoPtr Thread;
LHashTbl, ProjectNode*> Nodes;
int NextNodeId;
// Threads
LAutoPtr CreateMakefile;
// User info file
LString UserFile;
LHashTbl,int> UserNodeFlags;
IdeProjectPrivate(AppWnd *a, IdeProject *project) :
Project(project),
Settings(project)
{
App = a;
Dirty = false;
UserFileDirty = false;
ParentProject = 0;
NextNodeId = 1;
}
void CollectAllFiles(LTreeNode *Base, LArray &Files, bool SubProjects, int Platform);
};
LString ToPlatformPath(const char *s, IdePlatform platform)
{
LString p = s;
if (platform == PlatformWin)
return p.Replace("/", "\\");
else
return p.Replace("\\", "/");
}
class MakefileThread : public LThread, public LCancel
{
IdeProjectPrivate *d;
IdeProject *Proj;
IdePlatform Platform;
LStream *Log;
bool BuildAfterwards;
bool HasError;
public:
static int Instances;
MakefileThread(IdeProjectPrivate *priv, IdePlatform platform, bool Build) : LThread("MakefileThread")
{
Instances++;
d = priv;
Proj = d->Project;
Platform = platform;
BuildAfterwards = Build;
HasError = false;
Log = d->App->GetBuildLog();
Run();
}
~MakefileThread()
{
Cancel();
while (!IsExited())
LSleep(1);
Instances--;
}
void OnError(const char *Fmt, ...)
{
va_list Arg;
va_start(Arg, Fmt);
LStreamPrintf(Log, 0, Fmt, Arg);
va_end(Arg);
HasError = true;
}
int Main()
{
const char *PlatformName = PlatformNames[Platform];
const char *PlatformLibraryExt = NULL;
const char *PlatformStaticLibExt = NULL;
const char *PlatformExeExt = "";
LString LinkerFlags;
const char *TargetType = d->Settings.GetStr(ProjTargetType, NULL, Platform);
const char *CompilerName = d->Settings.GetStr(ProjCompiler);
LString CCompilerBinary = "gcc";
LString CppCompilerBinary = "g++";
LStream *Log = d->App->GetBuildLog();
bool IsExecutableTarget = TargetType && !stricmp(TargetType, "Executable");
bool IsDynamicLibrary = TargetType && !stricmp(TargetType, "DynamicLibrary");
LAssert(Log);
if (!Log)
return false;
Log->Print("CreateMakefile for '%s'...\n", PlatformName);
if (Platform == PlatformWin)
{
LinkerFlags = ",--enable-auto-import";
}
else
{
if (IsDynamicLibrary)
{
LinkerFlags = ",-soname,$(TargetFile)";
}
LinkerFlags += ",-export-dynamic,-R.";
}
auto Base = Proj->GetBasePath();
auto MakeFile = Proj->GetMakefile(Platform);
LString MakeFilePath;
Proj->CheckExists(MakeFile);
if (!MakeFile)
{
MakeFilePath = Base.Get();
LFile::Path p(MakeFilePath, "makefile");
MakeFile = p.GetFull();
}
else if (LIsRelativePath(MakeFile))
{
LFile::Path p(Base);
p += MakeFile;
MakeFile = p.GetFull();
p--;
MakeFilePath = p.GetFull();
}
else
{
LFile::Path p(MakeFile);
p--;
MakeFilePath = p.GetFull();
}
// LGI_LIBRARY_EXT
switch (Platform)
{
case PlatformWin:
PlatformLibraryExt = "dll";
PlatformStaticLibExt = "lib";
PlatformExeExt = ".exe";
break;
case PlatformLinux:
case PlatformHaiku:
PlatformLibraryExt = "so";
PlatformStaticLibExt = "a";
break;
case PlatformMac:
PlatformLibraryExt = "dylib";
PlatformStaticLibExt = "a";
break;
default:
LAssert(0);
break;
}
if (CompilerName)
{
if (!stricmp(CompilerName, "cross"))
{
LString CBin = d->Settings.GetStr(ProjCCrossCompiler, NULL, Platform);
if (CBin && !LFileExists(CBin))
FindInPath(CBin);
if (CBin && LFileExists(CBin))
CCompilerBinary = CBin;
else
Log->Print("%s:%i - Error: C cross compiler '%s' not found.\n", _FL, CBin.Get());
LString CppBin = d->Settings.GetStr(ProjCppCrossCompiler, NULL, Platform);
if (CppBin && !LFileExists(CppBin))
FindInPath(CppBin);
if (CppBin && LFileExists(CppBin))
CppCompilerBinary = CppBin;
else
Log->Print("%s:%i - Error: C++ cross compiler '%s' not found.\n", _FL, CppBin.Get());
}
}
LFile m;
if (!m.Open(MakeFile, O_WRITE))
{
Log->Print("Error: Failed to open '%s' for writing.\n", MakeFile.Get());
return false;
}
m.SetSize(0);
m.Print("#!/usr/bin/make\n"
"#\n"
"# This makefile generated by LgiIde\n"
"# http://www.memecode.com/lgi.php\n"
"#\n"
"\n"
".SILENT :\n"
"\n"
"CC = %s\n"
"CPP = %s\n",
CCompilerBinary.Get(),
CppCompilerBinary.Get());
// Collect all files that require building
LArray Files;
d->CollectAllFiles
(
Proj,
Files,
false,
1 << Platform
);
if (IsExecutableTarget)
{
LString Exe = Proj->GetExecutable(Platform);
if (Exe)
{
if (LIsRelativePath(Exe))
m.Print("Target = %s\n", ToPlatformPath(Exe, Platform).Get());
else
{
auto RelExe = LMakeRelativePath(Base, Exe);
if (Base && RelExe)
{
m.Print("Target = %s\n", ToPlatformPath(RelExe, Platform).Get());
}
else
{
Log->Print("%s:%i - Error: Missing path (%s, %s).\n", _FL, Base.Get(), RelExe.Get());
return false;
}
}
}
else
{
Log->Print("%s:%i - Error: No executable name specified (%s, %s).\n", _FL, TargetType, d->FileName.Get());
return false;
}
}
else
{
LString Target = Proj->GetTargetName(Platform);
if (Target)
m.Print("Target = %s\n", ToPlatformPath(Target, Platform).Get());
else
{
Log->Print("%s:%i - Error: No target name specified.\n", _FL);
return false;
}
}
// Output the build mode, flags and some paths
auto BuildMode = d->App->GetBuildMode();
auto BuildModeName = toString(BuildMode);
m.Print("ifndef Build\n"
" Build = %s\n"
"endif\n",
BuildModeName);
m.Print("MakeDir := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))\n");
LString sDefines[BuildMax];
LString sLibs[BuildMax];
LString sIncludes[BuildMax];
const char *ExtraLinkFlags = NULL;
const char *ExeFlags = NULL;
if (Platform == PlatformWin)
{
ExtraLinkFlags = "";
ExeFlags = " -mwindows";
m.Print("BuildDir = $(Build)\n"
"\n"
"Flags = -fPIC -w -fno-inline -fpermissive\n");
const char *DefDefs = "-DWIN32 -D_REENTRANT";
sDefines[BuildDebug] = DefDefs;
sDefines[BuildRelease] = DefDefs;
}
else
{
LString PlatformCap = PlatformName;
ExtraLinkFlags = "";
ExeFlags = "";
m.Print("BuildDir = $(Build)\n"
"\n"
"Flags = -fPIC -w -fno-inline -fpermissive\n" // -fexceptions
);
sDefines[0].Printf("-D%s -D_REENTRANT", PlatformCap.Upper().Get());
#ifdef LINUX
sDefines[BuildDebug] += " -D_FILE_OFFSET_BITS=64"; // >:-(
sDefines[BuildDebug] += " -DPOSIX";
#endif
sDefines[BuildRelease] = sDefines[BuildDebug];
}
List Deps;
Proj->GetChildProjects(Deps);
for (int Cfg = BuildDebug; Cfg < BuildMax; Cfg++)
{
// Set the config
auto cfgName = toString((BuildConfig)Cfg);
d->Settings.SetCurrentConfig(cfgName);
// Get the defines setup
auto PDefs = d->Settings.GetStr(ProjDefines, NULL, Platform);
if (ValidStr(PDefs))
{
LToken Defs(PDefs, " ;,\r\n");
for (int i=0; iSettings.GetStr(ProjLibraryPaths, NULL, Platform);
if (ValidStr(PLibPaths))
{
LString::Array LibPaths = PLibPaths.Split("\n");
for (auto i: LibPaths)
{
LString s, in = i.Strip();
if (!in.Length())
continue;
if (strchr("`-", in(0)))
{
s.Printf(" \\\n\t\t%s", in.Get());
}
else
{
LString Rel;
if (!LIsRelativePath(in))
Rel = LMakeRelativePath(Base, in);
LString Final = Rel ? Rel.Get() : in.Get();
if (!Proj->CheckExists(Final))
OnError("%s:%i - Library path '%s' doesn't exist (from %s).\n",
_FL, Final.Get(),
Proj->GetFileName());
s.Printf(" \\\n\t\t-L%s", ToUnixPath(Final));
}
sLibs[Cfg] += s;
}
}
const char *PLibs = d->Settings.GetStr(ProjLibraries, NULL, Platform);
if (ValidStr(PLibs))
{
LToken Libs(PLibs, "\r\n");
for (int i=0; iCheckExists(l);
s.Printf(" \\\n\t\t-l%s", ToUnixPath(l));
}
sLibs[Cfg] += s;
}
}
for (auto dep: Deps)
{
LString Target = dep->GetTargetName(Platform);
if (Target)
{
char t[MAX_PATH_LEN];
strcpy_s(t, sizeof(t), Target);
if (!strnicmp(t, "lib", 3))
memmove(t, t + 3, strlen(t + 3) + 1);
char *dot = strrchr(t, '.');
if (dot)
*dot = 0;
LString s, sTarget = t;
Proj->CheckExists(sTarget);
s.Printf(" \\\n\t\t-l%s$(Tag)", ToUnixPath(sTarget));
sLibs[Cfg] += s;
auto DepBase = dep->GetBasePath();
if (DepBase)
{
LString DepPath = DepBase.Get();
auto Rel = LMakeRelativePath(Base, DepPath);
LString Final = Rel ? Rel.Get() : DepPath.Get();
Proj->CheckExists(Final);
s.Printf(" \\\n\t\t-L%s/$(BuildDir)", ToUnixPath(Final.RStrip("/\\")));
sLibs[Cfg] += s;
}
}
}
// Includes
// Do include paths
LHashTbl,bool> Inc;
auto ProjIncludes = d->Settings.GetStr(ProjIncludePaths, NULL, Platform);
if (ValidStr(ProjIncludes))
{
// Add settings include paths.
LToken Paths(ProjIncludes, "\r\n");
for (int i=0; iCheckExists(pn))
OnError("%s:%i - Include path '%s' doesn't exist.\n", _FL, pn.Get());
else if (!Inc.Find(pn))
Inc.Add(pn, true);
}
}
const char *SysIncludes = d->Settings.GetStr(ProjSystemIncludes, NULL, Platform);
if (ValidStr(SysIncludes))
{
// Add settings include paths.
LToken Paths(SysIncludes, "\r\n");
for (int i=0; iCheckExists(pn))
OnError("%s:%i - System include path '%s' doesn't exist (from %s).\n",
_FL, pn.Get(),
Proj->GetFileName());
else if (!Inc.Find(pn))
Inc.Add(pn, true);
}
}
LString::Array Incs;
for (auto i: Inc)
Incs.New() = i.key;
Incs.Sort();
for (auto i: Incs)
{
LString s;
if (*i == '`')
{
// Pass though shell cmd
s.Printf(" \\\n\t\t%s", i.Get());
}
else
{
LFile::Path p;
if (LIsRelativePath(i))
{
p = Base.Get();
p += i;
}
else p = i;
auto rel = LMakeRelativePath(Base, p.GetFull());
s.Printf(" \\\n\t\t-I%s", ToUnixPath(rel ? rel : i));
}
sIncludes[Cfg] += s;
}
}
// Output the defs section for Debug and Release
// Debug specific
m.Print("\n"
"ifeq ($(Build),Debug)\n"
" Flags += -MMD -MP -g -std=c++14\n"
" Tag = d\n"
" Defs = -D_DEBUG %s\n"
" Libs = %s\n"
" Inc = %s\n",
CastEmpty(sDefines [BuildDebug].Get()),
CastEmpty(sLibs [BuildDebug].Get()),
CastEmpty(sIncludes[BuildDebug].Get()));
// Release specific
m.Print("else\n"
" Flags += -MMD -MP -s -Os -std=c++14\n"
" Defs = %s\n"
" Libs = %s\n"
" Inc = %s\n"
"endif\n"
"\n",
CastEmpty(sDefines [BuildRelease].Get()),
CastEmpty(sLibs [BuildRelease].Get()),
CastEmpty(sIncludes[BuildRelease].Get()));
if (Files.Length())
{
LArray VPath;
// Proj->BuildIncludePaths(VPath, false, false, Platform);
auto AddVPath = [&VPath](LString p)
{
for (auto s: VPath)
if (p == s)
return;
VPath.Add(p);
};
// Do source code list...
m.Print("# Dependencies\n"
"Source =\t");
LString::Array SourceFiles;
for (auto &n: Files)
{
if (n->GetType() == NodeSrc)
{
auto f = n->GetFileName();
auto path = ToPlatformPath(f, Platform);
LFile::Path p;
if (LIsRelativePath(path))
{
p = Base.Get();
p += path;
}
else
{
p = path;
}
auto rel = LMakeRelativePath(Base, p.GetFull());
if (rel && rel.Find("./") == 0)
rel = rel(2,-1);
SourceFiles.Add(rel ? rel : path);
if (true)
{
// Also add path of file to VPath.
p--;
AddVPath(p.GetFull());
}
}
}
SourceFiles.Sort();
int c = 0;
for (auto &src: SourceFiles)
{
if (c++) m.Print(" \\\n\t\t\t");
m.Print("%s", src.Get());
}
m.Print("\n"
"\n"
"SourceC := $(filter %%.c,$(Source))\n"
"ObjectsC := $(SourceC:.c=.o)\n"
"SourceCpp := $(filter %%.cpp,$(Source))\n"
"ObjectsCpp := $(SourceCpp:.cpp=.o)\n"
"Objects := $(notdir $(ObjectsC) $(ObjectsCpp))\n"
"Objects := $(addprefix $(BuildDir)/,$(Objects))\n"
"Deps := $(patsubst %%.o,%%.d,$(Objects))\n"
"\n"
"$(BuildDir)/%%.o: %%.c\n"
" mkdir -p $(@D)\n"
" echo $(notdir $<) [$(Build)]\n"
" $(CC) $(Inc) $(Flags) $(Defs) -c $< -o $@\n"
"\n"
"$(BuildDir)/%%.o: %%.cpp\n"
" mkdir -p $(@D)\n"
" echo $(notdir $<) [$(Build)]\n"
" $(CPP) $(Inc) $(Flags) $(Defs) -c $< -o $@\n"
"\n");
// Write out the target stuff
m.Print("# Target\n");
LHashTbl,bool> DepFiles;
if (TargetType)
{
if (IsExecutableTarget)
{
m.Print("# Executable target\n"
"$(Target) :");
LStringPipe Rules;
IdeProject *Dep;
uint64 Last = LCurrentTime();
int Count = 0;
auto It = Deps.begin();
for (Dep=*It; Dep && !IsCancelled(); Dep=*(++It), Count++)
{
// Get dependency to create it's own makefile...
Dep->CreateMakefile(Platform, false);
// Build a rule to make the dependency if any of the source changes...
auto DepBase = Dep->GetBasePath();
auto TargetFile = Dep->GetTargetFile(Platform);
if (DepBase && Base && TargetFile)
{
LString Rel = LMakeRelativePath(Base, DepBase);
ToNativePath(Rel);
// Add tag to target name
auto Parts = TargetFile.SplitDelimit(".");
if (Parts.Length() == 2)
TargetFile.Printf("lib%s$(Tag).%s", Parts[0].Get(), Parts[1].Get());
LFile::Path Buf(Rel);
Buf += "$(BuildDir)";
Buf += TargetFile;
m.Print(" %s", Buf.GetFull().Get());
LArray AllDeps;
Dep->GetAllDependencies(AllDeps, Platform);
LAssert(AllDeps.Length() > 0);
AllDeps.Sort(StrSort);
Rules.Print("%s : ", Buf.GetFull().Get());
for (int i=0; iGetMakefile(Platform);
// RenameMakefileForPlatform(Mk, Platform);
if (Mk)
{
char *DepMakefile = strrchr(Mk, DIR_CHAR);
if (DepMakefile)
Rules.Print(" -f %s", DepMakefile + 1);
}
else
{
Mk = Dep->GetMakefile(Platform);
OnError("%s:%i - No makefile for '%s'\n", _FL, Dep->GetFullPath().Get());
}
Rules.Print("\n\n");
}
uint64 Now = LCurrentTime();
if (Now - Last > 1000)
{
Last = Now;
Log->Print("Building deps %i%%...\n", (int) (((int64)Count+1)*100/Deps.Length()));
}
}
m.Print(" $(Objects)\n"
" mkdir -p $(BuildDir)\n"
" @echo Linking $(Target) [$(Build)]...\n"
" $(CPP)%s%s %s%s -o \\\n"
" $(Target) $(Objects) $(Libs)\n",
ExtraLinkFlags,
ExeFlags,
ValidStr(LinkerFlags) ? "-Wl" : "", LinkerFlags.Get());
if (Platform == PlatformHaiku)
{
// Is there an application icon configured?
const char *AppIcon = d->Settings.GetStr(ProjApplicationIcon, NULL, Platform);
if (AppIcon)
{
m.Print(" addattr -f %s -t \"'VICN'\" \"BEOS:ICON\" $(Target)\n", AppIcon);
}
}
LString PostBuildCmds = d->Settings.GetStr(ProjPostBuildCommands, NULL, Platform);
if (ValidStr(PostBuildCmds))
{
LString::Array a = PostBuildCmds.Split("\n");
for (unsigned i=0; iGetMakefile(Platform);
if (mk)
{
LAutoString dep_base = d->GetBasePath();
d->CheckExists(dep_base);
auto rel_dir = LMakeRelativePath(Base, dep_base);
d->CheckExists(rel_dir);
char *mk_leaf = strrchr(mk, DIR_CHAR);
m.Print(" +make -C \"%s\" -f \"%s\" clean\n",
ToUnixPath(rel_dir ? rel_dir.Get() : dep_base.Get()),
ToUnixPath(mk_leaf ? mk_leaf + 1 : mk.Get()));
}
}
m.Print("\n");
}
// Shared library
else if (!stricmp(TargetType, "DynamicLibrary"))
{
m.Print("TargetFile = lib$(Target)$(Tag).%s\n"
"$(TargetFile) : $(Objects)\n"
" mkdir -p $(BuildDir)\n"
" @echo Linking $(TargetFile) [$(Build)]...\n"
" $(CPP)$s -shared \\\n"
" %s%s \\\n"
" -o $(BuildDir)/$(TargetFile) \\\n"
" $(Objects) \\\n"
" $(Libs)\n",
PlatformLibraryExt,
ValidStr(ExtraLinkFlags) ? "-Wl" : "", ExtraLinkFlags,
LinkerFlags.Get());
LString PostBuildCmds = d->Settings.GetStr(ProjPostBuildCommands, NULL, Platform);
if (ValidStr(PostBuildCmds))
{
LString::Array a = PostBuildCmds.Split("\n");
for (unsigned i=0; iSettings.GetStr(ProjPostBuildCommands, NULL, Platform);
if (ValidStr(PostBuildCmds))
{
LString::Array a = PostBuildCmds.Split("\n");
for (unsigned i=0; iCheckExists(p);
if (p && !strchr(p, '`'))
{
if (!LIsRelativePath(p))
{
auto a = LMakeRelativePath(Base, p);
m.Print(" \\\n\t%s", ToPlatformPath(a ? a.Get() : p.Get(), Platform).Get());
}
else
{
m.Print(" \\\n\t%s", ToPlatformPath(p, Platform).Get());
}
}
}
m.Print("\n");
const char *OtherMakefileRules = d->Settings.GetStr(ProjMakefileRules, NULL, Platform);
if (ValidStr(OtherMakefileRules))
{
m.Print("\n%s\n", OtherMakefileRules);
}
}
}
else
{
m.Print("# No files require building.\n");
}
Log->Print("...Done: '%s'\n", MakeFile.Get());
if (BuildAfterwards)
{
if (!Proj->GetApp()->PostEvent(M_START_BUILD))
printf("%s:%i - PostEvent(M_START_BUILD) failed.\n", _FL);
}
return HasError;
}
void OnAfterMain()
{
Proj->GetApp()->PostEvent(M_MAKEFILES_CREATED, (LMessage::Param)Proj);
}
};
/////////////////////////////////////////////////////////////////////////////////////
NodeSource::~NodeSource()
{
if (nView)
{
nView->nSrc = 0;
}
}
NodeView::~NodeView()
{
if (nSrc)
{
nSrc->nView = 0;
}
}
//////////////////////////////////////////////////////////////////////////////////
int NodeSort(LTreeItem *a, LTreeItem *b, NativeInt d)
{
ProjectNode *A = dynamic_cast(a);
ProjectNode *B = dynamic_cast(b);
if (A && B)
{
if
(
(A->GetType() == NodeDir)
^
(B->GetType() == NodeDir)
)
{
return A->GetType() == NodeDir ? -1 : 1;
}
else
{
return Stricmp(a->GetText(0), b->GetText(0));
}
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool ReadVsProjFile(LString File, LString &Ver, LString::Array &Configs)
{
const char *Ext = LGetExtension(File);
if (!Ext || _stricmp(Ext, "vcxproj"))
return false;
LFile f;
if (!f.Open(File, O_READ))
return false;
LXmlTree Io;
LXmlTag r;
if (Io.Read(&r, &f) &&
r.IsTag("Project"))
{
Ver = r.GetAttr("ToolsVersion");
LXmlTag *ItemGroup = r.GetChildTag("ItemGroup");
if (ItemGroup)
for (auto c: ItemGroup->Children)
{
if (c->IsTag("ProjectConfiguration"))
{
char *s = c->GetAttr("Include");
if (s)
Configs.New() = s;
}
}
}
else return false;
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
BuildThread::BuildThread(IdeProject *proj, char *makefile, bool clean, BuildConfig config, bool all, int wordsize) : LThread("BuildThread")
{
Proj = proj;
Makefile = makefile;
Clean = clean;
Config = config;
All = all;
WordSize = wordsize;
Arch = DefaultArch;
Compiler = DefaultCompiler;
AppHnd = Proj->GetApp()->AddDispatch();
LString Cmds = proj->d->Settings.GetStr(ProjPostBuildCommands, NULL);
if (ValidStr(Cmds))
PostBuild = Cmds.SplitDelimit("\r\n");
auto Ext = LGetExtension(Makefile);
if (Ext && !_stricmp(Ext, "py"))
Compiler = PythonScript;
else if (Ext && !_stricmp(Ext, "sln"))
Compiler = VisualStudio;
else if (Ext && !Stricmp(Ext, "xcodeproj"))
Compiler = Xcode;
else
{
auto Comp = Proj->GetSettings()->GetStr(ProjCompiler);
if (Comp)
{
// Use the specified compiler...
if (!stricmp(Comp, "VisualStudio"))
Compiler = VisualStudio;
else if (!stricmp(Comp, "MingW"))
Compiler = MingW;
else if (!stricmp(Comp, "gcc"))
Compiler = Gcc;
else if (!stricmp(Comp, "cross"))
Compiler = CrossCompiler;
else if (!stricmp(Comp, "IAR"))
Compiler = IAR;
else if (!stricmp(Comp, "Cygwin"))
Compiler = Cygwin;
else
LAssert(!"Unknown compiler.");
}
}
if (Compiler == DefaultCompiler)
{
// Use default compiler for platform...
#ifdef WIN32
Compiler = VisualStudio;
#else
Compiler = Gcc;
#endif
}
Run();
}
BuildThread::~BuildThread()
{
if (SubProc)
{
bool b = SubProc->Interrupt();
LgiTrace("%s:%i - Sub process interrupt = %i.\n", _FL, b);
}
else LgiTrace("%s:%i - No sub process to interrupt.\n", _FL);
uint64 Start = LCurrentTime();
bool Killed = false;
while (!IsExited())
{
LSleep(10);
if (LCurrentTime() - Start > STOP_BUILD_TIMEOUT &&
SubProc)
{
if (Killed)
{
// Thread is stuck as well... ok kill that too!!! Argh - kill all the things!!!!
Terminate();
LgiTrace("%s:%i - Thread killed.\n", _FL);
Proj->GetApp()->PostEvent(M_BUILD_DONE);
break;
}
else
{
// Kill the sub-process...
bool b = SubProc->Kill();
Killed = true;
LgiTrace("%s:%i - Sub process killed.\n", _FL, b);
Start = LCurrentTime();
}
}
}
}
ssize_t BuildThread::Write(const void *Buffer, ssize_t Size, int Flags)
{
if (Proj->GetApp())
{
Proj->GetApp()->PostEvent(M_APPEND_TEXT, (LMessage::Param)NewStr((char*)Buffer, Size), AppWnd::BuildTab);
}
return Size;
}
#pragma comment(lib, "version.lib")
struct ProjInfo
{
LString Guid, Name, File;
LHashTbl,ssize_t> Configs;
};
LString BuildThread::FindExe()
{
LString::Array p = LGetPath();
if (Compiler == PythonScript)
{
#if defined(WINDOWS)
uint32_t BestVer = 0;
#else
LString BestVer;
#endif
static LString Best;
if (!Best)
{
const char *binName[] = {"python3", "python"};
for (int i=0; idwProductVersionMS > BestVer)
{
BestVer = v->dwProductVersionMS;
Best = Path;
}
}
}
else if (!Best)
{
Best = Path;
}
free(Buf);
#else
LSubProcess p(Path, "--version");
LStringPipe o;
if (p.Start())
p.Communicate(&o);
auto Out = o.NewLStr();
auto Ver = Out.SplitDelimit().Last();
printf("Ver=%s\n", Ver.Get());
if (!BestVer || Stricmp(Ver.Get(), BestVer.Get()) > 0)
{
Best = Path;
BestVer = Ver;
}
break;
#endif
}
}
}
}
return Best;
}
else if (Compiler == VisualStudio)
{
// Find the version we need:
double fVer = 0.0;
ProjInfo *First = NULL;
LHashTbl, ProjInfo*> Projects;
const char *Ext = LGetExtension(Makefile);
if (Ext && !_stricmp(Ext, "sln"))
{
LFile f;
if (f.Open(Makefile, O_READ))
{
LString ProjKey = "Project(";
LString StartSection = "GlobalSection(";
LString EndSection = "EndGlobalSection";
LString Section;
ssize_t Pos;
LString::Array Ln = f.Read().SplitDelimit("\r\n");
for (size_t i = 0; i < Ln.Length(); i++)
{
LString s = Ln[i].Strip();
if ((Pos = s.Find(ProjKey)) >= 0)
{
LString::Array p = s.SplitDelimit("(),=");
if (p.Length() > 5)
{
ProjInfo *i = new ProjInfo;
i->Name = p[3].Strip(" \t\"");
i->File = p[4].Strip(" \t\'\"");
i->Guid = p[5].Strip(" \t\"");
if (LIsRelativePath(i->File))
{
char f[MAX_PATH_LEN];
LMakePath(f, sizeof(f), Makefile, "..");
LMakePath(f, sizeof(f), f, i->File);
if (LFileExists(f))
i->File = f;
/*
else
LAssert(0);
*/
}
if (!First)
First = i;
Projects.Add(i->Guid, i);
}
}
else if (s.Find(StartSection) >= 0)
{
auto p = s.SplitDelimit("() \t");
Section = p[1];
}
else if (s.Find(EndSection) >= 0)
{
Section.Empty();
}
else if (Section == "ProjectConfigurationPlatforms")
{
auto p = s.SplitDelimit(". \t");
auto i = Projects.Find(p[0]);
if (i)
{
if (!i->Configs.Find(p[1]))
{
auto Idx = i->Configs.Length() + 1;
i->Configs.Add(p[1], Idx);
}
}
}
else if (Section == "SolutionConfigurationPlatforms")
{
auto p = s.SplitDelimit();
auto config = p[0];
for (auto &it: Projects)
{
auto proj = it.value;
if (!proj->Configs.Find(config))
proj->Configs.Add(config, proj->Configs.Length()+1);
}
}
}
}
}
else if (Ext && !_stricmp(Ext, "vcxproj"))
{
// ProjFile = Makefile;
}
else
{
if (Arch == DefaultArch)
{
if (sizeof(size_t) == 4)
Arch = ArchX32;
else
Arch = ArchX64;
}
#ifdef _MSC_VER
// Nmake file..
LString NmakePath;
switch (_MSC_VER)
{
#ifdef _MSC_VER_VS2013
case _MSC_VER_VS2013:
{
if (Arch == ArchX32)
NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\nmake.exe";
else
NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\amd64\\nmake.exe";
break;
}
#endif
#ifdef _MSC_VER_VS2015
case _MSC_VER_VS2015:
{
if (Arch == ArchX32)
NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\nmake.exe";
else
NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\amd64\\nmake.exe";
break;
}
#endif
default:
{
LAssert(!"Impl me.");
break;
}
}
if (LFileExists(NmakePath))
{
Compiler = Nmake;
return NmakePath;
}
#endif
}
/*
if (ProjFile && LFileExists(ProjFile))
{
LString sVer;
if (ReadVsProjFile(ProjFile, sVer, BuildConfigs))
{
fVer = sVer.Float();
}
}
*/
if (First)
{
for (auto i: First->Configs)
{
BuildConfigs[i.value - 1] = i.key;
LFile f(First->File, O_READ);
LXmlTree t;
LXmlTag r;
if (t.Read(&r, &f))
{
if (r.IsTag("Project"))
{
auto ToolsVersion = r.GetAttr("ToolsVersion");
if (ToolsVersion)
{
fVer = atof(ToolsVersion);
}
}
}
}
}
if (fVer > 0.0)
{
for (int i=0; i LatestVer)
{
LatestVer = p[2].Float();
Latest = n;
}
}
}
if (Latest &&
LMakePath(p, sizeof(p), p, Latest) &&
LMakePath(p, sizeof(p), p, "common\\bin\\IarBuild.exe"))
{
if (LFileExists(p))
return p;
}
}
else if (Compiler == Xcode)
{
return "/usr/bin/xcodebuild";
}
else if (Compiler == Cygwin)
{
#ifdef WINDOWS
LRegKey k(false, "HKEY_CURRENT_USER\\Software\\Cygwin\\Installations");
List n;
k.GetValueNames(n);
LString s;
for (auto i:n)
{
s = k.GetStr(i);
if (s.Find("\\??\\") == 0)
s = s(4,-1);
if (LDirExists(s))
{
CygwinPath = s;
break;
}
}
n.DeleteArrays();
LFile::Path p(s, "bin\\make.exe");
if (p.Exists())
return p.GetFull();
#endif
}
else
{
if (Compiler == MingW)
{
// Have a look in the default spot first...
const char *Def = "C:\\MinGW\\msys\\1.0\\bin\\make.exe";
if (LFileExists(Def))
{
return Def;
}
}
for (int i=0; iGetBasePath();
LString InitDir = InitFolder.Get();
LVariant Jobs;
if (!Proj->GetApp()->GetOptions()->GetValue(OPT_Jobs, Jobs) || Jobs.CastInt32() < 1)
Jobs = 2;
LString TmpArgs, Include, Lib, LibPath, Path;
if (Compiler == VisualStudio)
{
// TmpArgs.Printf("\"%s\" /make \"All - Win32 Debug\"", Makefile.Get());
LString BuildConf = "All - Win32 Debug";
if (BuildConfigs.Length())
{
auto Key = toString(Config);
for (size_t i=0; i= 0)
{
if (!BuildConf || (c.Find("x64") >= 0 && BuildConf.Find("x64") < 0))
BuildConf = c;
}
}
}
TmpArgs.Printf("\"%s\" %s \"%s\"", Makefile.Get(), Clean ? "/Clean" : "/Build", BuildConf.Get());
}
else if (Compiler == Nmake)
{
const char *DefInc[] = {
"C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\INCLUDE",
"C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\ATLMFC\\INCLUDE",
"C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\shared",
"C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\um",
"C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\winrt"
};
LString f;
#define ADD_PATHS(out, in) \
for (unsigned i=0; iGetChildTag("name") : NULL;
if (c)
Conf = c->GetContent();
}
}
TmpArgs.Printf("\"%s\" %s %s -log warnings", Makefile.Get(), Clean ? "-clean" : "-make", Conf.Get());
}
else if (Compiler == Xcode)
{
LString::Array Configs;
Configs.SetFixedLength(false);
LString a;
a.Printf("-list -project \"%s\"", Makefile.Get());
LSubProcess Ls(Exe, a);
if (Ls.Start())
{
LStringPipe o;
Ls.Communicate(&o);
auto key = "Build Configurations:";
auto lines = o.NewLStr().SplitDelimit("\n");
int inKey = -1;
for (auto l: lines)
{
int ws = 0;
for (int i=0; i=0)
inKey = ws;
else if (inKey>=0)
{
if (ws>inKey) { Configs.New() = l.Strip(); }
else { inKey = -1; }
}
// printf("%s\n", l.Get());
}
}
auto Config = Configs.Length() > 0 ? Configs[0] : LString("Debug");
TmpArgs.Printf("-project \"%s\" -configuration %s", Makefile.Get(), Config.Get());
}
else
{
if (Compiler == Cygwin)
{
LFile::Path p(CygwinPath, "bin");
Path = p.GetFull();
}
if (Compiler == MingW)
{
LString a;
char *Dir = strrchr(MakePath, DIR_CHAR);
#if 1
TmpArgs.Printf("/C \"%s\"", Exe.Get());
/* As of MSYS v1.0.18 the support for multiple jobs causes make to hang:
http://sourceforge.net/p/mingw/bugs/1950/
http://mingw-users.1079350.n2.nabble.com/MSYS-make-freezes-td7579038.html
Apparently it'll be "fixed" in v1.0.19. We'll see. >:-(
if (Jobs.CastInt32() > 1)
a.Print(" -j %i", Jobs.CastInt32());
*/
a.Printf(" -f \"%s\"", Dir ? Dir + 1 : MakePath.Get());
TmpArgs += a;
#else
TmpArgs.Printf("/C set");
#endif
Exe = "C:\\Windows\\System32\\cmd.exe";
}
else
{
if (Jobs.CastInt32())
TmpArgs.Printf("-j %i -f \"%s\"", Jobs.CastInt32(), MakePath.Get());
else
TmpArgs.Printf("-f \"%s\"", MakePath.Get());
}
if (Clean)
{
if (All)
TmpArgs += " cleanall";
else
TmpArgs += " clean";
}
if (Config == BuildRelease)
TmpArgs += " Build=Release";
}
PostThreadEvent(AppHnd, M_SELECT_TAB, AppWnd::BuildTab);
LString Msg;
Msg.Printf("Making: %s\n", MakePath.Get());
// Msg.Printf("InitDir: %s\n", InitDir.Get());
Proj->GetApp()->PostEvent(M_APPEND_TEXT, (LMessage::Param)NewStr(Msg), AppWnd::BuildTab);
LgiTrace("%s %s\n", Exe.Get(), TmpArgs.Get());
if (SubProc.Reset(new LSubProcess(Exe, TmpArgs)))
{
SubProc->SetNewGroup(false);
SubProc->SetInitFolder(InitDir);
if (Include)
SubProc->SetEnvironment("INCLUDE", Include);
if (Lib)
SubProc->SetEnvironment("LIB", Lib);
if (LibPath)
SubProc->SetEnvironment("LIBPATHS", LibPath);
if (Path)
{
LString Cur = getenv("PATH");
LString New = Cur + LGI_PATH_SEPARATOR + Path;
SubProc->SetEnvironment("PATH", New);
}
// SubProc->SetEnvironment("DLL", "1");
if (Compiler == MingW)
SubProc->SetEnvironment("PATH", "c:\\MingW\\bin;C:\\MinGW\\msys\\1.0\\bin;%PATH%");
if ((Status = SubProc->Start(true, false)))
{
// Read all the output
char Buf[256];
ssize_t rd;
while ( (rd = SubProc->Read(Buf, sizeof(Buf))) > 0 )
{
Write(Buf, rd);
}
uint32_t ex = SubProc->Wait();
Print("Make exited with %i (0x%x)\n", ex, ex);
if (Compiler == IAR &&
ex == 0 &&
PostBuild.Length())
{
for (auto Cmd : PostBuild)
{
auto p = Cmd.Split(" ", 1);
if (p[0].Equals("cd"))
{
if (p.Length() > 1)
FileDev->SetCurrentFolder(p[1]);
else
LAssert(!"No folder for cd?");
}
else
{
LSubProcess PostCmd(p[0], p.Length() > 1 ? p[1] : LString());
if (PostCmd.Start(true, false))
{
char Buf[256];
ssize_t rd;
while ( (rd = PostCmd.Read(Buf, sizeof(Buf))) > 0 )
{
Write(Buf, rd);
}
}
}
}
}
}
else
{
// Create a nice error message.
LString ErrStr = LErrorCodeToString(SubProc->GetErrorCode());
if (ErrStr)
{
char *e = ErrStr.Get() + ErrStr.Length();
while (e > ErrStr && strchr(" \t\r\n.", e[-1]))
*(--e) = 0;
}
sprintf_s(ErrBuf, sizeof(ErrBuf), "Running make failed with %i (%s)\n",
SubProc->GetErrorCode(),
ErrStr.Get());
Err = ErrBuf;
}
}
}
else
{
Err = "Couldn't find program to build makefile.";
LgiTrace("%s,%i - %s.\n", _FL, Err);
}
AppWnd *w = Proj->GetApp();
if (w)
{
w->PostEvent(M_BUILD_DONE);
if (Err)
Proj->GetApp()->PostEvent(M_BUILD_ERR, 0, (LMessage::Param)NewStr(Err));
}
else LAssert(0);
return 0;
}
//////////////////////////////////////////////////////////////////////////////////
IdeProject::IdeProject(AppWnd *App) : IdeCommon(NULL)
{
Project = this;
d = new IdeProjectPrivate(App, this);
Tag = NewStr("Project");
}
IdeProject::~IdeProject()
{
d->App->OnProjectDestroy(this);
LXmlTag::Empty(true);
DeleteObj(d);
}
bool IdeProject::OnNode(const char *Path, ProjectNode *Node, bool Add)
{
if (!Path || !Node)
{
LAssert(0);
return false;
}
char Full[MAX_PATH_LEN];
if (LIsRelativePath(Path))
{
LAutoString Base = GetBasePath();
if (LMakePath(Full, sizeof(Full), Base, Path))
{
Path = Full;
}
}
bool Status = false;
if (Add)
Status = d->Nodes.Add(Path, Node);
else
Status = d->Nodes.Delete(Path);
LString p = Path;
if (Status && CheckExists(p))
d->App->OnNode(p, Node, Add ? FindSymbolSystem::FileAdd : FindSymbolSystem::FileRemove);
return Status;
}
void IdeProject::ShowFileProperties(const char *File)
{
ProjectNode *Node = NULL;
// char *fp = FindFullPath(File, &Node);
if (Node)
{
Node->OnProperties();
}
}
const char *IdeProject::GetFileComment()
{
return d->Settings.GetStr(ProjCommentFile);
}
const char *IdeProject::GetFunctionComment()
{
return d->Settings.GetStr(ProjCommentFunction);
}
IdeProject *IdeProject::GetParentProject()
{
return d->ParentProject;
}
void IdeProject::SetParentProject(IdeProject *p)
{
d->ParentProject = p;
}
bool IdeProject::GetChildProjects(List &c)
{
CollectAllSubProjects(c);
return c.Length() > 0;
}
bool IdeProject::RelativePath(LString &Out, const char *In, bool Debug)
{
if (!In)
return false;
auto Base = GetBasePath();
if (!Base)
return false;
CheckExists(Base);
if (Debug) LgiTrace("XmlBase='%s'\n In='%s'\n", Base.Get(), In);
LToken b(Base, DIR_STR);
LToken i(In, DIR_STR);
char out[MAX_PATH_LEN] = "";
int outCh = 0;
if (Debug) LgiTrace("Len %i-%i\n", b.Length(), i.Length());
auto ILen = i.Length() + (LDirExists(In) ? 0 : 1);
auto Max = MIN(b.Length(), ILen);
int Common = 0;
for (; Common < Max; Common++)
{
#ifdef WIN32
#define StrCompare stricmp
#else
#define StrCompare strcmp
#endif
if (Debug) LgiTrace("Cmd '%s'-'%s'\n", b[Common], i[Common]);
if (StrCompare(b[Common], i[Common]) != 0)
{
break;
}
}
if (Debug) LgiTrace("Common=%i\n", Common);
if (Common > 0)
{
if (Common < b.Length())
{
out[outCh = 0] = 0;
auto Back = b.Length() - Common;
if (Debug) LgiTrace("Back=%i\n", (int)Back);
for (int n=0; nSettings.GetStr(ProjExe);
LString Exe;
if (!PExe)
{
// Use the default exe name?
Exe = GetExecutable(GetCurrentPlatform());
if (Exe)
{
printf("Exe='%s'\n", Exe.Get());
PExe = Exe;
}
}
if (!PExe)
return false;
if (LIsRelativePath(PExe))
{
auto Base = GetBasePath();
if (Base)
LMakePath(Path, Len, Base, PExe);
else
return false;
}
else
{
strcpy_s(Path, Len, PExe);
}
return true;
}
LString IdeProject::GetMakefile(IdePlatform Platform)
{
const char *PMakefile = d->Settings.GetStr(ProjMakefile, NULL, Platform);
if (!PMakefile)
return LString();
LString Path;
if (LIsRelativePath(PMakefile))
{
auto Base = GetBasePath();
if (Base)
{
char p[MAX_PATH_LEN];
LMakePath(p, sizeof(p), Base, PMakefile);
Path = p;
}
}
else
{
Path = PMakefile;
}
return Path;
}
void IdeProject::Clean(bool All, BuildConfig Config)
{
if (!d->Thread &&
d->Settings.GetStr(ProjMakefile))
{
auto m = GetMakefile(PlatformCurrent);
if (m)
{
CheckExists(m);
d->Thread.Reset(new BuildThread(this, m, true, Config, All, sizeof(ssize_t)*8));
}
}
}
char *QuoteStr(char *s)
{
LStringPipe p(256);
while (s && *s)
{
if (*s == ' ')
{
p.Push("\\ ", 2);
}
else p.Push(s, 1);
s++;
}
return p.NewStr();
}
class ExecuteThread : public LThread, public LStream, public LCancel
{
IdeProject *Proj;
LString Exe, Args, Path;
int Len;
ExeAction Act;
int AppHnd;
public:
ExecuteThread(IdeProject *proj, const char *exe, const char *args, char *path, ExeAction act) : LThread("ExecuteThread")
{
Len = 32 << 10;
Proj = proj;
Act = act;
Exe = exe;
Args = args;
Path = path;
DeleteOnExit = true;
AppHnd = proj->GetApp()->AddDispatch();
Run();
}
~ExecuteThread()
{
Cancel();
while (!IsExited())
LSleep(1);
}
int Main() override
{
PostThreadEvent(AppHnd, M_SELECT_TAB, AppWnd::OutputTab);
PostThreadEvent(AppHnd, M_APPEND_TEXT, 0, AppWnd::OutputTab);
if (Exe)
{
if (Act == ExeDebug)
{
LSubProcess sub("kdbg", Exe);
if (Path)
sub.SetInitFolder(Path);
if (sub.Start())
sub.Communicate(this, NULL, this);
}
else if (Act == ExeValgrind)
{
#ifdef LINUX
LString ExePath = Proj->GetExecutable(GetCurrentPlatform());
if (ExePath)
{
char Path[MAX_PATH_LEN];
char *ExeLeaf = LGetLeaf(Exe);
strcpy_s(Path, sizeof(Path), ExeLeaf ? ExeLeaf : Exe.Get());
LTrimDir(Path);
char *Term = 0;
char *WorkDir = 0;
char *Execute = 0;
switch (LGetWindowManager())
{
case WM_Kde:
Term = "konsole";
WorkDir = "--workdir ";
Execute = "-e";
break;
case WM_Gnome:
Term = "gnome-terminal";
WorkDir = "--working-directory=";
Execute = "-x";
break;
}
if (Term && WorkDir && Execute)
{
char *e = QuoteStr(ExePath);
char *p = QuoteStr(Path);
char *a = Proj->GetExeArgs() ? Proj->GetExeArgs() : (char*)"";
char Args[512];
sprintf(Args,
"%s%s "
"--noclose "
"%s valgrind --tool=memcheck --num-callers=20 %s %s",
WorkDir,
p,
Execute,
e, a);
LExecute(Term, Args);
}
}
#endif
}
else
{
LSubProcess sub(Exe, Args);
if (Path)
sub.SetInitFolder(Path);
if (sub.Start())
sub.Communicate(this, NULL, this);
}
}
return 0;
}
ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) override
{
if (Len <= 0)
return 0;
PostThreadEvent(AppHnd, M_APPEND_TEXT, (LMessage::Param)NewStr((char*)Buffer, Size), AppWnd::OutputTab);
Len -= Size;
return Size;
}
};
LDebugContext *IdeProject::Execute(ExeAction Act, LString *ErrMsg)
{
auto Base = GetBasePath();
if (!Base)
{
if (ErrMsg) *ErrMsg = "No base path for project.";
return NULL;
}
char e[MAX_PATH_LEN];
if (!GetExePath(e, sizeof(e)))
{
if (ErrMsg) *ErrMsg = "GetExePath failed.";
return NULL;
}
if (!LFileExists(e))
{
if (ErrMsg) ErrMsg->Printf("Executable '%s' doesn't exist.\n", e);
return NULL;
}
const char *Args = d->Settings.GetStr(ProjArgs);
const char *Env = d->Settings.GetStr(ProjEnv);
LString InitDir = d->Settings.GetStr(ProjInitDir);
int RunAsAdmin = d->Settings.GetInt(ProjDebugAdmin);
#ifndef HAIKU
if (Act == ExeDebug)
{
if (InitDir && LIsRelativePath(InitDir))
{
LFile::Path p(Base);
p += InitDir;
InitDir = p.GetFull();
}
return new LDebugContext(d->App, this, e, Args, RunAsAdmin != 0, Env, InitDir);
}
#endif
new ExecuteThread( this,
e,
Args,
Base,
#if defined(HAIKU) || defined(WINDOWS)
ExeRun // No gdb or valgrind
#else
Act
#endif
);
return NULL;
}
bool IdeProject::IsMakefileAScript()
{
auto m = GetMakefile(PlatformCurrent);
if (m)
{
auto Ext = LGetExtension(m);
if (!Stricmp(Ext, "py"))
{
// Not a makefile but a build script... can't update.
return true;
}
}
return false;
}
bool IdeProject::IsMakefileUpToDate()
{
if (IsMakefileAScript())
return true; // We can't know if it's up to date...
List Proj;
GetChildProjects(Proj);
Proj.Insert(this);
for (auto p: Proj)
{
// Is the project file modified after the makefile?
auto Proj = p->GetFullPath();
uint64 ProjModTime = 0, MakeModTime = 0;
LDirectory dir;
if (dir.First(Proj))
{
ProjModTime = dir.GetLastWriteTime();
dir.Close();
}
auto m = p->GetMakefile(PlatformCurrent);
if (!m)
{
d->App->GetBuildLog()->Print("Error: no makefile? (%s:%i)\n", _FL);
break;
}
auto Ext = LGetExtension(m);
if (!Stricmp(Ext, "py"))
{
// Not a makefile but a build script... can't update.
return true;
}
if (dir.First(m))
{
MakeModTime = dir.GetLastWriteTime();
dir.Close();
}
// printf("Proj=%s - Timestamps " LGI_PrintfInt64 " - " LGI_PrintfInt64 "\n", Proj.Get(), ProjModTime, MakeModTime);
if (ProjModTime != 0 &&
MakeModTime != 0 &&
ProjModTime > MakeModTime)
{
// Need to rebuild the makefile...
return false;
}
}
return true;
}
bool IdeProject::FindDuplicateSymbols()
{
LStream *Log = d->App->GetBuildLog();
Log->Print("FindDuplicateSymbols starting...\n");
List Proj;
CollectAllSubProjects(Proj);
Proj.Insert(this);
int Lines = 0;
LHashTbl,int64> Map(200000);
int Found = 0;
for (auto p: Proj)
{
LString s = p->GetExecutable(GetCurrentPlatform());
if (s)
{
LString Args;
Args.Printf("--print-size --defined-only -C %s", s.Get());
LSubProcess Nm("nm", Args);
if (Nm.Start(true, false))
{
char Buf[256];
LStringPipe q;
for (ssize_t Rd = 0; (Rd = Nm.Read(Buf, sizeof(Buf))); )
q.Write(Buf, Rd);
LString::Array a = q.NewLStr().SplitDelimit("\r\n");
LHashTbl,bool> Local(200000);
for (auto &Ln: a)
{
LString::Array p = Ln.SplitDelimit(" \t", 3);
if (!Local.Find(p.Last()))
{
Local.Add(p.Last(), true);
// const char *Sz = p[1];
int64 Ours = p[1].Int(16);
int64 Theirs = Map.Find(p.Last());
if (Theirs >= 0)
{
if (Ours != Theirs)
{
if (Found++ < 100)
Log->Print(" %s (" LPrintfInt64 " -> " LPrintfInt64 ")\n",
p.Last().Get(),
Ours, Theirs);
}
}
else if (Ours >= 0)
{
Map.Add(p.Last(), Ours);
}
else
{
printf("Bad line: %s\n", Ln.Get());
}
}
Lines++;
}
}
}
else printf("%s:%i - GetExecutable failed.\n", _FL);
}
/*
char *Sym;
for (int Count = Map.First(&Sym); Count; Count = Map.Next(&Sym))
{
if (Count > 1)
Log->Print(" %i: %s\n", Count, Sym);
}
*/
Log->Print("FindDuplicateSymbols finished (%i lines)\n", Lines);
return false;
}
bool IdeProject::FixMissingFiles()
{
FixMissingFilesDlg(this);
return true;
}
void IdeProject::Build(bool All, BuildConfig Config)
{
if (d->Thread)
{
d->App->GetBuildLog()->Print("Error: Already building (%s:%i)\n", _FL);
return;
}
auto m = GetMakefile(PlatformCurrent);
CheckExists(m);
if (!m)
{
d->App->GetBuildLog()->Print("Error: no makefile? (%s:%i)\n", _FL);
return;
}
if (GetApp())
GetApp()->PostEvent(M_APPEND_TEXT, 0, 0);
SetClean([this, m, All, Config](bool ok)
{
if (!ok)
return;
if (!IsMakefileUpToDate())
CreateMakefile(GetCurrentPlatform(), true);
else
// Start the build thread...
d->Thread.Reset
(
new BuildThread
(
this,
m,
false,
Config,
All,
sizeof(size_t)*8
)
);
});
}
void IdeProject::StopBuild()
{
d->Thread.Reset();
}
bool IdeProject::Serialize(bool Write)
{
return true;
}
AppWnd *IdeProject::GetApp()
{
return d->App;
}
const char *IdeProject::GetIncludePaths()
{
return d->Settings.GetStr(ProjIncludePaths);
}
const char *IdeProject::GetPreDefinedValues()
{
return d->Settings.GetStr(ProjDefines);
}
const char *IdeProject::GetExeArgs()
{
return d->Settings.GetStr(ProjArgs);
}
LString IdeProject::GetExecutable(IdePlatform Platform)
{
LString Bin = d->Settings.GetStr(ProjExe, NULL, Platform);
auto TargetType = d->Settings.GetStr(ProjTargetType, NULL, Platform);
auto Base = GetBasePath();
if (Bin)
{
if (LIsRelativePath(Bin) && Base)
{
char p[MAX_PATH_LEN];
if (LMakePath(p, sizeof(p), Base, Bin))
Bin = p;
}
return Bin;
}
// Create binary name from target:
auto Target = GetTargetName(Platform);
if (Target)
{
bool IsLibrary = Stristr(TargetType, "library");
int BuildMode = d->App->GetBuildMode();
const char *Name = BuildMode ? "Release" : "Debug";
const char *Postfix = BuildMode ? "" : "d";
switch (Platform)
{
case PlatformWin:
{
if (IsLibrary)
Bin.Printf("%s%s.dll", Target.Get(), Postfix);
else
Bin = Target;
break;
}
case PlatformMac:
{
if (IsLibrary)
Bin.Printf("lib%s%s.dylib", Target.Get(), Postfix);
else
Bin = Target;
break;
}
case PlatformLinux:
case PlatformHaiku:
{
if (IsLibrary)
Bin.Printf("lib%s%s.so", Target.Get(), Postfix);
else
Bin = Target;
break;
}
default:
{
LAssert(0);
printf("%s:%i - Unknown platform.\n", _FL);
return LString();
}
}
// Find the actual file...
if (!Base)
{
printf("%s:%i - GetBasePath failed.\n", _FL);
return LString();
}
char Path[MAX_PATH_LEN];
LMakePath(Path, sizeof(Path), Base, Name);
LMakePath(Path, sizeof(Path), Path, Bin);
if (LFileExists(Path))
Bin = Path;
else
printf("%s:%i - '%s' doesn't exist.\n", _FL, Path);
return Bin;
}
return LString();
}
const char *IdeProject::GetFileName()
{
return d->FileName;
}
int IdeProject::GetPlatforms()
{
return PLATFORM_ALL;
}
LAutoString IdeProject::GetFullPath()
{
LAutoString Status;
if (!d->FileName)
{
// LAssert(!"No path.");
return Status;
}
LArray sections;
IdeProject *proj = this;
while ( proj &&
proj->GetFileName() &&
LIsRelativePath(proj->GetFileName()))
{
sections.AddAt(0, proj->GetFileName());
proj = proj->GetParentProject();
}
if (!proj)
{
// LAssert(!"All projects have a relative path?");
return Status; // No absolute path in the parent projects?
}
char p[MAX_PATH_LEN];
strcpy_s(p, sizeof(p), proj->GetFileName()); // Copy the base path
if (sections.Length() > 0)
LTrimDir(p); // Trim off the filename
for (int i=0; iFileName.Empty();
d->UserFile.Empty();
d->App->GetTree()->Insert(this);
ProjectNode *f = new ProjectNode(this);
if (f)
{
f->SetName("Source");
f->SetType(NodeDir);
InsertTag(f);
}
f = new ProjectNode(this);
if (f)
{
f->SetName("Headers");
f->SetType(NodeDir);
InsertTag(f);
}
d->Settings.Set(ProjEditorTabSize, 4);
d->Settings.Set(ProjEditorIndentSize, 4);
d->Settings.Set(ProjEditorUseHardTabs, true);
d->Dirty = true;
Expanded(true);
}
ProjectStatus IdeProject::OpenFile(const char *FileName)
{
auto Log = d->App->GetBuildLog();
LProfile Prof("IdeProject::OpenFile");
Prof.HideResultsIfBelow(1000);
Empty();
Prof.Add("Init");
d->UserNodeFlags.Empty();
if (LIsRelativePath(FileName))
{
char p[MAX_PATH_LEN];
getcwd(p, sizeof(p));
LMakePath(p, sizeof(p), p, FileName);
d->FileName = p;
}
else
{
d->FileName = FileName;
}
d->UserFile = d->FileName + "." + LCurrentUserName();
if (!d->FileName)
{
Log->Print("%s:%i - No filename.\n", _FL);
return OpenError;
}
Prof.Add("FileOpen");
LFile f;
LString FullPath = d->FileName.Get();
if (!CheckExists(FullPath) ||
!f.Open(FullPath, O_READWRITE))
{
Log->Print("%s:%i - Error: Can't open '%s'.\n", _FL, FullPath.Get());
return OpenError;
}
Prof.Add("Xml");
LXmlTree x;
LXmlTag r;
if (!x.Read(&r, &f))
{
Log->Print("%s:%i - Error: Can't read XML: %s\n", _FL, x.GetErrorMsg());
return OpenError;
}
Prof.Add("Progress Setup");
#if DEBUG_OPEN_PROGRESS
int64 Nodes = r.CountTags();
LProgressDlg Prog(d->App, 1000);
Prog.SetDescription("Loading project...");
Prog.SetLimits(0, Nodes);
Prog.SetYieldTime(1000);
Prog.SetAlwaysOnTop(true);
#endif
Prof.Add("UserFile");
if (LFileExists(d->UserFile))
{
LFile Uf;
if (Uf.Open(d->UserFile, O_READ))
{
LString::Array Ln = Uf.Read().SplitDelimit("\n");
for (unsigned i=0; iUserNodeFlags.Add((int)p[0].Int(), (int)p[1].Int(16));
}
}
}
if (!r.IsTag("Project"))
{
Log->Print("%s:%i - No 'Project' tag.\n", _FL);
return OpenError;
}
Prof.Add("OnOpen");
bool Ok = OnOpen(
#if DEBUG_OPEN_PROGRESS
&Prog,
#else
NULL,
#endif
&r);
#if DEBUG_OPEN_PROGRESS
if (Prog.IsCancelled())
return OpenCancel;
else
#endif
if (!Ok)
return OpenError;
Prof.Add("Insert");
d->App->GetTree()->Insert(this);
Expanded(true);
Prof.Add("Serialize");
d->Settings.Serialize(&r, false /* read */);
return OpenOk;
}
bool IdeProject::SaveFile()
{
auto Full = GetFullPath();
if (ValidStr(Full) && d->Dirty)
{
LFile f;
if (f.Open(Full, O_WRITE))
{
f.SetSize(0);
LXmlTree x;
d->Settings.Serialize(this, true /* write */);
if (x.Write(this, &f))
d->Dirty = false;
else
LgiTrace("%s:%i - Failed to write XML.\n", _FL);
}
else LgiTrace("%s:%i - Couldn't open '%s' for writing.\n", _FL, Full.Get());
}
if (d->UserFileDirty)
{
LFile f;
LAssert(d->UserFile.Get());
if (f.Open(d->UserFile, O_WRITE))
{
f.SetSize(0);
// Save user file details..
// int Id;
// for (int Flags = d->UserNodeFlags.First(&Id); Flags >= 0; Flags = d->UserNodeFlags.Next(&Id))
for (auto i : d->UserNodeFlags)
{
f.Print("%i,%x\n", i.key, i.value);
}
d->UserFileDirty = false;
}
}
printf("\tIdeProject::SaveFile %i %i\n", d->Dirty, d->UserFileDirty);
return !d->Dirty &&
!d->UserFileDirty;
}
void IdeProject::SetDirty()
{
d->Dirty = true;
d->App->OnProjectChange();
}
void IdeProject::SetUserFileDirty()
{
d->UserFileDirty = true;
}
bool IdeProject::GetExpanded(int Id)
{
int f = d->UserNodeFlags.Find(Id);
return f >= 0 ? f : false;
}
void IdeProject::SetExpanded(int Id, bool Exp)
{
if (d->UserNodeFlags.Find(Id) != (int)Exp)
{
d->UserNodeFlags.Add(Id, Exp);
SetUserFileDirty();
}
}
int IdeProject::AllocateId()
{
return d->NextNodeId++;
}
template
bool CheckExists(LAutoString Base, T &p, Fn Setter, bool Debug)
{
LFile::Path Full;
bool WasRel = LIsRelativePath(p);
if (WasRel)
{
Full = Base.Get();
Full += p.Get();
}
else Full = p.Get();
bool Ret = Full.Exists();
if (!Ret)
{
// Is the case wrong?
for (int i=1; i%s\n", i, Leaf.Get(), dir.GetName());
Leaf = dir.GetName();
Matched = true;
break;
}
}
if (!Matched)
break;
}
}
if ((Ret = Full.Exists()))
{
LString Old = p.Get();
if (WasRel)
{
auto r = LMakeRelativePath(Base, Full);
Setter(p, r);
}
else
{
Setter(p, Full.GetFull());
}
if (Debug)
printf("%s -> %s\n", Old.Get(), p.Get());
}
}
if (Debug)
printf("CheckExists '%s' = %i\n", Full.GetFull().Get(), Ret);
return Ret;
}
bool IdeProject::CheckExists(LString &p, bool Debug)
{
return ::CheckExists(GetBasePath(), p, [](LString &o, const char *i) {
o = i;
}, Debug);
}
bool IdeProject::CheckExists(LAutoString &p, bool Debug)
{
return ::CheckExists(GetBasePath(), p, [](LAutoString &o, const char *i) {
o.Reset(NewStr(i));
}, Debug);
}
bool IdeProject::GetClean()
{
for (auto i: *this)
{
ProjectNode *p = dynamic_cast(i);
if (p && !p->GetClean())
return false;
}
return !d->Dirty && !d->UserFileDirty;
}
void IdeProject::SetClean(std::function OnDone)
{
auto CleanNodes = [this, OnDone]()
{
// printf("IdeProject.SetClean.CleanNodes\n");
for (auto i: *this)
{
ProjectNode *p = dynamic_cast(i);
if (!p) break;
p->SetClean();
}
if (OnDone)
OnDone(true);
};
// printf("IdeProject.SetClean dirty=%i,%i validfile=%i\n", d->Dirty, d->UserFileDirty, ValidStr(d->FileName));
if (d->Dirty || d->UserFileDirty)
{
if (ValidStr(d->FileName))
SaveFile();
else
{
auto s = new LFileSelect;
s->Parent(Tree);
s->Name("Project.xml");
s->Save([this, OnDone, CleanNodes](auto s, auto ok)
{
// printf("IdeProject.SetClean.FileSelect ok=%i\n", ok);
if (ok)
{
d->FileName = s->Name();
d->UserFile = d->FileName + "." + LCurrentUserName();
d->App->OnFile(d->FileName, true);
Update();
CleanNodes();
}
else
{
if (OnDone)
OnDone(false);
}
delete s;
});
return;
}
}
CleanNodes();
}
const char *IdeProject::GetText(int Col)
{
if (d->FileName)
{
char *s = strrchr(d->FileName, DIR_CHAR);
return s ? s + 1 : d->FileName.Get();
}
return Untitled;
}
int IdeProject::GetImage(int Flags)
{
return 0;
}
void IdeProject::Empty()
{
LXmlTag *t;
while (Children.Length() > 0 &&
(t = Children[0]))
{
ProjectNode *n = dynamic_cast(t);
if (n)
{
n->Remove();
}
DeleteObj(t);
}
}
LXmlTag *IdeProject::Create(char *Tag)
{
if (!stricmp(Tag, TagSettings))
return NULL;
return new ProjectNode(this);
}
void IdeProject::OnMouseClick(LMouse &m)
{
if (m.IsContextMenu())
{
LSubMenu Sub;
Sub.AppendItem("New Folder", IDM_NEW_FOLDER);
Sub.AppendItem("New Web Folder", IDM_WEB_FOLDER);
Sub.AppendSeparator();
Sub.AppendItem("Build", IDM_BUILD);
Sub.AppendItem("Clean Project", IDM_CLEAN_PROJECT);
Sub.AppendItem("Clean All", IDM_CLEAN_ALL);
Sub.AppendItem("Rebuild Project", IDM_REBUILD_PROJECT);
Sub.AppendItem("Rebuild All", IDM_REBUILD_ALL);
Sub.AppendSeparator();
Sub.AppendItem("Sort Children", IDM_SORT_CHILDREN);
Sub.AppendSeparator();
Sub.AppendItem("Settings", IDM_SETTINGS, true);
Sub.AppendItem("Insert Dependency", IDM_INSERT_DEP);
m.ToScreen();
auto c = _ScrollPos();
m.x -= c.x;
m.y -= c.y;
switch (Sub.Float(Tree, m.x, m.y))
{
case IDM_NEW_FOLDER:
{
auto Name = new LInput(Tree, "", "Name:", AppName);
Name->DoModal([this, Name](auto d, auto code)
{
if (code)
GetSubFolder(this, Name->GetStr(), true);
delete d;
});
break;
}
case IDM_WEB_FOLDER:
{
WebFldDlg *Dlg = new WebFldDlg(Tree, 0, 0, 0);
Dlg->DoModal([Dlg, this](auto d, auto code)
{
if (Dlg->Ftp && Dlg->Www)
{
IdeCommon *f = GetSubFolder(this, Dlg->Name, true);
if (f)
{
f->SetAttr(OPT_Ftp, Dlg->Ftp);
f->SetAttr(OPT_Www, Dlg->Www);
}
}
delete Dlg;
});
break;
}
case IDM_BUILD:
{
StopBuild();
Build(true, d->App->GetBuildMode());
break;
}
case IDM_CLEAN_PROJECT:
{
Clean(false, d->App->GetBuildMode());
break;
}
case IDM_CLEAN_ALL:
{
Clean(true, d->App->GetBuildMode());
break;
}
case IDM_REBUILD_PROJECT:
{
StopBuild();
Clean(false, d->App->GetBuildMode());
Build(false, d->App->GetBuildMode());
break;
}
case IDM_REBUILD_ALL:
{
StopBuild();
Clean(true, d->App->GetBuildMode());
Build(true, d->App->GetBuildMode());
break;
}
case IDM_SORT_CHILDREN:
{
SortChildren();
Project->SetDirty();
break;
}
case IDM_SETTINGS:
{
d->Settings.Edit(Tree, [this]()
{
SetDirty();
});
break;
}
case IDM_INSERT_DEP:
{
LFileSelect *s = new LFileSelect;
s->Parent(Tree);
s->Type("Project", "*.xml");
s->Open([this](auto s, bool ok)
{
if (ok)
{
ProjectNode *New = new ProjectNode(this);
if (New)
{
New->SetFileName(s->Name());
New->SetType(NodeDependancy);
InsertTag(New);
SetDirty();
}
}
delete s;
});
break;
}
}
}
}
char *IdeProject::FindFullPath(const char *File, ProjectNode **Node)
{
char *Full = 0;
for (auto i:*this)
{
ProjectNode *c = dynamic_cast(i);
if (!c) break;
ProjectNode *n = c->FindFile(File, &Full);
if (n)
{
if (Node)
*Node = n;
break;
}
}
return Full;
}
bool IdeProject::HasNode(ProjectNode *Node)
{
for (auto i:*this)
{
ProjectNode *c = dynamic_cast(i);
if (!c) break;
if (c->HasNode(Node))
return true;
}
return false;
}
bool IdeProject::GetAllNodes(LArray &Nodes)
{
for (auto i:*this)
{
ProjectNode *c = dynamic_cast(i);
if (!c) break;
c->AddNodes(Nodes);
}
return true;
}
bool IdeProject::InProject(bool FuzzyMatch, const char *Path, bool Open, IdeDoc **Doc)
{
if (!Path)
return false;
// Search complete path first...
ProjectNode *n = d->Nodes.Find(Path);
if (!n && FuzzyMatch)
{
// No match, do partial matching.
const char *Leaf = LGetLeaf(Path);
auto PathLen = strlen(Path);
auto LeafLen = strlen(Leaf);
uint32_t MatchingScore = 0;
// Traverse all nodes and try and find the best fit.
// const char *p;
// for (ProjectNode *Cur = d->Nodes.First(&p); Cur; Cur = d->Nodes.Next(&p))
for (auto Cur : d->Nodes)
{
int CurPlatform = Cur.value->GetPlatforms();
uint32_t Score = 0;
if (stristr(Cur.key, Path))
{
Score += PathLen;
}
else if (stristr(Cur.key, Leaf))
{
Score += LeafLen;
}
const char *pLeaf = LGetLeaf(Cur.key);
if (pLeaf && !stricmp(pLeaf, Leaf))
{
Score |= 0x40000000;
}
if (Score && (CurPlatform & PLATFORM_CURRENT) != 0)
{
Score |= 0x80000000;
}
if (Score > MatchingScore)
{
MatchingScore = Score;
n = Cur.value;
}
}
}
if (n && Doc)
{
*Doc = n->Open();
}
return n != 0;
}
char *GetQuotedStr(char *Str)
{
char *s = strchr(Str, '\"');
if (s)
{
s++;
char *e = strchr(s, '\"');
if (e)
{
return NewStr(s, e - s);
}
}
return 0;
}
void IdeProject::ImportDsp(const char *File)
{
if (File && LFileExists(File))
{
char Base[256];
strcpy(Base, File);
LTrimDir(Base);
char *Dsp = LReadTextFile(File);
if (Dsp)
{
LToken Lines(Dsp, "\r\n");
IdeCommon *Current = this;
bool IsSource = false;
for (int i=0; iGetSubFolder(this, Folder, true);
if (Sub)
{
Current = Sub;
}
DeleteArray(Folder);
}
}
else if (strnicmp(L, "# End Group", 11) == 0)
{
IdeCommon *Parent = dynamic_cast(Current->GetParent());
if (Parent)
{
Current = Parent;
}
}
else if (strnicmp(L, "# Begin Source", 14) == 0)
{
IsSource = true;
}
else if (strnicmp(L, "# End Source", 12) == 0)
{
IsSource = false;
}
else if (Current && IsSource && strncmp(L, "SOURCE=", 7) == 0)
{
ProjectNode *New = new ProjectNode(this);
if (New)
{
char *Src = 0;
if (strchr(L, '\"'))
{
Src = GetQuotedStr(L);
}
else
{
Src = NewStr(L + 7);
}
if (Src)
{
// Make absolute path
char Abs[256];
LMakePath(Abs, sizeof(Abs), Base, ToUnixPath(Src));
// Make relitive path
New->SetFileName(Src);
DeleteArray(Src);
}
Current->InsertTag(New);
SetDirty();
}
}
}
DeleteArray(Dsp);
}
}
}
IdeProjectSettings *IdeProject::GetSettings()
{
return &d->Settings;
}
bool IdeProject::BuildIncludePaths(LArray &Paths, bool Recurse, bool IncludeSystem, IdePlatform Platform)
{
List Projects;
if (Recurse)
GetChildProjects(Projects);
Projects.Insert(this, 0);
LHashTbl, bool> Map;
for (auto p: Projects)
{
LString ProjInclude = d->Settings.GetStr(ProjIncludePaths, NULL, Platform);
LAutoString Base = p->GetBasePath();
const char *Delim = ",;\r\n";
LString::Array In, Out;
LString::Array a = ProjInclude.SplitDelimit(Delim);
In = a;
if (IncludeSystem)
{
LString SysInclude = d->Settings.GetStr(ProjSystemIncludes, NULL, Platform);
a = SysInclude.SplitDelimit(Delim);
In.SetFixedLength(false);
In.Add(a);
}
for (unsigned i=0; i 1 ? a[1].Get() : NULL);
LStringPipe Buf;
if (Proc.Start())
{
Proc.Communicate(&Buf);
LString result = Buf.NewLStr();
a = result.Split(" \t\r\n");
for (int i=0; i Nodes;
if (p->GetAllNodes(Nodes))
{
auto Base = p->GetFullPath();
if (Base)
{
LTrimDir(Base);
for (auto &n: Nodes)
{
if (n->GetType() == NodeHeader && // Only look at headers.
(n->GetPlatforms() & (1 << Platform)) != 0) // Exclude files not on this platform.
{
auto f = n->GetFileName();
char p[MAX_PATH_LEN];
if (f &&
LMakePath(p, sizeof(p), Base, f))
{
char *l = strrchr(p, DIR_CHAR);
if (l)
*l = 0;
if (!Map.Find(p))
{
Map.Add(p, true);
}
}
}
}
}
}
}
// char *p;
// for (bool b = Map.First(&p); b; b = Map.Next(&p))
for (auto p : Map)
Paths.Add(p.key);
return true;
}
void IdeProjectPrivate::CollectAllFiles(LTreeNode *Base, LArray &Files, bool SubProjects, int Platform)
{
for (auto i:*Base)
{
IdeProject *Proj = dynamic_cast(i);
if (Proj)
{
if (Proj->GetParentProject() && !SubProjects)
{
continue;
}
}
else
{
ProjectNode *p = dynamic_cast(i);
if (p)
{
if (p->GetType() == NodeSrc ||
p->GetType() == NodeHeader)
{
if (p->GetPlatforms() & Platform)
{
Files.Add(p);
}
}
}
}
CollectAllFiles(i, Files, SubProjects, Platform);
}
}
LString IdeProject::GetTargetName(IdePlatform Platform)
{
LString Status;
const char *t = d->Settings.GetStr(ProjTargetName, NULL, Platform);
if (ValidStr(t))
{
// Take target name from the settings
Status = t;
}
else
{
char *s = strrchr(d->FileName, DIR_CHAR);
if (s)
{
// Generate the target executable name
char Target[MAX_PATH_LEN];
strcpy_s(Target, sizeof(Target), s + 1);
s = strrchr(Target, '.');
if (s) *s = 0;
strlwr(Target);
s = Target;
for (char *i = Target; *i; i++)
{
if (*i != '.' && *i != ' ')
{
*s++ = *i;
}
}
*s = 0;
Status = Target;
}
}
return Status;
}
LString IdeProject::GetTargetFile(IdePlatform Platform)
{
LString Target = GetTargetName(PlatformCurrent);
if (!Target)
{
LAssert(!"No target?");
return LString();
}
const char *TargetType = d->Settings.GetStr(ProjTargetType);
if (!TargetType)
{
LAssert(!"Needs a target type.");
return LString();
}
if (!stricmp(TargetType, "Executable"))
{
return Target;
}
else if (!stricmp(TargetType, "DynamicLibrary"))
{
char t[MAX_PATH_LEN];
auto DefExt = PlatformDynamicLibraryExt(Platform);
strcpy_s(t, sizeof(t), Target);
auto tCh = strlen(t);
char *ext = LGetExtension(t);
if (!ext)
snprintf(t+tCh, sizeof(t)-tCh, ".%s", DefExt);
else if (stricmp(ext, DefExt))
strcpy(ext, DefExt);
return t;
}
else if (!stricmp(TargetType, "StaticLibrary"))
{
LString Ret;
Ret.Printf("%s.%s", Target.Get(), PlatformSharedLibraryExt(Platform));
return Ret;
}
return LString();
}
struct ProjDependency
{
bool Scanned;
LAutoString File;
ProjDependency(const char *f)
{
Scanned = false;
File.Reset(NewStr(f));
}
};
bool IdeProject::GetAllDependencies(LArray &Files, IdePlatform Platform)
{
if (!GetTree()->Lock(_FL))
return false;
LHashTbl, ProjDependency*> Deps;
auto Base = GetBasePath();
// Build list of all the source files...
LArray Src;
CollectAllSource(Src, Platform);
// Get all include paths
LArray IncPaths;
BuildIncludePaths(IncPaths, false, false, Platform);
// Add all source to dependencies
for (int i=0; i Unscanned;
do
{
// Find all the unscanned dependencies
Unscanned.Length(0);
for (auto d : Deps)
{
if (!d.value->Scanned)
Unscanned.Add(d.value);
}
for (int i=0; iScanned = true;
char *Src = d->File;
char Full[MAX_PATH_LEN];
if (LIsRelativePath(d->File))
{
LMakePath(Full, sizeof(Full), Base, d->File);
Src = Full;
}
LArray SrcDeps;
if (GetDependencies(Src, IncPaths, SrcDeps, Platform))
{
for (auto File: SrcDeps)
{
// Add include to dependencies...
if (LIsRelativePath(File))
{
LMakePath(Full, sizeof(Full), Base, File);
File = Full;
}
if (!Deps.Find(File))
{
Deps.Add(File, new ProjDependency(File));
}
}
SrcDeps.DeleteArrays();
}
}
}
while (Unscanned.Length() > 0);
for (auto d : Deps)
{
Files.Add(d.value->File.Release());
}
Deps.DeleteObjects();
GetTree()->Unlock();
return true;
}
bool IdeProject::GetDependencies(const char *InSourceFile, LArray &IncPaths, LArray &Files, IdePlatform Platform)
{
LString SourceFile = InSourceFile;
if (!CheckExists(SourceFile))
{
LgiTrace("%s:%i - can't read '%s'\n", _FL, SourceFile.Get());
return false;
}
LAutoString c8(LReadTextFile(SourceFile));
if (!c8)
return false;
LArray Headers;
if (!BuildHeaderList(c8, Headers, IncPaths, false))
return false;
for (int n=0; nCreateMakefile)
{
if (d->CreateMakefile->IsExited())
d->CreateMakefile.Reset();
else
{
d->App->GetBuildLog()->Print("%s:%i - Makefile thread still running.\n", _FL);
return false;
}
}
if (Platform == PlatformCurrent)
Platform = GetCurrentPlatform();
return d->CreateMakefile.Reset(new MakefileThread(d, Platform, BuildAfterwards));
}
void IdeProject::OnMakefileCreated()
{
if (d->CreateMakefile)
{
d->CreateMakefile.Reset();
if (MakefileThread::Instances == 0)
GetApp()->PostEvent(M_LAST_MAKEFILE_CREATED);
}
}
////////////////////////////////////////////////////////////////////////////////////////////
IdeTree::IdeTree() : LTree(IDC_PROJECT_TREE, 0, 0, 100, 100)
{
Hit = 0;
MultiSelect(true);
}
void IdeTree::OnCreate()
{
SetWindow(this);
}
void IdeTree::OnDragExit()
{
SelectDropTarget(0);
}
int IdeTree::WillAccept(LDragFormats &Formats, LPoint p, int KeyState)
{
static bool First = true;
Formats.SupportsFileDrops();
Formats.Supports(NODE_DROP_FORMAT);
First = false;
if (Formats.Length() == 0)
{
LgiTrace("%s:%i - No valid drop formats.\n", _FL);
return DROPEFFECT_NONE;
}
Hit = ItemAtPoint(p.x, p.y);
if (!Hit)
{
SelectDropTarget(NULL);
return DROPEFFECT_NONE;
}
if (Formats.HasFormat(LGI_FileDropFormat))
{
SelectDropTarget(Hit);
return DROPEFFECT_LINK;
}
IdeCommon *Src = dynamic_cast(Selection());
IdeCommon *Dst = dynamic_cast(Hit);
if (Src && Dst)
{
// Check this folder is not a child of the src
for (IdeCommon *n=Dst; n; n=dynamic_cast(n->GetParent()))
{
if (n == Src)
return DROPEFFECT_NONE;
}
}
// Valid target
SelectDropTarget(Hit);
return DROPEFFECT_MOVE;
}
int IdeTree::OnDrop(LArray &Data, LPoint p, int KeyState)
{
int Ret = DROPEFFECT_NONE;
SelectDropTarget(NULL);
if (!(Hit = ItemAtPoint(p.x, p.y)))
return Ret;
for (unsigned n=0; nType == GV_BINARY && Data->Value.Binary.Length == sizeof(ProjectNode*))
{
ProjectNode *Src = ((ProjectNode**)Data->Value.Binary.Data)[0];
if (Src)
{
ProjectNode *Folder = dynamic_cast(Hit);
while (Folder && Folder->GetType() != NodeDir)
Folder = dynamic_cast(Folder->GetParent());
IdeCommon *Dst = dynamic_cast(Folder?Folder:Hit);
if (Dst)
{
// Check this folder is not a child of the src
for (IdeCommon *n=Dst; n; n=dynamic_cast(n->GetParent()))
{
if (n == Src)
return DROPEFFECT_NONE;
}
// Detach
LTreeItem *i = dynamic_cast(Src);
i->Detach();
if (Src->LXmlTag::Parent)
{
LAssert(Src->LXmlTag::Parent->Children.HasItem(Src));
Src->LXmlTag::Parent->Children.Delete(Src);
}
// Attach
Src->LXmlTag::Parent = Dst;
Dst->Children.SetFixedLength(false);
Dst->Children.Add(Src);
Dst->Children.SetFixedLength(true);
Dst->Insert(Src);
// Dirty
Src->GetProject()->SetDirty();
}
Ret = DROPEFFECT_MOVE;
}
}
}
else if (dd.IsFileDrop())
{
ProjectNode *Folder = dynamic_cast(Hit);
while (Folder && Folder->GetType() > NodeDir)
Folder = dynamic_cast(Folder->GetParent());
IdeCommon *Dst = dynamic_cast(Folder?Folder:Hit);
if (Dst)
{
AddFilesProgress Prog(this);
LDropFiles Df(dd);
for (int i=0; iAddFiles(&Prog, Df[i]))
Ret = DROPEFFECT_LINK;
}
}
}
else LgiTrace("%s:%i - Unknown drop format: %s.\n", _FL, dd.Format.Get());
}
return Ret;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
AddFilesProgress::AddFilesProgress(LViewI *par)
{
v = 0;
Cancel = false;
Msg = NULL;
SetParent(par);
Ts = LCurrentTime();
LRect r(0, 0, 140, 100);
SetPos(r);
MoveSameScreen(par);
Name("Importing files...");
LString::Array a = LString(DefaultExt).SplitDelimit(",");
for (unsigned i=0; iGetCell(0, 0);
c->Add(new LTextLabel(-1, 0, 0, -1, -1, "Loaded:"));
c = t->GetCell(1, 0);
c->Add(Msg = new LTextLabel(-1, 0, 0, -1, -1, "..."));
c = t->GetCell(0, 1, true, 2);
c->TextAlign(LCss::Len(LCss::AlignRight));
c->Add(new LButton(IDCANCEL, 0, 0, -1, -1, "Cancel"));
}
int64 AddFilesProgress::Value()
{
return v;
}
void AddFilesProgress::Value(int64 val)
{
v = val;
uint64 Now = LCurrentTime();
if (Visible())
{
if (Now - Ts > 200)
{
if (Msg)
{
Msg->Value(v);
Msg->SendNotify(LNotifyTableLayoutRefresh);
}
Ts = Now;
}
}
else if (Now - Ts > 1000)
{
DoModeless();
SetAlwaysOnTop(true);
}
}
int AddFilesProgress::OnNotify(LViewI *c, LNotification n)
{
if (c->GetId() == IDCANCEL)
Cancel = true;
return 0;
}
diff --git a/Ide/src/ProjectNode.cpp b/Ide/src/ProjectNode.cpp
--- a/Ide/src/ProjectNode.cpp
+++ b/Ide/src/ProjectNode.cpp
@@ -1,1583 +1,1585 @@
#include "lgi/common/Lgi.h"
#include "lgi/common/Token.h"
#include "lgi/common/Combo.h"
#include "lgi/common/ClipBoard.h"
#include "lgi/common/TableLayout.h"
#include "lgi/common/Menu.h"
#include "lgi/common/FileSelect.h"
#include "lgi/common/Charset.h"
#include "LgiIde.h"
#include "IdeProject.h"
#include "ProjectNode.h"
#include "AddFtpFile.h"
#include "WebFldDlg.h"
#define DEBUG_SHOW_NODE_COUNTS 0
const char *TypeNames[NodeMax] = {
"None",
"Folder",
"Source",
"Header",
"Dependancy",
"Resources",
"Graphic",
"Web",
"Text",
"MakeFile",
};
//////////////////////////////////////////////////////////////////////////////////
class FileProps : public LDialog
{
public:
NodeType Type;
LString Charset;
int Platforms;
FileProps(LView *p, char *m, NodeType t, int plat, const char *charset)
{
Platforms = plat;
Type = t;
SetParent(p);
if (LoadFromResource(IDD_FILE_PROPS))
{
MoveToCenter();
SetCtrlName(IDC_MSG, m);
LCombo *c;
if (GetViewById(IDC_TYPE, c))
{
for (int i=NodeNone; iInsert(TypeNames[i]);
}
c->Value(Type);
}
else LgiTrace("%s:%i - Failed to get Type combo.\n", _FL);
if (GetViewById(IDC_CHARSET, c))
{
if (!charset)
charset = "utf-8";
for (LCharset *cs = LGetCsList(); cs->Charset; cs++)
{
c->Insert(cs->Charset);
if (!Stricmp(charset, cs->Charset))
c->Value(c->Length()-1);
}
}
for (int i=0; PlatformNames[i]; i++)
{
SetCtrlValue(PlatformCtrlId[i], ((1 << i) & Platforms) != 0);
}
OnPosChange();
// Make sure the dialog can display the whole table...
LTableLayout *t;
if (GetViewById(IDC_TABLE, t))
{
LRect u = t->GetUsedArea();
u.Inset(-LTableLayout::CellSpacing, -LTableLayout::CellSpacing);
LRect p = GetPos();
if (u.X() < p.X() ||
u.Y() < p.Y())
{
p.SetSize(MAX(u.X(), p.X()),
MAX(u.Y(), p.Y()));
SetPos(p);
}
}
}
}
int OnNotify(LViewI *c, LNotification n)
{
switch (c->GetId())
{
case IDOK:
{
int64 t = GetCtrlValue(IDC_TYPE);
if (t >= NodeSrc)
{
Type = (NodeType) t;
}
Platforms = 0;
for (int i=0; PlatformNames[i]; i++)
{
if (GetCtrlValue(PlatformCtrlId[i]))
{
Platforms |= 1 << i;
}
}
Charset = GetCtrlName(IDC_CHARSET);
if (!Stricmp(Charset.Get(), "utf-8"))
Charset.Empty();
// fall thru
}
case IDCANCEL:
case IDC_COPY_PATH:
{
EndModal(c->GetId());
break;
}
}
return 0;
}
};
////////////////////////////////////////////////////////////////////
ProjectNode::ProjectNode(IdeProject *p) : IdeCommon(p)
{
Platforms = PLATFORM_ALL;
NodeId = 0;
Type = NodeNone;
ChildCount = -1;
IgnoreExpand = false;
Dep = 0;
Tag = NewStr("Node");
}
ProjectNode::~ProjectNode()
{
NeedsPulse(false);
if (sFile && Project)
Project->OnNode(sFile, this, false);
if (Dep)
DeleteObj(Dep);
}
void ProjectNode::NeedsPulse(bool yes)
{
if (!Project)
{
LAssert(!"No project.");
return;
}
auto app = Project->GetApp();
if (!app)
{
LAssert(!"No app.");
return;
}
app->NeedsPulse.Delete(this);
if (yes)
app->NeedsPulse.Add(this);
}
void ProjectNode::OpenLocalCache(IdeDoc *&Doc)
{
if (sLocalCache)
{
Doc = Project->GetApp()->OpenFile(sLocalCache, this);
if (Doc)
{
Doc->SetProject(Project);
IdeProjectSettings *Settings = Project->GetSettings();
Doc->SetEditorParams(Settings->GetInt(ProjEditorIndentSize),
Settings->GetInt(ProjEditorTabSize),
Settings->GetInt(ProjEditorUseHardTabs),
Settings->GetInt(ProjEditorShowWhiteSpace));
}
else
{
LgiMsg(Tree, "Couldn't open file '%s'", AppName, MB_OK, sLocalCache.Get());
}
}
}
int64 ProjectNode::CountNodes()
{
int64 n = 1;
for (auto i:*this)
{
ProjectNode *c = dynamic_cast(i);
if (!c) break;
n += c->CountNodes();
}
return n;
}
void ProjectNode::OnCmdComplete(FtpCmd *Cmd)
{
if (!Cmd)
return;
if (Cmd->Status && Cmd->File)
{
if (Cmd->Cmd == FtpRead)
{
sLocalCache = Cmd->File;
IdeDoc *Doc;
OpenLocalCache(Doc);
}
else if (Cmd->Cmd == FtpWrite)
{
Cmd->View->OnSaveComplete(Cmd->Status);
}
}
}
int ProjectNode::GetPlatforms()
{
return Platforms;
}
const char *ProjectNode::GetLocalCache()
{
return sLocalCache;
}
bool ProjectNode::Load(LDocView *Edit, NodeView *Callback)
{
bool Status = false;
if (IsWeb())
{
if (sLocalCache)
Status = Edit->Open(sLocalCache);
else
LAssert(!"No LocalCache");
}
else
{
auto Full = GetFullPath();
Status = Edit->Open(Full, Charset);
}
return Status;
}
bool ProjectNode::Save(LDocView *Edit, NodeView *Callback)
{
bool Status = false;
if (IsWeb())
{
if (sLocalCache)
{
if (Edit->Save(sLocalCache))
{
FtpThread *t = GetFtpThread();
if (t)
{
FtpCmd *c = new FtpCmd(FtpWrite, this);
if (c)
{
c->View = Callback;
c->Watch = Project->GetApp()->GetFtpLog();
c->Uri = NewStr(sFile);
c->File = NewStr(sLocalCache);
t->Post(c);
}
}
}
else LAssert(!"Editbox save failed.");
}
else LAssert(!"No LocalCache");
}
else
{
auto f = GetFullPath();
if (Project)
Project->CheckExists(f);
Status = Edit->Save(f, Charset);
if (Callback)
Callback->OnSaveComplete(Status);
}
return Status;
}
int ProjectNode::GetId()
{
if (!NodeId && Project)
NodeId = Project->AllocateId();
return NodeId;
}
bool ProjectNode::IsWeb()
{
char *Www = GetAttr(OPT_Www);
char *Ftp = GetAttr(OPT_Ftp);
if
(
Www ||
Ftp ||
(sFile && strnicmp(sFile, "ftp://", 6) == 0)
)
return true;
return false;
}
bool ProjectNode::HasNode(ProjectNode *Node)
{
printf("Has %s %s %p\n", sFile.Get(), sName.Get(), Dep);
if (this == Node)
return true;
if (Dep && Dep->HasNode(Node))
return true;
for (auto i:*this)
{
ProjectNode *c = dynamic_cast(i);
if (!c) break;
if (c->HasNode(Node))
return true;
}
return false;
}
void ProjectNode::AddNodes(LArray &Nodes)
{
Nodes.Add(this);
for (auto i:*this)
{
ProjectNode *c = dynamic_cast(i);
if (!c) break;
c->AddNodes(Nodes);
}
}
bool ProjectNode::GetClean()
{
if (Dep)
return Dep->GetClean();
return true;
}
void ProjectNode::SetClean()
{
auto CleanProj = [this]()
{
for (auto i: *this)
{
ProjectNode *p = dynamic_cast(i);
if (p)
p->SetClean();
}
};
if (Dep)
Dep->SetClean([this, CleanProj](bool ok)
{
if (ok)
CleanProj();
});
else
CleanProj();
}
IdeProject *ProjectNode::GetDep()
{
return Dep;
}
IdeProject *ProjectNode::GetProject()
{
return Project;
}
bool ProjectNode::GetFormats(LDragFormats &Formats)
{
Formats.Supports(NODE_DROP_FORMAT);
return true;
}
bool ProjectNode::GetData(LArray &Data)
{
for (unsigned i=0; iOnNode(sFile, this, false);
- if (Project->RelativePath(Rel, f, true))
+ if (Project->RelativePath(Rel, f))
sFile = Rel;
else
sFile = f;
+
+ printf("sFile='%s'\n", sFile.Get());
if (sFile)
{
auto FullPath = GetFullPath();
if (Project)
Project->OnNode(FullPath, this, true);
AutoDetectType();
}
Project->SetDirty();
}
char *ProjectNode::GetName()
{
return sName;
}
void ProjectNode::SetName(const char *f)
{
sName = f;
Type = NodeDir;
}
NodeType ProjectNode::GetType()
{
return Type;
}
void ProjectNode::SetType(NodeType t)
{
Type = t;
}
int ProjectNode::GetImage(int f)
{
if (IsWeb())
{
return sFile ? ICON_SOURCE : ICON_WEB;
}
switch (Type)
{
default:
break;
case NodeDir:
return ICON_FOLDER;
case NodeSrc:
return ICON_SOURCE;
case NodeDependancy:
return ICON_DEPENDANCY;
case NodeGraphic:
return ICON_GRAPHIC;
case NodeResources:
return ICON_RESOURCE;
}
return ICON_HEADER;
}
const char *ProjectNode::GetText(int c)
{
if (sFile)
{
char *d = 0;
if (IsWeb())
{
d = sFile ? strrchr(sFile, '/') : 0;
}
else
{
#ifdef WIN32
char Other = '/';
#else
char Other = '\\';
#endif
char *s;
while ((s = strchr(sFile, Other)))
{
*s = DIR_CHAR;
}
d = strrchr(sFile, DIR_CHAR);
}
if (d) return d + 1;
else return sFile;
}
#if DEBUG_SHOW_NODE_COUNTS
if (Type == NodeDir)
{
if (ChildCount < 0)
ChildCount = CountNodes();
Label.Printf("%s ("LGI_PrintfInt64")", Name, ChildCount);
return Label;
}
#endif
return sName ? sName.Get() : Untitled;
}
void ProjectNode::OnExpand(bool b)
{
if (!IgnoreExpand)
{
Project->SetExpanded(GetId(), b);
}
}
bool ProjectNode::Serialize(bool Write)
{
if (!Write && sFile)
Project->OnNode(sFile, this, false);
SerializeAttr("File", sFile);
SerializeAttr("Name", sName);
SerializeAttr("Charset", Charset);
SerializeAttr("Type", (int&)Type);
SerializeAttr("Platforms", (int&)Platforms);
if (!Write && sFile)
Project->OnNode(sFile, this, true);
if (Type == NodeNone)
{
AutoDetectType();
}
if (Type == NodeDir)
{
if (Write && !NodeId)
GetId(); // Make sure we have an ID
SerializeAttr("Id", NodeId);
if (Write)
Project->SetExpanded(GetId(), Expanded());
else
{
IgnoreExpand = true;
Expanded(Project->GetExpanded(GetId()));
IgnoreExpand = false;
}
}
else if (Type == NodeDependancy)
{
SerializeAttr("LinkAgainst", LinkAgainst);
if (!Write)
{
auto Full = GetFullPath();
Dep = Project->GetApp()->OpenProject(Full, Project, false, true);
}
}
else
{
#if 0
if (!Write)
{
// Check that file exists.
auto p = GetFullPath();
if (p)
{
if (LFileExists(p))
{
if (!LIsRelativePath(File))
{
// Try and fix up any non-relative paths that have crept in...
char Rel[MAX_PATH_LEN];
if (Project->RelativePath(Rel, File))
{
if (File)
Project->OnNode(File, this, false);
DeleteArray(File);
File = NewStr(Rel);
Project->SetDirty();
Project->OnNode(File, this, true);
}
}
}
else
{
// File doesn't exist... has it moved???
LAutoString Path = Project->GetBasePath();
if (Path)
{
// Find the file.
char *d = strrchr(p, DIR_CHAR);
LArray Files;
LArray Ext;
Ext.Add(d ? d + 1 : p.Get());
if (LRecursiveFileSearch(Path, &Ext, &Files))
{
if (Files.Length())
{
LStringPipe Buf;
Buf.Print( "The file:\n"
"\n"
"\t%s\n"
"\n"
"doesn't exist. Is this the right file:\n"
"\n"
"\t%s",
p.Get(),
Files[0]);
auto Msg = Buf.NewLStr();
if (Msg)
{
auto a = new LAlert(Project->GetApp(), "Missing File", Msg, "Yes", "No", "Browse...");
a->DoModal([this](auto dlg, auto code)
{
switch (code)
{
case 1: // Yes
{
SetFileName(Files[0]);
break;
}
case 2: // No
{
break;
}
case 3: // Browse
{
LFileSelect s;
s.Parent(Project->GetApp());
s.Type("Code", SourcePatterns);
if (s.Open())
{
SetFileName(s.Name());
}
break;
}
}
delete dlg;
});
}
}
else
{
LStringPipe Buf;
Buf.Print( "The file:\n"
"\n"
"\t%s\n"
"\n"
"doesn't exist.",
p.Get());
auto Msg = Buf.NewLStr();
if (Msg)
{
auto a = new LAlert(Project->GetApp(), "Missing File", Msg, "Skip", "Delete", "Browse...");
a->DoModal([this](auto dlg, auto code)
{
switch (code)
{
case 1: // Skip
{
break;
}
case 2: // Delete
{
Project->SetDirty();
delete this;
return false;
break;
}
case 3: // Browse
{
auto s = new LFileSelect;
s->Parent(Project->GetApp());
s->Type("Code", SourcePatterns);
s->Open([this](auto s, auto ok)
{
if (ok)
SetFileName(s->Name());
delete s;
});
break;
}
}
delete dlg;
});
}
}
}
}
else
{
LgiTrace("%s:%i - Project::GetBasePath failed.\n", _FL);
}
}
}
}
#endif
}
return true;
}
LString ProjectNode::GetFullPath()
{
LString FullPath;
if (LIsRelativePath(sFile))
{
// Relative path
auto Path = Project->GetBasePath();
if (Path)
{
char p[MAX_PATH_LEN];
LMakePath(p, sizeof(p), Path, sFile);
FullPath = p;
}
}
else
{
// Absolute path
FullPath = sFile;
}
return FullPath;
}
IdeDoc *ProjectNode::Open()
{
static bool Processing = false;
IdeDoc *Doc = 0;
if (Processing)
{
LAssert(!"Not recursive");
}
if (!Processing)
{
Processing = true;
if (IsWeb())
{
if (sFile)
{
if (sLocalCache)
{
OpenLocalCache(Doc);
}
else
{
FtpThread *t = GetFtpThread();
if (t)
{
FtpCmd *c = new FtpCmd(FtpRead, this);
if (c)
{
c->Watch = Project->GetApp()->GetFtpLog();
c->Uri = NewStr(sFile);
t->Post(c);
}
}
}
}
}
else
{
switch (Type)
{
case NodeDir:
{
Expanded(true);
break;
}
case NodeResources:
{
auto FullPath = GetFullPath();
if (FullPath)
{
char Exe[MAX_PATH_LEN];
LMakePath(Exe, sizeof(Exe), LGetExePath(), "..");
#if defined WIN32
LMakePath(Exe, sizeof(Exe), Exe, "Debug\\LgiRes.exe");
#elif defined LINUX
LMakePath(Exe, sizeof(Exe), Exe, "LgiRes/lgires");
#endif
if (LFileExists(Exe))
{
LExecute(Exe, FullPath);
}
}
else
{
LgiMsg(Tree, "No Path", AppName);
}
break;
}
case NodeGraphic:
{
auto FullPath = GetFullPath();
if (FullPath)
{
LExecute(FullPath);
}
else
{
LgiMsg(Tree, "No Path", AppName);
}
break;
}
default:
{
auto FullPath = GetFullPath();
if (Project->CheckExists(FullPath))
{
Doc = Project->GetApp()->FindOpenFile(FullPath);
if (!Doc)
{
Doc = Project->GetApp()->NewDocWnd(0, this);
if (Doc)
{
if (Doc->OpenFile(FullPath))
{
IdeProjectSettings *Settings = Project->GetSettings();
Doc->SetProject(Project);
Doc->SetEditorParams(Settings->GetInt(ProjEditorIndentSize),
Settings->GetInt(ProjEditorTabSize),
Settings->GetInt(ProjEditorUseHardTabs),
Settings->GetInt(ProjEditorShowWhiteSpace));
}
else
{
LgiMsg(Tree, "Couldn't open file '%s'", AppName, MB_OK, FullPath.Get());
}
}
}
else
{
Doc->Raise();
Doc->Focus(true);
}
}
else
{
LgiMsg(Tree, "No Path", AppName);
}
break;
}
}
}
Processing = false;
}
return Doc;
}
void ProjectNode::Delete()
{
if (Select())
{
LTreeItem *s = GetNext();
if (s || (s = GetParent()))
s->Select(true);
}
if (nView)
{
nView->OnDelete();
nView = NULL;
}
Project->SetDirty();
LXmlTag::RemoveTag();
delete this;
}
bool ProjectNode::OnKey(LKey &k)
{
if (k.Down())
{
if (k.vkey == LK_RETURN && k.IsChar)
{
Open();
return true;
}
else if (k.vkey == LK_DELETE)
{
Delete();
return true;
}
}
return false;
}
void ProjectNode::OnPulse()
{
auto StartTs = LCurrentTime();
int TimeSlice = 700; //ms
auto Start = strlen(ImportPath) + 1;
while (ImportFiles.Length())
{
LAutoString f(ImportFiles[0]); // Take ownership of the string
ImportFiles.DeleteAt(0);
if (ImportProg)
(*ImportProg)++;
LToken p(f + Start, DIR_STR);
ProjectNode *Insert = this;
// Find sub nodes, and drill into directory heirarchy,
// creating the nodes if needed.
for (int i=0; Insert && i(it);
if (!c) break;
if (c->GetType() == NodeDir &&
c->GetName() &&
stricmp(c->GetName(), p[i]) == 0)
{
Insert = c;
Found = true;
break;
}
}
if (!Found)
{
// Create the node
IdeCommon *Com = Insert->GetSubFolder(Project, p[i], true);
Insert = dynamic_cast(Com);
LAssert(Insert);
}
}
// Insert the file into the tree...
if (Insert)
{
ProjectNode *New = new ProjectNode(Project);
if (New)
{
New->SetFileName(f);
Insert->InsertTag(New);
Insert->SortChildren();
Project->SetDirty();
}
}
if (LCurrentTime() - StartTs >= TimeSlice)
break; // Yeild to the message loop
}
if (ImportFiles.Length() == 0)
NeedsPulse(false);
}
void ProjectNode::OnMouseClick(LMouse &m)
{
LTreeItem::OnMouseClick(m);
if (m.IsContextMenu())
{
LSubMenu Sub;
Select(true);
if (Type == NodeDir)
{
if (IsWeb())
{
Sub.AppendItem("Insert FTP File", IDM_INSERT_FTP, true);
}
else
{
Sub.AppendItem("Insert File", IDM_INSERT, true);
}
Sub.AppendItem("New Folder", IDM_NEW_FOLDER, true);
Sub.AppendItem("Import Folder", IDM_IMPORT_FOLDER, true);
Sub.AppendSeparator();
if (!IsWeb())
{
Sub.AppendItem("Rename", IDM_RENAME, true);
}
}
Sub.AppendItem("Remove", IDM_DELETE, true);
Sub.AppendItem("Sort", IDM_SORT_CHILDREN, true);
Sub.AppendSeparator();
Sub.AppendItem("Browse Folder", IDM_BROWSE_FOLDER, ValidStr(sFile));
Sub.AppendItem("Open Terminal", IDM_OPEN_TERM, ValidStr(sFile));
Sub.AppendItem("Properties", IDM_PROPERTIES, true);
m.ToScreen();
LPoint c = _ScrollPos();
m.x -= c.x;
m.y -= c.y;
switch (Sub.Float(Tree, m.x, m.y))
{
case IDM_INSERT_FTP:
{
AddFtpFile *Add = new AddFtpFile(Tree, GetAttr(OPT_Ftp));
Add->DoModal([this, Add](auto dlg, auto code)
{
if (code)
{
for (int i=0; iUris.Length(); i++)
{
ProjectNode *New = new ProjectNode(Project);
if (New)
{
New->SetFileName(Add->Uris[i]);
InsertTag(New);
SortChildren();
Project->SetDirty();
}
}
}
delete Add;
});
break;
}
case IDM_INSERT:
{
LFileSelect *s = new LFileSelect;
s->Parent(Tree);
s->Type("Source", SourcePatterns);
s->Type("Makefiles", "*makefile");
s->Type("All Files", LGI_ALL_FILES);
s->MultiSelect(true);
- LAutoString Dir = Project->GetBasePath();
+ auto Dir = Project->GetBasePath();
if (Dir)
- {
s->InitialDir(Dir);
- }
s->Open([this](auto s, auto ok)
{
if (ok)
{
for (int i=0; iLength(); i++)
{
- if (!Project->InProject(false, (*s)[i], false))
+ auto path = (*s)[i];
+
+ if (!Project->InProject(false, path, false))
{
- ProjectNode *New = new ProjectNode(Project);
+ auto New = new ProjectNode(Project);
if (New)
{
- New->SetFileName((*s)[i]);
+ New->SetFileName(path);
InsertTag(New);
SortChildren();
Project->SetDirty();
}
}
else
{
LgiMsg(Tree, "'%s' is already in the project.\n", AppName, MB_OK, (*s)[i]);
}
}
}
delete s;
});
break;
}
case IDM_IMPORT_FOLDER:
{
LFileSelect *s = new LFileSelect;
s->Parent(Tree);
auto Dir = Project->GetBasePath();
if (Dir)
s->InitialDir(Dir);
s->OpenFolder([this](auto s, auto ok)
{
if (ok)
{
ImportPath = s->Name();
LArray Ext;
LToken e(SourcePatterns, ";");
for (auto i: e)
{
Ext.Add(i);
}
if (LRecursiveFileSearch(ImportPath, &Ext, &ImportFiles))
{
ImportProg.Reset(new LProgressDlg(GetTree(), 300));
ImportProg->SetDescription("Importing...");
ImportProg->SetRange(ImportFiles.Length());
NeedsPulse(true);
}
}
delete s;
});
break;
}
case IDM_SORT_CHILDREN:
{
SortChildren();
Project->SetDirty();
break;
}
case IDM_NEW_FOLDER:
{
LInput *Name = new LInput(Tree, "", "Name:", AppName);
Name->DoModal([this, Name](auto dlg, auto ok)
{
if (ok)
GetSubFolder(Project, Name->GetStr(), true);
delete Name;
});
break;
}
case IDM_RENAME:
{
LInput *Name = new LInput(Tree, GetName(), "Name:", AppName);
Name->DoModal([this, Name](auto dlg, auto ok)
{
if (ok)
{
SetName(Name->GetStr());
Project->SetDirty();
Update();
}
delete Name;
});
break;
}
case IDM_DELETE:
{
Delete();
return;
break;
}
case IDM_BROWSE_FOLDER:
{
auto Path = GetFullPath();
if (Path)
LBrowseToFile(Path);
break;
}
case IDM_OPEN_TERM:
{
if (Type == NodeDir)
{
}
else
{
auto Path = GetFullPath();
if (Path)
{
LTrimDir(Path);
#if defined LINUX
char *Term = 0;
char *Format = 0;
switch (LGetWindowManager())
{
case WM_Kde:
Term = "konsole";
Format = "--workdir ";
break;
case WM_Gnome:
Term = "gnome-terminal";
Format = "--working-directory=";
break;
}
if (Term)
{
char s[256];
strcpy_s(s, sizeof(s), Format);
char *o = s + strlen(s), *i = Path;
*o++ = '\"';
while (*i)
{
if (*i == ' ')
{
*o++ = '\\';
}
*o++ = *i++;
}
*o++ = '\"';
*o++ = 0;
LExecute(Term, s);
}
#elif defined WIN32
LExecute("cmd", 0, Path);
#endif
}
}
break;
}
case IDM_PROPERTIES:
{
OnProperties();
break;
}
}
}
else if (m.Left())
{
if (Type != NodeDir && m.Double())
{
if
(
(
IsWeb()
||
Type != NodeDir
)
&&
ValidStr(sFile)
)
{
Open();
}
else
{
LAssert(!"Unknown file type");
}
}
}
}
#if 0
#define LOG_FIND_FILE(...) printf(__VA_ARGS__)
#else
#define LOG_FIND_FILE(...)
#endif
ProjectNode *ProjectNode::FindFile(const char *In, char **Full)
{
LOG_FIND_FILE("%s:%i Node='%s' '%s'\n", _FL, sFile.Get(), sName.Get());
if (sFile)
{
bool Match = false;
const char *AnyDir = "\\/";
if (IsWeb())
{
Match = sFile ? stricmp(In, sFile) == 0 : false;
}
else if (strchr(In, DIR_CHAR))
{
// Match partial or full path
char Full[MAX_PATH_LEN] = "";
if (LIsRelativePath(sFile))
{
auto Base = Project->GetBasePath();
if (Base)
LMakePath(Full, sizeof(Full), Base, sFile);
else
LgiTrace("%s,%i - Couldn't get full IDoc path.\n", _FL);
}
else
{
strcpy_s(Full, sizeof(Full), sFile);
}
LOG_FIND_FILE("%s:%i Full='%s'\n", _FL, Full);
LString MyPath(Full);
LString::Array MyArr = MyPath.SplitDelimit(AnyDir);
LString InPath(In);
LString::Array InArr = InPath.SplitDelimit(AnyDir);
auto Common = MIN(MyArr.Length(), InArr.Length());
Match = true;
for (int i = 0; i < Common; i++)
{
char *a = MyArr[MyArr.Length()-(i+1)];
char *b = InArr[InArr.Length()-(i+1)];
auto res = _stricmp(a, b);
LOG_FIND_FILE("%s:%i cmp '%s','%s'=%i\n", _FL, a, b, res);
if (res)
{
Match = false;
break;
}
}
}
else
{
// Match file name only
auto p = sFile.SplitDelimit(AnyDir);
Match = p.Last().Equals(In);
LOG_FIND_FILE("%s:%i cmp '%s','%s'=%i\n", _FL, p.Last().Get(), In, Match);
}
if (Match)
{
if (Full)
{
if (sFile(0) == '.')
{
LAutoString Base = Project->GetBasePath();
if (Base)
{
char f[256];
LMakePath(f, sizeof(f), Base, sFile);
*Full = NewStr(f);
}
}
else
{
*Full = NewStr(sFile);
}
}
return this;
}
}
for (auto i:*this)
{
ProjectNode *c = dynamic_cast(i);
if (!c)
{
LAssert(!"Not a node?");
break;
}
ProjectNode *n = c->FindFile(In, Full);
if (n)
{
return n;
}
}
return 0;
}
struct DepDlg : public LDialog
{
ProjectNode *Node;
DepDlg(ProjectNode *node) : Node(node)
{
auto proj = node->GetProject();
auto app = proj ? proj->GetApp() : NULL;
if (!app)
LAssert(!"Can't get app ptr.");
else
{
SetParent(app);
MoveSameScreen(app);
}
if (!LoadFromResource(IDD_DEP_NODE))
LAssert(!"Resource missing.");
else
{
auto Path = node->GetFullPath();
if (Path)
SetCtrlName(IDC_PATH, Path);
SetCtrlValue(IDC_LINK_AGAINST, node->GetLinkAgainst());
}
}
int OnNotify(LViewI *Ctrl, LNotification n)
{
switch (Ctrl->GetId())
{
case IDOK:
{
Node->SetLinkAgainst(GetCtrlValue(IDC_LINK_AGAINST));
break;
}
}
return LDialog::OnNotify(Ctrl, n);
}
};
void ProjectNode::OnProperties()
{
if (IsWeb())
{
bool IsFolder = sFile.IsEmpty();
WebFldDlg *Dlg = new WebFldDlg(Tree, sName, IsFolder ? GetAttr(OPT_Ftp) : sFile.Get(), GetAttr(OPT_Www));
Dlg->DoModal([this, IsFolder, Dlg](auto dlg, auto ok)
{
if (ok)
{
if (IsFolder)
{
SetName(Dlg->Name);
SetAttr(OPT_Ftp, Dlg->Ftp);
SetAttr(OPT_Www, Dlg->Www);
}
else
{
sFile = Dlg->Ftp;
}
Project->SetDirty();
Update();
}
delete Dlg;
});
}
else if (Type == NodeDir)
{
}
else if (Type == NodeDependancy)
{
auto dlg = new DepDlg(this);
dlg->DoModal(NULL);
}
else
{
auto Path = GetFullPath();
if (Path)
{
char Size[32];
int64 FSize = LFileSize(Path);
LFormatSize(Size, sizeof(Size), FSize);
char Msg[512];
snprintf(Msg, sizeof(Msg), "Source Code:\n\n\t%s\n\nSize: %s (%i bytes)", Path.Get(), Size, (int32)FSize);
FileProps *Dlg = new FileProps(Tree, Msg, Type, Platforms, Charset);
Dlg->DoModal([this, Dlg, Path](auto dlg, auto code)
{
switch (code)
{
case IDOK:
{
if (Type != Dlg->Type)
{
Type = Dlg->Type;
Project->SetDirty();
}
if (Platforms != Dlg->Platforms)
{
Platforms = Dlg->Platforms;
Project->SetDirty();
}
if (Charset != Dlg->Charset)
{
Charset = Dlg->Charset;
Project->SetDirty();
}
Update();
break;
}
case IDC_COPY_PATH:
{
LClipBoard Clip(Tree);
Clip.Text(Path);
break;
}
}
delete Dlg;
});
}
}
}