1
- // Copyright (c) .NET Foundation. All rights reserved.
1
+ // Copyright (c) .NET Foundation. All rights reserved.
2
2
// Licensed under the MIT License. See LICENSE in the project root for license information.
3
3
4
- using System . Reflection ;
5
4
using System . Runtime . InteropServices ;
6
5
using System . Text ;
6
+ using System . Text . RegularExpressions ;
7
7
using Azure . Functions . Cli . Common ;
8
8
using Colors . Net ;
9
9
using Microsoft . Azure . WebJobs . Extensions . Http ;
10
10
using static Azure . Functions . Cli . Common . OutputTheme ;
11
11
12
12
namespace Azure . Functions . Cli . Helpers
13
13
{
14
- public static class DotnetHelpers
14
+ public static partial class DotnetHelpers
15
15
{
16
- private const string WebJobsTemplateBasePackId = "Microsoft.Azure.WebJobs" ;
16
+ private const string InProcTemplateBasePackId = "Microsoft.Azure.WebJobs" ;
17
17
private const string IsolatedTemplateBasePackId = "Microsoft.Azure.Functions.Worker" ;
18
- private const string TemplatesLockFileName = "func_dotnet_templates.lock" ;
19
- private static readonly Lazy < Task < HashSet < string > > > _installedTemplatesList = new ( GetInstalledTemplatePackageIds ) ;
18
+
19
+ /// <summary>
20
+ /// Gets or sets test hook to intercept 'dotnet new' invocations for unit tests.
21
+ /// If null, real process execution is used.
22
+ /// </summary>
23
+ internal static Func < string , Task < int > > RunDotnetNewFunc { get ; set ; } = null ;
24
+
25
+ private static Task < int > RunDotnetNewAsync ( string args )
26
+ => ( RunDotnetNewFunc is not null )
27
+ ? RunDotnetNewFunc ( args )
28
+ : new Executable ( "dotnet" , args ) . RunAsync ( ) ;
20
29
21
30
public static void EnsureDotnet ( )
22
31
{
@@ -64,7 +73,18 @@ public static async Task<string> DetermineTargetFramework(string projectDirector
64
73
throw new CliException ( $ "Can not determine target framework for dotnet project at ${ projectDirectory } ") ;
65
74
}
66
75
67
- return output . ToString ( ) ;
76
+ // Extract the target framework moniker (TFM) from the output using regex pattern matching
77
+ var outputString = output . ToString ( ) ;
78
+
79
+ // Look for a line that looks like a target framework moniker
80
+ var tfm = TargetFrameworkHelper . TfmRegex . Match ( outputString ) ;
81
+
82
+ if ( ! tfm . Success )
83
+ {
84
+ throw new CliException ( $ "Could not parse target framework from output: { outputString } ") ;
85
+ }
86
+
87
+ return tfm . Value ;
68
88
}
69
89
70
90
public static async Task DeployDotnetProject ( string name , bool force , WorkerRuntime workerRuntime , string targetFramework = "" )
@@ -78,7 +98,8 @@ await TemplateOperationAsync(
78
98
var connectionString = RuntimeInformation . IsOSPlatform ( OSPlatform . Windows )
79
99
? $ "--StorageConnectionStringValue \" { Constants . StorageEmulatorConnectionString } \" "
80
100
: string . Empty ;
81
- var exe = new Executable ( "dotnet" , $ "new func { frameworkString } --AzureFunctionsVersion v4 --name { name } { connectionString } { ( force ? "--force" : string . Empty ) } ") ;
101
+ TryGetCustomHiveArg ( workerRuntime , out string customHive ) ;
102
+ var exe = new Executable ( "dotnet" , $ "new func { frameworkString } --AzureFunctionsVersion v4 --name { name } { connectionString } { ( force ? "--force" : string . Empty ) } { customHive } ") ;
82
103
var exitCode = await exe . RunAsync ( o => { } , e => ColoredConsole . Error . WriteLine ( ErrorColor ( e ) ) ) ;
83
104
if ( exitCode != 0 )
84
105
{
@@ -109,7 +130,8 @@ await TemplateOperationAsync(
109
130
}
110
131
}
111
132
112
- var exe = new Executable ( "dotnet" , exeCommandArguments ) ;
133
+ TryGetCustomHiveArg ( workerRuntime , out string customHive ) ;
134
+ var exe = new Executable ( "dotnet" , exeCommandArguments + customHive ) ;
113
135
string dotnetNewErrorMessage = string . Empty ;
114
136
var exitCode = await exe . RunAsync ( o => { } , e =>
115
137
{
@@ -277,119 +299,110 @@ public static string GetCsprojOrFsproj()
277
299
}
278
300
}
279
301
280
- private static async Task TemplateOperationAsync ( Func < Task > action , WorkerRuntime workerRuntime )
302
+ internal static async Task TemplateOperationAsync ( Func < Task > action , WorkerRuntime workerRuntime )
281
303
{
282
304
EnsureDotnet ( ) ;
283
305
306
+ // If we have enabled custom hives (for E2E tests), install templates there and run the action
307
+ if ( UseCustomTemplateHive ( ) )
308
+ {
309
+ await EnsureTemplatesInCustomHiveAsync ( action , workerRuntime ) ;
310
+ return ;
311
+ }
312
+
313
+ // Default CLI behaviour: Templates are installed globally, so we need to install/uninstall them around the action
284
314
if ( workerRuntime == WorkerRuntime . DotnetIsolated )
285
315
{
286
- await EnsureIsolatedTemplatesInstalled ( ) ;
316
+ await EnsureIsolatedTemplatesInstalledAsync ( action ) ;
287
317
}
288
318
else
289
319
{
290
- await EnsureWebJobsTemplatesInstalled ( ) ;
320
+ await EnsureInProcTemplatesInstalledAsync ( action ) ;
291
321
}
292
-
293
- await action ( ) ;
294
322
}
295
323
296
- private static async Task EnsureIsolatedTemplatesInstalled ( )
324
+ private static async Task EnsureTemplatesInCustomHiveAsync ( Func < Task > action , WorkerRuntime workerRuntime )
297
325
{
298
- if ( AreDotnetTemplatePackagesInstalled ( await _installedTemplatesList . Value , WebJobsTemplateBasePackId ) )
299
- {
300
- await UninstallWebJobsTemplates ( ) ;
301
- }
302
-
303
- if ( AreDotnetTemplatePackagesInstalled ( await _installedTemplatesList . Value , IsolatedTemplateBasePackId ) )
326
+ // If the custom hive already has templates installed, just run the action and skip installation
327
+ string hivePackagesDir = Path . Combine ( GetHivePath ( workerRuntime ) , "packages" ) ;
328
+ if ( FileSystemHelpers . EnsureDirectoryNotEmpty ( hivePackagesDir ) )
304
329
{
330
+ await action ( ) ;
305
331
return ;
306
332
}
307
333
308
- await FileLockHelper . WithFileLockAsync ( TemplatesLockFileName , InstallIsolatedTemplates ) ;
334
+ // Install only, no need to uninstall as we are using a custom hive
335
+ Func < Task > installTemplates = workerRuntime == WorkerRuntime . DotnetIsolated
336
+ ? InstallIsolatedTemplates
337
+ : InstallInProcTemplates ;
338
+
339
+ await installTemplates ( ) ;
340
+ await action ( ) ;
309
341
}
310
342
311
- private static async Task EnsureWebJobsTemplatesInstalled ( )
343
+ private static async Task EnsureIsolatedTemplatesInstalledAsync ( Func < Task > action )
312
344
{
313
- if ( AreDotnetTemplatePackagesInstalled ( await _installedTemplatesList . Value , IsolatedTemplateBasePackId ) )
345
+ try
314
346
{
315
- await UninstallIsolatedTemplates ( ) ;
316
- }
347
+ // Uninstall any existing webjobs templates, as they conflict with isolated templates
348
+ await UninstallInProcTemplates ( ) ;
317
349
318
- if ( AreDotnetTemplatePackagesInstalled ( await _installedTemplatesList . Value , WebJobsTemplateBasePackId ) )
350
+ // Install the latest isolated templates
351
+ await InstallIsolatedTemplates ( ) ;
352
+ await action ( ) ;
353
+ }
354
+ finally
319
355
{
320
- return ;
356
+ await UninstallIsolatedTemplates ( ) ;
321
357
}
322
-
323
- await FileLockHelper . WithFileLockAsync ( TemplatesLockFileName , InstallWebJobsTemplates ) ;
324
- }
325
-
326
- internal static bool AreDotnetTemplatePackagesInstalled ( HashSet < string > templates , string packageIdPrefix )
327
- {
328
- var hasProjectTemplates = templates . Contains ( $ "{ packageIdPrefix } .ProjectTemplates", StringComparer . OrdinalIgnoreCase ) ;
329
- var hasItemTemplates = templates . Contains ( $ "{ packageIdPrefix } .ItemTemplates", StringComparer . OrdinalIgnoreCase ) ;
330
-
331
- return hasProjectTemplates && hasItemTemplates ;
332
358
}
333
359
334
- private static async Task < HashSet < string > > GetInstalledTemplatePackageIds ( )
360
+ private static async Task EnsureInProcTemplatesInstalledAsync ( Func < Task > action )
335
361
{
336
- var exe = new Executable ( "dotnet" , "new uninstall" , shareConsole : false ) ;
337
- var output = new StringBuilder ( ) ;
338
- var exitCode = await exe . RunAsync ( o => output . AppendLine ( o ) , e => output . AppendLine ( e ) ) ;
362
+ try
363
+ {
364
+ // Uninstall any existing isolated templates, as they conflict with webjobs templates
365
+ await UninstallIsolatedTemplates ( ) ;
339
366
340
- if ( exitCode != 0 )
367
+ // Install the latest webjobs templates
368
+ await InstallInProcTemplates ( ) ;
369
+ await action ( ) ;
370
+ }
371
+ finally
341
372
{
342
- throw new CliException ( "Failed to get list of installed template packages" ) ;
373
+ await UninstallInProcTemplates ( ) ;
343
374
}
375
+ }
344
376
345
- var lines = output . ToString ( )
346
- . Split ( [ '\r ' , '\n ' ] , StringSplitOptions . RemoveEmptyEntries ) ;
347
-
348
- var packageIds = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
349
-
350
- const string uninstallPrefix = "dotnet new uninstall " ;
377
+ private static string [ ] GetNupkgFiles ( string templatesPath )
378
+ {
379
+ var templatesLocation = Path . Combine (
380
+ Path . GetDirectoryName ( AppContext . BaseDirectory ) ,
381
+ Path . Combine ( templatesPath ) ) ;
351
382
352
- foreach ( var line in lines )
383
+ if ( ! FileSystemHelpers . DirectoryExists ( templatesLocation ) )
353
384
{
354
- var trimmed = line . Trim ( ) ;
355
-
356
- if ( trimmed . StartsWith ( uninstallPrefix , StringComparison . OrdinalIgnoreCase ) )
357
- {
358
- var packageId = trimmed . Substring ( uninstallPrefix . Length ) . Trim ( ) ;
359
- if ( ! string . IsNullOrWhiteSpace ( packageId ) )
360
- {
361
- packageIds . Add ( packageId ) ;
362
- }
363
- }
385
+ throw new CliException ( $ "Can't find templates location. Looked under '{ templatesLocation } '") ;
364
386
}
365
387
366
- return packageIds ;
388
+ return Directory . GetFiles ( templatesLocation , "*.nupkg" , SearchOption . TopDirectoryOnly ) ;
367
389
}
368
390
369
- private static Task UninstallIsolatedTemplates ( ) => DotnetTemplatesAction ( "uninstall " , nugetPackageList : [ $ " { IsolatedTemplateBasePackId } .ProjectTemplates" , $ "{ IsolatedTemplateBasePackId } .ItemTemplates" ] ) ;
391
+ private static Task InstallIsolatedTemplates ( ) => DotnetTemplatesAction ( "install " , WorkerRuntime . DotnetIsolated , Path . Combine ( "templates" , $ "net-isolated" ) ) ;
370
392
371
- private static Task UninstallWebJobsTemplates ( ) => DotnetTemplatesAction ( "uninstall" , nugetPackageList : [ $ "{ WebJobsTemplateBasePackId } .ProjectTemplates", $ "{ WebJobsTemplateBasePackId } .ItemTemplates"] ) ;
393
+ private static Task UninstallIsolatedTemplates ( ) => DotnetTemplatesAction ( "uninstall" , WorkerRuntime . DotnetIsolated , nugetPackageList : [ $ "{ IsolatedTemplateBasePackId } .ProjectTemplates", $ "{ IsolatedTemplateBasePackId } .ItemTemplates"] ) ;
372
394
373
- private static Task InstallWebJobsTemplates ( ) => DotnetTemplatesAction ( "install" , "templates" ) ;
395
+ private static Task InstallInProcTemplates ( ) => DotnetTemplatesAction ( "install" , WorkerRuntime . Dotnet , "templates" ) ;
374
396
375
- private static Task InstallIsolatedTemplates ( ) => DotnetTemplatesAction ( "install " , Path . Combine ( "templates ", $ "net-isolated" ) ) ;
397
+ private static Task UninstallInProcTemplates ( ) => DotnetTemplatesAction ( "uninstall " , WorkerRuntime . Dotnet , nugetPackageList : [ $ " { InProcTemplateBasePackId } .ProjectTemplates ", $ "{ InProcTemplateBasePackId } .ItemTemplates" ] ) ;
376
398
377
- private static async Task DotnetTemplatesAction ( string action , string templateDirectory = null , string [ ] nugetPackageList = null )
399
+ private static async Task DotnetTemplatesAction ( string action , WorkerRuntime workerRuntime , string templateDirectory = null , string [ ] nugetPackageList = null )
378
400
{
379
401
string [ ] list ;
380
402
381
403
if ( ! string . IsNullOrEmpty ( templateDirectory ) )
382
404
{
383
- var templatesLocation = Path . Combine (
384
- Path . GetDirectoryName ( Assembly . GetExecutingAssembly ( ) . Location ) ,
385
- templateDirectory ) ;
386
-
387
- if ( ! FileSystemHelpers . DirectoryExists ( templatesLocation ) )
388
- {
389
- throw new CliException ( $ "Can't find templates location. Looked under '{ templatesLocation } '") ;
390
- }
391
-
392
- list = Directory . GetFiles ( templatesLocation , "*.nupkg" , SearchOption . TopDirectoryOnly ) ;
405
+ list = GetNupkgFiles ( templateDirectory ) ;
393
406
}
394
407
else
395
408
{
@@ -398,8 +411,9 @@ private static async Task DotnetTemplatesAction(string action, string templateDi
398
411
399
412
foreach ( var nupkg in list )
400
413
{
401
- var exe = new Executable ( "dotnet" , $ "new { action } \" { nupkg } \" ") ;
402
- await exe . RunAsync ( ) ;
414
+ TryGetCustomHiveArg ( workerRuntime , out string customHive ) ;
415
+ var args = $ "new { action } \" { nupkg } \" { customHive } ";
416
+ await RunDotnetNewAsync ( args ) ;
403
417
}
404
418
}
405
419
}
0 commit comments