@@ -353,7 +353,7 @@ private string BuildLocalApp([CallerMemberName] string testName = "TestName", st
353
353
public async Task EndToEnd_MultiProjectSolution ( )
354
354
{
355
355
ILogger logger = _loggerFactory . CreateLogger ( nameof ( EndToEnd_MultiProjectSolution ) ) ;
356
- DirectoryInfo newSolutionDir = new ( Path . Combine ( TestSettings . TestArtifactsDirectory , $ "CreateNewImageTest_EndToEnd_MultiProjectSolution" ) ) ;
356
+ DirectoryInfo newSolutionDir = new ( Path . Combine ( TestSettings . TestArtifactsDirectory , nameof ( EndToEnd_MultiProjectSolution ) ) ) ;
357
357
358
358
if ( newSolutionDir . Exists )
359
359
{
@@ -431,6 +431,82 @@ public async Task EndToEnd_MultiProjectSolution()
431
431
commandResult . Should ( ) . HaveStdOutContaining ( "Pushed image 'consoleapp:latest'" ) ;
432
432
}
433
433
434
+ /// <summary>
435
+ /// Tests that a multi-project solution with a library that targets multiple frameworks can be published.
436
+ /// This is interesting because before https://github.com/dotnet/sdk/pull/47693 the container targets
437
+ /// wouldn't be loaded for multi-TFM project evaluations, so any calls to the PublishContainer target
438
+ /// for libraries (which may be multi-targeted even when referenced from a single-target published app project) would fail.
439
+ /// It's safe to load the target for libraries in a multi-targeted context because libraries don't have EnableSdkContainerSupport
440
+ /// enabled by default, so the target will be skipped.
441
+ /// </summary>
442
+ [ DockerAvailableFact ]
443
+ public async Task EndToEnd_MultiProjectSolution_with_multitargeted_library ( )
444
+ {
445
+ ILogger logger = _loggerFactory . CreateLogger ( nameof ( EndToEnd_MultiProjectSolution_with_multitargeted_library ) ) ;
446
+ DirectoryInfo newSolutionDir = new ( Path . Combine ( TestSettings . TestArtifactsDirectory , nameof ( EndToEnd_MultiProjectSolution_with_multitargeted_library ) ) ) ;
447
+
448
+ if ( newSolutionDir . Exists )
449
+ {
450
+ newSolutionDir . Delete ( recursive : true ) ;
451
+ }
452
+
453
+ newSolutionDir . Create ( ) ;
454
+
455
+ // Create solution with projects
456
+ new DotnetNewCommand ( _testOutput , "sln" , "-n" , nameof ( EndToEnd_MultiProjectSolution_with_multitargeted_library ) )
457
+ . WithVirtualHive ( )
458
+ . WithWorkingDirectory ( newSolutionDir . FullName )
459
+ . Execute ( )
460
+ . Should ( ) . Pass ( ) ;
461
+
462
+ new DotnetNewCommand ( _testOutput , "web" , "-n" , "WebApp" )
463
+ . WithVirtualHive ( )
464
+ . WithWorkingDirectory ( newSolutionDir . FullName )
465
+ . Execute ( )
466
+ . Should ( ) . Pass ( ) ;
467
+
468
+ new DotnetCommand ( _testOutput , "sln" , "add" , Path . Combine ( "WebApp" , "WebApp.csproj" ) )
469
+ . WithWorkingDirectory ( newSolutionDir . FullName )
470
+ . Execute ( )
471
+ . Should ( ) . Pass ( ) ;
472
+
473
+ new DotnetNewCommand ( _testOutput , "classlib" , "-n" , "Library" )
474
+ . WithVirtualHive ( )
475
+ . WithWorkingDirectory ( newSolutionDir . FullName )
476
+ . Execute ( )
477
+ . Should ( ) . Pass ( ) ;
478
+
479
+ new DotnetCommand ( _testOutput , "sln" , "add" , Path . Combine ( "Library" , "Library.csproj" ) )
480
+ . WithWorkingDirectory ( newSolutionDir . FullName )
481
+ . Execute ( )
482
+ . Should ( ) . Pass ( ) ;
483
+
484
+ // Set TFMs for Library - use current toolset + NS2.0 for compatibility
485
+ // also set IsPublishable to false
486
+ using ( FileStream stream = File . Open ( Path . Join ( newSolutionDir . FullName , "Library" , "Library.csproj" ) , FileMode . Open , FileAccess . ReadWrite ) )
487
+ {
488
+ XDocument document = await XDocument . LoadAsync ( stream , LoadOptions . None , CancellationToken . None ) ;
489
+ var tfmNode =
490
+ document
491
+ . Descendants ( )
492
+ . First ( e => e . Name . LocalName == "TargetFramework" ) ;
493
+ var propertyGroupNode = tfmNode . Parent ! ;
494
+ tfmNode . Remove ( ) ;
495
+ propertyGroupNode . Add ( new XElement ( "TargetFrameworks" , $ "{ ToolsetInfo . CurrentTargetFramework } ;netstandard2.0") ) ;
496
+ propertyGroupNode . Add ( new XElement ( "IsPublishable" , "false" ) ) ;
497
+ stream . SetLength ( 0 ) ;
498
+ await document . SaveAsync ( stream , SaveOptions . None , CancellationToken . None ) ;
499
+ }
500
+
501
+ // Publish
502
+ CommandResult commandResult = new DotnetCommand ( _testOutput , "publish" , "/t:PublishContainer" )
503
+ . WithWorkingDirectory ( newSolutionDir . FullName )
504
+ . Execute ( ) ;
505
+
506
+ commandResult . Should ( ) . Pass ( ) ;
507
+ commandResult . Should ( ) . HaveStdOutContaining ( "Pushed image 'webapp:latest'" ) ;
508
+ }
509
+
434
510
[ DockerAvailableTheory ( ) ]
435
511
[ InlineData ( "webapi" , false ) ]
436
512
[ InlineData ( "webapi" , true ) ]
@@ -1014,7 +1090,7 @@ public void EndToEndMultiArch_RemoteRegistry()
1014
1090
. And . HaveStdOutContaining ( $ "Pushed image '{ imageX64 } ' to registry '{ registry } '.")
1015
1091
. And . HaveStdOutContaining ( $ "Pushed image '{ imageArm64 } ' to registry '{ registry } '.")
1016
1092
. And . HaveStdOutContaining ( $ "Pushed image index '{ imageIndex } ' to registry '{ registry } '.") ;
1017
-
1093
+
1018
1094
// Check that the containers can be run
1019
1095
// First pull the image from the registry for each platform
1020
1096
ContainerCli . PullCommand (
@@ -1031,7 +1107,7 @@ public void EndToEndMultiArch_RemoteRegistry()
1031
1107
imageFromRegistry )
1032
1108
. Execute ( )
1033
1109
. Should ( ) . Pass ( ) ;
1034
-
1110
+
1035
1111
// Run the containers
1036
1112
ContainerCli . RunCommand (
1037
1113
_testOutput ,
@@ -1353,4 +1429,45 @@ static string[] DecideEntrypoint(string rid, string appName, string workingDir)
1353
1429
return new [ ] { $ "{ workingDir } /{ binary } " } ;
1354
1430
}
1355
1431
}
1432
+
1433
+ [ DockerAvailableFact ( checkContainerdStoreAvailability : true ) ]
1434
+ public void EnforcesOciSchemaForMultiRIDTarballOutput ( )
1435
+ {
1436
+ string imageName = NewImageName ( ) ;
1437
+ string tag = "1.0" ;
1438
+
1439
+ // Create new console app
1440
+ DirectoryInfo newProjectDir = CreateNewProject ( "webapp" ) ;
1441
+
1442
+ // Run PublishContainer for multi-arch with ContainerGenerateLabels
1443
+ var publishResult = new DotnetCommand (
1444
+ _testOutput ,
1445
+ "publish" ,
1446
+ "/t:PublishContainer" ,
1447
+ "/p:RuntimeIdentifiers=\" linux-x64;linux-arm64\" " ,
1448
+ $ "/p:ContainerBaseImage={ DockerRegistryManager . FullyQualifiedBaseImageAspNet } ",
1449
+ $ "/p:ContainerRepository={ imageName } ",
1450
+ $ "/p:ContainerImageTag={ tag } ",
1451
+ "/p:EnableSdkContainerSupport=true" ,
1452
+ "/p:ContainerArchiveOutputPath=archive.tar.gz" ,
1453
+ "-getProperty:GeneratedImageIndex" ,
1454
+ "-getItem:GeneratedContainers" ,
1455
+ "/bl" )
1456
+ . WithWorkingDirectory ( newProjectDir . FullName )
1457
+ . Execute ( ) ;
1458
+
1459
+ publishResult . Should ( ) . Pass ( ) ;
1460
+ var jsonDump = JsonDocument . Parse ( publishResult . StdOut ) ;
1461
+ var index = JsonDocument . Parse ( jsonDump . RootElement . GetProperty ( "Properties" ) . GetProperty ( "GeneratedImageIndex" ) . ToString ( ) ) ;
1462
+ var containers = jsonDump . RootElement . GetProperty ( "Items" ) . GetProperty ( "GeneratedContainers" ) . EnumerateArray ( ) . ToArray ( ) ;
1463
+
1464
+ index . RootElement . GetProperty ( "mediaType" ) . GetString ( ) . Should ( ) . Be ( "application/vnd.oci.image.index.v1+json" ) ;
1465
+ containers . Should ( ) . HaveCount ( 2 ) ;
1466
+ foreach ( var container in containers )
1467
+ {
1468
+ container . GetProperty ( "ManifestMediaType" ) . GetString ( ) . Should ( ) . Be ( "application/vnd.oci.image.manifest.v1+json" ) ;
1469
+ }
1470
+ // Cleanup
1471
+ newProjectDir . Delete ( true ) ;
1472
+ }
1356
1473
}
0 commit comments