Skip to content

Commit 38f058c

Browse files
gfraiteurclaude
andcommitted
Add NuGet config generation for TeamCity restored artifacts and PowerShell build step improvements.
- Refactor NuGet config generation using inheritance pattern (NuGetConfigGenerator base class with Standard, WSL, and RestoredArtifacts generators) - Add nuget.restored.config generation for TeamCity artifact restoration scenario with relative paths and dependency filtering - Move NuGet-related files to Build/Files/NuGet/ subdirectory - Create PowerShellCommandBuildStep for executing commands directly - Rename PowerShellBuildStep to PowerShellScriptBuildStep - Enhance PowershellAdditionalCiBuildConfiguration to automatically copy nuget.restored.config when BuildSnapshotDependency is set Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent a238289 commit 38f058c

13 files changed

+997
-738
lines changed

src/PostSharp.Engineering.BuildTools/Build/BuildCommand.cs

Lines changed: 374 additions & 367 deletions
Large diffs are not rendered by default.

src/PostSharp.Engineering.BuildTools/Build/Files/MasterGenerator.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
22

3+
using PostSharp.Engineering.BuildTools.Build.Files.NuGet;
34
using PostSharp.Engineering.BuildTools.Build.Model;
4-
using PostSharp.Engineering.BuildTools.Dependencies.Model;
55
using PostSharp.Engineering.BuildTools.Utilities;
66
using System.Diagnostics.CodeAnalysis;
7-
using System.Runtime.InteropServices;
87

98
namespace PostSharp.Engineering.BuildTools.Build.Files;
109

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
2+
3+
namespace PostSharp.Engineering.BuildTools.Build.Files.NuGet;
4+
5+
/// <summary>
6+
/// Writes <c>nuget.config</c>.
7+
/// </summary>
8+
internal static class NuGetConfigFile
9+
{
10+
internal static bool TryWrite( BuildContext context, DependenciesConfigurationFile dependenciesConfigurationFile, BuildConfiguration configuration )
11+
{
12+
var product = context.Product;
13+
14+
if ( !product.GenerateNuGetConfig )
15+
{
16+
return true;
17+
}
18+
19+
// Fetch to resolve the VersionFile properties.
20+
if ( !dependenciesConfigurationFile.Fetch( context ) )
21+
{
22+
return false;
23+
}
24+
25+
// Generate standard nuget.config
26+
var standardGenerator = new StandardNuGetConfigGenerator();
27+
28+
if ( !standardGenerator.TryGenerate( context, dependenciesConfigurationFile, configuration ) )
29+
{
30+
return false;
31+
}
32+
33+
// Generate WSL version if AddWslSupport is enabled
34+
if ( product.AddWslSupport )
35+
{
36+
var wslGenerator = new WslNuGetConfigGenerator();
37+
38+
if ( !wslGenerator.TryGenerate( context, dependenciesConfigurationFile, configuration ) )
39+
{
40+
return false;
41+
}
42+
}
43+
44+
return true;
45+
}
46+
47+
/// <summary>
48+
/// Generates nuget.restored.config for TeamCity artifact restoration scenario.
49+
/// This file uses relative paths and assumes dependencies are in dependencies/&lt;name&gt;/ directory.
50+
/// </summary>
51+
internal static bool TryWriteRestoredArtifacts(
52+
BuildContext context,
53+
DependenciesConfigurationFile dependenciesConfigurationFile,
54+
BuildConfiguration configuration )
55+
{
56+
var generator = new RestoredArtifactsNuGetConfigGenerator();
57+
58+
return generator.TryGenerate( context, dependenciesConfigurationFile, configuration );
59+
}
60+
}

src/PostSharp.Engineering.BuildTools/Build/Files/NuGetConfigFile.cs renamed to src/PostSharp.Engineering.BuildTools/Build/Files/NuGet/NuGetConfigGenerator.cs

Lines changed: 76 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,47 @@
1-
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
1+
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
22

33
using PostSharp.Engineering.BuildTools.Dependencies.Model;
44
using PostSharp.Engineering.BuildTools.Utilities;
55
using System;
66
using System.IO;
77
using System.Xml.Linq;
88

9-
namespace PostSharp.Engineering.BuildTools.Build.Files;
9+
namespace PostSharp.Engineering.BuildTools.Build.Files.NuGet;
1010

1111
/// <summary>
12-
/// Writes <c>nuget.config</c>.
12+
/// Abstract base class for generating nuget.config files with different path strategies.
1313
/// </summary>
14-
internal static class NuGetConfigFile
14+
internal abstract class NuGetConfigGenerator
1515
{
16-
private static string ConvertToWslPath( string path )
17-
{
18-
// Convert Windows path to WSL: C:\path -> /mnt/c/path
19-
if ( path is [_, ':', _, ..] && (path[2] == '\\' || path[2] == '/') )
20-
{
21-
var drive = char.ToLower( path[0], System.Globalization.CultureInfo.InvariantCulture );
22-
var remainder = path.Substring( 2 ).Replace( "\\", "/", StringComparison.Ordinal );
23-
24-
return $"/mnt/{drive}{remainder}";
25-
}
26-
27-
return path;
28-
}
29-
30-
internal static bool TryWrite( BuildContext context, DependenciesConfigurationFile dependenciesConfigurationFile, BuildConfiguration configuration )
31-
{
32-
var product = context.Product;
33-
34-
if ( !product.GenerateNuGetConfig )
35-
{
36-
return true;
37-
}
38-
39-
// Fetch to resolve the VersionFile properties.
40-
if ( !dependenciesConfigurationFile.Fetch( context ) )
41-
{
42-
return false;
43-
}
44-
45-
// Generate regular nuget.config
46-
if ( !GenerateNuGetConfig( context, dependenciesConfigurationFile, configuration, "nuget.config", path => path ) )
47-
{
48-
return false;
49-
}
50-
51-
// Generate WSL version if AddWslSupport is enabled
52-
if ( product.AddWslSupport )
53-
{
54-
if ( !GenerateNuGetConfig( context, dependenciesConfigurationFile, configuration, "nuget.wsl.config", ConvertToWslPath ) )
55-
{
56-
return false;
57-
}
58-
}
59-
60-
return true;
61-
}
62-
63-
private static bool GenerateNuGetConfig(
16+
/// <summary>
17+
/// Generates a nuget.config file.
18+
/// </summary>
19+
public bool TryGenerate(
6420
BuildContext context,
6521
DependenciesConfigurationFile dependenciesConfigurationFile,
66-
BuildConfiguration configuration,
67-
string targetFileName,
68-
Func<string, string> pathTransform )
22+
BuildConfiguration configuration )
6923
{
7024
var product = context.Product;
71-
var baseFilePath = Path.Combine( context.RepoDirectory, "nuget.base.config" );
72-
var targetFilePath = Path.Combine( context.RepoDirectory, targetFileName );
25+
var targetFilePath = this.GetTargetFilePath( context, configuration );
7326

7427
XDocument document;
7528
XElement rootElement;
7629

77-
if ( File.Exists( baseFilePath ) )
30+
if ( this.ShouldLoadBaseConfig() )
7831
{
79-
document = XDocument.Load( baseFilePath );
80-
rootElement = document.Root!;
32+
var baseFilePath = Path.Combine( context.RepoDirectory, "nuget.base.config" );
33+
34+
if ( File.Exists( baseFilePath ) )
35+
{
36+
document = XDocument.Load( baseFilePath );
37+
rootElement = document.Root!;
38+
}
39+
else
40+
{
41+
document = new XDocument();
42+
rootElement = new XElement( "configuration" );
43+
document.Add( rootElement );
44+
}
8145
}
8246
else
8347
{
@@ -116,8 +80,7 @@ private static bool GenerateNuGetConfig(
11680
}
11781

11882
// Add the current artifact directory.
119-
var artifactDirectory =
120-
product.GetPrivateArtifactsAbsoluteDirectory( context, configuration );
83+
var artifactDirectory = this.GetCurrentProductDirectory( context, configuration );
12184

12285
AddDirectory( product.ProductName, artifactDirectory, product.DependencyDefinition.PackagePatterns );
12386

@@ -139,15 +102,22 @@ private static bool GenerateNuGetConfig(
139102
}
140103

141104
var dependencyDefinition = product.GetDependencyDefinition( dependencySource.Key );
142-
var dependencyDirectory = Path.GetDirectoryName( dependencySource.Value.VersionFile )!;
143105

144-
if ( dependencySource.Value.SourceKind == DependencySourceKind.Local )
106+
if ( !this.ShouldIncludeDependency( dependencyDefinition ) )
145107
{
146-
dependencyDirectory = Path.Combine(
147-
dependencyDirectory,
148-
dependencyDefinition.GetPrivateArtifactsDirectory( configuration ) );
108+
// Skip this dependency based on generator-specific logic
109+
packageSourcesElement.Add( new XComment( $" {dependencySource.Key} excluded by generator. " ) );
110+
111+
continue;
149112
}
150113

114+
var dependencyDirectory = this.GetDependencyDirectory(
115+
context,
116+
dependencySource.Key,
117+
dependencySource.Value,
118+
dependencyDefinition,
119+
configuration );
120+
151121
if ( !AddDirectory( dependencySource.Key, dependencyDirectory, dependencyDefinition.PackagePatterns ) )
152122
{
153123
return false;
@@ -165,8 +135,8 @@ bool AddDirectory( string name, string directory, string[]? patterns )
165135
throw new ArgumentNullException( nameof(directory), $"Null directory for source '{name}'." );
166136
}
167137

168-
// Apply path transformation (e.g., Windows to WSL)
169-
var transformedDirectory = pathTransform( directory );
138+
// Apply path transformation
139+
var transformedDirectory = this.TransformPath( directory, context );
170140

171141
var addElement = new XElement( "add" );
172142
addElement.Add( new XAttribute( "key", name ) );
@@ -192,4 +162,41 @@ void AddPattern( string pattern )
192162
}
193163
}
194164
}
165+
166+
/// <summary>
167+
/// Gets the target file path where the nuget.config will be written.
168+
/// </summary>
169+
protected abstract string GetTargetFilePath( BuildContext context, BuildConfiguration configuration );
170+
171+
/// <summary>
172+
/// Transforms a path (e.g., to WSL format or relative path).
173+
/// </summary>
174+
protected abstract string TransformPath( string path, BuildContext context );
175+
176+
/// <summary>
177+
/// Gets the directory for the current product's packages.
178+
/// </summary>
179+
protected abstract string GetCurrentProductDirectory( BuildContext context, BuildConfiguration configuration );
180+
181+
/// <summary>
182+
/// Gets the directory for a dependency's packages.
183+
/// </summary>
184+
protected abstract string GetDependencyDirectory(
185+
BuildContext context,
186+
string dependencyKey,
187+
DependencySource dependencySource,
188+
DependencyDefinition dependencyDefinition,
189+
BuildConfiguration configuration );
190+
191+
/// <summary>
192+
/// Determines whether a dependency should be included in the nuget.config.
193+
/// </summary>
194+
/// <param name="dependencyDefinition">The dependency definition to check.</param>
195+
/// <returns>True if the dependency should be included; otherwise, false.</returns>
196+
protected virtual bool ShouldIncludeDependency( DependencyDefinition dependencyDefinition ) => true;
197+
198+
/// <summary>
199+
/// Determines whether to load the base config file (nuget.base.config).
200+
/// </summary>
201+
protected virtual bool ShouldLoadBaseConfig() => true;
195202
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
2+
3+
using PostSharp.Engineering.BuildTools.Dependencies.Model;
4+
using System.IO;
5+
6+
namespace PostSharp.Engineering.BuildTools.Build.Files.NuGet;
7+
8+
/// <summary>
9+
/// Generates nuget.restored.config with relative paths for TeamCity artifact restoration scenario.
10+
/// Assumes all dependencies have been restored to dependencies/&lt;name&gt;/ directory.
11+
/// All paths are relative to the repository root.
12+
/// </summary>
13+
internal sealed class RestoredArtifactsNuGetConfigGenerator : NuGetConfigGenerator
14+
{
15+
protected override string GetTargetFilePath( BuildContext context, BuildConfiguration configuration )
16+
=> Path.Combine(
17+
context.Product.GetPrivateArtifactsAbsoluteDirectory( context, configuration ),
18+
"nuget.restored.config" );
19+
20+
protected override string TransformPath( string path, BuildContext context )
21+
{
22+
// Make path relative to repository root
23+
var relativePath = Path.GetRelativePath( context.RepoDirectory, path );
24+
25+
// Normalize to forward slashes for consistency
26+
return relativePath.Replace( '\\', '/' );
27+
}
28+
29+
protected override string GetCurrentProductDirectory( BuildContext context, BuildConfiguration configuration )
30+
=> context.Product.GetPrivateArtifactsAbsoluteDirectory( context, configuration );
31+
32+
protected override string GetDependencyDirectory(
33+
BuildContext context,
34+
string dependencyKey,
35+
DependencySource dependencySource,
36+
DependencyDefinition dependencyDefinition,
37+
BuildConfiguration configuration )
38+
{
39+
// For restored artifacts, dependencies are always under dependencies/<name>/{PrivateArtifactsDirectory}
40+
// regardless of the source kind (this is the TeamCity artifact restoration structure)
41+
return Path.Combine(
42+
context.RepoDirectory,
43+
"dependencies",
44+
dependencyKey,
45+
dependencyDefinition.GetPrivateArtifactsDirectory( configuration ) );
46+
}
47+
48+
protected override bool ShouldIncludeDependency( DependencyDefinition dependencyDefinition )
49+
{
50+
// Only include dependencies that generate snapshot dependencies
51+
// Dependencies with GenerateSnapshotDependency = false won't be restored as TeamCity artifacts
52+
return dependencyDefinition.GenerateSnapshotDependency;
53+
}
54+
55+
protected override bool ShouldLoadBaseConfig() => false; // TeamCity scenario doesn't need base config
56+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
2+
3+
using PostSharp.Engineering.BuildTools.Dependencies.Model;
4+
using System.IO;
5+
6+
namespace PostSharp.Engineering.BuildTools.Build.Files.NuGet;
7+
8+
/// <summary>
9+
/// Generates standard nuget.config with absolute Windows paths.
10+
/// </summary>
11+
internal class StandardNuGetConfigGenerator : NuGetConfigGenerator
12+
{
13+
protected override string GetTargetFilePath( BuildContext context, BuildConfiguration configuration )
14+
=> Path.Combine( context.RepoDirectory, "nuget.config" );
15+
16+
protected override string TransformPath( string path, BuildContext context ) => path; // No transformation - use absolute paths as-is
17+
18+
protected override string GetCurrentProductDirectory( BuildContext context, BuildConfiguration configuration )
19+
=> context.Product.GetPrivateArtifactsAbsoluteDirectory( context, configuration );
20+
21+
protected override string GetDependencyDirectory(
22+
BuildContext context,
23+
string dependencyKey,
24+
DependencySource dependencySource,
25+
DependencyDefinition dependencyDefinition,
26+
BuildConfiguration configuration )
27+
{
28+
var dependencyDirectory = Path.GetDirectoryName( dependencySource.VersionFile )!;
29+
30+
if ( dependencySource.SourceKind == DependencySourceKind.Local )
31+
{
32+
dependencyDirectory = Path.Combine(
33+
dependencyDirectory,
34+
dependencyDefinition.GetPrivateArtifactsDirectory( configuration ) );
35+
}
36+
37+
return dependencyDirectory;
38+
}
39+
}

0 commit comments

Comments
 (0)