diff --git a/corebuild/integration/ILLink.Tasks/ILLink.Tasks.targets b/corebuild/integration/ILLink.Tasks/ILLink.Tasks.targets index ce76e7136831..70c051ec28e2 100644 --- a/corebuild/integration/ILLink.Tasks/ILLink.Tasks.targets +++ b/corebuild/integration/ILLink.Tasks/ILLink.Tasks.targets @@ -99,7 +99,7 @@ - + @@ -114,7 +114,7 @@ <_DebugSymbolsIntermediatePath Include="@(_LinkedDebugSymbols)" Condition=" '$(_DebugSymbolsProduced)' == 'true' " /> - + + + + + + <_ReferencedLibraries Include="@(ReferencePath->'%(ResolvedPath)')" Exclude="@(_ReferencedLibrariesToExclude)" /> + <_ManagedAssembliesToLink Include="@(_ReferencedLibraries)" /> + + @@ -307,7 +327,7 @@ output and the generated deps.json file. Excluding it from _ManagedResolvedAssembliesToPublish will prevent it from getting filtered out of the publish output later. - + In the future we may want to detect ngen assemblies and filter them more robustly. --> - @@ -363,7 +384,7 @@ - @@ -373,13 +394,19 @@ - - + + + + + @@ -425,7 +452,7 @@ <_RemovedNativeDeps Include="@(_NativeResolvedDepsToPublish)" /> <_RemovedNativeDeps Remove="@(_NativeKeptDepsToPublish)" /> - + <_PublishConflictPackageFiles Include="@(_RemovedManagedAssemblies)" /> <_PublishConflictPackageFiles Include="@(_RemovedNativeDeps)" /> diff --git a/corebuild/integration/test/CommandRunner.cs b/corebuild/integration/test/CommandRunner.cs index 58c4b3416743..efee6cbbd7f0 100644 --- a/corebuild/integration/test/CommandRunner.cs +++ b/corebuild/integration/test/CommandRunner.cs @@ -67,6 +67,7 @@ public int Run(out string commandOutput) RedirectStandardOutput = true, RedirectStandardError = true, }; + outputHelper.WriteLine($"caller working directory: {Environment.CurrentDirectory}"); if (!String.IsNullOrEmpty(args)) { psi.Arguments = args; outputHelper.WriteLine($"{command} {args}"); @@ -74,7 +75,7 @@ public int Run(out string commandOutput) outputHelper.WriteLine($"{command}"); } if (!String.IsNullOrEmpty(workingDir)) { - outputHelper.WriteLine("$working directory: {workingDir}"); + outputHelper.WriteLine($"working directory: {workingDir}"); psi.WorkingDirectory = workingDir; } if (!String.IsNullOrEmpty(additionalPath)) { diff --git a/corebuild/integration/test/HelloWorldTest.cs b/corebuild/integration/test/HelloWorldTest.cs index 009601e8875c..23e4662c2062 100644 --- a/corebuild/integration/test/HelloWorldTest.cs +++ b/corebuild/integration/test/HelloWorldTest.cs @@ -7,11 +7,21 @@ namespace ILLink.Tests { public class HelloWorldTest : IntegrationTestBase { - public HelloWorldTest(ITestOutputHelper output) : base(output) {} + private string csproj; + + public HelloWorldTest(ITestOutputHelper output) : base(output) { + csproj = SetupProject(); + } public string SetupProject() { string projectRoot = "helloworld"; + string csproj = Path.Combine(projectRoot, $"{projectRoot}.csproj"); + + if (File.Exists(csproj)) { + output.WriteLine($"using existing project {csproj}"); + return csproj; + } if (Directory.Exists(projectRoot)) { Directory.Delete(projectRoot, true); @@ -24,20 +34,28 @@ public string SetupProject() Assert.True(false); } - string csproj = Path.Combine(projectRoot, $"{projectRoot}.csproj"); + AddLinkerReference(csproj); + return csproj; } [Fact] - public void RunHelloWorld() + public void RunHelloWorldStandalone() { - string csproj = SetupProject(); - - AddLinkerReference(csproj); + string executablePath = BuildAndLink(csproj, selfContained: true); + CheckOutput(executablePath, selfContained: true); + } - BuildAndLink(csproj, null); + [Fact] + public void RunHelloWorldPortable() + { + string target = BuildAndLink(csproj, selfContained: false); + CheckOutput(target, selfContained: false); + } - int ret = RunApp(csproj, out string commandOutput); + void CheckOutput(string target, bool selfContained = false) + { + int ret = RunApp(target, out string commandOutput, selfContained: selfContained); Assert.True(ret == 0); Assert.True(commandOutput.Contains("Hello World!")); } diff --git a/corebuild/integration/test/IntegrationTestBase.cs b/corebuild/integration/test/IntegrationTestBase.cs index 951e5d575aeb..99218ba3e36a 100644 --- a/corebuild/integration/test/IntegrationTestBase.cs +++ b/corebuild/integration/test/IntegrationTestBase.cs @@ -60,14 +60,18 @@ protected int RunCommand(string command, string args, string workingDir, string /// that the project already contains a reference to the /// linker task package. /// Optionally takes a list of root descriptor files. + /// Returns the path to the built app, either the renamed + /// host for self-contained publish, or the dll containing + /// the entry point. /// - public void BuildAndLink(string csproj, List rootFiles = null, Dictionary extraPublishArgs = null) + public string BuildAndLink(string csproj, List rootFiles = null, Dictionary extraPublishArgs = null, bool selfContained = false) { - string rid = context.RuntimeIdentifier; - string config = context.Configuration; string demoRoot = Path.GetDirectoryName(csproj); - string publishArgs = $"publish -r {rid} -c {config} /v:n /p:ShowLinkerSizeComparison=true"; + string publishArgs = $"publish -c {context.Configuration} /v:n /p:ShowLinkerSizeComparison=true"; + if (selfContained) { + publishArgs += $" -r {context.RuntimeIdentifier}"; + } string rootFilesStr; if (rootFiles != null && rootFiles.Any()) { rootFilesStr = String.Join(";", rootFiles); @@ -83,28 +87,45 @@ public void BuildAndLink(string csproj, List rootFiles = null, Dictionar if (ret != 0) { output.WriteLine("publish failed, returning " + ret); Assert.True(false); - return; } - } - public int RunApp(string csproj, out string processOutput, int timeout = Int32.MaxValue, string terminatingOutput = null) - { - string demoRoot = Path.GetDirectoryName(csproj); // detect the target framework for which the app was published string tfmDir = Path.Combine(demoRoot, "bin", context.Configuration); string tfm = Directory.GetDirectories(tfmDir).Select(p => Path.GetFileName(p)).Single(); - string executablePath = Path.Combine(tfmDir, tfm, - context.RuntimeIdentifier, "publish", - Path.GetFileNameWithoutExtension(csproj) - ); - if (context.RuntimeIdentifier.Contains("win")) { - executablePath += ".exe"; + string builtApp = Path.Combine(tfmDir, tfm); + if (selfContained) { + builtApp = Path.Combine(builtApp, context.RuntimeIdentifier); } - Assert.True(File.Exists(executablePath)); + builtApp = Path.Combine(builtApp, "publish", + Path.GetFileNameWithoutExtension(csproj)); + if (selfContained) { + if (context.RuntimeIdentifier.Contains("win")) { + builtApp += ".exe"; + } + } else { + builtApp += ".dll"; + } + Assert.True(File.Exists(builtApp)); + return builtApp; + } - int ret = RunCommand(executablePath, null, - Directory.GetParent(executablePath).FullName, - null, out processOutput, timeout, terminatingOutput); + public int RunApp(string target, out string processOutput, int timeout = Int32.MaxValue, + string terminatingOutput = null, bool selfContained = false) + { + Assert.True(File.Exists(target)); + int ret; + if (selfContained) { + ret = RunCommand( + target, null, + Directory.GetParent(target).FullName, + null, out processOutput, timeout, terminatingOutput); + } else { + ret = RunCommand( + Path.GetFullPath(context.DotnetToolPath), + Path.GetFullPath(target), + Directory.GetParent(target).FullName, + null, out processOutput, timeout, terminatingOutput); + } return ret; } diff --git a/corebuild/integration/test/MusicStoreTest.cs b/corebuild/integration/test/MusicStoreTest.cs index 7aa53d414aff..a28e996d2489 100644 --- a/corebuild/integration/test/MusicStoreTest.cs +++ b/corebuild/integration/test/MusicStoreTest.cs @@ -10,8 +10,6 @@ namespace ILLink.Tests { public class MusicStoreTest : IntegrationTestBase { - public MusicStoreTest(ITestOutputHelper output) : base(output) {} - private static List rootFiles = new List { "MusicStoreReflection.xml" }; private static string gitRepo = "http://github.com/aspnet/JitBench"; @@ -34,40 +32,140 @@ public MusicStoreTest(ITestOutputHelper output) : base(output) {} // The version of Microsoft.AspNetCore.All to publish with. private static string aspNetVersion = "2.1.0-preview1-27654"; + private static Dictionary versionPublishArgs; + private static Dictionary VersionPublishArgs + { + get { + if (versionPublishArgs != null) { + return versionPublishArgs; + } + versionPublishArgs = new Dictionary(); + versionPublishArgs.Add("JITBENCH_FRAMEWORK_VERSION", runtimeVersion); + versionPublishArgs.Add("JITBENCH_ASPNET_VERSION", aspNetVersion); + return versionPublishArgs; + } + } + + private static string csproj; + + public MusicStoreTest(ITestOutputHelper output) : base(output) { + csproj = SetupProject(); + + // MusicStore targets .NET Core 2.1, so it must be built + // using an SDK that can target 2.1. We obtain that SDK + // here. + context.DotnetToolPath = ObtainSDK(context.TestBin, repoName); + } + + [Fact] + public void RunMusicStoreStandalone() + { + string executablePath = BuildAndLink(csproj, rootFiles, VersionPublishArgs, selfContained: true); + CheckOutput(executablePath, selfContained: true); + } + [Fact] - public void RunMusicStore() + public void RunMusicStorePortable() { - string csproj = SetupProject(); + Dictionary extraPublishArgs = new Dictionary(VersionPublishArgs); + extraPublishArgs.Add("PublishWithAspNetCoreTargetManifest", "false"); + string target = BuildAndLink(csproj, null, extraPublishArgs, selfContained: false); + CheckOutput(target, selfContained: false); + } + + void CheckOutput(string target, bool selfContained = false) + { + int ret = RunApp(target, out string commandOutput, selfContained: selfContained); + + Assert.True(commandOutput.Contains("Starting request to http://localhost:5000")); + Assert.True(commandOutput.Contains("Response: OK")); + Assert.True(commandOutput.Contains("Running 100 requests")); + Assert.True(ret == 0); + } + + // returns path to .csproj project file + string SetupProject() + { + int ret; + string demoRoot = Path.Combine(repoName, Path.Combine("src", "MusicStore")); + string csproj = Path.Combine(demoRoot, "MusicStore.csproj"); + + if (File.Exists(csproj)) { + output.WriteLine($"using existing project {csproj}"); + return csproj; + } + + if (Directory.Exists(repoName)) { + Directory.Delete(repoName, true); + } + + ret = RunCommand("git", $"clone {gitRepo} {repoName}"); + if (ret != 0) { + output.WriteLine("git failed"); + Assert.True(false); + } + + if (!Directory.Exists(demoRoot)) { + output.WriteLine($"{demoRoot} does not exist"); + Assert.True(false); + } + + ret = RunCommand("git", $"checkout {gitRevision}", demoRoot); + if (ret != 0) { + output.WriteLine($"problem checking out revision {gitRevision}"); + Assert.True(false); + } // Copy root files into the project directory - string demoRoot= Path.GetDirectoryName(csproj); CopyRootFiles(demoRoot); // This is necessary because JitBench comes with a // NuGet.Config that has a line, preventing // NuGet.Config sources defined in outer directories from // applying. - string nugetConfig = Path.Combine("JitBench", "NuGet.config"); + string nugetConfig = Path.Combine(repoName, "NuGet.config"); AddLocalNugetFeedAfterClear(nugetConfig); AddLinkerReference(csproj); - Dictionary extraPublishArgs = new Dictionary(); - extraPublishArgs.Add("JITBENCH_FRAMEWORK_VERSION", runtimeVersion); - extraPublishArgs.Add("JITBENCH_ASPNET_VERSION", aspNetVersion); - BuildAndLink(csproj, rootFiles, extraPublishArgs); + AddGlobalJson(repoName); - int ret = RunApp(csproj, out string commandOutput); - Assert.True(commandOutput.Contains("Starting request to http://localhost:5000")); - Assert.True(commandOutput.Contains("Response: OK")); - Assert.True(commandOutput.Contains("Running 100 requests")); - Assert.True(ret == 0); + return csproj; + } + + void AddGlobalJson(string repoDir) + { + string globalJson = Path.Combine(repoDir, "global.json"); + string globalJsonContents = "{ \"sdk\": { \"version\": \"" + sdkVersion + "\" } }\n"; + File.WriteAllText(globalJson, globalJsonContents); + } + + + string GetDotnetToolPath(string dotnetDir) + { + string dotnetToolName = Directory.GetFiles(dotnetDir) + .Select(p => Path.GetFileName(p)) + .Where(p => p.Contains("dotnet")) + .Single(); + string dotnetToolPath = Path.Combine(dotnetDir, dotnetToolName); + + if (!File.Exists(dotnetToolPath)) { + output.WriteLine("repo-local dotnet tool does not exist."); + Assert.True(false); + } + + return dotnetToolPath; } string ObtainSDK(string rootDir, string repoDir) { int ret; string dotnetDirName = ".dotnet"; + string dotnetDir = Path.Combine(rootDir, dotnetDirName); + if (Directory.Exists(dotnetDir)) { + return GetDotnetToolPath(dotnetDir); + } + string dotnetInstall = Path.Combine(Path.GetFullPath(repoDir), "dotnet-install"); if (context.RuntimeIdentifier.Contains("win")) { dotnetInstall += ".ps1"; @@ -103,57 +201,7 @@ string ObtainSDK(string rootDir, string repoDir) } } - string dotnetDir = Path.Combine(rootDir, dotnetDirName); - string dotnetToolName = Directory.GetFiles(dotnetDir) - .Select(p => Path.GetFileName(p)) - .Where(p => p.Contains("dotnet")) - .Single(); - string dotnetToolPath = Path.Combine(dotnetDir, dotnetToolName); - if (!File.Exists(dotnetToolPath)) { - output.WriteLine("repo-local dotnet tool does not exist."); - Assert.True(false); - } - - string globalJson = Path.Combine(repoDir, "global.json"); - string globalJsonContents = "{ \"sdk\": { \"version\": \"" + sdkVersion + "\" } }\n"; - File.WriteAllText(globalJson, globalJsonContents); - - return dotnetToolPath; - } - - // returns path to .csproj project file - string SetupProject() - { - int ret; - if (Directory.Exists(repoName)) { - Directory.Delete(repoName, true); - } - - ret = RunCommand("git", $"clone {gitRepo}"); - if (ret != 0) { - output.WriteLine("git failed"); - Assert.True(false); - } - - string demoRoot = Path.Combine("JitBench", Path.Combine("src", "MusicStore")); - if (!Directory.Exists(demoRoot)) { - output.WriteLine($"{demoRoot} does not exist"); - Assert.True(false); - } - - ret = RunCommand("git", $"checkout {gitRevision}", demoRoot); - if (ret != 0) { - output.WriteLine($"problem checking out revision {gitRevision}"); - Assert.True(false); - } - - // MusicStore targets .NET Core 2.1, so it must be built - // using an SDK that can target 2.1. We obtain that SDK - // here. - context.DotnetToolPath = ObtainSDK(context.TestBin, repoName); - - string csproj = Path.Combine(demoRoot, "MusicStore.csproj"); - return csproj; + return GetDotnetToolPath(dotnetDir); } static void CopyRootFiles(string demoRoot) diff --git a/corebuild/integration/test/WebApiTest.cs b/corebuild/integration/test/WebApiTest.cs index 2d948e951aec..c7e8683e0210 100644 --- a/corebuild/integration/test/WebApiTest.cs +++ b/corebuild/integration/test/WebApiTest.cs @@ -8,11 +8,21 @@ namespace ILLink.Tests { public class WebApiTest : IntegrationTestBase { - public WebApiTest(ITestOutputHelper output) : base(output) {} + private string csproj; + + public WebApiTest(ITestOutputHelper output) : base(output) { + csproj = SetupProject(); + } public string SetupProject() { string projectRoot = "webapi"; + string csproj = Path.Combine(projectRoot, $"{projectRoot}.csproj"); + + if (File.Exists(csproj)) { + output.WriteLine($"using existing project {csproj}"); + return csproj; + } if (Directory.Exists(projectRoot)) { Directory.Delete(projectRoot, true); @@ -25,7 +35,10 @@ public string SetupProject() Assert.True(false); } - string csproj = Path.Combine(projectRoot, $"{projectRoot}.csproj"); + PreventPublishFiltering(csproj); + + AddLinkerReference(csproj); + return csproj; } @@ -49,18 +62,23 @@ private void PreventPublishFiltering(string csproj) { } [Fact] - public void RunWebApi() + public void RunWebApiStandalone() { - string csproj = SetupProject(); - - PreventPublishFiltering(csproj); - - AddLinkerReference(csproj); + string executablePath = BuildAndLink(csproj, selfContained: true); + CheckOutput(executablePath, selfContained: true); + } - BuildAndLink(csproj); + [Fact] + public void RunWebApiPortable() + { + string target = BuildAndLink(csproj, selfContained: false); + CheckOutput(target, selfContained: false); + } + void CheckOutput(string target, bool selfContained = false) + { string terminatingOutput = "Now listening on: http://localhost:5000"; - int ret = RunApp(csproj, out string commandOutput, 60000, terminatingOutput); + int ret = RunApp(target, out string commandOutput, 60000, terminatingOutput, selfContained: selfContained); Assert.True(commandOutput.Contains("Application started. Press Ctrl+C to shut down.")); Assert.True(commandOutput.Contains(terminatingOutput)); }