summaryrefslogtreecommitdiff
path: root/modules/mono/editor/GodotSharpTools
diff options
context:
space:
mode:
authorAndreas Haas <Hinsbart@users.noreply.github.com>2017-10-03 00:13:40 +0200
committerGitHub <noreply@github.com>2017-10-03 00:13:40 +0200
commitb0194a33f65970948ef66819913bf3034a3a22e8 (patch)
tree35e21b53b10bbd525506bb5b72b7f89214e1234f /modules/mono/editor/GodotSharpTools
parent5cd68abf8896fd86a33c048d6fece61c3cd3f8e5 (diff)
parentd5caf71c3fcdeb422d1b0ea97a836fcdb57a8713 (diff)
Merge pull request #11739 from neikeq/tengo-el-mono
Moved mono module here
Diffstat (limited to 'modules/mono/editor/GodotSharpTools')
-rw-r--r--modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs335
-rw-r--r--modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs58
-rw-r--r--modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj45
-rw-r--r--modules/mono/editor/GodotSharpTools/GodotSharpTools.sln17
-rw-r--r--modules/mono/editor/GodotSharpTools/GodotSharpTools.userprefs14
-rw-r--r--modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs49
-rw-r--r--modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs216
-rw-r--r--modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs17
-rw-r--r--modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs27
-rw-r--r--modules/mono/editor/GodotSharpTools/StringExtensions.cs52
10 files changed, 830 insertions, 0 deletions
diff --git a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs
new file mode 100644
index 0000000000..256e64ddde
--- /dev/null
+++ b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs
@@ -0,0 +1,335 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Security;
+using Microsoft.Build.Framework;
+
+namespace GodotSharpTools.Build
+{
+ public class BuildInstance : IDisposable
+ {
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_BuildInstance_ExitCallback(string solution, string config, int exitCode);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static string godot_icall_BuildInstance_get_MSBuildPath();
+
+ private static string MSBuildPath
+ {
+ get { return godot_icall_BuildInstance_get_MSBuildPath(); }
+ }
+
+ private string solution;
+ private string config;
+
+ private Process process;
+
+ private int exitCode;
+ public int ExitCode { get { return exitCode; } }
+
+ public bool IsRunning { get { return process != null && !process.HasExited; } }
+
+ public BuildInstance(string solution, string config)
+ {
+ this.solution = solution;
+ this.config = config;
+ }
+
+ public bool Build(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null)
+ {
+ string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customProperties);
+
+ ProcessStartInfo startInfo = new ProcessStartInfo(MSBuildPath, compilerArgs);
+
+ // No console output, thanks
+ startInfo.RedirectStandardOutput = true;
+ startInfo.RedirectStandardError = true;
+ startInfo.UseShellExecute = false;
+
+ // Needed when running from Developer Command Prompt for VS
+ RemovePlatformVariable(startInfo.EnvironmentVariables);
+
+ using (Process process = new Process())
+ {
+ process.StartInfo = startInfo;
+
+ process.Start();
+
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ process.WaitForExit();
+
+ exitCode = process.ExitCode;
+ }
+
+ return true;
+ }
+
+ public bool BuildAsync(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null)
+ {
+ if (process != null)
+ throw new InvalidOperationException("Already in use");
+
+ string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customProperties);
+
+ ProcessStartInfo startInfo = new ProcessStartInfo("msbuild", compilerArgs);
+
+ // No console output, thanks
+ startInfo.RedirectStandardOutput = true;
+ startInfo.RedirectStandardError = true;
+ startInfo.UseShellExecute = false;
+
+ // Needed when running from Developer Command Prompt for VS
+ RemovePlatformVariable(startInfo.EnvironmentVariables);
+
+ process = new Process();
+ process.StartInfo = startInfo;
+ process.EnableRaisingEvents = true;
+ process.Exited += new EventHandler(BuildProcess_Exited);
+
+ process.Start();
+
+ return true;
+ }
+
+ private string BuildArguments(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties)
+ {
+ string arguments = string.Format("{0} /v:normal /t:Build /p:{1} /l:{2},{3};{4}",
+ solution,
+ "Configuration=" + config,
+ typeof(GodotBuildLogger).FullName,
+ loggerAssemblyPath,
+ loggerOutputDir
+ );
+
+ if (customProperties != null)
+ {
+ foreach (string customProperty in customProperties)
+ {
+ arguments += " /p:" + customProperty;
+ }
+ }
+
+ return arguments;
+ }
+
+ private void RemovePlatformVariable(StringDictionary environmentVariables)
+ {
+ // EnvironmentVariables is case sensitive? Seriously?
+
+ List<string> platformEnvironmentVariables = new List<string>();
+
+ foreach (string env in environmentVariables.Keys)
+ {
+ if (env.ToUpper() == "PLATFORM")
+ platformEnvironmentVariables.Add(env);
+ }
+
+ foreach (string env in platformEnvironmentVariables)
+ environmentVariables.Remove(env);
+ }
+
+ private void BuildProcess_Exited(object sender, System.EventArgs e)
+ {
+ exitCode = process.ExitCode;
+
+ godot_icall_BuildInstance_ExitCallback(solution, config, exitCode);
+
+ Dispose();
+ }
+
+ public void Dispose()
+ {
+ if (process != null)
+ {
+ process.Dispose();
+ process = null;
+ }
+ }
+ }
+
+ public class GodotBuildLogger : ILogger
+ {
+ public string Parameters { get; set; }
+ public LoggerVerbosity Verbosity { get; set; }
+
+ public void Initialize(IEventSource eventSource)
+ {
+ if (null == Parameters)
+ throw new LoggerException("Log directory was not set.");
+
+ string[] parameters = Parameters.Split(';');
+
+ string logDir = parameters[0];
+
+ if (String.IsNullOrEmpty(logDir))
+ throw new LoggerException("Log directory was not set.");
+
+ if (parameters.Length > 1)
+ throw new LoggerException("Too many parameters passed.");
+
+ string logFile = Path.Combine(logDir, "msbuild_log.txt");
+ string issuesFile = Path.Combine(logDir, "msbuild_issues.csv");
+
+ try
+ {
+ if (!Directory.Exists(logDir))
+ Directory.CreateDirectory(logDir);
+
+ this.logStreamWriter = new StreamWriter(logFile);
+ this.issuesStreamWriter = new StreamWriter(issuesFile);
+ }
+ catch (Exception ex)
+ {
+ if
+ (
+ ex is UnauthorizedAccessException
+ || ex is ArgumentNullException
+ || ex is PathTooLongException
+ || ex is DirectoryNotFoundException
+ || ex is NotSupportedException
+ || ex is ArgumentException
+ || ex is SecurityException
+ || ex is IOException
+ )
+ {
+ throw new LoggerException("Failed to create log file: " + ex.Message);
+ }
+ else
+ {
+ // Unexpected failure
+ throw;
+ }
+ }
+
+ eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted);
+ eventSource.TaskStarted += new TaskStartedEventHandler(eventSource_TaskStarted);
+ eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised);
+ eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised);
+ eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised);
+ eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished);
+ }
+
+ void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
+ {
+ string line = String.Format("{0}({1},{2}): error {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message);
+
+ if (e.ProjectFile.Length > 0)
+ line += string.Format(" [{0}]", e.ProjectFile);
+
+ WriteLine(line);
+
+ string errorLine = String.Format(@"error,{0},{1},{2},{3},{4},{5}",
+ e.File.CsvEscape(), e.LineNumber, e.ColumnNumber,
+ e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile.CsvEscape());
+ issuesStreamWriter.WriteLine(errorLine);
+ }
+
+ void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
+ {
+ string line = String.Format("{0}({1},{2}): warning {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, e.ProjectFile);
+
+ if (e.ProjectFile != null && e.ProjectFile.Length > 0)
+ line += string.Format(" [{0}]", e.ProjectFile);
+
+ WriteLine(line);
+
+ string warningLine = String.Format(@"warning,{0},{1},{2},{3},{4},{5}",
+ e.File.CsvEscape(), e.LineNumber, e.ColumnNumber,
+ e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty);
+ issuesStreamWriter.WriteLine(warningLine);
+ }
+
+ void eventSource_MessageRaised(object sender, BuildMessageEventArgs e)
+ {
+ // BuildMessageEventArgs adds Importance to BuildEventArgs
+ // Let's take account of the verbosity setting we've been passed in deciding whether to log the message
+ if ((e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal))
+ || (e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal))
+ || (e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed))
+ )
+ {
+ WriteLineWithSenderAndMessage(String.Empty, e);
+ }
+ }
+
+ void eventSource_TaskStarted(object sender, TaskStartedEventArgs e)
+ {
+ // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName
+ // To keep this log clean, this logger will ignore these events.
+ }
+
+ void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
+ {
+ WriteLine(e.Message);
+ indent++;
+ }
+
+ void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
+ {
+ indent--;
+ WriteLine(e.Message);
+ }
+
+ /// <summary>
+ /// Write a line to the log, adding the SenderName
+ /// </summary>
+ private void WriteLineWithSender(string line, BuildEventArgs e)
+ {
+ if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/))
+ {
+ // Well, if the sender name is MSBuild, let's leave it out for prettiness
+ WriteLine(line);
+ }
+ else
+ {
+ WriteLine(e.SenderName + ": " + line);
+ }
+ }
+
+ /// <summary>
+ /// Write a line to the log, adding the SenderName and Message
+ /// (these parameters are on all MSBuild event argument objects)
+ /// </summary>
+ private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e)
+ {
+ if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/))
+ {
+ // Well, if the sender name is MSBuild, let's leave it out for prettiness
+ WriteLine(line + e.Message);
+ }
+ else
+ {
+ WriteLine(e.SenderName + ": " + line + e.Message);
+ }
+ }
+
+ private void WriteLine(string line)
+ {
+ for (int i = indent; i > 0; i--)
+ {
+ logStreamWriter.Write("\t");
+ }
+ logStreamWriter.WriteLine(line);
+ }
+
+ public void Shutdown()
+ {
+ logStreamWriter.Close();
+ issuesStreamWriter.Close();
+ }
+
+ public bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity)
+ {
+ return this.Verbosity >= checkVerbosity;
+ }
+
+ private StreamWriter logStreamWriter;
+ private StreamWriter issuesStreamWriter;
+ private int indent;
+ }
+}
diff --git a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs b/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs
new file mode 100644
index 0000000000..303be3b732
--- /dev/null
+++ b/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs
@@ -0,0 +1,58 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace GodotSharpTools.Editor
+{
+ public class MonoDevelopInstance
+ {
+ private Process process;
+ private string solutionFile;
+
+ public void Execute(string[] files)
+ {
+ bool newWindow = process == null || process.HasExited;
+
+ List<string> args = new List<string>();
+
+ args.Add("--ipc-tcp");
+
+ if (newWindow)
+ args.Add("\"" + Path.GetFullPath(solutionFile) + "\"");
+
+ foreach (var file in files)
+ {
+ int semicolonIndex = file.IndexOf(';');
+
+ string filePath = semicolonIndex < 0 ? file : file.Substring(0, semicolonIndex);
+ string cursor = semicolonIndex < 0 ? string.Empty : file.Substring(semicolonIndex);
+
+ args.Add("\"" + Path.GetFullPath(filePath.NormalizePath()) + cursor + "\"");
+ }
+
+ if (newWindow)
+ {
+ ProcessStartInfo startInfo = new ProcessStartInfo(MonoDevelopFile, string.Join(" ", args));
+ process = Process.Start(startInfo);
+ }
+ else
+ {
+ Process.Start(MonoDevelopFile, string.Join(" ", args));
+ }
+ }
+
+ public MonoDevelopInstance(string solutionFile)
+ {
+ this.solutionFile = solutionFile;
+ }
+
+ private static string MonoDevelopFile
+ {
+ get
+ {
+ return "monodevelop";
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj b/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj
new file mode 100644
index 0000000000..981083a3c2
--- /dev/null
+++ b/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <RootNamespace>GodotSharpTools</RootNamespace>
+ <AssemblyName>GodotSharpTools</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG;</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>full</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="Microsoft.Build" />
+ <Reference Include="Microsoft.Build.Framework" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="StringExtensions.cs" />
+ <Compile Include="Build\BuildSystem.cs" />
+ <Compile Include="Editor\MonoDevelopInstance.cs" />
+ <Compile Include="Project\ProjectExtensions.cs" />
+ <Compile Include="Project\ProjectGenerator.cs" />
+ <Compile Include="Project\ProjectUtils.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln b/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln
new file mode 100644
index 0000000000..7eabcdff5d
--- /dev/null
+++ b/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln
@@ -0,0 +1,17 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharpTools", "GodotSharpTools.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.userprefs b/modules/mono/editor/GodotSharpTools/GodotSharpTools.userprefs
new file mode 100644
index 0000000000..0cbafdc20d
--- /dev/null
+++ b/modules/mono/editor/GodotSharpTools/GodotSharpTools.userprefs
@@ -0,0 +1,14 @@
+<Properties StartupItem="GodotSharpTools.csproj">
+ <MonoDevelop.Ide.Workspace ActiveConfiguration="Debug" />
+ <MonoDevelop.Ide.Workbench ActiveDocument="Build/BuildSystem.cs">
+ <Files>
+ <File FileName="Build/ProjectExtensions.cs" Line="1" Column="1" />
+ <File FileName="Build/ProjectGenerator.cs" Line="1" Column="1" />
+ <File FileName="Build/BuildSystem.cs" Line="37" Column="14" />
+ </Files>
+ </MonoDevelop.Ide.Workbench>
+ <MonoDevelop.Ide.DebuggingService.Breakpoints>
+ <BreakpointStore />
+ </MonoDevelop.Ide.DebuggingService.Breakpoints>
+ <MonoDevelop.Ide.DebuggingService.PinnedWatches />
+</Properties> \ No newline at end of file
diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs
new file mode 100644
index 0000000000..6a97731539
--- /dev/null
+++ b/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs
@@ -0,0 +1,49 @@
+using System;
+using Microsoft.Build.Construction;
+
+namespace GodotSharpTools.Project
+{
+ public static class ProjectExtensions
+ {
+ public static bool HasItem(this ProjectRootElement root, string itemType, string include)
+ {
+ string includeNormalized = include.NormalizePath();
+
+ foreach (var itemGroup in root.ItemGroups)
+ {
+ if (itemGroup.Condition.Length != 0)
+ continue;
+
+ foreach (var item in itemGroup.Items)
+ {
+ if (item.ItemType == itemType)
+ {
+ if (item.Include.NormalizePath() == includeNormalized)
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public static void AddItemChecked(this ProjectRootElement root, string itemType, string include)
+ {
+ if (!root.HasItem(itemType, include))
+ {
+ root.AddItem(itemType, include);
+ }
+ }
+
+ public static Guid GetGuid(this ProjectRootElement root)
+ {
+ foreach (var property in root.Properties)
+ {
+ if (property.Name == "ProjectGuid")
+ return Guid.Parse(property.Value);
+ }
+
+ return Guid.Empty;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs
new file mode 100644
index 0000000000..6bf54a0156
--- /dev/null
+++ b/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs
@@ -0,0 +1,216 @@
+using System;
+using System.IO;
+using Microsoft.Build.Construction;
+
+namespace GodotSharpTools.Project
+{
+ public static class ProjectGenerator
+ {
+ public static string GenCoreApiProject(string dir, string[] compileItems)
+ {
+ string path = Path.Combine(dir, CoreApiProject + ".csproj");
+
+ ProjectPropertyGroupElement mainGroup;
+ var root = CreateLibraryProject(CoreApiProject, out mainGroup);
+
+ mainGroup.AddProperty("DocumentationFile", Path.Combine("$(OutputPath)", "$(AssemblyName).xml"));
+ mainGroup.SetProperty("RootNamespace", "Godot");
+
+ GenAssemblyInfoFile(root, dir, CoreApiProject,
+ new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProject + "\")]" },
+ new string[] { "System.Runtime.CompilerServices" });
+
+ foreach (var item in compileItems)
+ {
+ root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\"));
+ }
+
+ root.Save(path);
+
+ return root.GetGuid().ToString().ToUpper();
+ }
+
+ public static string GenEditorApiProject(string dir, string coreApiHintPath, string[] compileItems)
+ {
+ string path = Path.Combine(dir, EditorApiProject + ".csproj");
+
+ ProjectPropertyGroupElement mainGroup;
+ var root = CreateLibraryProject(EditorApiProject, out mainGroup);
+
+ mainGroup.AddProperty("DocumentationFile", Path.Combine("$(OutputPath)", "$(AssemblyName).xml"));
+ mainGroup.SetProperty("RootNamespace", "Godot");
+
+ GenAssemblyInfoFile(root, dir, EditorApiProject);
+
+ foreach (var item in compileItems)
+ {
+ root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\"));
+ }
+
+ var coreApiRef = root.AddItem("Reference", CoreApiProject);
+ coreApiRef.AddMetadata("HintPath", coreApiHintPath);
+ coreApiRef.AddMetadata("Private", "False");
+
+ root.Save(path);
+
+ return root.GetGuid().ToString().ToUpper();
+ }
+
+ public static string GenGameProject(string dir, string name, string[] compileItems)
+ {
+ string path = Path.Combine(dir, name + ".csproj");
+
+ ProjectPropertyGroupElement mainGroup;
+ var root = CreateLibraryProject(name, out mainGroup);
+
+ mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)"));
+ mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj"));
+ mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)"));
+
+ var toolsGroup = root.AddPropertyGroup();
+ toolsGroup.Condition = " '$(Configuration)|$(Platform)' == 'Tools|AnyCPU' ";
+ toolsGroup.AddProperty("DebugSymbols", "true");
+ toolsGroup.AddProperty("DebugType", "full");
+ toolsGroup.AddProperty("Optimize", "false");
+ toolsGroup.AddProperty("DefineConstants", "DEBUG;TOOLS;");
+ toolsGroup.AddProperty("ErrorReport", "prompt");
+ toolsGroup.AddProperty("WarningLevel", "4");
+ toolsGroup.AddProperty("ConsolePause", "false");
+
+ var coreApiRef = root.AddItem("Reference", CoreApiProject);
+ coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProject + ".dll"));
+ coreApiRef.AddMetadata("Private", "False");
+
+ var editorApiRef = root.AddItem("Reference", EditorApiProject);
+ editorApiRef.Condition = " '$(Configuration)' == 'Tools' ";
+ editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProject + ".dll"));
+ editorApiRef.AddMetadata("Private", "False");
+
+ GenAssemblyInfoFile(root, dir, name);
+
+ foreach (var item in compileItems)
+ {
+ root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\"));
+ }
+
+ root.Save(path);
+
+ return root.GetGuid().ToString().ToUpper();
+ }
+
+ public static void GenAssemblyInfoFile(ProjectRootElement root, string dir, string name, string[] assemblyLines = null, string[] usingDirectives = null)
+ {
+
+ string propertiesDir = Path.Combine(dir, "Properties");
+ if (!Directory.Exists(propertiesDir))
+ Directory.CreateDirectory(propertiesDir);
+
+ string usingDirectivesText = string.Empty;
+
+ if (usingDirectives != null)
+ {
+ foreach (var usingDirective in usingDirectives)
+ usingDirectivesText += "\nusing " + usingDirective + ";";
+ }
+
+ string assemblyLinesText = string.Empty;
+
+ if (assemblyLines != null)
+ {
+ foreach (var assemblyLine in assemblyLines)
+ assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
+ }
+
+ string content = string.Format(assemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
+
+ string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs");
+
+ File.WriteAllText(assemblyInfoFile, content);
+
+ root.AddItem("Compile", assemblyInfoFile.RelativeToPath(dir).Replace("/", "\\"));
+ }
+
+ public static ProjectRootElement CreateLibraryProject(string name, out ProjectPropertyGroupElement mainGroup)
+ {
+ var root = ProjectRootElement.Create();
+ root.DefaultTargets = "Build";
+
+ mainGroup = root.AddPropertyGroup();
+ mainGroup.AddProperty("Configuration", "Debug").Condition = " '$(Configuration)' == '' ";
+ mainGroup.AddProperty("Platform", "AnyCPU").Condition = " '$(Platform)' == '' ";
+ mainGroup.AddProperty("ProjectGuid", "{" + Guid.NewGuid().ToString().ToUpper() + "}");
+ mainGroup.AddProperty("OutputType", "Library");
+ mainGroup.AddProperty("OutputPath", Path.Combine("bin", "$(Configuration)"));
+ mainGroup.AddProperty("RootNamespace", name);
+ mainGroup.AddProperty("AssemblyName", name);
+ mainGroup.AddProperty("TargetFrameworkVersion", "v4.5");
+
+ var debugGroup = root.AddPropertyGroup();
+ debugGroup.Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ";
+ debugGroup.AddProperty("DebugSymbols", "true");
+ debugGroup.AddProperty("DebugType", "full");
+ debugGroup.AddProperty("Optimize", "false");
+ debugGroup.AddProperty("DefineConstants", "DEBUG;");
+ debugGroup.AddProperty("ErrorReport", "prompt");
+ debugGroup.AddProperty("WarningLevel", "4");
+ debugGroup.AddProperty("ConsolePause", "false");
+
+ var releaseGroup = root.AddPropertyGroup();
+ releaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ";
+ releaseGroup.AddProperty("DebugType", "full");
+ releaseGroup.AddProperty("Optimize", "true");
+ releaseGroup.AddProperty("ErrorReport", "prompt");
+ releaseGroup.AddProperty("WarningLevel", "4");
+ releaseGroup.AddProperty("ConsolePause", "false");
+
+ // References
+ var referenceGroup = root.AddItemGroup();
+ referenceGroup.AddItem("Reference", "System");
+
+ root.AddImport(Path.Combine("$(MSBuildBinPath)", "Microsoft.CSharp.targets").Replace("/", "\\"));
+
+ return root;
+ }
+
+ private static void AddItems(ProjectRootElement elem, string groupName, params string[] items)
+ {
+ var group = elem.AddItemGroup();
+
+ foreach (var item in items)
+ {
+ group.AddItem(groupName, item);
+ }
+ }
+
+ public const string CoreApiProject = "GodotSharp";
+ public const string EditorApiProject = "GodotSharpEditor";
+
+ private const string assemblyInfoTemplate =
+@"using System.Reflection;{0}
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle(""{1}"")]
+[assembly: AssemblyDescription("""")]
+[assembly: AssemblyConfiguration("""")]
+[assembly: AssemblyCompany("""")]
+[assembly: AssemblyProduct("""")]
+[assembly: AssemblyCopyright("""")]
+[assembly: AssemblyTrademark("""")]
+[assembly: AssemblyCulture("""")]
+
+// The assembly version has the format ""{{Major}}.{{Minor}}.{{Build}}.{{Revision}}"".
+// The form ""{{Major}}.{{Minor}}.*"" will automatically update the build and revision,
+// and ""{{Major}}.{{Minor}}.{{Build}}.*"" will update just the revision.
+
+[assembly: AssemblyVersion(""1.0.*"")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("""")]
+{2}";
+ }
+}
diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs
new file mode 100644
index 0000000000..a50b4fb064
--- /dev/null
+++ b/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs
@@ -0,0 +1,17 @@
+using System;
+using System.IO;
+using Microsoft.Build.Construction;
+
+namespace GodotSharpTools.Project
+{
+ public static class ProjectUtils
+ {
+ public static void AddItemToProjectChecked(string projectPath, string itemType, string include)
+ {
+ var dir = Directory.GetParent(projectPath).FullName;
+ var root = ProjectRootElement.Open(projectPath);
+ root.AddItemChecked(itemType, include.RelativeToPath(dir).Replace("/", "\\"));
+ root.Save();
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..7115d8fc71
--- /dev/null
+++ b/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs
@@ -0,0 +1,27 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("GodotSharpTools")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("ignacio")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
diff --git a/modules/mono/editor/GodotSharpTools/StringExtensions.cs b/modules/mono/editor/GodotSharpTools/StringExtensions.cs
new file mode 100644
index 0000000000..b66c86f8ce
--- /dev/null
+++ b/modules/mono/editor/GodotSharpTools/StringExtensions.cs
@@ -0,0 +1,52 @@
+using System;
+using System.IO;
+
+namespace GodotSharpTools
+{
+ public static class StringExtensions
+ {
+ public static string RelativeToPath(this string path, string dir)
+ {
+ // Make sure the directory ends with a path separator
+ dir = Path.Combine(dir, " ").TrimEnd();
+
+ if (Path.DirectorySeparatorChar == '\\')
+ dir = dir.Replace("/", "\\") + "\\";
+
+ Uri fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute);
+ Uri relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute);
+
+ return relRoot.MakeRelativeUri(fullPath).ToString();
+ }
+
+ public static string NormalizePath(this string path)
+ {
+ bool rooted = path.IsAbsolutePath();
+
+ path = path.Replace('\\', '/');
+
+ string[] parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
+
+ path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim();
+
+ return rooted ? Path.DirectorySeparatorChar.ToString() + path : path;
+ }
+
+ private static readonly string driveRoot = Path.GetPathRoot(Environment.CurrentDirectory);
+
+ public static bool IsAbsolutePath(this string path)
+ {
+ return path.StartsWith("/") || path.StartsWith("\\") || path.StartsWith(driveRoot);
+ }
+
+ public static string CsvEscape(this string value, char delimiter = ',')
+ {
+ bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1;
+
+ if (hasSpecialChar)
+ return "\"" + value.Replace("\"", "\"\"") + "\"";
+
+ return value;
+ }
+ }
+}