1
0
Fork 0
mirror of https://github.com/beefytech/Beef.git synced 2025-06-19 00:20:25 +02:00
Beef/IDE/src/BeefConfig.bf
2024-10-21 09:18:07 -04:00

293 lines
7.3 KiB
Beef

using IDE.Util;
using System;
using System.Collections;
using System.IO;
using Beefy.utils;
using System.Threading;
using System.Diagnostics;
namespace IDE
{
class BeefConfig
{
public class RegistryEntry
{
public String mProjName ~ delete _;
public Project.TargetType mTargetType;
public SemVer mVersion ~ delete _;
public VerSpec mLocation ~ _.Dispose();
public ConfigFile mConfigFile;
public bool mParsedConfig;
}
public class Registry
{
public List<RegistryEntry> mEntries = new List<RegistryEntry>() ~ DeleteContainerAndItems!(_);
public Monitor mMonitor = new .() ~ delete _;
public WaitEvent mEvent = new .() ~ delete _;
public Thread mThread ~ delete _;
public ~this()
{
mEvent.WaitFor();
}
public void ParseConfig(RegistryEntry entry)
{
entry.mParsedConfig = true;
if (entry.mLocation case .Path(let path))
{
String configPath = scope String()..AppendF("{}/BeefProj.toml", path);
StructuredData sd = scope .();
if (sd.Load(configPath) case .Ok)
{
using (sd.Open("Project"))
{
var projName = scope String();
sd.GetString("Name", projName);
if (!projName.IsEmpty)
{
using (mMonitor.Enter())
entry.mProjName.Set(projName);
}
if (sd.Contains("StartupObject"))
entry.mTargetType = .BeefConsoleApplication;
else
entry.mTargetType = .BeefLib;
var targetTypeName = scope String();
sd.GetString("TargetType", targetTypeName);
if (!targetTypeName.IsEmpty)
{
switch (targetTypeName)
{ // Handle Legacy names first
case "BeefWindowsApplication":
entry.mTargetType = .BeefGUIApplication;
case "C_WindowsApplication":
entry.mTargetType = .C_GUIApplication;
case "BeefDynLib":
entry.mTargetType = .BeefLib;
default:
entry.mTargetType = sd.GetEnum<Project.TargetType>("TargetType", entry.mTargetType);
}
}
}
}
}
}
public void WaitFor()
{
mEvent.WaitFor();
}
void Resolve()
{
// NOTE: We allow a race condition where ParseConfig can possibly occur on multiple threads
// at the same time
for (var entry in mEntries)
{
if (!entry.mParsedConfig)
ParseConfig(entry);
}
mEvent.Set(true);
}
public void StartResolve()
{
delete mThread;
mThread = new Thread(new => Resolve);
mThread.Start(false);
}
}
public class LibDirectory
{
public String mPath ~ delete _;
public ConfigFile mConfigFile;
}
public class ConfigFile
{
public String mFilePath ~ delete _;
public String mConfigDir ~ delete _;
}
List<ConfigFile> mConfigFiles = new List<ConfigFile>() ~ DeleteContainerAndItems!(_);
public Registry mRegistry ~ delete _;
List<String> mConfigPathQueue = new List<String>() ~ DeleteContainerAndItems!(_);
List<LibDirectory> mLibDirectories = new List<LibDirectory>() ~ DeleteContainerAndItems!(_);
List<FileSystemWatcher> mWatchers = new .() ~ DeleteContainerAndItems!(_);
public String mManagedLibPath = new .() ~ delete _;
public bool mLibsChanged;
void LibsChanged()
{
mLibsChanged = true;
}
Result<void> Load(StringView path)
{
let data = scope StructuredData();
if (data.Load(path) case .Err)
return .Err;
let configFile = new ConfigFile();
configFile.mFilePath = new String(path);
configFile.mConfigDir = new String();
Path.GetDirectoryPath(configFile.mFilePath, configFile.mConfigDir).IgnoreError();
for (let projName in data.Enumerate("Registry"))
{
RegistryEntry regEntry = new RegistryEntry();
regEntry.mProjName = new String(projName);
mRegistry.mEntries.Add(regEntry);
regEntry.mConfigFile = configFile;
var verString = scope String();
data.GetString("Version", verString);
regEntry.mVersion = new SemVer();
regEntry.mVersion.Parse(verString).IgnoreError();
using (data.Open("Location"))
regEntry.mLocation.Parse(data).IgnoreError();
}
void AddFromLibraryPath(String absPath)
{
for (var entry in Directory.EnumerateDirectories(absPath))
{
String projName = scope .();
entry.GetFileName(projName);
String filePath = scope .();
entry.GetFilePath(filePath);
String projFilePath = scope .();
projFilePath.Concat(filePath, "/BeefProj.toml");
if (File.Exists(projFilePath))
{
RegistryEntry regEntry = new RegistryEntry();
regEntry.mProjName = new String(projName);
mRegistry.mEntries.Add(regEntry);
regEntry.mConfigFile = configFile;
regEntry.mVersion = new SemVer();
regEntry.mVersion.Parse("0.0.0");
regEntry.mLocation = .Path(new String(filePath));
}
AddFromLibraryPath(filePath);
}
}
for (data.Enumerate("UnversionedLibDirs"))
{
String dirStr = scope .();
data.GetCurString(dirStr);
if (!dirStr.IsWhiteSpace)
{
LibDirectory libDir = new .();
libDir.mPath = new String(dirStr);
libDir.mConfigFile = configFile;
mLibDirectories.Add(libDir);
String absPath = scope .();
Path.GetAbsolutePath(libDir.mPath, configFile.mConfigDir, absPath);
AddFromLibraryPath(absPath);
absPath.Append(Path.DirectorySeparatorChar);
#if !CLI
FileSystemWatcher watcher = new FileSystemWatcher(absPath);
watcher.OnChanged.Add(new (fileName) => LibsChanged());
watcher.OnCreated.Add(new (fileName) => LibsChanged());
watcher.OnDeleted.Add(new (fileName) => LibsChanged());
watcher.OnRenamed.Add(new (newName, oldName) => LibsChanged());
watcher.OnError.Add(new () => LibsChanged());
watcher.StartRaisingEvents().IgnoreError();
mWatchers.Add(watcher);
#endif
}
}
data.GetString("ManagedLibDir", mManagedLibPath);
if ((mManagedLibPath.IsEmpty) && (!mLibDirectories.IsEmpty))
{
var libPath = Path.GetAbsolutePath(mLibDirectories[0].mPath, gApp.mInstallDir, .. scope .());
var managedPath = Path.GetAbsolutePath("../BeefManaged", libPath, .. scope .());
if (Directory.Exists(managedPath))
mManagedLibPath.Set(managedPath);
}
mConfigFiles.Add(configFile);
return .Ok;
}
public void Refresh()
{
Load().IgnoreError();
}
public void QueuePaths(StringView topLevelDir)
{
let dir = scope String(topLevelDir);
if (!dir.EndsWith(IDEUtils.cNativeSlash))
dir.Append(IDEUtils.cNativeSlash);
while (true)
{
let path = scope String(dir);
path.Append("BeefConfig.toml");
if (File.Exists(path))
{
if (mConfigPathQueue.Contains(path))
break; // We have this and everything under it already
mConfigPathQueue.Add(new String(path));
}
// We had logic to check parent directories, but this seems unsound. Revisit this decision when we have
// better usage cases in mind.
break;
/*if (dir.Length < 2)
break;
int slashPos = dir.LastIndexOf(IDEUtils.cNativeSlash, dir.Length - 2);
if (slashPos == -1)
break;
dir.RemoveToEnd(slashPos + 1);*/
}
}
public Result<void> Load()
{
mLibsChanged = false;
delete mRegistry;
mRegistry = new Registry();
ClearAndDeleteItems(mConfigFiles);
ClearAndDeleteItems(mWatchers);
for (int i = mConfigPathQueue.Count - 1; i >= 0; i--)
{
let path = mConfigPathQueue[i];
Try!(Load(path));
}
mRegistry.StartResolve();
return .Ok;
}
}
}