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 mEntries = new List() ~ 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("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 mConfigFiles = new List() ~ DeleteContainerAndItems!(_); public Registry mRegistry ~ delete _; List mConfigPathQueue = new List() ~ DeleteContainerAndItems!(_); List mLibDirectories = new List() ~ DeleteContainerAndItems!(_); List mWatchers = new .() ~ DeleteContainerAndItems!(_); public String mManagedLibPath = new .() ~ delete _; public bool mLibsChanged; void LibsChanged() { mLibsChanged = true; } Result 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 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; } } }