/*
* Filename: Main.cs
* Product: Versioning Controlled Build
* Solution: BuildAutoIncrement
* Project: CommandLine
* Description: Entry class for command-line utility.
* Copyright: Julijan �ribar, 2004-2013
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the author(s) be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
using System;
using System.Collections;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Resources;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
namespace BuildAutoIncrement {
/// <summary>
/// Main class used for command line tool.
/// </summary>
public class CommandLineMain {
#region FileDialogProxy
/// <summary>
/// Class used to ensure FileDialog is shown on taskbar when opened
/// from command prompt.
/// </summary>
private class FileDialogProxy : System.Windows.Forms.Form {
public FileDialogProxy() {
ShowInTaskbar = true;
Text = Title;
Icon = AppIcon;
Size = Size.Empty;
Opacity = 0;
}
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
if (m.Msg == (int)Win32Api.WM.SHOWWINDOW) {
if ((int)m.WParam == 1) {
// hack to display form in the taskbar
WindowState = FormWindowState.Minimized;
#if !DEBUG
TopMost = true;
#endif
OpenFileDialog fd = new OpenFileDialog();
fd.Filter = SolutionFilesFilter;
fd.Multiselect = false;
fd.Title = OpenSolutionFile;
DialogResult = fd.ShowDialog(this);
Filename = fd.FileName;
Close();
}
}
}
public string Filename;
}
#endregion // FileDialogProxy
#region Constructors
/// <summary>
/// Creates empty <c>CommandLineMain</c> object.
/// </summary>
private CommandLineMain() {
}
/// <summary>
/// Creates <c>CommandLineMain</c> object with command line arguments
/// provided.
/// </summary>
/// <param name="args">
/// Command line arguments.
/// </param>
public CommandLineMain(string[] args) : this() {
Console.WriteLine(CreateInfoFromAttributes());
if (args.Length == 1 && args[0] == "/?") {
WriteUsage();
return;
}
VisualStyles.EnableStyles();
m_commandLineArgs = new CommandLineArgs(args);
Debug.Assert(m_commandLineArgs != null);
SolutionFilename = m_commandLineArgs.SolutionFilename;
Debug.Assert(SolutionFilename != null);
// if no solution filename has been supplied, show OpenFileDialog
// to select a solution or desktop file
if (SolutionFilename.Length == 0) {
FileDialogProxy fdp = new FileDialogProxy();
if (fdp.ShowDialog() == DialogResult.OK) {
SolutionFilename = fdp.Filename;
}
}
if (File.Exists(SolutionFilename)) {
// load configuration
VcbConfiguration configuration = ConfigurationPersister.Instance.Configuration;
if (!configuration.ConfigurationFileRead)
Console.WriteLine("Warning: Failed to read configuration file - using default configuration.");
// apply command line switches to modify configuration
ApplyCommandLineArgsToIncrementScheme(configuration.NumberingOptions);
// now validate version string
if (m_commandLineArgs.NewVersion.Length > 0 && !ProjectVersion.IsValidPattern(m_commandLineArgs.NewVersion)) {
if (MustBeValidVersionString(configuration.NumberingOptions)) {
throw new InvalidCommandLineArgumentException(string.Format(InvalidVersionFormat, m_commandLineArgs.NewVersion));
}
else if (!configuration.NumberingOptions.AllowArbitraryInformationalVersion) {
Console.WriteLine(string.Format(InvalidVersionFormat, m_commandLineArgs.NewVersion));
}
}
// create file readers
if (SolutionFilename.ToLower().EndsWith(".dsw")) {
m_sfr = new DswFileReader(SolutionFilename, configuration, m_commandLineArgs.SourceSafeUserOptions);
}
else if (SolutionFilename.ToLower().EndsWith(".sln")) {
m_sfr = SlnFileReader.SlnFileReaderFactory.GetSlnFileReader(SolutionFilename, configuration, m_commandLineArgs.SourceSafeUserOptions);
}
else {
throw new InvalidCommandLineArgumentException(NotSupportedFilesError);
}
if (ValidateSolutionIntegrity() && ValidateProjectnames()) {
Debug.Assert(m_sfr != null);
m_sfr.ApplyConfiguration(configuration);
IProjectFilter projectFilter = new ProjectFilterByName(configuration.NumberingOptions.IncludeSetupProjects, configuration.DisplayOptions.ShowNonVersionableProjects,
configuration.DisplayOptions.ShowSubProjectRoot, configuration.DisplayOptions.ShowEnterpriseTemplateProjectRoot,
m_commandLineArgs.ProjectsAllowedForVersionModification, m_commandLineArgs.ProjectsToExclude, m_commandLineArgs.ProjectsToForce);
m_sfr.ApplyFilter(projectFilter);
if (m_commandLineArgs.StartGui) {
StartGui();
}
else {
ExecuteBatchCommand(configuration.NumberingOptions);
}
}
}
else {
Console.WriteLine(NoSolutionFileProvided);
}
#if DEBUG
Console.WriteLine("Finished. Press any key...");
Console.ReadLine();
#endif
}
#endregion // Constructors
#region Private methods
/// <summary>
/// Executes batch command.
/// </summary>
/// <param name="sfr">
/// <c>SolutionFileReader</c> object used to explore the solution.
/// </param>
/// <param name="commandLineArgs">
/// Command line arguments.
/// </param>
private void ExecuteBatchCommand(NumberingOptions numberingOptions) {
Debug.Assert(m_sfr != null);
Debug.Assert(m_commandLineArgs != null);
// find to which versions new version should be applied
AssemblyVersionType applyToTypes = numberingOptions.DefaultVersionType;
if (numberingOptions.ApplyToAllTypes)
applyToTypes = AssemblyVersionType.All;
// if command-line provided version(s) to apply changes to, then
// use them and override configuration settings
if (m_commandLineArgs.ApplyToTypes != AssemblyVersionType.None)
applyToTypes = m_commandLineArgs.ApplyToTypes;
MarkForUpdate(applyToTypes);
if (SourceSafeCommandLine.IsUnderSourceSafeControl(SolutionFilename))
CheckOutItems();
// apply versions to individual types
if ((applyToTypes & AssemblyVersionType.AssemblyVersion) == AssemblyVersionType.AssemblyVersion)
ApplyVersion(AssemblyVersionType.AssemblyVersion, numberingOptions);
if ((applyToTypes & AssemblyVersionType.AssemblyInformationalVersion) == AssemblyVersionType.AssemblyInformationalVersion)
ApplyVersion(AssemblyVersionType.AssemblyInformationalVersion, numberingOptions);
if ((applyToTypes & AssemblyVersionType.AssemblyFileVersion) == AssemblyVersionType.AssemblyFileVersion)
ApplyVersion(AssemblyVersionType.AssemblyFileVersion, numberingOptions);
if (m_commandLineArgs.DisplaySummaryOptions.DoOutputSummary)
OutputSummary();
}
/// <summary>
/// Marks projects for update according to configuration settings
/// and command line arguments.
/// </summary>
/// <param name="applyToTypes">
/// <c>AssemblyVersionType</c> flags indicating which version types
/// may be updated.
/// </param>
private void MarkForUpdate(AssemblyVersionType applyToTypes) {
Debug.Assert(m_sfr != null);
foreach (ProjectInfo pi in m_sfr.ProjectInfoList) {
if (m_commandLineArgs.ApplyToAllProjects || pi.Modified) {
pi.MarkAssemblyVersionsForUpdate(applyToTypes);
}
else {
// if in the list of projects forced for update
foreach (string projectName in m_commandLineArgs.ProjectsToForce) {
if (pi.CompareTo(projectName) == 0) {
pi.MarkAssemblyVersionsForUpdate(applyToTypes);
}
}
}
}
}
/// <summary>
/// Checks out version files if marked for updated.
/// </summary>
private void CheckOutItems() {
ArrayList projectsToCheckOut = new ArrayList();
SourceSafeCommandLine sscl = new SourceSafeCommandLine(SolutionFilename, m_commandLineArgs.SourceSafeUserOptions);
foreach (ProjectInfo pi in m_sfr.ProjectInfoList) {
if (pi.ToUpdate) {
projectsToCheckOut.Add(pi);
}
}
m_sfr.CheckOutProjectVersionFiles((ProjectInfo[])projectsToCheckOut.ToArray(typeof(ProjectInfo)));
}
/// <summary>
/// Outputs version update summary.
/// </summary>
private void OutputSummary() {
Debug.Assert(m_sfr != null && m_sfr.UpdateSummary != null);
string updateSummary = m_sfr.UpdateSummary.ToString();
Debug.Assert(updateSummary.Length > 0);
string failedToCheckOut = SummaryOfFailedToCheckOut();
string filesCheckedOut = SummaryOfCheckedOutFiles();
// output this to console always:
Console.Write(failedToCheckOut);
if (m_commandLineArgs.DisplaySummaryOptions.ToConsole) {
Console.Write(filesCheckedOut);
Console.WriteLine(updateSummary);
}
// create summary file name
string summaryFilename = m_commandLineArgs.DisplaySummaryOptions.SummaryFilename;
if (summaryFilename.Length == 0) {
summaryFilename = UpdateSummary.CreateSummaryFilename(m_sfr.SolutionName);
}
// if files already exists, delete it
if (File.Exists(summaryFilename)) {
try {
File.Delete(summaryFilename);
}
catch (Exception exception) {
Console.WriteLine(exception.Message);
}
}
// output summary to file
if (m_commandLineArgs.DisplaySummaryOptions.ToFile) {
using (StreamWriter sw = new StreamWriter(summaryFilename)) {
sw.Write(failedToCheckOut);
sw.Write(filesCheckedOut);
sw.WriteLine(updateSummary);
}
}
}
/// <summary>
/// Gets a formated string of checked out filenames.
/// </summary>
/// <returns>
/// String of check-out operation.
/// </returns>
private string SummaryOfCheckedOutFiles() {
using (StringWriter sw = new StringWriter()) {
if (m_sfr.FilesCheckedOut.Length > 0) {
sw.WriteLine(FilesCheckedOutCaption);
foreach (string filename in m_sfr.FilesCheckedOut) {
sw.WriteLine(filename);
}
sw.WriteLine();
}
sw.Flush();
return sw.ToString();
}
}
/// <summary>
/// Gets a formated string of files failed to check out.
/// </summary>
/// <returns>
/// Summary for files failed to check out.
/// </returns>
private string SummaryOfFailedToCheckOut() {
Debug.Assert(m_sfr.FilesFailedToCheckOut != null);
using (StringWriter sw = new StringWriter()) {
if (m_sfr.FilesFailedToCheckOut.Length > 0) {
sw.WriteLine(FailedToCheckOutCaption);
for (int i = 0; i < m_sfr.FilesFailedToCheckOut.Length; i++) {
sw.WriteLine(m_sfr.FilesFailedToCheckOut[i]);
}
sw.WriteLine();
}
sw.Flush();
return sw.ToString();
}
}
/// <summary>
/// Applies new version(s).
/// </summary>
/// <param name="sfr"></param>
/// <param name="commandLineArgs"></param>
/// <param name="versionToChange"></param>
/// <summary>
/// Applies new version(s).
/// </summary>
/// <param name="versionToChange">
/// <c>AssemblyVersionType</c> flags for version types to change.
/// </param>
/// <param name="numberingOptions">
/// <c>NumberingOptions</c> object.
/// </param>
private void ApplyVersion(AssemblyVersionType versionToChange, NumberingOptions numberingOptions) {
Debug.Assert(m_sfr != null);
Debug.Assert(m_commandLineArgs != null);
Debug.Assert(versionToChange != AssemblyVersionType.All && versionToChange != AssemblyVersionType.None);
NewVersionProvider nvp = new NewVersionProvider(numberingOptions);
foreach (ProjectInfo pi in m_sfr.ProjectsToUpdate) {
if (pi.CurrentAssemblyVersions.ContainsVersion(versionToChange)) {
// if a new version hasn't been provided
if (m_commandLineArgs.NewVersion.Length == 0) {
if (pi.ToUpdate) {
pi.SetToBecomeVersion(nvp);
string newVersion = pi.ToBecomeAssemblyVersions.HighestProjectVersion.ToString();
SaveVersion(pi, versionToChange, newVersion);
}
}
else {
if (ProjectVersion.IsValidPattern(m_commandLineArgs.NewVersion)) {
int buildAndRevisionResetValue = (int)numberingOptions.ResetBuildAndRevisionTo;
string newVersion = ProjectVersion.ApplyVersionPattern(m_commandLineArgs.NewVersion, pi.CurrentAssemblyVersions[versionToChange].ToString(), buildAndRevisionResetValue);
SaveVersion(pi, versionToChange, newVersion);
}
else {
// irregular pattern cannot be applied to ProductVersion of VC++ projects
if (pi.ProjectTypeInfo.ProjectType != ProjectType.VCppProject) {
Debug.Assert(versionToChange == AssemblyVersionType.AssemblyInformationalVersion);
SaveVersion(pi, versionToChange, m_commandLineArgs.NewVersion);
}
}
}
}
}
}
/// <summary>
/// Checks if validation of version string may be performed.
/// </summary>
/// <param name="numberingOptions">
/// <c>NumberingOptions</c> object.
/// </param>
/// <returns>
/// <c>true</c> if version string must be a valid one.
/// </returns>
private bool MustBeValidVersionString(NumberingOptions numberingOptions) {
Debug.Assert(numberingOptions != null);
Debug.Assert(m_commandLineArgs != null);
// if defined explicitely by command-line argument
if (m_commandLineArgs.ApplyToTypes == AssemblyVersionType.AssemblyInformationalVersion)
return false;
// else use configuration settings
if ((m_commandLineArgs.ApplyToTypes == AssemblyVersionType.None) && !numberingOptions.ApplyToAllTypes && (numberingOptions.DefaultVersionType == AssemblyVersionType.AssemblyInformationalVersion))
return false;
return true;
}
/// <summary>
/// Saves version(s).
/// </summary>
/// <param name="pi">
/// <c>ProjectInfo</c> for which new version must be saved.
/// </param>
/// <param name="versionToChange">
/// <c>AssemblyVersionType</c> flag for version to change.
/// </param>
/// <param name="newVersion">
/// New version to apply.
/// </param>
private void SaveVersion(ProjectInfo pi, AssemblyVersionType versionToChange, string newVersion) {
Debug.Assert(m_sfr != null);
Debug.Assert(pi != null);
Debug.Assert(versionToChange != AssemblyVersionType.All && versionToChange != AssemblyVersionType.None);
Debug.Assert(newVersion != null && newVersion.Length > 0);
if (pi.Save(versionToChange, newVersion)) {
m_sfr.UpdateSummary.SetUpdated(pi, versionToChange, newVersion);
}
}
/// <summary>
/// Apply command line arguments to configuration.
/// </summary>
/// <param name="numberingOptions">
/// <c>NumberingOptions</c> object.
/// </param>
private void ApplyCommandLineArgsToIncrementScheme(NumberingOptions numberingOptions) {
numberingOptions.ApplyToAllTypes = m_commandLineArgs.ApplyToTypes != numberingOptions.DefaultVersionType;
switch (m_commandLineArgs.SynchronizeProjects) {
case CommandLineArgs.ProjectsSynchronization.IncrementAndSynchronize:
numberingOptions.BatchCommandIncrementScheme = BatchCommandIncrementScheme.IncrementAllAndSynchronize;
break;
case CommandLineArgs.ProjectsSynchronization.Synchronize:
numberingOptions.BatchCommandIncrementScheme = BatchCommandIncrementScheme.IncrementModifiedOnlyAndSynchronize;
break;
case CommandLineArgs.ProjectsSynchronization.None:
numberingOptions.BatchCommandIncrementScheme = BatchCommandIncrementScheme.IncrementModifiedIndependently;
break;
case CommandLineArgs.ProjectsSynchronization.NotDefined:
// use as configured
break;
default:
Debug.Assert(false, "Not supported value of CommandLineArgs.ProjectsSynchronization");
break;
}
}
/// <summary>
/// Starts GUI form if "/g" command line switch was provided.
/// </summary>
/// <param name="sb">
/// Solution browser used to fill the listview.
/// </param>
private void StartGui() {
MainForm mainForm = new MainForm(m_sfr);
#if !DEBUG
mainForm.TopMost = true;
#endif
mainForm.ShowInTaskbar = true;
mainForm.BuildButtonsVisible = false;
mainForm.Icon = AppIcon;
mainForm.MinimizeBox = true;
if (mainForm.ShowDialog() != DialogResult.Cancel) {
mainForm.SaveVersions();
}
}
/// <summary>
/// Outputs command line tool usage to the console.
/// </summary>
private void WriteUsage() {
Console.WriteLine(Description);
Console.WriteLine();
string name = Assembly.GetEntryAssembly().GetName().Name.ToUpper();
CommandLineArgs.OuputDescription(name);
}
/// <summary>
/// Creates product information from custom attributes.
/// </summary>
/// <returns>
/// String with product information.
/// </returns>
private string CreateInfoFromAttributes() {
string title = "";
string product = "";
string copyright = "";
string company = "";
foreach (object obj in Assembly.GetExecutingAssembly().GetCustomAttributes(true)) {
AssemblyProductAttribute assProduct = obj as AssemblyProductAttribute;
if (assProduct != null)
product = assProduct.Product;
AssemblyTitleAttribute assTitle = obj as AssemblyTitleAttribute;
if (assTitle != null)
title = assTitle.Title;
AssemblyCopyrightAttribute assCopyright = obj as AssemblyCopyrightAttribute;
if (assCopyright != null)
copyright = assCopyright.Copyright.Replace("�", "(c)");
AssemblyCompanyAttribute assCompany = obj as AssemblyCompanyAttribute;
if (assCompany != null)
company = assCompany.Company;
}
StringBuilder sb = new StringBuilder(product);
Version ver = Assembly.GetExecutingAssembly().GetName().Version;
sb.AppendFormat(" {0}.{1}.{2}", ver.Major, ver.Minor, ver.Revision);
sb.Append(Environment.NewLine);
sb.Append(copyright);
sb.Append(Environment.NewLine);
return sb.ToString();
}
/// <summary>
/// Validates solution integrity i.e. if all project files and all
/// items in each project file are found.
/// </summary>
/// <returns>
/// <c>true</c> if solution is complete.
/// </returns>
private bool ValidateSolutionIntegrity() {
using (StringWriter sw = new StringWriter()) {
foreach (string projectName in m_sfr.ProjectsNotFound) {
sw.WriteLine(ProjectFileNotFound, projectName);
}
IDictionaryEnumerator en = m_sfr.FilesNotFound.GetEnumerator();
while (en.MoveNext()) {
string projectName = (string)en.Key;
sw.WriteLine(FilesNotFoundForProject, projectName);
string[] filesNotFound = (string[])en.Value;
foreach (string filename in filesNotFound) {
sw.WriteLine(" {0}", filename);
}
}
sw.Flush();
string output = sw.ToString();
if (output.Length > 0)
Console.WriteLine(sw.ToString());
#if DEBUG
return true;
#else
return output.Length == 0;
#endif
}
}
/// <summary>
/// Validates if project names provided by some switches exist in the
/// solution.
/// </summary>
/// <returns>
/// <c>true</c> if all filenames provided are correct or a switch to
/// ignore invalid names is set.
/// </returns>
private bool ValidateProjectnames() {
Debug.Assert(m_commandLineArgs != null);
using (StringWriter sw = new StringWriter()) {
foreach (string projectName in m_commandLineArgs.ProjectsAllowedForVersionModification) {
if (!m_sfr.ProjectInfoList.Contains(projectName)) {
sw.WriteLine(ProjectNotFound, projectName);
}
}
foreach (string projectName in m_commandLineArgs.ProjectsToExclude) {
if (!m_sfr.ProjectInfoList.Contains(projectName)) {
sw.WriteLine(ProjectNotFound, projectName);
}
}
foreach (string projectName in m_commandLineArgs.ProjectsToForce) {
if (!m_sfr.ProjectInfoList.Contains(projectName)) {
sw.WriteLine(ProjectNotFound, projectName);
}
}
sw.Flush();
string output = sw.ToString();
if (output.Length > 0)
Console.WriteLine(sw.ToString());
return output.Length == 0 || !m_commandLineArgs.CheckProjectsExistance;
}
}
#endregion // Private methods
#region Private fields
private SolutionFileReader m_sfr = null;
private CommandLineArgs m_commandLineArgs = null;
private readonly string SolutionFilename = null;
#endregion // Private fields
#region Static and const strings
private const string Title = "Versioning Controlled Build";
private const string Description = "Increments versions of modules in Visual Studio solution.";
private const string OpenSolutionFile = "Open Solution File";
private const string SolutionFilesFilter = "All solution files (*.sln;*.dsw)|*.sln;*.dsw|Solution files (*.sln)|*.sln|Compatible desktop files (*.dsw)|*.dsw";
private const string NotSupportedFilesError = "Only files with \'.sln\' and \'.dsw\' extensions are supported.";
private const string NoSolutionFileProvided = "Solution file name not provided - application closed without any changes.";
private const string ProjectFileNotFound = "ERROR: Project file '{0}' not found.";
private const string FilesNotFoundForProject = "ERROR: Following file(s) in project \'{0}\' not found:";
private const string ProjectNotFound = "Project '{0}' not found in current solution.";
private const string FailedToCheckOutCaption = "Failed to check out following files:";
private const string FilesCheckedOutCaption = "Files checked out:";
private const string InvalidVersionFormat = "Invalid version pattern format: \'{0}\'";
private static readonly Icon AppIcon;
#endregion // Static and const strings
#region Static constructor and methods
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args) {
try {
CommandLineMain clm = new CommandLineMain(args);
}
catch (Exception exception) {
#if DEBUG
Console.WriteLine("ERROR: {0}", exception.Message);
Console.WriteLine(exception.ToString());
Console.ReadLine();
#else
Console.WriteLine("ERROR: {0}", exception.Message);
#endif
}
}
/// <summary>
/// Static constructor.
/// </summary>
static CommandLineMain() {
ResourceManager resources = new ResourceManager("BuildAutoIncrement.Resources.CommandLine", typeof(CommandLineMain).Assembly);
Debug.Assert(resources != null);
AppIcon = ((System.Drawing.Icon)(resources.GetObject("AppIcon")));
Debug.Assert(AppIcon != null);
}
#endregion // Static constructor and methods
}
}