Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1d51531
Implemented resolving of AssemblyMetadata attributes
0x6d61726b Oct 17, 2020
f0fd6b9
Update BuildTime if a new version is requested (v0.3 implemention kee…
0x6d61726b Oct 17, 2020
ddd988d
Build configuration name format strings {bconf}, {BCONF} and {bconf:<…
0x6d61726b Oct 17, 2020
ba26cab
Implemented regex match pattern to trigger a build error if the repos…
0x6d61726b Oct 17, 2020
ad5ab2b
{bconf:<sep>:<ref>} and {BCONF:<sep>:<ref>} changed to support case-i…
0x6d61726b Oct 24, 2020
56485af
Apply default revision 'v0.0.0.0' when repository is not yet tagged t…
0x6d61726b Nov 12, 2020
297564b
Fixed ArgumentNullException on ConfigurationName
0x6d61726b Dec 19, 2020
885c758
Fixed whitespace/tab intents
0x6d61726b Dec 19, 2020
089b6d6
added public StrongName signing for Release build configuration
0x6d61726b Jan 2, 2021
78e6227
Merge branch 'patch-02' into patch-03
0x6d61726b Jan 2, 2021
d928359
Merge branch 'patch-01' into patch-03
0x6d61726b Jan 2, 2021
89b2657
allow Visual Studio to run project-specific pre-build event before ex…
0x6d61726b Jan 2, 2021
7f577e1
Merge branch 'patch-04' into patch-03
0x6d61726b Jan 2, 2021
c9a5c89
'Interactive API' extension added
0x6d61726b Jan 2, 2021
a941ade
Merge remote-tracking branch 'upstream/master'
0x6d61726b Jul 16, 2023
c92ce74
Revert changes except 'Interactive API' extension added (clean-up ope…
0x6d61726b Jul 16, 2023
81c4f90
Merge remote-tracking branch 'upstream/master' into patch-03
0x6d61726b Jul 16, 2023
2c46289
Merge remote-tracking branch 'remotes/origin/master' into patch-03
0x6d61726b Jul 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 165 additions & 4 deletions NetRevisionTask/Api.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.IO;
using System.Reflection;

namespace NetRevisionTask
{
Expand All @@ -7,19 +9,24 @@ namespace NetRevisionTask
/// </summary>
public class Api
{
#region MSBuild API

public static string GetVersion(
string projectDir = null,
string requiredVcs = null,
string revisionFormat = null,
string tagMatch = "v[0-9]*",
bool removeTagV = true,
string copyright = null)
string copyright = null,
string configurationName = null,
string errorOnModifiedRepoPattern = null)
{
if (string.IsNullOrEmpty(projectDir))
projectDir = Directory.GetCurrentDirectory();
var logger = new ConsoleLogger();

var (success, _, informationalVersion, _) = Common.GetVersion(projectDir, requiredVcs, revisionFormat, tagMatch, removeTagV, copyright ?? "", logger, true);
var (success, _, informationalVersion, _) = Common.GetVersion(projectDir, requiredVcs, revisionFormat, tagMatch, removeTagV, copyright ?? "", logger, true,
configurationName, errorOnModifiedRepoPattern);
if (!success)
{
return null;
Expand All @@ -33,18 +40,172 @@ public static string GetShortVersion(
string revisionFormat = null,
string tagMatch = "v[0-9]*",
bool removeTagV = true,
string copyright = null)
string copyright = null,
string configurationName = null,
string errorOnModifiedRepoPattern = null)
{
if (string.IsNullOrEmpty(projectDir))
projectDir = Directory.GetCurrentDirectory();
var logger = new ConsoleLogger();

var (success, version, _, _) = Common.GetVersion(projectDir, requiredVcs, revisionFormat, tagMatch, removeTagV, copyright ?? "", logger, true);
var (success, version, _, _) = Common.GetVersion(projectDir, requiredVcs, revisionFormat, tagMatch, removeTagV, copyright ?? "", logger, true,
configurationName, errorOnModifiedRepoPattern);
if (!success)
{
return null;
}
return version;
}

#endregion

#region Interactive API

/// <summary>
/// The instance that contains data about a revision of the project directory.
/// </summary>
public RevisionData RevisionData = null;

/// <summary>
/// The instance of the logger used by the Interactive API.
/// </summary>
private ILogger logger = null;

/// <summary>
/// The revision format template.
/// </summary>
private string revisionFormat = null;

/// <summary>
/// The instance that resolves a revision format with placeholders to a revision ID from the
/// specified revision data.
/// </summary>
private RevisionFormatter revisionFormatter = null;

/// <summary>
/// Create an instance of the Interactive API.
/// </summary>
/// <param name="projectDir">
/// The project directory to process by the version control system.
/// </param>
/// <param name="requiredVcs">
/// The required <see cref="IVcsProvider.Name"/>, or null if any VCS is acceptable.
/// </param>
/// <param name="revisionFormat">The revision format template.</param>
/// <param name="tagMatch">
/// The global pattern of tag names to match. If empty or "*", all tags are accepted.
/// </param>
/// <param name="removeTagV">
/// The value indicating whether a leading "v" followed by a digit will be removed from the
/// tag name.
/// </param>
/// <param name="configurationName">The value of the build configuration name.</param>
/// <returns>True if initialization was successful, false otherwise.</returns>
public Api(
ILogger logger = null,
string projectDir = null,
string requiredVcs = null,
string revisionFormat = null,
string tagMatch = "v[0-9]*",
bool removeTagV = true,
string configurationName = null)
{
// instantiate the console logger if no custom logger was provided
if (logger == null)
{
logger = new ConsoleLogger();
}
this.logger = logger;
logger.Success(typeof(Api).GetTypeInfo().Assembly
.GetCustomAttribute<AssemblyTitleAttribute>().Title + " v" +
typeof(Api).GetTypeInfo().Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
.InformationalVersion);
logger.Trace($"Constructing {typeof(Api).FullName} instance");
logger.Trace($"Assigned logger '{logger.GetType().FullName}'");

// analyze the working directory
RevisionData = Common.ProcessDirectory(projectDir, requiredVcs, tagMatch, logger);
if (!string.IsNullOrEmpty(requiredVcs) && RevisionData.VcsProvider == null)
{
string message = $"The required version control system '{requiredVcs}' is not " +
"available or not used in the project directory";
logger.Error(message);

throw new Exception(message);
}

// initialize the revision format
if (string.IsNullOrEmpty(revisionFormat))
{
revisionFormat = Common.GetRevisionFormat(projectDir, logger, true);
}
if (string.IsNullOrEmpty(revisionFormat))
{
revisionFormat = RevisionData.GetDefaultRevisionFormat(logger);
}
this.revisionFormat = revisionFormat;

// initialize the RevisionFormatter
revisionFormatter = new RevisionFormatter
{
RevisionData = RevisionData,
RemoveTagV = removeTagV,
BuildTime = DateTimeOffset.Now,
ConfigurationName = configurationName
};

// initialization successfully completed
logger.Trace($"Instantiation of {typeof(Api).FullName} successfully completed");
}

/// <summary>
/// Destroy the instance of the Interactive API.
/// </summary>
~Api()
{
// initialization successfully completed
if (logger != null)
{
logger.Trace($"Destructing {typeof(Api).FullName} instance");
}
}

/// <summary>
/// Get the short version.
/// </summary>
/// <returns>The short version on success, null otherwise.</returns>
public string GetShortVersion()
{
return revisionFormatter.ResolveShort(revisionFormat);
}

/// <summary>
/// Get the full (informational) version.
/// </summary>
/// <returns>The full (informational) version on success, null otherwise.</returns>
public string GetVersion()
{
return revisionFormatter.Resolve(revisionFormat);
}

/// <summary>
/// Resolves placeholders in a revision format string using the current data.
/// </summary>
/// <param name="str">The revision format string to resolve.</param>
/// <returns>The resolved revision string.</returns>
public string Resolve(string str)
{
if (string.IsNullOrEmpty(str))
{
return str;
}
else
{
return revisionFormatter.Resolve(str);
}
}

#endregion Interactive API
}
}
26 changes: 23 additions & 3 deletions NetRevisionTask/AssemblyInfoHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public AssemblyInfoHelper(string projectDir, bool throwOnMissingFile, ILogger lo
/// <param name="revOnly">Indicates whether only the last number is replaced by the revision number.</param>
/// <param name="copyrightAttribute">Indicates whether the copyright year is replaced.</param>
/// <param name="echo">Indicates whether the final informational version string is displayed.</param>
/// <param name="metadataAttribute">Indicates whether the AssemblyMetadata attribute is processed.</param>
/// <returns>The name of the patched AssemblyInfo file.</returns>
public string PatchFile(
string patchedFileDir,
Expand All @@ -87,7 +88,8 @@ public string PatchFile(
bool informationalAttribute,
bool revOnly,
bool copyrightAttribute,
bool echo)
bool echo,
bool metadataAttribute)
{
logger?.Trace($@"Patching file ""{fileName}""...");
ReadFileLines(FullFileName);
Expand All @@ -111,7 +113,7 @@ public string PatchFile(
}

// Process all lines in the file
ResolveAllLines(rf, simpleAttributes, informationalAttribute, revOnly, copyrightAttribute, echo);
ResolveAllLines(rf, simpleAttributes, informationalAttribute, revOnly, copyrightAttribute, echo, metadataAttribute);

// Write all lines to the file
string patchedFileName = Path.Combine(patchedFileDir, "Nrt" + Path.GetFileName(fileName));
Expand Down Expand Up @@ -250,7 +252,8 @@ private void WriteFileLines(string patchedFileName)
/// <param name="revOnly">Indicates whether only the last number is replaced by the revision number.</param>
/// <param name="copyrightAttribute">Indicates whether the copyright year is replaced.</param>
/// <param name="echo">Indicates whether the final informational version string is displayed.</param>
private void ResolveAllLines(RevisionFormatter rf, bool simpleAttributes, bool informationalAttribute, bool revOnly, bool copyrightAttribute, bool echo)
/// <param name="metadataAttribute">Indicates whether the AssemblyMetadata attribute is processed.</param>
private void ResolveAllLines(RevisionFormatter rf, bool simpleAttributes, bool informationalAttribute, bool revOnly, bool copyrightAttribute, bool echo, bool metadataAttribute)
{
// Preparing a truncated dotted-numeric version if we may need it
string truncVersion = null;
Expand Down Expand Up @@ -385,6 +388,23 @@ private void ResolveAllLines(RevisionFormatter rf, bool simpleAttributes, bool i
logger?.Trace($@" Replaced ""{match.Groups[2].Value}"" with ""{copyrightText}"".");
}
}

if (metadataAttribute)
{
// Replace the value part of AssemblyMetadata with the resolved string of what
// was already there. Format: [assembly: AssemblyMetadata("Key", "Value")]
match = Regex.Match(
lines[i],
@"^(\s*\" + attrStart + @"\s*assembly\s*:\s*AssemblyMetadata\s*\(\s*"")(.*?)(""\s*,\s*"")(.*?)(""\s*\)\s*\" + attrEnd + @".*)$",
RegexOptions.IgnoreCase);
if (match.Success)
{
string metadataText = rf.Resolve(match.Groups[4].Value);
lines[i] = match.Groups[1].Value + match.Groups[2].Value + match.Groups[3].Value + metadataText + match.Groups[5].Value;
logger?.Success("Found AssemblyMetadata attribute.");
logger?.Trace($@" Replaced [{match.Groups[2].Value}] => ""{match.Groups[4].Value}"" with ""{metadataText}"".");
}
}
}
}

Expand Down
35 changes: 33 additions & 2 deletions NetRevisionTask/Common.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using NetRevisionTask.VcsProviders;

namespace NetRevisionTask
{
internal class Common
{
public static (bool Success, string Version, string InformationalVersion, string Copyright)
GetVersion(string projectDir, string requiredVcs, string revisionFormat, string tagMatch, bool removeTagV, string copyright, ILogger logger, bool warnOnMissing)
GetVersion(string projectDir, string requiredVcs, string revisionFormat, string tagMatch, bool removeTagV, string copyright, ILogger logger, bool warnOnMissing,
string configurationName, string configurationNameErrorPattern)
{
// Analyse working directory
RevisionData data = ProcessDirectory(projectDir, requiredVcs, tagMatch, logger);
Expand All @@ -17,6 +19,10 @@ public static (bool Success, string Version, string InformationalVersion, string
logger.Error($@"The required version control system ""{requiredVcs}"" is not available or not used in the project directory.");
return (false, null, null, null);
}
if (TriggerErrorIfRepoModified(logger, data, configurationNameErrorPattern, configurationName))
{
return (false, null, null, null);
}
if (string.IsNullOrEmpty(revisionFormat))
{
revisionFormat = GetRevisionFormat(projectDir, logger, warnOnMissing);
Expand All @@ -26,7 +32,7 @@ public static (bool Success, string Version, string InformationalVersion, string
revisionFormat = data.GetDefaultRevisionFormat(logger);
}

var rf = new RevisionFormatter { RevisionData = data, RemoveTagV = removeTagV };
var rf = new RevisionFormatter { RevisionData = data, RemoveTagV = removeTagV, BuildTime = DateTimeOffset.Now, ConfigurationName = configurationName };
try
{
return (true, rf.ResolveShort(revisionFormat), rf.Resolve(revisionFormat), rf.Resolve(copyright));
Expand Down Expand Up @@ -145,5 +151,30 @@ public static string GetRevisionFormat(string projectDir, ILogger logger, bool w
}
return revisionFormat;
}

/// <summary>
/// Determines if a modified repository that matches the given pattern triggers a build error.
/// </summary>
/// <param name="data">The data about the current revision of the source directory.</param>
/// <param name="cfgNamePattern">The match pattern that shall trigger the error.</param>
/// <param name="cfgName">The name of the build configuration.</param>
/// <returns>True if an error shall be triggered, false otherwise.</returns>
public static bool TriggerErrorIfRepoModified(ILogger logger, RevisionData data, string cfgNameMatchPattern, string cfgName)
{
if (!string.IsNullOrEmpty(cfgNameMatchPattern) && !string.IsNullOrEmpty(cfgName))
{
if (data.IsModified)
{
var match = Regex.Match(cfgName, $@"^{cfgNameMatchPattern}$", RegexOptions.IgnoreCase);
if (match.Success)
{
logger.Error($@"The ""{cfgName}"" configuration does not allow builds with a modified {data.VcsProvider.Name} repository.");
return true;
}
}
}

return false;
}
}
}
2 changes: 1 addition & 1 deletion NetRevisionTask/ILogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/// <summary>
/// An interface that is used for logging internal and error events.
/// </summary>
internal interface ILogger
public interface ILogger
{
/// <summary>
/// Logs a raw output line of an executed application.
Expand Down
Loading