Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 49 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@ jobs:
dotnet-version: ['8.x', '9.x', '10.x']

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Download package
uses: actions/download-artifact@v7
with:
Expand Down Expand Up @@ -253,13 +256,30 @@ jobs:
|| { echo "✗ Self-validation failed"; exit 1; }
echo "✓ Self-validation succeeded"

- name: Capture tool versions
shell: bash
run: |
echo "Capturing tool versions..."
# Create short job ID: int-win-8, int-win-9, int-ubuntu-8, etc.
OS_SHORT=$(echo "${{ matrix.os }}" | sed 's/windows-latest/win/;s/ubuntu-latest/ubuntu/')
DOTNET_SHORT=$(echo "${{ matrix.dotnet-version }}" | sed 's/\.x$//')
JOB_ID="int-${OS_SHORT}-${DOTNET_SHORT}"
versionmark --capture --job-id "${JOB_ID}" -- dotnet git
echo "✓ Tool versions captured"

- name: Upload validation test results
if: always()
uses: actions/upload-artifact@v6
with:
name: validation-test-results-${{ matrix.os }}-dotnet${{ matrix.dotnet-version }}
path: validation-${{ matrix.os }}-dotnet${{ matrix.dotnet-version }}.trx

- name: Upload version capture
uses: actions/upload-artifact@v6
with:
name: version-capture-${{ matrix.os }}-dotnet${{ matrix.dotnet-version }}
path: versionmark-int-*.json

# Builds the supporting documentation including user guides, requirements,
# trace matrices, code quality reports, and build notes.
build-docs:
Expand Down Expand Up @@ -297,6 +317,13 @@ jobs:
name: codeql-sarif
path: codeql-results

- name: Download all version captures
uses: actions/download-artifact@v7
with:
path: version-captures
pattern: 'version-capture-*'
continue-on-error: true

- name: Setup dotnet
uses: actions/setup-dotnet@v5
with:
Expand All @@ -317,6 +344,14 @@ jobs:
- name: Restore Tools
run: dotnet tool restore

- name: Capture tool versions for build-docs
shell: bash
run: |
echo "Capturing tool versions..."
versionmark --capture --job-id "build-docs" -- \
dotnet git node npm pandoc weasyprint sarifmark sonarmark reqstream buildmark
echo "✓ Tool versions captured"

- name: Generate Requirements Report, Justifications, and Trace Matrix
run: >
dotnet reqstream
Expand Down Expand Up @@ -370,6 +405,20 @@ jobs:
--report docs/buildnotes.md
--report-depth 1

- name: Publish Tool Versions
shell: bash
run: |
echo "Publishing tool versions..."
versionmark --publish --report docs/buildnotes/versions.md --report-depth 1 \
-- "versionmark-*.json" "version-captures/**/versionmark-*.json"
echo "✓ Tool versions published"

- name: Display Tool Versions Report
shell: bash
run: |
echo "=== Tool Versions Report ==="
cat docs/buildnotes/versions.md

- name: Generate Build Notes HTML with Pandoc
shell: bash
run: >
Expand Down
1 change: 1 addition & 0 deletions docs/buildnotes/definition.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ input-files:
- docs/buildnotes/title.txt
- docs/buildnotes/introduction.md
- docs/buildnotes.md
- docs/buildnotes/versions.md
template: template.html
table-of-contents: true
number-sections: true
28 changes: 11 additions & 17 deletions src/DemaConsulting.VersionMark/MarkdownFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,49 +98,43 @@ private static string GenerateMarkdown(
foreach (var tool in sortedTools)
{
var versions = toolVersions[tool];
var versionEntry = FormatVersionEntry(versions);
markdown.AppendLine($"- **{tool}**: {versionEntry}");
FormatVersionEntries(markdown, tool, versions);
}

return markdown.ToString();
}

/// <summary>
/// Formats a version entry for a tool based on whether versions are uniform or different.
/// Formats version entries for a tool as multiple bullets when versions differ.
/// </summary>
/// <param name="markdown">The StringBuilder to append to.</param>
/// <param name="tool">The tool name.</param>
/// <param name="versions">List of job ID and version pairs for a tool.</param>
/// <returns>Formatted version string with job IDs when appropriate.</returns>
private static string FormatVersionEntry(List<(string JobId, string Version)> versions)
private static void FormatVersionEntries(StringBuilder markdown, string tool, List<(string JobId, string Version)> versions)
{
// Check if all versions are the same
var distinctVersions = versions.Select(v => v.Version).Distinct().ToList();

// If all versions are the same, show "All jobs"
// If all versions are the same, show single entry without job IDs
if (distinctVersions.Count == 1)
{
return $"{distinctVersions[0]} (All jobs)";
markdown.AppendLine($"- **{tool}**: {distinctVersions[0]}");
return;
}

// Otherwise, group by version and show job IDs
// When versions differ across jobs, we need to show which jobs have which versions
// Otherwise, create multiple bullets - one for each version group
var versionGroups = versions
.GroupBy(v => v.Version)
.OrderBy(g => g.Key, StringComparer.OrdinalIgnoreCase);

// Build formatted version strings with subscripted job IDs
// Each version gets its own entry showing which jobs use it
var formattedVersions = new List<string>();
foreach (var group in versionGroups)
{
// For each unique version, collect and sort the job IDs that use it
var jobIds = group.Select(v => v.JobId).OrderBy(j => j, StringComparer.OrdinalIgnoreCase);
var jobIdList = string.Join(", ", jobIds);

// Format as "version <sub>(job1, job2)</sub>" for HTML subscript rendering
formattedVersions.Add($"{group.Key} <sub>({jobIdList})</sub>");
// Format as separate bullet with tool name and version, showing which jobs use it
markdown.AppendLine($"- **{tool}**: {group.Key} ({jobIdList})");
}

// Join all version entries with commas to create the final output
return string.Join(", ", formattedVersions);
}
}
6 changes: 3 additions & 3 deletions src/DemaConsulting.VersionMark/Validation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,15 @@ private static void RunCaptureTest(Context context, DemaConsulting.TestResults.T
test.ErrorMessage = $"Expected job-id 'test-job', got '{versionInfo.JobId}'";
context.WriteError($"✗ Captures Versions Test - FAILED: Wrong job-id");
}
// Verify dotnet version was captured
else if (!versionInfo.Versions.ContainsKey("dotnet"))
// Verify dotnet version was captured and is not empty
else if (!versionInfo.Versions.TryGetValue("dotnet", out var dotnetVersion))
{
test.Outcome = DemaConsulting.TestResults.TestOutcome.Failed;
test.ErrorMessage = "Output JSON missing 'dotnet' version";
context.WriteError($"✗ Captures Versions Test - FAILED: Missing dotnet version");
}
// Verify dotnet version is not empty
else if (string.IsNullOrWhiteSpace(versionInfo.Versions["dotnet"]))
else if (string.IsNullOrWhiteSpace(dotnetVersion))
{
test.Outcome = DemaConsulting.TestResults.TestOutcome.Failed;
test.ErrorMessage = "Dotnet version is empty";
Expand Down
24 changes: 13 additions & 11 deletions src/DemaConsulting.VersionMark/VersionMarkConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,27 +336,22 @@ public VersionInfo FindVersions(IEnumerable<string> toolNames, string jobId)
/// </remarks>
private static string RunCommand(string command)
{
// Split command into executable and arguments
var parts = command.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0)
{
throw new InvalidOperationException("Command is empty");
}

var fileName = parts[0];
var arguments = parts.Length > 1 ? parts[1] : string.Empty;
// To support .cmd/.bat files on Windows and shell features on all platforms,
// we run commands through the appropriate shell using ArgumentList to avoid escaping issues
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

try
{
var processStartInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
FileName = isWindows ? "cmd.exe" : "/bin/sh",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
processStartInfo.ArgumentList.Add(isWindows ? "/c" : "-c");
processStartInfo.ArgumentList.Add(command);

using var process = Process.Start(processStartInfo);
if (process == null)
Expand All @@ -371,6 +366,13 @@ private static string RunCommand(string command)
var output = outputTask.Result;
var error = errorTask.Result;

// Check exit code - if non-zero, command failed
if (process.ExitCode != 0)
{
var errorMessage = string.IsNullOrEmpty(error) ? output : error;
throw new InvalidOperationException($"Failed to run command '{command}': {errorMessage}");
}

// Combine stdout and stderr with newline separator for better debuggability
if (string.IsNullOrEmpty(error))
{
Expand Down
Loading