You're breathtaking!

This commit is contained in:
Wojtek Figat
2020-12-07 23:40:54 +01:00
commit 6fb9eee74c
5143 changed files with 1153594 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using System.IO;
using System.Text;
namespace Flax.Build
{
/// <summary>
/// The <see cref="StreamWriter"/> implementation with a custom encoding.
/// </summary>
/// <seealso cref="System.IO.StringWriter" />
public sealed class StringWriterWithEncoding : StringWriter
{
/// <summary>
/// Initializes a new instance of the <see cref="StringWriterWithEncoding"/> class.
/// </summary>
public StringWriterWithEncoding()
: this(Encoding.UTF8)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="StringWriterWithEncoding"/> class.
/// </summary>
/// <param name="encoding">The encoding.</param>
public StringWriterWithEncoding(Encoding encoding)
{
Encoding = encoding;
}
/// <summary>
/// Gets the <see cref="T:System.Text.Encoding" /> in which the output is written.
/// </summary>
public override Encoding Encoding { get; }
}
}

View File

@@ -0,0 +1,515 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace Flax.Build
{
/// <summary>
/// Types of the tokens supported by the <see cref="Tokenizer"/>.
/// </summary>
public enum TokenType
{
/// <summary>
/// A whitespace.
/// </summary>
Whitespace,
/// <summary>
/// A Newline.
/// </summary>
Newline,
/// <summary>
/// A multi line comment.
/// </summary>
CommentMultiLine,
/// <summary>
/// A single line comment.
/// </summary>
CommentSingleLine,
/// <summary>
/// An identifier.
/// </summary>
Identifier,
/// <summary>
/// A number in hexadecimal form.
/// </summary>
Hex,
/// <summary>
/// A number.
/// </summary>
Number,
/// <summary>
/// The symbol '='.
/// </summary>
Equal,
/// <summary>
/// A comma ','.
/// </summary>
Comma,
/// <summary>
/// A Semicolon ';'.
/// </summary>
SemiColon,
/// <summary>
/// A left curly brace '{'.
/// </summary>
LeftCurlyBrace,
/// <summary>
/// A right curly brace '}'.
/// </summary>
RightCurlyBrace,
/// <summary>
/// A left parenthesis '('.
/// </summary>
LeftParent,
/// <summary>
/// A right parenthesis ')'.
/// </summary>
RightParent,
/// <summary>
/// A left bracket '['.
/// </summary>
LeftBracket,
/// <summary>
/// A right bracket ']'.
/// </summary>
RightBracket,
/// <summary>
/// A text.
/// </summary>
String,
/// <summary>
/// An character.
/// </summary>
Character,
/// <summary>
/// A preprocessor token '#'
/// </summary>
Preprocessor,
/// <summary>
/// A colon ':'.
/// </summary>
Colon,
/// <summary>
/// A double colon '::'.
/// </summary>
DoubleColon,
/// <summary>
/// A dot '.'.
/// </summary>
Dot,
/// <summary>
/// A '&lt;'.
/// </summary>
LessThan,
/// <summary>
/// A '&gt;'.
/// </summary>
GreaterThan,
/// <summary>
/// A '&amp;'.
/// </summary>
And,
/// <summary>
/// A '*'.
/// </summary>
Multiply,
/// <summary>
/// A '/'.
/// </summary>
Divide,
/// <summary>
/// A '+'.
/// </summary>
Add,
/// <summary>
/// A '-'.
/// </summary>
Sub,
/// <summary>
/// An unknown symbol.
/// </summary>
Unknown,
/// <summary>
/// A end of file token.
/// </summary>
EndOfFile,
/// <summary>
/// A '&lt;'.
/// </summary>
LeftAngleBracket = LessThan,
/// <summary>
/// A '&gt;'.
/// </summary>
RightAngleBracket = GreaterThan,
}
/// <summary>
/// Contains information about a token language.
/// </summary>
public class Token : IEquatable<Token>
{
/// <summary>
/// Initializes a new instance of the <see cref="Token"/> class.
/// </summary>
public Token()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Token" /> struct.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="value">The value.</param>
public Token(TokenType type, string value)
{
Type = type;
Value = value;
}
/// <summary>
/// The type of the token.
/// </summary>
public TokenType Type;
/// <summary>
/// Value of the token.
/// </summary>
public string Value;
/// <inheritdoc />
public override string ToString()
{
return string.Format("{{{0}:{1}}}", Type, Value);
}
/// <inheritdoc />
public bool Equals(Token other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return Type == other.Type && Value == other.Value;
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj.GetType() != this.GetType())
return false;
return Equals((Token)obj);
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
return ((int)Type * 397) ^ (Value != null ? Value.GetHashCode() : 0);
}
}
}
/// <summary>
/// The tokens parsing utility that implements basic logic for generic C-like syntax source code parsing.
/// </summary>
public class Tokenizer : IDisposable
{
private static readonly Regex RegexTokenizer = new Regex
(
@"(?<ws>[ \t]+)|" +
@"(?<nl>(?:\r\n|\n))|" +
@"(?<commul>/\*(?:(?!\*/)(?:.|[\r\n]+))*\*/)|" +
@"(?<comsin>//(.*?)\r?\n)|" +
@"(?<ident>[a-zA-Z_][a-zA-Z0-9_]*)|" +
@"(?<hex>0x[0-9a-fA-F]+)|" +
@"(?<number>[\-\+]?\s*[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?f?)|" +
@"(?<equal>=)|" +
@"(?<comma>,)|" +
@"(?<semicolon>;)|" +
@"(?<lcb>\{)|" +
@"(?<rcb>\})|" +
@"(?<lpar>\()|" +
@"(?<rpar>\))|" +
@"(?<lb>\[)|" +
@"(?<rb>\])|" +
@"(?<str>""[^""\\]*(?:\\.[^""\\]*)*"")|" +
@"(?<char>'[^'\\]*(?:\\.[^'\\]*)*')|" +
@"(?<prep>#)|" +
@"(?<colon>:)|" +
@"(?<doublecolon>::)|" +
@"(?<dot>\.)|" +
@"(?<lt>\<)|" +
@"(?<gt>\>)|" +
@"(?<and>\&)|" +
@"(?<mul>\*)|" +
@"(?<div>\/)|" +
@"(?<add>\+)|" +
@"(?<sub>\-)|" +
@"(?<unk>[^\s]+)",
RegexOptions.Compiled
);
private ITwoWayEnumerator<Token> _tokenEnumerator;
private int _line = 1;
/// <summary>
/// Gets the current token.
/// </summary>
public Token CurrentToken => _tokenEnumerator.Current;
/// <summary>
/// Gets the current line number (starting from number 1).
/// </summary>
public int CurrentLine => _line;
/// <summary>
/// Tokenizes the given file (through constructor).
/// </summary>
/// <param name="sourceCode">The source code for this tokenizer to run on.</param>
public void Tokenize(string sourceCode)
{
if (_tokenEnumerator != null)
throw new Exception("This code is already parsed!");
var tokens = TokenizeInternal(sourceCode);
_tokenEnumerator = tokens.GetTwoWayEnumerator();
}
/// <summary>
/// Gets next token.
/// </summary>
/// <param name="includeWhitespaces">When false, all white-space tokens will be ignored.</param>
/// <param name="includeComments">When false, all comment (single line and multi-line) tokens will be ignored.</param>
/// <returns>The token. Check for EndOfFile token-type to detect end-of-file.</returns>
public Token NextToken(bool includeWhitespaces = false, bool includeComments = false)
{
while (_tokenEnumerator.MoveNext())
{
var token = _tokenEnumerator.Current;
if (token == null)
continue;
_line += CountLines(token);
if (token.Type == TokenType.Newline)
{
if (includeWhitespaces)
return token;
continue;
}
if (!includeWhitespaces && token.Type == TokenType.Whitespace)
{
continue;
}
return token;
}
return new Token(TokenType.EndOfFile, string.Empty);
}
/// <summary>
/// Moves to the previous the token.
/// </summary>
/// <param name="includeWhitespaces">If set to <c>true</c> includes whitespaces.</param>
/// <param name="includeComments">If set to <c>true</c> include comments.</param>
/// <returns>The token. Check for EndOfFile token-type to detect end-of-file.</returns>
public Token PreviousToken(bool includeWhitespaces = false, bool includeComments = false)
{
while (_tokenEnumerator.MovePrevious())
{
var token = _tokenEnumerator.Current;
if (token == null)
continue;
_line -= CountLines(token);
if (token.Type == TokenType.Newline)
{
if (includeWhitespaces)
return token;
continue;
}
if (!includeWhitespaces && token.Type == TokenType.Whitespace)
{
continue;
}
return token;
}
return new Token(TokenType.EndOfFile, string.Empty);
}
/// <summary>
/// Expects any token of given types. Throws <see cref="Exception"/> when token is not found.
/// </summary>
/// <param name="tokenTypes">The allowed token types.</param>
/// <param name="includeWhitespaces">When false, all white-space tokens will be ignored.</param>
/// <param name="includeComments">When false, all comment (single line and multi-line) tokens will be ignored.</param>
/// <returns>The found token.</returns>
public Token ExpectAnyTokens(TokenType[] tokenTypes, bool includeWhitespaces = false, bool includeComments = false)
{
var token = NextToken(includeWhitespaces, includeComments);
if (tokenTypes.Contains(token.Type))
return token;
throw new Exception($"Expected {string.Join(" or ", tokenTypes)}, but got {token} at line {_line}.");
}
/// <summary>
/// Expects token of given types in the same order. Throws <see cref="Exception"/> when token is not found.
/// </summary>
/// <param name="tokenTypes">The allowed token types.</param>
/// <param name="includeWhitespaces">When false, all white-space tokens will be ignored.</param>
/// <param name="includeComments">When false, all comment (single line and multi-line) tokens will be ignored.</param>
/// <returns>The found token.</returns>
public void ExpectAllTokens(TokenType[] tokenTypes, bool includeWhitespaces = false, bool includeComments = false)
{
foreach (var tokenType in tokenTypes)
{
var token = NextToken(includeWhitespaces, includeComments);
if (token.Type != tokenType)
throw new Exception($"Expected {tokenType}, but got {token} at line {_line}.");
}
}
/// <summary>
/// Expects any token of given type. Throws <see cref="Exception"/> when token is not found.
/// </summary>
/// <param name="tokenType">The only allowed token type.</param>
/// <param name="includeWhitespaces">When false, all white-space tokens will be ignored.</param>
/// <param name="includeComments">When false, all comment (single line and multi-line) tokens will be ignored.</param>
/// <returns>The found token.</returns>
public Token ExpectToken(TokenType tokenType, bool includeWhitespaces = false, bool includeComments = false)
{
var token = NextToken(includeWhitespaces, includeComments);
if (token.Type == tokenType)
return token;
throw new Exception($"Expected {tokenType}, but got {token} at line {_line}.");
}
/// <summary>
/// Skips all tokens until the tokenizer steps into token of given type (and it is also skipped, so, NextToken will give the next token).
/// </summary>
/// <param name="tokenType">The expected token type.</param>
public void SkipUntil(TokenType tokenType)
{
do
{
} while (NextToken(true).Type != tokenType);
}
/// <summary>
/// Skips all tokens until the tokenizer steps into token of given type (and it is also skipped, so, NextToken will give the next token).
/// </summary>
/// <param name="tokenType">The expected token type.</param>
/// <param name="context">The output contents of the skipped tokens.</param>
public void SkipUntil(TokenType tokenType, out string context)
{
context = string.Empty;
while (NextToken(true).Type != tokenType)
{
context += CurrentToken.Value;
}
}
/// <summary>
/// Disposes the <see cref="Tokenizer"/>.
/// </summary>
public void Dispose()
{
_tokenEnumerator?.Dispose();
}
private IEnumerable<Token> TokenizeInternal(string input)
{
var matches = RegexTokenizer.Matches(input);
foreach (Match match in matches)
{
var i = 0;
foreach (Group group in match.Groups)
{
var matchValue = group.Value;
if (group.Success && i > 1)
{
yield return new Token
{
Type = (TokenType)(i - 2),
Value = matchValue
};
}
i++;
}
}
}
private int CountLines(Token token)
{
int result = 0;
switch (token.Type)
{
case TokenType.Newline:
case TokenType.CommentSingleLine:
result = 1;
break;
case TokenType.CommentMultiLine:
for (int i = 0; i < token.Value.Length; i++)
{
if (token.Value[i] == '\n')
result++;
}
break;
}
return result;
}
}
}

View File

@@ -0,0 +1,119 @@
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
namespace Flax.Build
{
/// <summary>
/// The two-way enumerator interface that can move forward or backwards.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <seealso cref="System.Collections.Generic.IEnumerator{T}" />
public interface ITwoWayEnumerator<T> : IEnumerator<T>
{
/// <summary>
/// Advances the enumerator to the previous element of the collection.
/// </summary>
/// <returns>
/// <see langword="true" /> if the enumerator was successfully advanced to the previous element; <see langword="false" /> if the enumerator has passed the beginning of the collection.</returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
bool MovePrevious();
}
/// <summary>
/// The implementation of the <see cref="ITwoWayEnumerator{T}"/> that uses a list.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <seealso cref="Flax.Build.ITwoWayEnumerator{T}" />
public class TwoWayEnumerator<T> : ITwoWayEnumerator<T>
{
private IEnumerator<T> _enumerator;
private List<T> _buffer;
private int _index;
/// <summary>
/// Initializes a new instance of the <see cref="TwoWayEnumerator{T}"/> class.
/// </summary>
/// <param name="enumerator">The enumerator.</param>
public TwoWayEnumerator(IEnumerator<T> enumerator)
{
_enumerator = enumerator ?? throw new ArgumentNullException("enumerator");
_buffer = new List<T>();
_index = -1;
}
/// <summary>
/// Advances the enumerator to the previous element of the collection.
/// </summary>
/// <returns><see langword="true" /> if the enumerator was successfully advanced to the previous element; <see langword="false" /> if the enumerator has passed the beginning of the collection.</returns>
public bool MovePrevious()
{
if (_index <= 0)
{
return false;
}
--_index;
return true;
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns><see langword="true" /> if the enumerator was successfully advanced to the next element; <see langword="false" /> if the enumerator has passed the end of the collection.</returns>
public bool MoveNext()
{
if (_index < _buffer.Count - 1)
{
++_index;
return true;
}
if (_enumerator.MoveNext())
{
_buffer.Add(_enumerator.Current);
++_index;
return true;
}
return false;
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
public T Current
{
get
{
if (_index < 0 || _index >= _buffer.Count)
throw new InvalidOperationException();
return _buffer[_index];
}
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
public void Reset()
{
_enumerator.Reset();
_buffer.Clear();
_index = -1;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
_enumerator.Dispose();
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
object System.Collections.IEnumerator.Current => Current;
}
}

View File

@@ -0,0 +1,591 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
namespace Flax.Build
{
/// <summary>
/// The utilities.
/// </summary>
public static class Utilities
{
/// <summary>
/// Gets the size of the file as a readable string.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>The file size text.</returns>
public static string GetFileSize(string path)
{
return (Math.Floor(new FileInfo(path).Length / 1024.0f / 1024 * 100) / 100) + " MB";
}
/// <summary>
/// Adds the range of the items to the hash set.
/// </summary>
/// <typeparam name="T">The item type.</typeparam>
/// <param name="source">The hash set to modify.</param>
/// <param name="items">The items collection to append.</param>
public static void AddRange<T>(this HashSet<T> source, IEnumerable<T> items)
{
foreach (T item in items)
{
source.Add(item);
}
}
/// <summary>
/// Gets the two way enumerator for the given enumerable collection.
/// </summary>
/// <typeparam name="T">The item type.</typeparam>
/// <param name="source">The source collection.</param>
/// <returns>The enumerator.</returns>
public static ITwoWayEnumerator<T> GetTwoWayEnumerator<T>(this IEnumerable<T> source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
return new TwoWayEnumerator<T>(source.GetEnumerator());
}
/// <summary>
/// Copies the file.
/// </summary>
/// <param name="srcFilePath">The source file path.</param>
/// <param name="dstFilePath">The destination file path.</param>
/// <param name="overwrite"><see langword="true" /> if the destination file can be overwritten; otherwise, <see langword="false" />.</param>
public static void FileCopy(string srcFilePath, string dstFilePath, bool overwrite = true)
{
if (string.IsNullOrEmpty(srcFilePath))
throw new ArgumentNullException(nameof(srcFilePath));
if (string.IsNullOrEmpty(dstFilePath))
throw new ArgumentNullException(nameof(dstFilePath));
Log.Verbose(srcFilePath + " -> " + dstFilePath);
try
{
File.Copy(srcFilePath, dstFilePath, overwrite);
}
catch (Exception ex)
{
if (!File.Exists(srcFilePath))
Log.Error("Failed to copy file. Missing source file.");
else
Log.Error("Failed to copy file. " + ex.Message);
throw;
}
}
/// <summary>
/// Copies the directories.
/// </summary>
/// <param name="srcDirectoryPath">The source directory path.</param>
/// <param name="dstDirectoryPath">The destination directory path.</param>
/// <param name="copySubdirs">If set to <c>true</c> copy sub-directories (recursive copy operation).</param>
/// <param name="overrideFiles">If set to <c>true</c> override target files if any is existing.</param>
/// <param name="searchPattern">The custom filter for the filenames to copy. Can be used to select files to copy. Null if unused.</param>
public static void DirectoryCopy(string srcDirectoryPath, string dstDirectoryPath, bool copySubdirs = true, bool overrideFiles = false, string searchPattern = null)
{
Log.Verbose(srcDirectoryPath + " -> " + dstDirectoryPath);
var dir = new DirectoryInfo(srcDirectoryPath);
if (!dir.Exists)
{
throw new DirectoryNotFoundException("Missing source directory to copy. " + srcDirectoryPath);
}
if (!Directory.Exists(dstDirectoryPath))
{
Directory.CreateDirectory(dstDirectoryPath);
}
var files = searchPattern != null ? dir.GetFiles(searchPattern) : dir.GetFiles();
for (int i = 0; i < files.Length; i++)
{
string tmp = Path.Combine(dstDirectoryPath, files[i].Name);
File.Copy(files[i].FullName, tmp, overrideFiles);
}
if (copySubdirs)
{
var dirs = dir.GetDirectories();
for (int i = 0; i < dirs.Length; i++)
{
string tmp = Path.Combine(dstDirectoryPath, dirs[i].Name);
DirectoryCopy(dirs[i].FullName, tmp, true, overrideFiles, searchPattern);
}
}
}
private static void DirectoryDelete(DirectoryInfo dir)
{
var subdirs = dir.GetDirectories();
foreach (var subdir in subdirs)
{
DirectoryDelete(subdir);
}
var files = dir.GetFiles();
foreach (var file in files)
{
try
{
file.Delete();
}
catch (UnauthorizedAccessException)
{
File.SetAttributes(file.FullName, FileAttributes.Normal);
file.Delete();
}
}
dir.Delete();
}
/// <summary>
/// Deletes the directory.
/// </summary>
/// <param name="directoryPath">The directory path.</param>
/// <param name="withSubdirs">if set to <c>true</c> with sub-directories (recursive delete operation).</param>
public static void DirectoryDelete(string directoryPath, bool withSubdirs = true)
{
var dir = new DirectoryInfo(directoryPath);
if (!dir.Exists)
return;
if (withSubdirs)
DirectoryDelete(dir);
else
dir.Delete();
}
/// <summary>
/// The process run options.
/// </summary>
[Flags]
public enum RunOptions
{
/// <summary>
/// The none.
/// </summary>
None = 0,
/// <summary>
/// The application must exist.
/// </summary>
AppMustExist = 1 << 0,
/// <summary>
/// Skip waiting for exit.
/// </summary>
NoWaitForExit = 1 << 1,
/// <summary>
/// Skip standard output redirection to log.
/// </summary>
NoStdOutRedirect = 1 << 2,
/// <summary>
/// Skip logging of command run.
/// </summary>
NoLoggingOfRunCommand = 1 << 3,
/// <summary>
/// Uses UTF-8 output encoding format.
/// </summary>
UTF8Output = 1 << 4,
/// <summary>
/// Skips logging of run duration.
/// </summary>
NoLoggingOfRunDuration = 1 << 5,
/// <summary>
/// The default options.
/// </summary>
Default = AppMustExist,
}
private static void StdOut(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
Log.Verbose(e.Data);
}
}
private static void StdErr(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
Log.Verbose(e.Data);
}
}
/// <summary>
/// Runs the external program.
/// </summary>
/// <param name="app">Program filename.</param>
/// <param name="commandLine">Commandline</param>
/// <param name="input">Optional Input for the program (will be provided as stdin)</param>
/// <param name="workspace">Optional custom workspace directory. Use null to keep the same directory.</param>
/// <param name="options">Defines the options how to run. See RunOptions.</param>
/// <param name="envVars">Custom environment variables to pass to the child process.</param>
/// <returns>The exit code of the program.</returns>
public static int Run(string app, string commandLine = null, string input = null, string workspace = null, RunOptions options = RunOptions.Default, Dictionary<string, string> envVars = null)
{
// Check if the application exists, including the PATH directories.
if (options.HasFlag(RunOptions.AppMustExist) && !File.Exists(app))
{
bool existsInPath = false;
if (!app.Contains(Path.DirectorySeparatorChar) && !app.Contains(Path.AltDirectorySeparatorChar))
{
string[] pathDirectories = Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator);
foreach (string pathDirectory in pathDirectories)
{
string tryApp = Path.Combine(pathDirectory, app);
if (File.Exists(tryApp))
{
app = tryApp;
existsInPath = true;
break;
}
}
}
if (!existsInPath)
{
throw new Exception(string.Format("Couldn't find the executable to run: {0}", app));
}
}
var startTime = DateTime.UtcNow;
if (!options.HasFlag(RunOptions.NoLoggingOfRunCommand))
{
Log.Verbose("Running: " + app + " " + (string.IsNullOrEmpty(commandLine) ? "" : commandLine));
}
bool redirectStdOut = (options & RunOptions.NoStdOutRedirect) != RunOptions.NoStdOutRedirect;
Process proc = new Process();
proc.StartInfo.FileName = app;
proc.StartInfo.Arguments = string.IsNullOrEmpty(commandLine) ? "" : commandLine;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardInput = input != null;
proc.StartInfo.CreateNoWindow = true;
if (workspace != null)
{
proc.StartInfo.WorkingDirectory = workspace;
}
if (redirectStdOut)
{
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.OutputDataReceived += StdOut;
proc.ErrorDataReceived += StdErr;
}
if (envVars != null)
{
foreach (var env in envVars)
{
if (env.Key == "PATH")
{
proc.StartInfo.EnvironmentVariables[env.Key] = proc.StartInfo.EnvironmentVariables[env.Key] + ';' + env.Value;
}
else
{
proc.StartInfo.EnvironmentVariables[env.Key] = env.Value;
}
}
}
if ((options & RunOptions.UTF8Output) == RunOptions.UTF8Output)
{
proc.StartInfo.StandardOutputEncoding = new UTF8Encoding(false, false);
}
proc.Start();
if (redirectStdOut)
{
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
}
if (string.IsNullOrEmpty(input) == false)
{
proc.StandardInput.WriteLine(input);
proc.StandardInput.Close();
}
if (!options.HasFlag(RunOptions.NoWaitForExit))
{
proc.WaitForExit();
}
int result = -1;
if (!options.HasFlag(RunOptions.NoWaitForExit))
{
var buildDuration = (DateTime.UtcNow - startTime).TotalMilliseconds;
result = proc.ExitCode;
if (!options.HasFlag(RunOptions.NoLoggingOfRunCommand) || options.HasFlag(RunOptions.NoLoggingOfRunDuration))
{
Log.Info(string.Format("Took {0}s to run {1}, ExitCode={2}", buildDuration / 1000, Path.GetFileName(app), result));
}
}
return result;
}
/// <summary>
/// Constructs a relative path from the given base directory.
/// </summary>
/// <param name="path">The source path to convert from absolute into a relative format.</param>
/// <param name="directory">The directory to create a relative path from.</param>
/// <returns>Thew relative path from the given directory.</returns>
public static string MakePathRelativeTo(string path, string directory)
{
// Find how much of the path is common between the two paths. This length does not include a trailing directory separator character
int commonDirectoryLength = -1;
for (int i = 0;; i++)
{
if (i == path.Length)
{
// Check if two paths are the same
if (i == directory.Length)
{
return string.Empty;
}
// Check if we're finishing on a complete directory name
if (directory[i] == Path.DirectorySeparatorChar)
{
commonDirectoryLength = i;
}
break;
}
if (i == directory.Length)
{
// Check whether the end of the directory name coincides with a boundary for the current name
if (path[i] == Path.DirectorySeparatorChar)
{
commonDirectoryLength = i;
}
break;
}
// Check the two paths match, and bail if they don't. Increase the common directory length if we've reached a separator
if (string.Compare(path, i, directory, i, 1, StringComparison.OrdinalIgnoreCase) != 0)
{
break;
}
if (path[i] == Path.DirectorySeparatorChar)
{
commonDirectoryLength = i;
}
}
// If there's no relative path, just return the absolute path
if (commonDirectoryLength == -1)
{
return path;
}
// Append all the '..' separators to get back to the common directory, then the rest of the string to reach the target item
StringBuilder result = new StringBuilder();
for (int i = commonDirectoryLength + 1; i < directory.Length; i++)
{
// Move up a directory
result.Append("..");
result.Append(Path.DirectorySeparatorChar);
// Scan to the next directory separator
while (i < directory.Length && directory[i] != Path.DirectorySeparatorChar)
{
i++;
}
}
if (commonDirectoryLength + 1 < path.Length)
{
result.Append(path, commonDirectoryLength + 1, path.Length - commonDirectoryLength - 1);
}
return result.ToString();
}
/// <summary>
/// Normalizes the path to the standard Flax format (all separators are '/' except for drive 'C:\').
/// </summary>
/// <param name="path">The path.</param>
/// <returns>The normalized path.</returns>
public static string NormalizePath(string path)
{
if (string.IsNullOrEmpty(path))
return path;
var chars = path.ToCharArray();
// Convert all '\' to '/'
for (int i = 0; i < chars.Length; i++)
{
if (chars[i] == '\\')
chars[i] = '/';
}
// Fix case 'C:/' to 'C:\'
if (chars.Length > 2 && !char.IsDigit(chars[0]) && chars[1] == ':')
{
chars[2] = '\\';
}
return new string(chars);
}
/// <summary>
/// Removes the relative parts from the path. For instance it replaces 'xx/yy/../zz' with 'xx/zz'.
/// </summary>
/// <param name="path">The input path.</param>
/// <returns>The output path.</returns>
public static string RemovePathRelativeParts(string path)
{
if (string.IsNullOrEmpty(path))
return path;
path = NormalizePath(path);
string[] components = path.Split('/');
Stack<string> stack = new Stack<string>();
foreach (var bit in components)
{
if (bit == "..")
{
if (stack.Count != 0)
{
var popped = stack.Pop();
if (popped == "..")
{
stack.Push(popped);
stack.Push(bit);
}
}
else
{
stack.Push(bit);
}
}
else if (bit == ".")
{
// Skip /./
}
else
{
stack.Push(bit);
}
}
bool isRooted = path.StartsWith("/");
string result = string.Join(Path.DirectorySeparatorChar.ToString(), stack.Reverse());
if (isRooted)
result = result.Insert(0, "/");
return result;
}
/// <summary>
/// Writes the file contents. Before writing reads the existing file and discards operation if contents are the same.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="contents">The file contents.</param>
/// <returns>True if file has been modified, otherwise false.</returns>
public static bool WriteFileIfChanged(string path, string contents)
{
if (File.Exists(path))
{
string oldContents = null;
try
{
oldContents = File.ReadAllText(path);
}
catch (Exception)
{
Log.Warning(string.Format("Failed to read file contents while trying to save it.", path));
}
if (string.Equals(contents, oldContents, StringComparison.OrdinalIgnoreCase))
{
Log.Verbose(string.Format("Skipped saving file to {0}", path));
return false;
}
}
try
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
File.WriteAllText(path, contents, new UTF8Encoding());
Log.Verbose(string.Format("Saved file to {0}", path));
}
catch
{
Log.Error(string.Format("Failed to save file {0}", path));
throw;
}
return true;
}
/// <summary>
/// Replaces the given text with other one in the files.
/// </summary>
/// <param name="folderPath">The relative or absolute path to the directory to search.</param>
/// <param name="searchPattern">The search string to match against the names of files in <paramref name="folderPath" />. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions.</param>
/// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include all subdirectories or only the current directory.</param>
/// <param name="findWhat">The text to replace.</param>
/// <param name="replaceWith">The value to replace to.</param>
public static void ReplaceInFiles(string folderPath, string searchPattern, SearchOption searchOption, string findWhat, string replaceWith)
{
var files = Directory.GetFiles(folderPath, searchPattern, searchOption);
ReplaceInFiles(files, findWhat, replaceWith);
}
/// <summary>
/// Replaces the given text with other one in the files.
/// </summary>
/// <param name="files">The list of the files to process.</param>
/// <param name="findWhat">The text to replace.</param>
/// <param name="replaceWith">The value to replace to.</param>
public static void ReplaceInFiles(string[] files, string findWhat, string replaceWith)
{
foreach (var file in files)
{
ReplaceInFile(file, findWhat, replaceWith);
}
}
/// <summary>
/// Replaces the given text with other one in the file.
/// </summary>
/// <param name="file">The file to process.</param>
/// <param name="findWhat">The text to replace.</param>
/// <param name="replaceWith">The value to replace to.</param>
public static void ReplaceInFile(string file, string findWhat, string replaceWith)
{
var text = File.ReadAllText(file);
text = text.Replace(findWhat, replaceWith);
File.WriteAllText(file, text);
}
}
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using System;
using System.Runtime.InteropServices;
namespace Flax.Build
{
static class WinAPI
{
public static class dbghelp
{
[DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SymInitialize(IntPtr hProcess, string UserSearchPath, [MarshalAs(UnmanagedType.Bool)] bool fInvadeProcess);
[DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SymCleanup(IntPtr hProcess);
[DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern ulong SymLoadModuleEx(IntPtr hProcess, IntPtr hFile, string ImageName, string ModuleName, long BaseOfDll, int DllSize, IntPtr Data, int Flags);
[DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SymEnumerateSymbols64(IntPtr hProcess, ulong BaseOfDll, SymEnumerateSymbolsProc64 EnumSymbolsCallback, IntPtr UserContext);
public delegate bool SymEnumerateSymbolsProc64(string SymbolName, ulong SymbolAddress, uint SymbolSize, IntPtr UserContext);
}
}
}