Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
BuildScript.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.CodeDom.Compiler;
5 using System.Collections.Generic;
6 using System.IO;
7 using System.Linq;
8 using System.Reflection;
9 using System.Text.RegularExpressions;
10 
11 using Microsoft.CSharp;
12 
13 namespace SiliconStudio.BuildEngine
14 {
15  public class BuildScript
16  {
17  /// <summary>
18  /// Indicate the location of this <see cref="BuildScript"/>.
19  /// </summary>
20  public string ScriptPath { get; private set; }
21 
22  /// <summary>
23  /// Indicate source base directory, which is used as working directory for the Build Engine. The path of every file accessed by the build script must be relative to this directory.
24  /// </summary>
25  public string SourceBaseDirectory { get; private set; }
26 
27  /// <summary>
28  /// List of every source folders used in this script, relative to the <see cref="SourceBaseDirectory"/>. The key describe the variable name, and the value is the relative path.
29  /// </summary>
30  public IEnumerable<KeyValuePair<string, string>> SourceFolders { get; private set; }
31 
32  /// <summary>
33  /// Indicate the build directory. This is where the Build Engine will write its cache information as well as the asset database (if used and if <see cref="OutputDirectory"/> is null)
34  /// </summary>
35  public string BuildDirectory { get; private set; }
36 
37  /// <summary>
38  /// Indicate the output directory. This is where the Build Engine will output the asset database files. If null, <see cref="BuildDirectory"/> value is used.
39  /// </summary>
40  public string OutputDirectory { get; private set; }
41 
42  /// <summary>
43  /// Indicate wheither the BuildScript generated errors while it was compiled or loaded
44  /// </summary>
45  public string MetadataDatabaseDirectory { get; private set; }
46 
47  /// <summary>
48  /// Indicate wheither the BuildScript generated errors while it was compiled
49  /// </summary>
50  public bool HasErrors { get { return GetErrors().Any(); } }
51 
52  /// <summary>
53  /// Indicate wheither the BuildScript has been compiled
54  /// </summary>
55  public bool IsCompiled { get; private set; }
56 
57  private string source;
58  private CompilerResults compilerResult;
59  private readonly List<string> parsingErrors = new List<string>();
60  private readonly List<string> parsingWarnings = new List<string>();
61  private readonly BuildParameterCollection parameters = new BuildParameterCollection();
62 
63  private BuildScript(string paradoxSdkDir)
64  {
65  SourceFolders = new Dictionary<string, string>();
66  ((Dictionary<string, string>)SourceFolders).Add("ParadoxSdkDir", paradoxSdkDir);
67  }
68 
69  public static BuildScript LoadFromFile(string paradoxSdkDir, string filePath)
70  {
71  var script = new BuildScript(paradoxSdkDir) { ScriptPath = filePath, source = File.ReadAllText(filePath) };
72  return script;
73  }
74 
75  private static string StripQuotes(string str)
76  {
77  if (str.StartsWith("\"") && str.EndsWith("\"") && str.Length >= 2)
78  return str.Substring(1, str.Length - 2);
79  return str;
80  }
81 
82  private void ParseParameters()
83  {
84  // Simple parameters, following this syntax: // #[Name] [Value]
85  var parameterRegex = new Regex(@"^//\s*#(\w+)\s+(""[^""]+?""|[\w.\-/\\]+)\s*$", RegexOptions.Multiline);
86  foreach (Match match in parameterRegex.Matches(source))
87  {
88  parameters.Add(StripQuotes(match.Groups[1].Value), StripQuotes(match.Groups[2].Value));
89  }
90  // Key-Value parameters, following this syntax: // #[Name] [Key] [Value]
91  parameterRegex = new Regex(@"^//\s*#(\w+)\s+(""[^""]+?""|[\w.\-/\\]+)\s+(""[^""]+?""|[\w.\-/\\]+)\s*$", RegexOptions.Multiline);
92  foreach (Match match in parameterRegex.Matches(source))
93  {
94  parameters.Add(StripQuotes(match.Groups[1].Value) + "Key", StripQuotes(match.Groups[2].Value));
95  parameters.Add(StripQuotes(match.Groups[1].Value) + "Value", StripQuotes(match.Groups[3].Value));
96  }
97  }
98 
99  public bool Compile(PluginResolver pluginResolver)
100  {
101  // Prepare compilation of C# makefile
102  var assemblyLocations = new List<string> {
103  "mscorlib.dll",
104  "System.dll",
105  "System.Core.dll",
106  typeof(Command).Assembly.Location, // Add BuildTool.Shared by default
107  };
108 
109  ParseParameters();
110 
111  foreach (string additionalPluginDirectories in parameters.GetRange("PluginDirectory"))
112  {
113  pluginResolver.AddPluginFolder(Path.Combine(Path.GetDirectoryName(ScriptPath) ?? "", additionalPluginDirectories));
114  }
115 
116  foreach (string assemblyLocation in parameters.GetRange("Assembly"))
117  {
118  // 1. Try to find relative to source location first
119  var testAssemblyLocation = Path.Combine(Path.GetDirectoryName(ScriptPath) ?? "", assemblyLocation);
120 
121  if (!File.Exists(testAssemblyLocation))
122  {
123  // 2. Try to find in plugin folders
124  testAssemblyLocation = pluginResolver.FindAssembly(Path.GetFileName(assemblyLocation));
125 
126  if (!File.Exists(testAssemblyLocation))
127  {
128  // 3. Try to find in current assembly directory
129  testAssemblyLocation = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "", Path.GetFileName(assemblyLocation) ?? "");
130 
131  if (!File.Exists(testAssemblyLocation))
132  parsingErrors.Add(string.Format("Could not find assembly {0}", assemblyLocation));
133  }
134  }
135  assemblyLocations.Add(testAssemblyLocation);
136  }
137 
138  // SourceBaseDirectory - optional
139  if (parameters.ContainsSingle("SourceBaseDirectory"))
140  SourceBaseDirectory = parameters.GetSingle("SourceBaseDirectory");
141  else if (parameters.ContainsMultiple("BuildDirectory"))
142  parsingErrors.Add("Source base directory defined multiple times.");
143  else
144  parsingWarnings.Add("Source base directory not defined.");
145 
146  // BuildDirectory - mandatory
147  if (parameters.ContainsSingle("BuildDirectory"))
148  BuildDirectory = parameters.GetSingle("BuildDirectory");
149  else if (parameters.ContainsMultiple("BuildDirectory"))
150  parsingErrors.Add("Build directory defined multiple times.");
151  else
152  parsingErrors.Add("Build directory not defined.");
153 
154  // OutputDirectory - optional
155  if (parameters.ContainsSingle("OutputDirectory"))
156  OutputDirectory = parameters.GetSingle("OutputDirectory");
157  else if (parameters.ContainsMultiple("OutputDirectory"))
158  parsingErrors.Add("Output directory defined multiple times.");
159  else
160  parsingWarnings.Add("Output directory not defined.");
161 
162  // MetadataDatabaseDirectory - optional
163  if (parameters.ContainsSingle("MetadataDatabaseDirectory"))
164  MetadataDatabaseDirectory = parameters.GetSingle("MetadataDatabaseDirectory");
165  else if (parameters.ContainsMultiple("MetadataDatabaseDirectory"))
166  parsingErrors.Add("Metadata database directory defined multiple times.");
167  else
168  parsingWarnings.Add("Metadata database not defined.");
169 
170  var sourceFolderKeys = parameters.GetRange("SourceFolderKey").ToArray();
171  var sourceFolderValue = parameters.GetRange("SourceFolderValue").ToArray();
172 
173  for (int i = 0; i < sourceFolderKeys.Length; ++i)
174  {
175  ((Dictionary<string, string>)SourceFolders).Add(sourceFolderKeys[i], sourceFolderValue[i]);
176  }
177 
178  if (HasErrors)
179  {
180  return false;
181  }
182 
183  // Compile C# makefile
184  var csc = new CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v4.0" } });
185  var compileParams = new CompilerParameters(assemblyLocations.ToArray()) { GenerateInMemory = true, IncludeDebugInformation = true };
186  compilerResult = csc.CompileAssemblyFromFile(compileParams, ScriptPath);
187 
188  IsCompiled = !HasErrors;
189  return IsCompiled;
190  }
191 
193  {
194  IEnumerable<string> errors = parsingErrors;
195  if (compilerResult != null)
196  {
197  errors = errors.Concat(compilerResult.Errors.Cast<CompilerError>().Where(x => !x.IsWarning).Select(x => x.FileName + "(" + x.Line + "): " + x.ErrorText));
198  }
199  return errors;
200  }
201 
203  {
204  IEnumerable<string> warnings = parsingWarnings;
205  if (compilerResult != null)
206  {
207  warnings = warnings.Concat(compilerResult.Errors.Cast<CompilerError>().Where(x => x.IsWarning).Select(x => x.FileName + "(" + x.Line + "): " + x.ErrorText));
208  }
209  return warnings;
210  }
211 
212  public void Execute(Builder builder)
213  {
214  // Execute the command C# makefile
215  Type type = compilerResult.CompiledAssembly.GetType("BuildScript");
216  object makefile = Activator.CreateInstance(type);
217  MethodInfo executeMethod = type.GetMethod("Execute", BindingFlags.Public | BindingFlags.Instance);
218  executeMethod.Invoke(makefile, new object[] { builder, builder.Root });
219  }
220 
221  public void Execute(ListBuildStep root)
222  {
223  // Execute the command C# makefile
224  Type type = compilerResult.CompiledAssembly.GetType("BuildScript");
225  if (type != null)
226  {
227  object makefile = Activator.CreateInstance(type);
228  MethodInfo executeMethod = type.GetMethod("Execute", BindingFlags.Public | BindingFlags.Instance);
229  executeMethod.Invoke(makefile, new object[] { null, root });
230  }
231  }
232  }
233 }
static BuildScript LoadFromFile(string paradoxSdkDir, string filePath)
Definition: BuildScript.cs:69
IEnumerable< string > GetWarnings()
Definition: BuildScript.cs:202
System.IO.File File
IEnumerable< string > GetErrors()
Definition: BuildScript.cs:192
void Execute(ListBuildStep root)
Definition: BuildScript.cs:221
bool Compile(PluginResolver pluginResolver)
Definition: BuildScript.cs:99