@@ -4,43 +4,53 @@ namespace LocalStack.Build;
44
55public sealed class BuildContext : FrostingContext
66{
7+ public const string LocalStackClientProjName = "LocalStack.Client" ;
8+ public const string LocalStackClientExtensionsProjName = "LocalStack.Client.Extensions" ;
9+
10+ public const string GitHubPackageSource = "github" ;
11+ public const string NuGetPackageSource = "nuget" ;
12+ public const string MyGetPackageSource = "myget" ;
13+
714 public BuildContext ( ICakeContext context ) : base ( context )
815 {
916 BuildConfiguration = context . Argument ( "config" , "Release" ) ;
10- ForceBuild = context . Argument ( "force-build" , false ) ;
11- ForceRestore = context . Argument ( "force-restore" , false ) ;
17+ ForceBuild = context . Argument ( "force-build" , defaultValue : false ) ;
18+ ForceRestore = context . Argument ( "force-restore" , defaultValue : false ) ;
1219 PackageVersion = context . Argument ( "package-version" , "x.x.x" ) ;
1320 PackageId = context . Argument ( "package-id" , default ( string ) ) ;
1421 PackageSecret = context . Argument ( "package-secret" , default ( string ) ) ;
15- PackageSource = context . Argument ( "package-source" , default ( string ) ) ;
16- SkipFunctionalTest = context . Argument ( "skipFunctionalTest" , true ) ;
22+ PackageSource = context . Argument ( "package-source" , GitHubPackageSource ) ;
23+ SkipFunctionalTest = context . Argument ( "skipFunctionalTest" , defaultValue : true ) ;
24+
25+ // New version generation arguments
26+ UseDirectoryPropsVersion = context . Argument ( "use-directory-props-version" , defaultValue : false ) ;
27+ BranchName = context . Argument ( "branch-name" , "master" ) ;
1728
1829 var sourceBuilder = ImmutableDictionary . CreateBuilder < string , string > ( ) ;
19- sourceBuilder . AddRange ( new [ ]
20- {
21- new KeyValuePair < string , string > ( "myget" , "https://www.myget.org/F/localstack-dotnet-client/api/v3/index.json" ) ,
22- new KeyValuePair < string , string > ( "nuget" , "https://api.nuget.org/v3/index.json" ) ,
23- new KeyValuePair < string , string > ( "github" , "https://nuget.pkg.github.com/localstack-dotnet/index.json" ) ,
24- } ) ;
30+ sourceBuilder . AddRange ( [
31+ new KeyValuePair < string , string > ( MyGetPackageSource , "https://www.myget.org/F/localstack-dotnet-client/api/v3/index.json" ) ,
32+ new KeyValuePair < string , string > ( NuGetPackageSource , "https://api.nuget.org/v3/index.json" ) ,
33+ new KeyValuePair < string , string > ( GitHubPackageSource , "https://nuget.pkg.github.com/localstack-dotnet/index.json" ) ,
34+ ] ) ;
2535 PackageSourceMap = sourceBuilder . ToImmutable ( ) ;
2636
2737 SolutionRoot = context . Directory ( "../../" ) ;
2838 SrcPath = SolutionRoot + context . Directory ( "src" ) ;
2939 TestsPath = SolutionRoot + context . Directory ( "tests" ) ;
3040 BuildPath = SolutionRoot + context . Directory ( "build" ) ;
3141 ArtifactOutput = SolutionRoot + context . Directory ( "artifacts" ) ;
32- LocalStackClientFolder = SrcPath + context . Directory ( "LocalStack.Client" ) ;
33- LocalStackClientExtFolder = SrcPath + context . Directory ( "LocalStack.Client.Extensions" ) ;
42+ LocalStackClientFolder = SrcPath + context . Directory ( LocalStackClientProjName ) ;
43+ LocalStackClientExtFolder = SrcPath + context . Directory ( LocalStackClientExtensionsProjName ) ;
3444 SlnFilePath = SolutionRoot + context . File ( "LocalStack.sln" ) ;
35- LocalStackClientProjFile = LocalStackClientFolder + context . File ( "LocalStack.Client .csproj") ;
36- LocalStackClientExtProjFile = LocalStackClientExtFolder + context . File ( "LocalStack.Client.Extensions .csproj") ;
45+ LocalStackClientProjFile = LocalStackClientFolder + context . File ( $ " { LocalStackClientProjName } .csproj") ;
46+ LocalStackClientExtProjFile = LocalStackClientExtFolder + context . File ( $ " { LocalStackClientExtensionsProjName } .csproj") ;
3747
3848 var packIdBuilder = ImmutableDictionary . CreateBuilder < string , FilePath > ( ) ;
39- packIdBuilder . AddRange ( new [ ]
40- {
41- new KeyValuePair < string , FilePath > ( "LocalStack.Client" , LocalStackClientProjFile ) ,
42- new KeyValuePair < string , FilePath > ( "LocalStack.Client.Extensions" , LocalStackClientExtProjFile ) ,
43- } ) ;
49+ packIdBuilder . AddRange (
50+ [
51+ new KeyValuePair < string , FilePath > ( LocalStackClientProjName , LocalStackClientProjFile ) ,
52+ new KeyValuePair < string , FilePath > ( LocalStackClientExtensionsProjName , LocalStackClientExtProjFile ) ,
53+ ] ) ;
4454 PackageIdProjMap = packIdBuilder . ToImmutable ( ) ;
4555 }
4656
@@ -60,6 +70,10 @@ public BuildContext(ICakeContext context) : base(context)
6070
6171 public string PackageSource { get ; }
6272
73+ public bool UseDirectoryPropsVersion { get ; }
74+
75+ public string BranchName { get ; }
76+
6377 public ImmutableDictionary < string , string > PackageSourceMap { get ; }
6478
6579 public ImmutableDictionary < string , FilePath > PackageIdProjMap { get ; }
@@ -92,27 +106,10 @@ public static void ValidateArgument(string argumentName, string argument)
92106 }
93107 }
94108
95- public void InstallXUnitNugetPackage ( )
96- {
97- if ( ! Directory . Exists ( "testrunner" ) )
98- {
99- Directory . CreateDirectory ( "testrunner" ) ;
100- }
101-
102- var nugetInstallSettings = new NuGetInstallSettings
103- {
104- Version = "2.8.1" , Verbosity = NuGetVerbosity . Normal , OutputDirectory = "testrunner" , WorkingDirectory = "." ,
105- } ;
106-
107- this . NuGetInstall ( "xunit.runner.console" , nugetInstallSettings ) ;
108- }
109-
110109 public IEnumerable < ProjMetadata > GetProjMetadata ( )
111110 {
112111 DirectoryPath testsRoot = this . Directory ( TestsPath ) ;
113- List < FilePath > csProjFile = this . GetFiles ( $ "{ testsRoot } /**/*.csproj")
114- . Where ( fp => fp . FullPath . EndsWith ( "Tests.csproj" , StringComparison . InvariantCulture ) )
115- . ToList ( ) ;
112+ List < FilePath > csProjFile = [ .. this . GetFiles ( $ "{ testsRoot } /**/*.csproj") . Where ( fp => fp . FullPath . EndsWith ( "Tests.csproj" , StringComparison . InvariantCulture ) ) ] ;
116113
117114 var projMetadata = new List < ProjMetadata > ( ) ;
118115
@@ -194,49 +191,142 @@ public void InstallMonoOnLinux()
194191 this . Information ( "✅ Mono installation completed successfully" ) ;
195192 }
196193
197- public void RunXUnitUsingMono ( string targetFramework , string assemblyPath )
194+ public string GetProjectVersion ( )
198195 {
199- int exitCode = this . StartProcess (
200- "mono" , new ProcessSettings { Arguments = $ "./testrunner/xunit.runner.console.2.8.1/tools/{ targetFramework } /xunit.console.exe { assemblyPath } " } ) ;
201-
202- if ( exitCode != 0 )
196+ if ( UseDirectoryPropsVersion )
203197 {
204- throw new InvalidOperationException ( $ "Exit code: { exitCode } ") ;
198+ return GetDynamicVersionFromProps ( "PackageMainVersion ") ;
205199 }
206- }
207200
208- public string GetProjectVersion ( )
209- {
201+ // Original logic for backward compatibility
210202 FilePath file = this . File ( "./src/Directory.Build.props" ) ;
211-
212203 this . Information ( file . FullPath ) ;
213204
214205 string project = File . ReadAllText ( file . FullPath , Encoding . UTF8 ) ;
215206 int startIndex = project . IndexOf ( "<Version>" , StringComparison . Ordinal ) + "<Version>" . Length ;
216207 int endIndex = project . IndexOf ( "</Version>" , startIndex , StringComparison . Ordinal ) ;
217208
218- string version = project . Substring ( startIndex , endIndex - startIndex ) ;
209+ string version = project [ startIndex .. endIndex ] ;
219210 version = $ "{ version } .{ PackageVersion } ";
220211
221212 return version ;
222213 }
223214
224215 public string GetExtensionProjectVersion ( )
225216 {
226- FilePath file = this . File ( LocalStackClientExtProjFile ) ;
217+ if ( UseDirectoryPropsVersion )
218+ {
219+ return GetDynamicVersionFromProps ( "PackageExtensionVersion" ) ;
220+ }
227221
222+ // Original logic for backward compatibility
223+ FilePath file = this . File ( LocalStackClientExtProjFile ) ;
228224 this . Information ( file . FullPath ) ;
229225
230226 string project = File . ReadAllText ( file . FullPath , Encoding . UTF8 ) ;
231227 int startIndex = project . IndexOf ( "<Version>" , StringComparison . Ordinal ) + "<Version>" . Length ;
232228 int endIndex = project . IndexOf ( "</Version>" , startIndex , StringComparison . Ordinal ) ;
233229
234- string version = project . Substring ( startIndex , endIndex - startIndex ) ;
230+ string version = project [ startIndex .. endIndex ] ;
235231 version = $ "{ version } .{ PackageVersion } ";
236232
237233 return version ;
238234 }
239235
236+ /// <summary>
237+ /// Gets the target frameworks for a specific package using the existing proven method
238+ /// </summary>
239+ /// <param name="packageId">The package identifier</param>
240+ /// <returns>Comma-separated target frameworks</returns>
241+ public string GetPackageTargetFrameworks ( string packageId )
242+ {
243+ if ( ! PackageIdProjMap . TryGetValue ( packageId , out FilePath ? projectFile ) || projectFile == null )
244+ {
245+ throw new ArgumentException ( $ "Unknown package ID: { packageId } ", nameof ( packageId ) ) ;
246+ }
247+
248+ string [ ] frameworks = GetProjectTargetFrameworks ( projectFile . FullPath ) ;
249+ return string . Join ( ", " , frameworks ) ;
250+ }
251+
252+ /// <summary>
253+ /// Generates dynamic version from Directory.Build.props with build metadata
254+ /// </summary>
255+ /// <param name="versionPropertyName">The property name to extract (PackageMainVersion or PackageExtensionVersion)</param>
256+ /// <returns>Version with build metadata (e.g., 2.0.0-preview1.20240715.a1b2c3d)</returns>
257+ private string GetDynamicVersionFromProps ( string versionPropertyName )
258+ {
259+ // Extract base version from Directory.Build.props
260+ FilePath propsFile = this . File ( "../../Directory.Build.props" ) ;
261+ string content = File . ReadAllText ( propsFile . FullPath , Encoding . UTF8 ) ;
262+
263+ string startElement = $ "<{ versionPropertyName } >";
264+ string endElement = $ "</{ versionPropertyName } >";
265+
266+ int startIndex = content . IndexOf ( startElement , StringComparison . Ordinal ) + startElement . Length ;
267+ int endIndex = content . IndexOf ( endElement , startIndex , StringComparison . Ordinal ) ;
268+
269+ if ( startIndex < startElement . Length || endIndex < 0 )
270+ {
271+ throw new InvalidOperationException ( $ "Could not find { versionPropertyName } in Directory.Build.props") ;
272+ }
273+
274+ string baseVersion = content [ startIndex ..endIndex ] ;
275+
276+ // Generate build metadata
277+ string buildDate = DateTime . UtcNow . ToString ( "yyyyMMdd" , System . Globalization . CultureInfo . InvariantCulture ) ;
278+ string commitSha = GetGitCommitSha ( ) ;
279+ string safeBranchName = BranchName . Replace ( '/' , '-' ) . Replace ( '_' , '-' ) ;
280+
281+ // Simplified NuGet-compliant version format
282+ if ( BranchName == "master" )
283+ {
284+ // For master: 2.0.0-preview1-20240715-a1b2c3d
285+ return $ "{ baseVersion } -{ buildDate } -{ commitSha } ";
286+ }
287+ else
288+ {
289+ // For feature branches: 2.0.0-preview1-feature-branch-20240715-a1b2c3d
290+ return $ "{ baseVersion } -{ safeBranchName } -{ buildDate } -{ commitSha } ";
291+ }
292+ }
293+
294+ /// <summary>
295+ /// Gets the short git commit SHA for version metadata
296+ /// </summary>
297+ /// <returns>Short commit SHA or timestamp fallback</returns>
298+ private string GetGitCommitSha ( )
299+ {
300+ try
301+ {
302+ var processSettings = new ProcessSettings
303+ {
304+ Arguments = "rev-parse --short HEAD" ,
305+ RedirectStandardOutput = true ,
306+ RedirectStandardError = true ,
307+ Silent = true ,
308+ } ;
309+
310+ var exitCode = this . StartProcess ( "git" , processSettings , out IEnumerable < string > output ) ;
311+
312+ if ( exitCode == 0 && output ? . Any ( ) == true )
313+ {
314+ string ? commitSha = output . FirstOrDefault ( ) ? . Trim ( ) ;
315+ if ( ! string . IsNullOrEmpty ( commitSha ) )
316+ {
317+ return commitSha ;
318+ }
319+ }
320+ }
321+ catch ( Exception ex )
322+ {
323+ this . Warning ( $ "Failed to get git commit SHA: { ex . Message } ") ;
324+ }
325+
326+ // Fallback to timestamp-based identifier
327+ return DateTime . UtcNow . ToString ( "HHmmss" , System . Globalization . CultureInfo . InvariantCulture ) ;
328+ }
329+
240330 private string [ ] GetProjectTargetFrameworks ( string csprojPath )
241331 {
242332 FilePath file = this . File ( csprojPath ) ;
@@ -249,7 +339,7 @@ private string[] GetProjectTargetFrameworks(string csprojPath)
249339 int startIndex = project . IndexOf ( startElement , StringComparison . Ordinal ) + startElement . Length ;
250340 int endIndex = project . IndexOf ( endElement , startIndex , StringComparison . Ordinal ) ;
251341
252- string targetFrameworks = project . Substring ( startIndex , endIndex - startIndex ) ;
342+ string targetFrameworks = project [ startIndex .. endIndex ] ;
253343
254344 return targetFrameworks . Split ( ';' ) ;
255345 }
@@ -268,14 +358,14 @@ private string GetAssemblyName(string csprojPath)
268358 int startIndex = project . IndexOf ( "<AssemblyName>" , StringComparison . Ordinal ) + "<AssemblyName>" . Length ;
269359 int endIndex = project . IndexOf ( "</AssemblyName>" , startIndex , StringComparison . Ordinal ) ;
270360
271- assemblyName = project . Substring ( startIndex , endIndex - startIndex ) ;
361+ assemblyName = project [ startIndex .. endIndex ] ;
272362 }
273363 else
274364 {
275365 int startIndex = csprojPath . LastIndexOf ( '/' ) + 1 ;
276366 int endIndex = csprojPath . IndexOf ( ".csproj" , startIndex , StringComparison . Ordinal ) ;
277367
278- assemblyName = csprojPath . Substring ( startIndex , endIndex - startIndex ) ;
368+ assemblyName = csprojPath [ startIndex .. endIndex ] ;
279369 }
280370
281371 return assemblyName ;
0 commit comments