Sharpmake.Application/CommandLineArguments.cs (361 lines of code) (raw):
// Copyright (c) Ubisoft. All Rights Reserved.
// Licensed under the Apache 2.0 License. See LICENSE.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
namespace Sharpmake.Application
{
public static partial class Program
{
public class Argument
{
[Serializable]
public class Error : Exception
{
public Error(string message, params object[] args)
: base(string.Format(message, args))
{ }
protected Error(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
public enum InputType
{
File,
Assembly,
Undefined
}
public string[] Sources = new string[0];
public string[] Assemblies = new string[0];
public HashSet<string> Defines = new HashSet<string>();
public InputType Input = InputType.Undefined;
public string ProfileFile = null;
public bool Exit = false;
public bool BlobOnly = false;
public bool CleanBlobsOnly = false;
public bool Multithreaded = true;
public bool RegexMatchCacheEnabled = true;
// Default capacity based on a big project numbers
public int RegexMatchCacheInitialCapacity = (1 << 20) + 1;
public bool SkipInvalidPath = false;
public bool DebugLog = false;
public bool Debug = false;
public bool Diagnostics = false;
public bool WriteFiles = true;
public bool DumpDependency = false;
private bool _testOptionValid = true;
internal TestOptions TestOption;
internal bool RegressionDiff = true;
public DirectoryInfo OutputDirectory;
public DirectoryInfo ReferenceDirectory;
public DirectoryInfo RemapRoot;
public string MutexSuffix = string.Empty;
public bool GenerateDebugSolution = false;
public bool GenerateDebugSolutionOnly = false;
public string DebugSolutionStartArguments = string.Empty;
public string DebugSolutionPath = string.Empty;
public DevEnv DebugSolutionDevEnv = DebugProjectGenerator.DefaultDevEnv;
[CommandLine.Option("sources", @"sharpmake sources files: ex: /sources( ""project1.sharpmake"", ""..\..\project2.sharpmake"" )")]
public void SetSources(params string[] files)
{
Sources = ValidateFiles(files);
DebugWriteLine("input sources: ");
Array.ForEach(Sources, (string source) => DebugWriteLine(" " + source));
}
[CommandLine.Option("assemblies", "This option is *deprecated*, please use '/sources(...)' with '[module: Sharpmake.Reference(...)]' instead")]
public void SetAssembly(params string[] files)
{
Assemblies = ValidateFiles(files);
WarningWriteLine("'/assemblies(...)' is *deprecated*, please use '/sources(...)' with '[module: Sharpmake.Reference(...)]' instead");
DebugWriteLine("input assemblies: ");
Array.ForEach(Assemblies, (string assembly) => DebugWriteLine(" " + assembly));
}
[CommandLine.Option("defines", @"sharpmake compilation defines: ex: /defines( ""SHARPMAKE_0_8_0"", ""GITLAB"" )")]
public void SetDefines(params string[] defines)
{
Defines = ValidateDefines(defines);
DebugWriteLine("compilation defines: ");
foreach (string define in Defines)
DebugWriteLine(" " + define);
}
[CommandLine.Option("profile", @"Profile file used to activate and output profiling: ex: /profile( @""D:\profile.json"" )")]
public void SetProfileFile(string profileFile)
{
ProfileFile = profileFile;
}
[CommandLine.Option("projectlogfiles", @"log files contained in a project for debug purpose: ex: /projectlogfiles( ""s:\p4\ac\dev\sharpmake\projects\win32\system\system.vcproj"" )")]
public void ProjectLogFiles(string projectFile)
{
Tools.ProjectLogFiles(projectFile);
Exit = true;
}
[CommandLine.Option("blobonly", @"Only generate blob and work blob files: ex: /blobonly")]
public void CommandLineBlobOnly()
{
BlobOnly = true;
}
[CommandLine.Option("breakintodebugger", @"Trigger a debugger break at the beginning of the program.")]
public void CommandLineBreakIntoDebugger()
{
// Validated in the main for priority
}
[CommandLine.Option("cleanblobsonly", @"Only clean blob and work blob files: ex: /cleanblobsonly")]
public void CommandLineCleanBlobsOnly()
{
CleanBlobsOnly = true;
}
[CommandLine.Option("multithreaded", @"Run multithreaded sharpmake: ex: /multithreaded(<true|false>)")]
public void CommandLineMultithreaded(bool value)
{
Multithreaded = value;
}
[CommandLine.Option("regexMatchCache", @"Enables/disables regex match cache optimization. Might improve performance on large projects. Enabled by default. ex: /regexMatchCache(false)")]
public void CommandLineRegexMatchCacheEnabled(bool value)
{
RegexMatchCacheEnabled = value;
}
[CommandLine.Option("regexMatchCacheInitialCapacity", @"Initial capacity of regex match cache. Should be set to a value higher or equal to the size which the cache is expected to reach over a run, otherwise performance might suffer. ex: /regexMatchCacheInitialCapacity(1048577)")]
public void CommandLineRegexMatchCacheInitialCapacity(int value)
{
RegexMatchCacheInitialCapacity = value;
}
[CommandLine.Option("DumpDependency", @"Dump projects dependencies in dot format: ex: /DumpDependency")]
public void CommandLineDumpDependency()
{
DumpDependency = true;
}
[CommandLine.Option("debuglog", @"Write debug log ( slow ): ex: /debuglog(<true|false>)")]
public void CommandLineDebugLog(bool value)
{
DebugLog = value;
}
[CommandLine.Option("diagnostics", @"Output more errors and warnings (slow): ex: /diagnostics")]
public void CommandLineDiagnostics()
{
Diagnostics = true;
}
[CommandLine.Option("debug", @"Run debug mode (implicit verbose and require a key to close console): ex: /debug")]
public void CommandLineDebug()
{
Debug = true;
}
[CommandLine.Option("sharpmakemutexsuffix", @"Allow custom mutex name suffix. Useful to debug concurrently multiple sharpmake running from different branches. Ex: /sharpmakemutexsuffix(""Name"")")]
public void CommandLineSharpmakeMutexSuffix(string name)
{
MutexSuffix = name;
}
[CommandLine.Option("skipinvalidpath", @"Skip invalid paths when resolving projects \ solutions : ex: /skipinvalidpath(<true|false>)")]
public void CommandLineSkipInvalidPath(bool value)
{
SkipInvalidPath = value;
}
[CommandLine.Option("test", @"Validates .sharpmake input so it respect a minimal coding standard.
Regression: tests if the dir provided in output is equal to the reference dir after a generation. returns -1 if different
QuickConfigure: tests if the configure methods are reversible. returns -1 if it is not reversible
Configure: tests if the configure methods are reversible, track the problems. return -1 if it is not reversible
(validates configure order): ex: /test(<""Regression""|""QuickConfigure""|""Configure"">)")]
public void CommandLineStrict(string option)
{
_testOptionValid = Enum.TryParse(option, out TestOption);
}
[CommandLine.Option("regressionDiff", @"Use diff tool if found to show regression differences (enabled by default): ex: /regressionDiff(<true|false>)")]
public void CommandLineRegressionDiff(bool value)
{
RegressionDiff = value;
}
[CommandLine.Option("writefiles", @"Sets if the generated files should be written or not. Default value: true. ex: /writefiles(<true|false>)")]
public void CommandLineWriteFiles(bool value)
{
WriteFiles = value;
}
[CommandLine.Option("filewritessecondarypath", @"Alternate path where to save modified files. This can be used to generate a reference directory when we want to compare changes made by sharpmake for example when upgrading it.")]
public void CommandLineFileWritesSecondaryPath(string filePath)
{
BuildContext.GenerateAll.s_fileWritesSecondaryPath = filePath;
if (!Directory.Exists(filePath))
Directory.CreateDirectory(filePath);
}
[CommandLine.Option("outputdir", @"Redirect solutions and projects output folder.
If this is set, autocleanup will be ignored.
It defines where the files should be written: ex: /outputdir(""C:\outputdirectory"")")]
public void CommandLineSetOutput(string output)
{
OutputDirectory = new DirectoryInfo(output);
DebugWriteLine("output directory : " + OutputDirectory.FullName);
}
[CommandLine.Option("referencedir", @"solutions and projects reference folder.
Must be grouped with /outputdir(...) and /test(...).
It defines what should the files look like: ex: /referencedir(""C:\referencedirectory"")")]
public void CommandLineSetReference(string reference)
{
ReferenceDirectory = new DirectoryInfo(reference);
DebugWriteLine("reference directory : " + ReferenceDirectory.FullName);
}
[CommandLine.Option("remaproot", @"Path to remove from the beginning of default output paths when remapping.
Used when /outputdir is set.
ex: /remaproot(""C:\p4ws\projectRoot\"")")]
public void CommandLineSetRemapRoot(string remapRoot)
{
RemapRoot = new DirectoryInfo(remapRoot);
DebugWriteLine("remap root directory : " + RemapRoot.FullName);
}
[CommandLine.Option("fakesourcedirfile", @"path to a file containing the list of files in the source tree
This list will be used instead of the real source path
ex: /fakesourcedirfile( ""files.txt"" ")]
public void CommandLineFakeSourceDirFile(string fakeSourceDirFile)
{
string fakeSourceDirFileFullPath = Path.GetFullPath(fakeSourceDirFile);
if (File.Exists(fakeSourceDirFileFullPath))
{
DebugWriteLine("fake source directory file:" + Environment.NewLine + fakeSourceDirFileFullPath);
// Detect "Everything" file format
bool everythingFileFormat = false;
string extension = Path.GetExtension(fakeSourceDirFileFullPath);
everythingFileFormat = extension == ".efu";
FileInfo fakeSourceDirFileInfo = new FileInfo(fakeSourceDirFileFullPath);
using (StreamReader projectFileStream = fakeSourceDirFileInfo.OpenText())
{
if (everythingFileFormat)
{
// Skip the first line, which is the legend:
// Filename,Size,Date Modified, Date Created,Attributes
projectFileStream.ReadLine();
// Get the folder prefix of everything on the second line
string folder = projectFileStream.ReadLine().Split(',')[0].Trim('"');
Util.FakePathPrefix = folder;
string line = projectFileStream.ReadLine();
while (line != null)
{
if (line != string.Empty)
{
var splitLine = line.Split(',');
uint attributes;
bool success = uint.TryParse(splitLine[4], out attributes);
if (success && (attributes & 0x10) != 0x10) // 16 is directory
{
string filePath = splitLine[0].Substring(folder.Length + 1).TrimEnd('"');
int size;
Util.AddNewFakeFile(filePath, int.TryParse(splitLine[1], out size) ? size : 0);
}
}
line = projectFileStream.ReadLine();
}
}
else
{
string line = projectFileStream.ReadLine();
while (line != null)
{
if (line != string.Empty)
{
string filePath = Util.PathMakeStandard(line);
Util.AddNewFakeFile(filePath, 0);
}
line = projectFileStream.ReadLine();
}
}
}
DebugWriteLine("found " + Util.CountFakeFiles() + " fake files");
}
else
{
throw new Error("Fake source directory file cannot be found! Please check the command line.");
}
}
[CommandLine.Option("verbose", @"Writes time and action information during progress: ex:/verbose")]
public void CommandLineVerbose()
{
// Validated in the main for priority
}
[CommandLine.Option("help", @"Prints other arguments (name and description): ex:/help")]
public void CommandLineHelp()
{
// Validated in the main for priority
}
[CommandLine.Option("generateDebugSolution", @"Generate debug solution.: /generateDebugSolution")]
public void CommandLineGenerateDebugSolution()
{
GenerateDebugSolution = true;
}
[CommandLine.Option("debugSolutionStartArguments", @"Adds arguments to the debug commandline
of the project generated by /generateDebugSolution. ex: /debugSolutionStartArguments(""/diagnostics"")")]
public void CommandLineDebugSolutionStartArguments(string arguments)
{
DebugSolutionStartArguments = arguments;
}
[CommandLine.Option("debugSolutionPath", @"Specifies the path of the solution generated by /generateDebugSolution. Absolute or relative to current directory. ex: /debugSolutionPath(""generated/debug"")")]
public void CommandLineDebugSolutionPath(string path)
{
DebugSolutionPath = path;
}
[CommandLine.Option("debugSolutionDevEnv", @"Specifies the devenv version of the solution generated by /generateDebugSolution. ex: /debugSolutionDevEnv(""vs2022"")")]
public void CommandLineDebugSolutionDevEnv(string devEnv)
{
if (!Enum.TryParse(devEnv, out DebugSolutionDevEnv))
throw new Error($"Invalid /debugSolutionDevEnv argument '{devEnv}'");
}
[CommandLine.Option("generateDebugSolutionOnly", @"Generate debug solution only (don't generate solution/project from user code).: /generateDebugSolutionOnly")]
public void CommandLineGenerateDebugSolutionOnly()
{
CommandLineGenerateDebugSolution();
GenerateDebugSolutionOnly = true;
}
[CommandLine.Option("forcecleanup", @"Path to an autocleanup db.
If this is set, all the files listed in the DB will be removed, and sharpmake will exit.
ex: /forcecleanup( ""sharpmakeautocleanupdb.bin"" ")]
public void CommandLineForceCleanup(string autocleanupDb)
{
if (!File.Exists(autocleanupDb))
throw new FileNotFoundException(autocleanupDb);
Util.s_forceFilesCleanup = true;
Util.s_overrideFilesAutoCleanupDBPath = autocleanupDb;
Util.ExecuteFilesAutoCleanup();
Exit = true;
}
public void Validate()
{
if (Assemblies.Length == 0 && Sources.Length == 0)
throw new Error("command line error, input missing, use /sources() or /assemblies()");
if (Assemblies.Length != 0 && Sources.Length != 0)
throw new Error("command line error, parameters must not be used together: /sources() and /assemblies()");
if (!_testOptionValid)
throw new Error("command line error, invalid test option given. See help for more details");
if (TestOption == TestOptions.Regression)
{
if (OutputDirectory == null || ReferenceDirectory == null)
throw new Error(@"command line error, /test(""Regression"") must define a reference and an output directory (/referencedir() and /outputdir())");
if (!ReferenceDirectory.Exists)
throw new Error("command line error, reference directory doesn't exist: {0}", ReferenceDirectory);
}
else if (ReferenceDirectory != null && TestOption == TestOptions.None)
{
throw new Error("command line error, /referencedir can't be used without a /test()");
}
if (Sources.Length != 0)
Input = InputType.File;
if (Assemblies.Length != 0)
Input = InputType.Assembly;
}
// Will check the presence of the given files and return the list with their full path
public static string[] ValidateFiles(string[] files)
{
string[] fullPathFiles = new string[files.Length];
for (int i = 0; i < files.Length; ++i)
{
fullPathFiles[i] = Path.GetFullPath(files[i]);
if (!File.Exists(fullPathFiles[i]))
throw new Error("error: input file not found: {0}", files[i]);
}
return fullPathFiles;
}
// Will check that the given compilation defines are valid
public static HashSet<string> ValidateDefines(string[] defines)
{
Regex defineValidationRegex = new Regex(@"^\w+$");
HashSet<string> uniqueDefines = new HashSet<string>();
foreach (string define in defines)
{
if (!defineValidationRegex.IsMatch(define))
throw new Error("error: invalid define '{0}', a define must be a single word", define);
if (uniqueDefines.Contains(define))
throw new Error("error: define '{0}' already defined", define);
uniqueDefines.Add(define);
}
return uniqueDefines;
}
}
}
}