mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
473 lines
14 KiB
Beef
473 lines
14 KiB
Beef
using System;
|
|
using System.IO;
|
|
using Bon;
|
|
using GlitchyEngine;
|
|
using GlitchyEngine.Scripting;
|
|
using Xml_Beef;
|
|
|
|
namespace GlitchyEditor;
|
|
|
|
[BonTarget]
|
|
class Project
|
|
{
|
|
[BonInclude]
|
|
private String _projectName ~ delete _;
|
|
|
|
[BonIgnore]
|
|
private String _workspacePath ~ delete _;
|
|
|
|
[BonIgnore]
|
|
private String _assetsFolder ~ delete _;
|
|
|
|
[BonIgnore]
|
|
private String _scriptFolder ~ delete _;
|
|
|
|
[BonIgnore]
|
|
private ProjectUserSettings _userSettings ~ delete _;
|
|
|
|
public StringView Name => _projectName;
|
|
public StringView WorkspacePath => _workspacePath;
|
|
|
|
public StringView AssetsFolder => _assetsFolder;
|
|
|
|
public ProjectUserSettings UserSettings => _userSettings;
|
|
|
|
[AllowAppend]
|
|
private this(StringView workspacePath)
|
|
{
|
|
_workspacePath = new String(workspacePath);
|
|
|
|
_assetsFolder = new String();
|
|
PathInProject(_assetsFolder, "Assets");
|
|
|
|
InitUserSettings();
|
|
}
|
|
|
|
/// Writes the the absolute path of the give project-file into target.
|
|
/// @param target The string that will contain the full path.
|
|
/// @param relativePath The path of the file relative to the workspace folder.
|
|
public void PathInProject(String target, StringView relativePath)
|
|
{
|
|
Path.Combine(target, WorkspacePath, relativePath);
|
|
}
|
|
|
|
/// Creates a new scoped string that contains the absolute path of the give project-file
|
|
/// @param relativePath The path of the file relative to the workspace folder.
|
|
public mixin GetScopedPath(StringView relativePath)
|
|
{
|
|
String target = scope:mixin String();
|
|
|
|
PathInProject(target, relativePath);
|
|
|
|
target
|
|
}
|
|
|
|
public void GetPathToScriptSolutionFile(String outTarget)
|
|
{
|
|
PathInProject(outTarget, scope $"{Name}.slnx");
|
|
}
|
|
|
|
public void GetPathToScriptProjectFile(String outTarget)
|
|
{
|
|
PathInProject(outTarget, scope $"{Name}.csproj");
|
|
}
|
|
|
|
private static void GetScriptCoreProjectFilePath(String outTarget)
|
|
{
|
|
Directory.GetCurrentDirectory(outTarget);
|
|
Path.Combine(outTarget, "../ScriptCore/ScriptCore.csproj");
|
|
Path.ToActualPath(outTarget);
|
|
}
|
|
|
|
/// Loads or creates the user specific settings for this project.
|
|
private void InitUserSettings()
|
|
{
|
|
_userSettings = new ProjectUserSettings();
|
|
|
|
String userSettingsFile = scope .();
|
|
PathInProject(userSettingsFile, ProjectUserSettings.FileName);
|
|
|
|
// It's not a problem if we couldn't find or deserialize the settings. We will simply restore the defaults.
|
|
if (Bon.DeserializeFromFile(ref _userSettings, userSettingsFile) case .Err)
|
|
{
|
|
// Log anyway, just in case...
|
|
Log.EngineLogger.Info($"Failed to deserialize project user settings file \"{userSettingsFile}\".");
|
|
_userSettings.RestoreDefaults();
|
|
}
|
|
}
|
|
|
|
public void SaveUserSettings()
|
|
{
|
|
if (_userSettings == null || !_userSettings.SettignsDirty)
|
|
return;
|
|
|
|
String userSettingsFile = scope .();
|
|
PathInProject(userSettingsFile, ProjectUserSettings.FileName);
|
|
|
|
// It's not a big problem, if we fail to serialize this file.
|
|
if (Bon.SerializeIntoFile(_userSettings, userSettingsFile) case .Err)
|
|
{
|
|
// Log anyway, just in case...
|
|
Log.EngineLogger.Error($"Failed to deserialize project user settings file \"{userSettingsFile}\".");
|
|
}
|
|
}
|
|
|
|
/// Creates a new project with the given directory and name.
|
|
public static Result<Project> CreateNew(StringView projectDirectory, StringView projectName)
|
|
{
|
|
Project project = new Project(projectDirectory);
|
|
project._projectName = new String(projectName);
|
|
|
|
String settingsFile = scope .();
|
|
project.PathInProject(settingsFile, "project.gep");
|
|
|
|
/// This is not really fatal, however the project might end up unusable
|
|
if (Bon.SerializeIntoFile(project, settingsFile) case .Err)
|
|
{
|
|
Log.EngineLogger.Warning($"Failed to save project file \"{settingsFile}\".");
|
|
delete project;
|
|
|
|
return .Err;
|
|
}
|
|
|
|
return project;
|
|
}
|
|
|
|
/// Creates a new project with the given name in the specified directory basend on the provided template.
|
|
public static Result<Project> CreateNewFromTemplate(StringView projectDirectory, StringView projectName, StringView templatePath)
|
|
{
|
|
Project newProject = Try!(Project.CreateNew(projectDirectory, projectName));
|
|
|
|
if (Project.InitializeTemplate(newProject, templatePath) case .Err)
|
|
{
|
|
delete newProject;
|
|
return .Err;
|
|
}
|
|
|
|
return newProject;
|
|
}
|
|
|
|
/// Initializes a project with the provided template.
|
|
public static Result<void> InitializeTemplate(Project project, StringView templatePath)
|
|
{
|
|
Try!(CopyTemplate(project, templatePath));
|
|
|
|
Try!(RenameAndFixupFiles(project.WorkspacePath, project));
|
|
|
|
Try!(project.FixupScriptCorePath());
|
|
|
|
return .Ok;
|
|
}
|
|
|
|
/// Copies the template files into the given project.
|
|
private static Result<void> CopyTemplate(Project project, StringView templatePath)
|
|
{
|
|
if (Directory.Copy(templatePath, project.WorkspacePath) case .Err)
|
|
{
|
|
Log.EngineLogger.Error($"CopyTemplate: Failed to copy template {templatePath} to {project.WorkspacePath}.");
|
|
return .Err;
|
|
}
|
|
|
|
return .Ok;
|
|
}
|
|
|
|
const String ProjectNamePlaceholder = "[ProjectName]";
|
|
|
|
/// Moves files so that [ProjectName] in their paths will be replaced by the actual project name
|
|
private static Result<void> RenameAndFixupFiles(StringView directoryPath, Project project)
|
|
{
|
|
String fullPath = scope .(256);
|
|
String modifiedFileName = scope String(256);
|
|
|
|
for (FileFindEntry entry in Directory.Enumerate(scope $"{directoryPath}/*", .Files | .Directories))
|
|
{
|
|
entry.GetFilePath(fullPath..Clear());
|
|
|
|
if (fullPath.Contains(ProjectNamePlaceholder))
|
|
{
|
|
modifiedFileName.Set(fullPath);
|
|
modifiedFileName.Replace(ProjectNamePlaceholder, project.Name);
|
|
|
|
if (entry.IsDirectory)
|
|
Try!(Directory.Move(fullPath, modifiedFileName));
|
|
else
|
|
Try!(File.Move(fullPath, modifiedFileName));
|
|
|
|
Swap!(fullPath, modifiedFileName);
|
|
}
|
|
|
|
if (entry.IsDirectory)
|
|
{
|
|
Try!(RenameAndFixupFiles(fullPath, project));
|
|
}
|
|
else
|
|
{
|
|
Try!(FixupFile(fullPath, project));
|
|
}
|
|
}
|
|
|
|
return .Ok;
|
|
}
|
|
|
|
// Fixups a file by replacing placeholders for e.g. the project name.
|
|
private static Result<void> FixupFile(StringView filePath, Project project)
|
|
{
|
|
String fileContent = scope String();
|
|
if (File.ReadAllText(filePath, fileContent, true) case .Err)
|
|
{
|
|
Log.EngineLogger.Error($"FixupFile: Failed to read file {filePath}.");
|
|
return .Err;
|
|
}
|
|
|
|
if (!fileContent.Contains(ProjectNamePlaceholder))
|
|
return .Ok;
|
|
|
|
fileContent.Replace(ProjectNamePlaceholder, project.Name);
|
|
|
|
if (File.WriteAllText(filePath, fileContent, false) case .Err)
|
|
{
|
|
Log.EngineLogger.Error($"FixupFile: Failed to write file {filePath}.");
|
|
return .Err;
|
|
}
|
|
|
|
return .Ok;
|
|
}
|
|
|
|
public static Result<Project> Load(StringView projectDirectory)
|
|
{
|
|
Project project = new Project(projectDirectory);
|
|
|
|
String settingsFile = scope .();
|
|
project.PathInProject(settingsFile, "project.gep");
|
|
|
|
if (File.Exists(settingsFile))
|
|
{
|
|
Result<void> result = Bon.DeserializeFromFile(ref project, settingsFile);
|
|
|
|
if (result case .Err)
|
|
Log.EngineLogger.Warning($"Failed to deserialize project file \"{settingsFile}\".");
|
|
}
|
|
else
|
|
{
|
|
Log.EngineLogger.Warning($"Project file \"{settingsFile}\" doesn't exist.");
|
|
|
|
if (project.Name.IsWhiteSpace)
|
|
{
|
|
project._projectName ??= new String();
|
|
|
|
Path.GetFileName(project.WorkspacePath, project._projectName);
|
|
|
|
/// This is not really fatal, however the project might end up unusable
|
|
if (Bon.SerializeIntoFile(project, settingsFile) case .Err)
|
|
{
|
|
Log.EngineLogger.Warning($"Failed to save project file \"{settingsFile}\".");
|
|
delete project;
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
Try!(project.FixupScriptCorePath());
|
|
|
|
return project;
|
|
}
|
|
|
|
/// If necessary adds or removes the reference to ScriptCore.csproj from the solution (.slnx) file.
|
|
private Result<void> FixupScriptSolutionFile(bool referenceScriptCoreProject)
|
|
{
|
|
String solutionPath = scope .();
|
|
GetPathToScriptSolutionFile(solutionPath);
|
|
|
|
String solutionFileContent = scope String();
|
|
if (File.ReadAllText(solutionPath, solutionFileContent, true) case .Err)
|
|
{
|
|
Log.EngineLogger.Error($"FixupFile: Failed to read solution file {solutionPath}.");
|
|
return .Err;
|
|
}
|
|
|
|
Xml solution = new Xml();
|
|
solution.PreserveWhitespace = true;
|
|
defer delete solution;
|
|
|
|
solution.LoadFromString(solutionFileContent);
|
|
|
|
XmlNode scriptCoreProjectReference = null;
|
|
for (XmlNode projectNode in solution.DocumentElement.EnumerateNodes("Project", recursive: true))
|
|
{
|
|
if (projectNode.TryGetAttribute("Path", let includeAttribute))
|
|
{
|
|
if (includeAttribute.Value.Contains("ScriptCore.csproj", true))
|
|
{
|
|
scriptCoreProjectReference = projectNode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (referenceScriptCoreProject)
|
|
{
|
|
Log.EngineLogger.Info("Debug mode enabled. Referencing ScriptCore.csproj in Script-Solution.");
|
|
|
|
if (scriptCoreProjectReference == null)
|
|
{
|
|
Log.EngineLogger.Warning("No reference to ScriptCore.csproj found. Adding...");
|
|
scriptCoreProjectReference = solution.DocumentElement.AddChild("Project");
|
|
}
|
|
|
|
String projectFilePath = scope .();
|
|
GetScriptCoreProjectFilePath(projectFilePath);
|
|
scriptCoreProjectReference["Path"] = projectFilePath;
|
|
}
|
|
else if (scriptCoreProjectReference != null)
|
|
{
|
|
Log.EngineLogger.Warning("Reference to ScriptCore.csproj found. Removing...");
|
|
scriptCoreProjectReference.RemoveFromParent(true);
|
|
}
|
|
|
|
String modifiedSolution = new String(solutionFileContent.Length * 2);
|
|
defer delete modifiedSolution;
|
|
solution.SaveToString(modifiedSolution);
|
|
|
|
if (File.WriteAllText(solutionPath, modifiedSolution) case .Err)
|
|
{
|
|
Log.EngineLogger.Error($"Failed to modify {solutionPath}");
|
|
return .Err;
|
|
}
|
|
|
|
return .Ok;
|
|
}
|
|
|
|
/// If necessary adds or removes the reference to ScriptCore.dll from the project (.csproj) file.
|
|
private Result<void> FixupScriptProjectFile(bool referenceScriptCoreProject)
|
|
{
|
|
String csprojPath = scope .();
|
|
GetPathToScriptProjectFile(csprojPath);
|
|
|
|
String projectFileContent = scope String();
|
|
if (File.ReadAllText(csprojPath, projectFileContent, true) case .Err)
|
|
{
|
|
Log.EngineLogger.Error($"FixupFile: Failed to read project file {csprojPath}.");
|
|
return .Err;
|
|
}
|
|
|
|
Xml project = new Xml();
|
|
project.PreserveWhitespace = true;
|
|
defer delete project;
|
|
|
|
project.LoadFromString(projectFileContent);
|
|
|
|
// Try to find a reference to the csproj
|
|
XmlNode scriptCoreProjectReference = null;
|
|
for (XmlNode projectReferenceNode in project.DocumentElement.EnumerateNodes("ProjectReference", recursive: true))
|
|
{
|
|
if (projectReferenceNode.TryGetAttribute("Include", let includeAttribute))
|
|
{
|
|
if (includeAttribute.Value.Contains("ScriptCore.csproj", true))
|
|
{
|
|
scriptCoreProjectReference = projectReferenceNode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to find a reference to the dll
|
|
XmlNode dllReferenceNode = project.DocumentElement.Find("Reference", "Include", "ScriptCore", recursive: true);
|
|
|
|
if (referenceScriptCoreProject)
|
|
{
|
|
Log.EngineLogger.Info("Debug mode enabled. Referencing ScriptCore.csproj in Script-Project.");
|
|
|
|
if (dllReferenceNode != null)
|
|
{
|
|
Log.EngineLogger.Warning("Reference to ScriptCore.dll found. Removing...");
|
|
dllReferenceNode.RemoveFromParent(true);
|
|
}
|
|
|
|
if (scriptCoreProjectReference == null)
|
|
{
|
|
Log.EngineLogger.Warning("Reference to ScriptCore.csproj missing. Adding...");
|
|
|
|
XmlNode itemGroup = project.DocumentElement.Find("ItemGroup", recursive: true);
|
|
|
|
scriptCoreProjectReference = itemGroup.AddChild("ProjectReference");
|
|
|
|
String scriptCoreProjectPath = scope .();
|
|
Directory.GetCurrentDirectory(scriptCoreProjectPath);
|
|
Path.Combine(scriptCoreProjectPath, "../ScriptCore/ScriptCore.csproj");
|
|
Path.ToActualPath(scriptCoreProjectPath);
|
|
|
|
scriptCoreProjectReference.SetAttribute("Include", scriptCoreProjectPath);
|
|
scriptCoreProjectReference.SetAttribute("OutputItemType", "Analyzer");
|
|
scriptCoreProjectReference.SetAttribute("ReferenceOutputAssembly", "true");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (scriptCoreProjectReference != null)
|
|
{
|
|
Log.EngineLogger.Warning("Reference to ScriptCore.csproj found. Removing...");
|
|
scriptCoreProjectReference.RemoveFromParent(true);
|
|
}
|
|
|
|
if (dllReferenceNode == null)
|
|
{
|
|
Log.EngineLogger.Warning("Reference to ScriptCore.dll missing. Adding...");
|
|
|
|
XmlNode itemGroup = project.DocumentElement.Find("ItemGroup", recursive: true);
|
|
|
|
dllReferenceNode = itemGroup.AddChild("Reference");
|
|
}
|
|
|
|
dllReferenceNode.SetAttribute("Include", "ScriptCore");
|
|
dllReferenceNode.SetAttribute("OutputItemType", "Analyzer");
|
|
dllReferenceNode.SetAttribute("ReferenceOutputAssembly", "true");
|
|
|
|
XmlNode hintPathNode = dllReferenceNode.Find("HintPath");
|
|
if (hintPathNode == null)
|
|
{
|
|
hintPathNode = dllReferenceNode.AddChild("HintPath");
|
|
}
|
|
|
|
String pathToScriptCoreDll = scope .();
|
|
Directory.GetCurrentDirectory(pathToScriptCoreDll);
|
|
Path.Combine(pathToScriptCoreDll, ScriptEngine.ScriptCorePath);
|
|
Path.ToActualPath(pathToScriptCoreDll);
|
|
|
|
if (!File.Exists(pathToScriptCoreDll))
|
|
{
|
|
Log.EngineLogger.Error($"Could not fild \"{pathToScriptCoreDll}\"");
|
|
return .Err;
|
|
}
|
|
|
|
hintPathNode.SetText(pathToScriptCoreDll);
|
|
}
|
|
|
|
String modfiedProject = new String(projectFileContent.Length * 2);
|
|
defer delete modfiedProject;
|
|
project.SaveToString(modfiedProject);
|
|
|
|
if (File.WriteAllText(csprojPath, modfiedProject) case .Err)
|
|
{
|
|
Log.EngineLogger.Error($"Failed to modify {csprojPath}");
|
|
}
|
|
|
|
return .Ok;
|
|
}
|
|
|
|
/// Fixes the references to ScriptCore in the script-project and solution file.
|
|
public Result<void> FixupScriptCorePath()
|
|
{
|
|
bool referenceScriptCoreProject = false;
|
|
|
|
#if DEBUG
|
|
Settings settings = Application.Instance.Settings;
|
|
referenceScriptCoreProject = settings.DevSettings.UseScriptCoreDll;
|
|
#endif
|
|
|
|
Try!(FixupScriptSolutionFile(referenceScriptCoreProject));
|
|
Try!(FixupScriptProjectFile(referenceScriptCoreProject));
|
|
|
|
return .Ok;
|
|
}
|
|
} |