(ded4a3e0a) v0.9.0.7
This commit is contained in:
@@ -0,0 +1,586 @@
|
||||
// MonoGame - Copyright (C) The MonoGame Team
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// file 'LICENSE.txt', which is part of this source code package.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using MGCB;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using PathHelper = MonoGame.Framework.Content.Pipeline.Builder.PathHelper;
|
||||
|
||||
namespace MonoGame.Tools.Pipeline
|
||||
{
|
||||
public class PipelineProjectParser
|
||||
{
|
||||
#region Other Data
|
||||
|
||||
private readonly PipelineProject _project;
|
||||
private readonly IContentItemObserver _observer;
|
||||
private readonly OpaqueDataDictionary _processorParams = new OpaqueDataDictionary();
|
||||
|
||||
private string _processor;
|
||||
|
||||
#endregion
|
||||
|
||||
#region CommandLineParameters
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "outputDir",
|
||||
ValueName = "directoryPath",
|
||||
Description = "The directory where all content is written.")]
|
||||
public string OutputDir { set { _project.OutputDir = value; } }
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "intermediateDir",
|
||||
ValueName = "directoryPath",
|
||||
Description = "The directory where all intermediate files are written.")]
|
||||
public string IntermediateDir { set { _project.IntermediateDir = value; } }
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "reference",
|
||||
ValueName = "assemblyNameOrFile",
|
||||
Description = "Adds an assembly reference for resolving content importers, processors, and writers.")]
|
||||
public List<string> References
|
||||
{
|
||||
set { _project.References = value; }
|
||||
get { return _project.References; }
|
||||
}
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "platform",
|
||||
ValueName = "targetPlatform",
|
||||
Description = "Set the target platform for this build. Defaults to Windows.")]
|
||||
public TargetPlatform Platform { set { _project.Platform = value; } }
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "profile",
|
||||
ValueName = "graphicsProfile",
|
||||
Description = "Set the target graphics profile for this build. Defaults to HiDef.")]
|
||||
public GraphicsProfile Profile { set { _project.Profile = value; } }
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "config",
|
||||
ValueName = "string",
|
||||
Description = "The optional build config string from the build system.")]
|
||||
public string Config { set { _project.Config = value; } }
|
||||
|
||||
#pragma warning disable 414
|
||||
|
||||
// Allow a MGCB file containing the /rebuild parameter to be imported without error
|
||||
[CommandLineParameter(
|
||||
Name = "rebuild",
|
||||
ValueName = "bool",
|
||||
Description = "Forces a rebuild of the project.")]
|
||||
public bool Rebuild { set { _rebuild = value; } }
|
||||
private bool _rebuild;
|
||||
|
||||
// Allow a MGCB file containing the /clean parameter to be imported without error
|
||||
[CommandLineParameter(
|
||||
Name = "clean",
|
||||
ValueName = "bool",
|
||||
Description = "Removes intermediate and output files.")]
|
||||
public bool Clean { set { _clean = value; } }
|
||||
private bool _clean;
|
||||
|
||||
#pragma warning restore 414
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "compress",
|
||||
ValueName = "bool",
|
||||
Description = "Content files can be compressed for smaller file sizes.")]
|
||||
public bool Compress { set { _project.Compress = value; } }
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "importer",
|
||||
ValueName = "className",
|
||||
Description = "Defines the class name of the content importer for reading source content.")]
|
||||
public string Importer;
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "processor",
|
||||
ValueName = "className",
|
||||
Description = "Defines the class name of the content processor for processing imported content.")]
|
||||
public string Processor
|
||||
{
|
||||
get { return _processor; }
|
||||
set
|
||||
{
|
||||
_processor = value;
|
||||
_processorParams.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "processorParam",
|
||||
ValueName = "name=value",
|
||||
Description = "Defines a parameter name and value to set on a content processor.")]
|
||||
public void AddProcessorParam(string nameAndValue)
|
||||
{
|
||||
var keyAndValue = nameAndValue.Split('=');
|
||||
if (keyAndValue.Length != 2)
|
||||
{
|
||||
// Do we error out or something?
|
||||
return;
|
||||
}
|
||||
|
||||
_processorParams.Remove(keyAndValue[0]);
|
||||
_processorParams.Add(keyAndValue[0], keyAndValue[1]);
|
||||
}
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "build",
|
||||
ValueName = "sourceFile",
|
||||
Description = "Build the content source file using the previously set switches and options.")]
|
||||
public void OnBuild(string sourceFile)
|
||||
{
|
||||
AddContent(sourceFile, false);
|
||||
}
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "launchDebugger",
|
||||
ValueName = "sourceFile")]
|
||||
public void OnDebug()
|
||||
{
|
||||
_project.LaunchDebugger = true;
|
||||
}
|
||||
|
||||
public bool AddContent(string sourceFile, bool skipDuplicates)
|
||||
{
|
||||
string link = null;
|
||||
|
||||
if (sourceFile.Contains(";"))
|
||||
{
|
||||
var split = sourceFile.Split(';');
|
||||
sourceFile = split[0];
|
||||
|
||||
if (split.Length > 0)
|
||||
link = split[1];
|
||||
}
|
||||
|
||||
// Make sure the source file is relative to the project.
|
||||
var projectDir = ProjectDirectory + Path.DirectorySeparatorChar;
|
||||
|
||||
sourceFile = PathHelper.GetRelativePath(projectDir, sourceFile);
|
||||
|
||||
// Do we have a duplicate?
|
||||
var previous = _project.ContentItems.FindIndex(e => string.Equals(e.OriginalPath, sourceFile, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (previous != -1)
|
||||
{
|
||||
if (skipDuplicates)
|
||||
return false;
|
||||
|
||||
// Replace the duplicate.
|
||||
_project.ContentItems.RemoveAt(previous);
|
||||
}
|
||||
|
||||
// Create the item for processing later.
|
||||
var item = new ContentItem
|
||||
{
|
||||
Observer = _observer,
|
||||
BuildAction = BuildAction.Build,
|
||||
OriginalPath = sourceFile,
|
||||
DestinationPath = string.IsNullOrEmpty(link) ? sourceFile : link,
|
||||
ImporterName = Importer,
|
||||
ProcessorName = Processor,
|
||||
ProcessorParams = new OpaqueDataDictionary()
|
||||
};
|
||||
_project.ContentItems.Add(item);
|
||||
|
||||
// Copy the current processor parameters blind as we
|
||||
// will validate and remove invalid parameters during
|
||||
// the build process later.
|
||||
foreach (var pair in _processorParams)
|
||||
item.ProcessorParams.Add(pair.Key, pair.Value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[CommandLineParameter(
|
||||
Name = "copy",
|
||||
ValueName = "sourceFile",
|
||||
Description = "Copy the content source file verbatim to the output directory.")]
|
||||
public void OnCopy(string sourceFile)
|
||||
{
|
||||
string link = null;
|
||||
|
||||
if (sourceFile.Contains(";"))
|
||||
{
|
||||
var split = sourceFile.Split(';');
|
||||
sourceFile = split[0];
|
||||
|
||||
if (split.Length > 0)
|
||||
link = split[1];
|
||||
}
|
||||
|
||||
// Make sure the source file is relative to the project.
|
||||
var projectDir = ProjectDirectory + Path.DirectorySeparatorChar;
|
||||
|
||||
sourceFile = PathHelper.GetRelativePath(projectDir, sourceFile);
|
||||
|
||||
// Remove duplicates... keep this new one.
|
||||
var previous = _project.ContentItems.FirstOrDefault(e => e.OriginalPath.Equals(sourceFile));
|
||||
if (previous != null)
|
||||
_project.ContentItems.Remove(previous);
|
||||
|
||||
// Create the item for processing later.
|
||||
var item = new ContentItem
|
||||
{
|
||||
BuildAction = BuildAction.Copy,
|
||||
OriginalPath = sourceFile,
|
||||
DestinationPath = string.IsNullOrEmpty(link) ? sourceFile : link,
|
||||
ProcessorParams = new OpaqueDataDictionary()
|
||||
};
|
||||
_project.ContentItems.Add(item);
|
||||
|
||||
// Copy the current processor parameters blind as we
|
||||
// will validate and remove invalid parameters during
|
||||
// the build process later.
|
||||
foreach (var pair in _processorParams)
|
||||
item.ProcessorParams.Add(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public PipelineProjectParser(IContentItemObserver observer, PipelineProject project)
|
||||
{
|
||||
_observer = observer;
|
||||
_project = project;
|
||||
}
|
||||
|
||||
public void OpenProject(string projectFilePath, MGBuildParser.ErrorCallback errorCallback)
|
||||
{
|
||||
_project.ContentItems.Clear();
|
||||
|
||||
// Store the file name for saving later.
|
||||
_project.OriginalPath = projectFilePath;
|
||||
|
||||
var parser = new MGBuildParser(this);
|
||||
parser.Title = "Pipeline";
|
||||
|
||||
if (errorCallback != null)
|
||||
parser.OnError += errorCallback;
|
||||
|
||||
var commands = new string[]
|
||||
{
|
||||
string.Format("/@:{0}", projectFilePath),
|
||||
};
|
||||
parser.Parse(commands);
|
||||
}
|
||||
|
||||
public void SaveProject()
|
||||
{
|
||||
using (var io = File.CreateText(_project.OriginalPath))
|
||||
SaveProject(io, null);
|
||||
}
|
||||
|
||||
public void SaveProject(TextWriter io, Func<ContentItem, bool> filterItem)
|
||||
{
|
||||
const string lineFormat = "/{0}:{1}";
|
||||
const string processorParamFormat = "{0}={1}";
|
||||
string line;
|
||||
|
||||
line = FormatDivider("Global Properties");
|
||||
io.WriteLine(line);
|
||||
|
||||
line = string.Format(lineFormat, "outputDir", _project.OutputDir);
|
||||
io.WriteLine(line);
|
||||
|
||||
line = string.Format(lineFormat, "intermediateDir", _project.IntermediateDir);
|
||||
io.WriteLine(line);
|
||||
|
||||
line = string.Format(lineFormat, "platform", _project.Platform);
|
||||
io.WriteLine(line);
|
||||
|
||||
line = string.Format(lineFormat, "config", _project.Config);
|
||||
io.WriteLine(line);
|
||||
|
||||
line = string.Format(lineFormat, "profile", _project.Profile);
|
||||
io.WriteLine(line);
|
||||
|
||||
line = string.Format(lineFormat, "compress", _project.Compress);
|
||||
io.WriteLine(line);
|
||||
|
||||
if (_project.LaunchDebugger)
|
||||
io.WriteLine("/launchdebugger");
|
||||
|
||||
line = FormatDivider("References");
|
||||
io.WriteLine(line);
|
||||
|
||||
foreach (var i in _project.References)
|
||||
{
|
||||
line = string.Format(lineFormat, "reference", i);
|
||||
io.WriteLine(line);
|
||||
}
|
||||
|
||||
line = FormatDivider("Content");
|
||||
io.WriteLine(line);
|
||||
|
||||
// Sort the items alphabetically to ensure a consistent output
|
||||
// and better mergability of the resulting MGCB file.
|
||||
var sortedItems = _project.ContentItems.OrderBy(c => c.OriginalPath, StringComparer.InvariantCulture);
|
||||
|
||||
foreach (var i in sortedItems)
|
||||
{
|
||||
// Reject any items that don't pass the filter.
|
||||
if (filterItem != null && filterItem(i))
|
||||
continue;
|
||||
|
||||
// Wrap content item lines with a begin comment line
|
||||
// to make them more cohesive (for version control).
|
||||
line = string.Format("#begin {0}", i.OriginalPath);
|
||||
io.WriteLine(line);
|
||||
|
||||
if (i.BuildAction == BuildAction.Copy)
|
||||
{
|
||||
string path = i.OriginalPath;
|
||||
if (i.OriginalPath != i.DestinationPath)
|
||||
path += ";" + i.DestinationPath;
|
||||
line = string.Format(lineFormat, "copy", path);
|
||||
io.WriteLine(line);
|
||||
io.WriteLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Write importer.
|
||||
{
|
||||
line = string.Format(lineFormat, "importer", i.ImporterName);
|
||||
io.WriteLine(line);
|
||||
}
|
||||
|
||||
// Write processor.
|
||||
{
|
||||
line = string.Format(lineFormat, "processor", i.ProcessorName);
|
||||
io.WriteLine(line);
|
||||
}
|
||||
|
||||
// Write processor parameters.
|
||||
{
|
||||
if (i.Processor == PipelineTypes.MissingProcessor)
|
||||
{
|
||||
// Could still be missing the real processor.
|
||||
// If so, write the string parameters from import.
|
||||
foreach (var j in i.ProcessorParams)
|
||||
{
|
||||
line = string.Format(lineFormat, "processorParam", string.Format(processorParamFormat, j.Key, j.Value));
|
||||
io.WriteLine(line);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, write only values which are defined by the real processor.
|
||||
foreach (var j in i.Processor.Properties)
|
||||
{
|
||||
object value = null;
|
||||
if (i.ProcessorParams.ContainsKey(j.Name))
|
||||
value = i.ProcessorParams[j.Name];
|
||||
|
||||
// JCF: I 'think' writting an empty string for null would be appropriate but to be on the safe side
|
||||
// im just not writting the value at all.
|
||||
if (value != null)
|
||||
{
|
||||
var converter = PipelineTypes.FindConverter(value.GetType());
|
||||
var valueStr = converter.ConvertTo(null, CultureInfo.InvariantCulture, value, typeof(string));
|
||||
line = string.Format(lineFormat, "processorParam", string.Format(processorParamFormat, j.Name, valueStr));
|
||||
io.WriteLine(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string buildValue = i.OriginalPath;
|
||||
if (i.OriginalPath != i.DestinationPath)
|
||||
buildValue += ";" + i.DestinationPath;
|
||||
line = string.Format(lineFormat, "build", buildValue);
|
||||
io.WriteLine(line);
|
||||
io.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ImportProject(string projectFilePath)
|
||||
{
|
||||
_project.OriginalPath = projectFilePath.Remove(projectFilePath.LastIndexOf('.')) + ".mgcb";
|
||||
|
||||
using (var io = XmlReader.Create(File.OpenText(projectFilePath)))
|
||||
{
|
||||
while (io.Read())
|
||||
{
|
||||
if (io.NodeType == XmlNodeType.Element)
|
||||
{
|
||||
var buildAction = io.LocalName;
|
||||
if (buildAction.Equals("Reference"))
|
||||
{
|
||||
string include, hintPath;
|
||||
ReadIncludeReference(io, out include, out hintPath);
|
||||
|
||||
if (!string.IsNullOrEmpty(hintPath) &&
|
||||
hintPath.IndexOf("microsoft", StringComparison.CurrentCultureIgnoreCase) == -1 &&
|
||||
hintPath.IndexOf("monogamecontentprocessors", StringComparison.CurrentCultureIgnoreCase) == -1)
|
||||
{
|
||||
_project.References.Add(hintPath);
|
||||
}
|
||||
}
|
||||
else if (buildAction.Equals("Content") || buildAction.Equals("None"))
|
||||
{
|
||||
string include, copyToOutputDirectory;
|
||||
ReadIncludeContent(io, out include, out copyToOutputDirectory);
|
||||
|
||||
if (!string.IsNullOrEmpty(copyToOutputDirectory) && !copyToOutputDirectory.Equals("Never"))
|
||||
{
|
||||
var sourceFilePath = Path.GetDirectoryName(projectFilePath);
|
||||
sourceFilePath += "\\" + include;
|
||||
|
||||
OnCopy(sourceFilePath);
|
||||
}
|
||||
}
|
||||
else if (buildAction.Equals("Compile"))
|
||||
{
|
||||
string include, name, importer, processor;
|
||||
string[] processorParams;
|
||||
ReadIncludeCompile(io, out include, out name, out importer, out processor, out processorParams);
|
||||
|
||||
Importer = importer;
|
||||
Processor = processor;
|
||||
if (processorParams != null)
|
||||
{
|
||||
foreach (var i in processorParams)
|
||||
AddProcessorParam(i);
|
||||
}
|
||||
|
||||
var sourceFilePath = Path.GetDirectoryName(projectFilePath);
|
||||
sourceFilePath += "\\" + include;
|
||||
|
||||
OnBuild(sourceFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string ProjectDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return _project.Location;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadIncludeReference(XmlReader io, out string include, out string hintPath)
|
||||
{
|
||||
include = io.GetAttribute("Include").Unescape();
|
||||
hintPath = null;
|
||||
|
||||
if (!io.IsEmptyElement)
|
||||
{
|
||||
var depth = io.Depth;
|
||||
for (io.Read(); io.Depth != depth; io.Read())
|
||||
{
|
||||
// process sub nodes
|
||||
if (io.IsStartElement("HintPath"))
|
||||
{
|
||||
io.Read();
|
||||
hintPath = io.Value.Unescape();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadIncludeContent(XmlReader io, out string include, out string copyToOutputDirectory)
|
||||
{
|
||||
copyToOutputDirectory = null;
|
||||
include = io.GetAttribute("Include").Unescape();
|
||||
|
||||
if (!io.IsEmptyElement)
|
||||
{
|
||||
var depth = io.Depth;
|
||||
for (io.Read(); io.Depth != depth; io.Read())
|
||||
{
|
||||
// process sub nodes here.
|
||||
|
||||
if (io.IsStartElement())
|
||||
{
|
||||
switch (io.LocalName)
|
||||
{
|
||||
case "CopyToOutputDirectory":
|
||||
io.Read();
|
||||
copyToOutputDirectory = io.Value.Unescape();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadIncludeCompile(XmlReader io,
|
||||
out string include,
|
||||
out string name,
|
||||
out string importer,
|
||||
out string processor,
|
||||
out string[] processorParams)
|
||||
{
|
||||
name = null;
|
||||
importer = null;
|
||||
processor = null;
|
||||
|
||||
include = io.GetAttribute("Include").Unescape();
|
||||
var parameters = new List<string>();
|
||||
|
||||
if (!io.IsEmptyElement)
|
||||
{
|
||||
var depth = io.Depth;
|
||||
for (io.Read(); io.Depth != depth; io.Read())
|
||||
{
|
||||
// process sub nodes here.
|
||||
|
||||
if (io.IsStartElement())
|
||||
{
|
||||
switch (io.LocalName)
|
||||
{
|
||||
case "Name":
|
||||
io.Read();
|
||||
name = io.Value.Unescape();
|
||||
break;
|
||||
case "Importer":
|
||||
io.Read();
|
||||
importer = io.Value.Unescape();
|
||||
break;
|
||||
case "Processor":
|
||||
io.Read();
|
||||
processor = io.Value.Unescape();
|
||||
break;
|
||||
default:
|
||||
if (io.LocalName.Contains("ProcessorParameters_"))
|
||||
{
|
||||
var line = io.LocalName.Replace("ProcessorParameters_", "");
|
||||
line += "=";
|
||||
io.Read();
|
||||
line += io.Value;
|
||||
parameters.Add(line.Unescape());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processorParams = parameters.ToArray();
|
||||
}
|
||||
|
||||
private string FormatDivider(string label)
|
||||
{
|
||||
var commentFormat = Environment.NewLine + "#----------------------------------------------------------------------------#" + Environment.NewLine;
|
||||
|
||||
label = " " + label + " ";
|
||||
var src = commentFormat.Length / 2 - label.Length / 2;
|
||||
var dst = src + label.Length;
|
||||
|
||||
return commentFormat.Substring(0, src) + label + commentFormat.Substring(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user