Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support deploying multiple exes as a single self-contained set #53834

Open
agocke opened this issue Jun 7, 2021 · 22 comments
Open

Support deploying multiple exes as a single self-contained set #53834

agocke opened this issue Jun 7, 2021 · 22 comments

Comments

@agocke
Copy link
Member

agocke commented Jun 7, 2021

This is not currently supported. The closest supported deployment is publishing multiple framework-dependent apps, then setting DOTNET_ROOT before running them to use an unzipped copy of the shared framework.

Supporting this will require both runtime and SDK work to select the appropriate set of shared assemblies.

@ghost
Copy link

ghost commented Jun 7, 2021

Tagging subscribers to this area: @vitek-karas, @agocke, @VSadov
See info in area-owners.md if you want to be subscribed.

Issue Details

This will require both runtime and SDK work to select the appropriate set of shared assemblies.

Author: agocke
Assignees: -
Labels:

area-Host, feature request

Milestone: Future

@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Jun 7, 2021
@agocke agocke removed the untriaged New issue has not been triaged by the area owner label Jun 7, 2021
@ThomasGoulet73
Copy link
Contributor

@agocke Does this include publishing an executable projet as self-contained which itself references another executable project ?Both project should end up in the publish folder and use the same self-contained framework.

@alexrp
Copy link
Contributor

alexrp commented Jun 8, 2021

This feature would be incredibly useful for projects that ship multiple small and focused command line utilities in a single release.

Bonus points if trimming can be done based on the shared assembly set.

@Symbai
Copy link

Symbai commented Jun 8, 2021

@agocke Does this include publishing an executable projet as self-contained which itself references another executable project ?Both project should end up in the publish folder and use the same self-contained framework.

#52974

@agocke
Copy link
Member Author

agocke commented Jun 8, 2021

Presupposes the UI a bit by assuming that "project references to exe projects" is the right way to design this, but yeah, that's the general idea.

@MeikTranel
Copy link

MeikTranel commented Jun 8, 2021

This would be just splendid to be supported. I've been thinking about designs for this for a while now. One idea i had would be updating the FrameworkReference Item to take a filepath to a project file. The Project file in question would define the framework and maybe even PackageReference items used to define common libraries used by more than one project.
The latter would be increasingly useful where a modularized install scenario requires subdirectories for certain App A, B & C that all require the dotnet runtime but may depend on different versions of the same library thus should be isolated in their own directories.

A.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net5.0-windows</TargetFramework>
    <OutputType>winexe</OutputType>
    <UseWPF>true</UseWPF>
    <DisableImplicitFrameworkReference>true</DisableImplicitFrameworkReference>
    <OutputPath>$(SharedOutputPath)/client</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <FrameworkReference Include="../runtime/runtime.msbuildproj" />
  </ItemGroup>
</Project>

B.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net5.0-windows</TargetFramework>
    <DisableImplicitFrameworkReference>true</DisableImplicitFrameworkReference>
    <OutputPath>$(SharedOutputPath)/server</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <FrameworkReference Include="../runtime/runtime.msbuildproj" />
  </ItemGroup>
</Project>

runtime.msbuildproj

<Project Sdk="Microsoft.NET.Sdk.RuntimeCache">
  <PropertyGroup>
    <TargetFramework>net5.0-windows</TargetFramework>
    <OutputPath>$(SharedOutputPath)/runtime</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="13.0.0" />
  </ItemGroup>
</Project>

The sdk could then use the FrameworkReference to traverse and query the outputpath of the runtimecache project and tell the respective targets to either generate .deps.json respecting those path (comparable to how it is run during debug when dlls of packagereferences etc. are resolved via path in deps) OR by declaring the outputpath of the frameworkreference project as a probingPath in the deps.json.

I know this sounds like a stretch from the original requirement, but it really is in the spirit of the change needed, because theres almost no two individual apps that would generate the exact same sets of dependencies (especially with trimming). If you build everything into a single directory msbuild would have to A) concurrently build in the same directory which is problematic of it self, but theres also the problem of a second app publishing their dependencies in the same directory which might override hard dependencies onto different versions thus generating a whole other range of issues.
We have this exact usecase and started producing per app directories for a while now and it really is a more sane experience.

@agocke
Copy link
Member Author

agocke commented Jun 8, 2021

@MeikTranel Thanks, this seems very interesting.

Fair warning though, no timeline or commitment to this yet. .NET 6 is almost certainly impossible, we have too much work to do for existing scenarios.

This could be on the table for .NET 7 though, depending on competing priorities.

@MeikTranel
Copy link

MeikTranel commented Jun 8, 2021

Haha sure no worries - I was expecting this 😊

One thing that would really help immensely would be just the ability to specify a probing path as a relative directory in the runtimeconconfig.json - one can already specify it in as an environment variable with an absolute path so with my utterly minimal knowledge of the host I assumed it should be possible.

@vitek-karas
Copy link
Member

@MeikTranel I just want to make sure I understand what you mean by "probing path". Is it the path to the runtime install location (specified via DOTNET_ROOT env. variable), or is it a probing path for additional assemblies (which can currently be done via for example command line --additionalprobingpaths)?

@MeikTranel
Copy link

The first one primarily - the latter should already be working with the runtimeconfig.json right?

One use case we often use is something like a aspnetcore webapi bundled with a windows service host thats registered via install. Env variables are somewhat awkward in those situations. It almost always requires some sort of wrapping that makes it awkward. It would help tremendously if the runtimeconfig.json parsing or at least small parts of it would happen before the hostfxr is loaded. Basically any mechanism that can be stored permanently within the files built during dotnet build

@vitek-karas
Copy link
Member

Thanks @MeikTranel. Personally I don't like parsing .runtimeconfig.json from the apphost itself (it would bloat the file, which is part of every application).

One of the ideas was something like:

  • Only allow the path to be relative to the application directory
  • Bake the path into the apphost executable as a constant

This would have the downside that it's not possible to modify the path with simple text editor, basically one would have to rebuild the app to do that. The upside is that it's simple and doesn't require too much new code in the apphost.

@MeikTranel
Copy link

That seems fine with me.

@Falco20019
Copy link

Falco20019 commented Jan 11, 2022

Very interested in this. We currently try to achieve this, by basically packing the application as NuGet package through a custom SDK:

** Sdk\Tool.props of Company.SDK.Bundle **

<Project>
    
    <PropertyGroup>
		<IncludeBuildOutput>false</IncludeBuildOutput>
		<CopyBuildOutputToPublishDirectory>false</CopyBuildOutputToPublishDirectory>
		<NoWarn>$(NoWarn);NU5100;NU5128</NoWarn>
    </PropertyGroup>

    <ItemGroup>
		<None Include="$(TargetDir)$(TargetName).*" Pack="true" PackageCopyToOutput="true" PackagePath="contentFiles/any/$(TargetFramework)" />
		<None Remove="$(TargetDir)$(TargetName).runtimeconfig.dev.json" />
		<None Remove="$(TargetDir)$(TargetName).pdb" />
	</ItemGroup>

</Project>

This allows us, to later deploy them together by creating a Dummy project using just PackageReferences onto these packages and setting some properties, to avoid, this generating an empty DLL.

** Sdk\Sdk.props of Company.SDK.Bundle **

<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <PropertyGroup>
    <BootstrapperName>NameOfSharedConfiguration</BootstrapperName>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <ProjectDepsFileName>$(BootstrapperName).deps.json</ProjectDepsFileName>
    <GenerateDependencyFile>true</GenerateDependencyFile>
    <ProjectRuntimeConfigFileName>$(BootstrapperName).runtimeconfig.json</ProjectRuntimeConfigFileName>
    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
    <IncludeMainProjectInDepsFile>false</IncludeMainProjectInDepsFile>
    <CopyBuildOutputToPublishDirectory>false</CopyBuildOutputToPublishDirectory>
    <CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
  </PropertyGroup>

  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" />

</Project>

The bundle itself is then created using a simple csproj file:

** bundle.csproj **

<Project Sdk="Company.SDK.Bundle">
 
    <ItemGroup>
        <PackageReference Include="Company.Tool.Tool1" Version="0.2.*" />
        <PackageReference Include="Company.Tool.Tool2" Version="0.1.2" />
    </ItemGroup>  
		
    <ItemGroup>
        <None Update="some_additional_file_to_bundle" CopyToOutputDirectory="PreserveNewest" />
    </ItemGroup>
</Project>

This will also create the deps.json and the runtimeconfig.json for all those tools. The only downside is, that you will need to execute them using dotnet exec --depsfile NameOfSharedConfiguration.deps.json Tool1.dll instead of the executable, to avoid issues described in #63617.

This has the benefit, that all common dependencies are published together and the dependency trees are calculated over all of them. We started doing this, because we have a dependency on gRPC, which will raise size pretty quickly due to it's native runtimes included per tool. In a regular deployment, we would have 3 copies of the same files for 90% of the DLLs...

@bilbothebaggins
Copy link

Noob here. I can understand that this issue asks about the dotnet publish command that cannot do this out of the box.

But, what is stopping you from publishing your n executables to separate folders and then just copying them all into a single output folder. AFAICT, at least insofar I see my publish folders, there should be no incompatible conflicts/overwrites if one just takes a bunch of exe deploy folders and merges all these into a single output folder (post publish). Yes, there would be a deps.json file for each executable, but that seems like a low overhead to me.

Since I'm fiddling with these systems myself at the moment, clarification would really help me, although it might not help this issue here overmuch, sorry.

@vitek-karas
Copy link
Member

@bilbothebaggins One of the problems is how to guarantee dependency version consistency. Simple example:

  • A.exe depends on C.dll v1
  • B.exe depends on C.dll v2

The separate publish processes will produce C.dll to the output, but each will get a different version. If you copy over the directories you'll end up with one of those versions. Part of the feature should be that this gets resolved at build time, and the dependency is unified across the multiple apps (or you have a way to opt-out and have per-app dependencies).

@bilbothebaggins
Copy link

@vitek-karas Ah ok thanks.

Where we hit a similar situation, we already unify all library versions across all projects via CPM and transitive pinning. So I took this for granted.

Given unified transitive versions for all dependencies of all cooperating executables, would you expect any other problems when throwing them all in the same folder?

@vitek-karas
Copy link
Member

Given unified transitive versions for all dependencies of all cooperating executables, would you expect any other problems when throwing them all in the same folder?

It should probably work - but I didn't go through all the details and possible implications, so don't take this as an official statement please.

One other thing to be mindful of - all the apps should be built with the same target framework, and same publish options (self-contained or not, and so on). Any differences in those could cause potential problems.

@MichalPetryka
Copy link
Contributor

One of the problems is how to guarantee dependency version consistency.

Another would be trimming I guess? Also, are there any plans to work on this in .NET 9?

@MeikTranel
Copy link

Given unified transitive versions for all dependencies of all cooperating executables, would you expect any other problems when throwing them all in the same folder?

The version is the least of the problems tho. We still have to guarantee that a shared dependency folder is being written to from different rid actors.

@julealgon
Copy link

Very interesting issue. We also have a scenario that would benefit from this, as we have this windows service host that relies on 2 command line tools that we also control.

Initially, both tools were being copied to the same folder as the host itself, which created massive issues with dependencies like @vitek-karas described. Recently, we isolated each tool to its own subfolder inside of the host process but this results in quite a bit of duplication as now dependencies that both tools need have to be duplicated.

I'd love to be able to natively have a "host" with "tools" where the dependencies are managed more efficiently, and it seems this here would allow at least some of that.

@Falco20019
Copy link

Falco20019 commented Jul 29, 2024

We extended what I posted in #53834 (comment) to resolve some of those issues. We added some tasks to the Company.SDK.Bundle for this. This is fixing the deps.json and the runtimeconfig.json to work even in cases where i.e. one tool adds ASP framework, leading to some framework-dependencies being left out from the publish.

But it would still be A LOT easier to just have a concept for this directly available by a concept of .NET (local) tool. Especially since this workaround needs to pre-compile and bundle files which also lead to requirements for the build chain (i.e. if you expect an EXE to be contained, the NuPkg has to be build on a Windows runner).

** AddToolDependencies.cs of Company.SDK.Bundle **

using JetBrains.Annotations;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Company.SDK.Bundle
{
    /// <summary>
    /// Add dependency JSON files for all tools in the project.
    /// </summary>
    /// <inheritdoc />
    [PublicAPI]
    public class AddToolDependencies : Task
    {
        private const string RuntimeConfigFileName = ".runtimeconfig.json";
        private const string DepsJsonFileName = ".deps.json";
        private const string RelativePathMetadataName = "RelativePath";

        /// <summary>
        /// The path to the generated dependency file. This should be used as template
        /// for all other tool dependency files.
        /// </summary>
        [Required]
        public string ProjectDepsFilePath { get; set; } = null!;

        /// <summary>
        /// All files to be published. The task computes possible outputs from these
        /// files, and returns them in the AdditionalOutputs list.
        /// It detects tools by checking for an .runtimeconfig.json entry.
        /// </summary>
        [Required]
        public ITaskItem[] ResolvedFileToPublish { get; set; } = null!;

        /// <summary>
        /// Output items per each tool output. They will have the ProjectDepsFilePath set
        /// as Identity and the tool dependency file as RelativePath.
        /// </summary>
        [Output]
        public ITaskItem[] AdditionalOutputs { get; private set; } = null!;

        /// <inheritdoc />
        public override bool Execute()
        {
            var mainDepsFileRelativeName = Path.GetFileName(ProjectDepsFilePath);
            var inputs = ResolvedFileToPublish
                .Select(input => input.GetMetadata(RelativePathMetadataName))
                .Where(input => input.EndsWith(RuntimeConfigFileName));

            var outputs = new List<ITaskItem>();
            foreach (var input in inputs)
            {
                var relativeName = input.Replace(RuntimeConfigFileName, DepsJsonFileName);
                if (relativeName.Equals(mainDepsFileRelativeName)) continue;

                var ti = new TaskItem(ProjectDepsFilePath);
                ti.SetMetadata(RelativePathMetadataName, relativeName);

                outputs.Add(ti);
            }

            AdditionalOutputs = outputs.ToArray();

            return true;
        }
    }
}

** PatchRuntimeConfigs.cs of Company.SDK.Bundle **

using JetBrains.Annotations;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Company.SDK.Bundle
{
    /// <summary>
    /// Path runtime configuration JSON files for all tools in the project.
    /// </summary>
    /// <inheritdoc />
    [PublicAPI]
    public class PatchRuntimeConfigs : Task
    {
        private static readonly JsonSerializer Serializer = new JsonSerializer
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver(),
            Formatting = Formatting.Indented,
            DefaultValueHandling = DefaultValueHandling.Ignore
        };

        [PublicAPI]
        private class RuntimeConfigFramework
        {
            private bool Equals(RuntimeConfigFramework other)
            {
                return Name == other.Name && Version == other.Version;
            }

            public override bool Equals(object? obj)
            {
                if (ReferenceEquals(null, obj)) return false;
                if (ReferenceEquals(this, obj)) return true;
                if (obj.GetType() != GetType()) return false;
                return Equals((RuntimeConfigFramework)obj);
            }

            public override int GetHashCode()
            {
                // It's not possible to make those readonly since they are initialized by property through JSON deserialization.
                // The correct way would be to have the properties marked as "init" instead of "set", but this requires C# 8 which
                // is not possible with netstandard2.1...

                // ReSharper disable NonReadonlyMemberInGetHashCode
                return HashCode.Combine(Name, Version);
                // ReSharper restore NonReadonlyMemberInGetHashCode
            }

            public string? Name { get; set; }
            public string? Version { get; set; }
        }

        private const string RuntimeConfigFileName = ".runtimeconfig.json";
        private const string CopyToPublishDirectoryMetadataName = "CopyToPublishDirectory";

        /// <summary>
        /// The file name of the generated runtime configuration file.
        /// </summary>
        [Required]
        public string ProjectRuntimeConfigFileName { get; set; } = null!;

        /// <summary>
        /// The path to the generated runtime configuration file. This should be used to patch
        /// all other tool runtime configuration files.
        /// </summary>
        [Required]
        public string ProjectRuntimeConfigFilePath { get; set; } = null!;

        /// <summary>
        /// All files that were published. The task computes possible outputs from these
        /// files, and returns them in the PatchedOutputs list.
        /// It detects tools by checking for an .runtimeconfig.json entry.
        /// </summary>
        [Required]
        public ITaskItem[] FileWrites { get; set; } = null!;

        /// <summary>
        /// Output items per each patched tool. They are references to the original (patched) items.
        /// </summary>
        [Output]
        public ITaskItem[] PatchedOutputs { get; private set; } = null!;

        /// <inheritdoc />
        public override bool Execute()
        {
            var inputs = FileWrites
                .Where(input => input.GetMetadata(CopyToPublishDirectoryMetadataName) != string.Empty &&
                                input.ItemSpec.EndsWith(RuntimeConfigFileName));

            var mainRuntimeConfig = ReadFromJsonFile(ProjectRuntimeConfigFilePath);
            var tfm = GetTfm(mainRuntimeConfig);
            if (tfm == null)
            {
                Log.LogError("Not able to fetch tfm from ProjectRuntimeConfigFilePath. Patching not possible.");
                return false;
            }

            var frameworks = GetFrameworks(mainRuntimeConfig);
            if (frameworks == null)
            {
                Log.LogError("Not able to fetch frameworks from ProjectRuntimeConfigFilePath. Patching not possible.");
                return false;
            }

            var outputs = new List<ITaskItem>();
            foreach (var input in inputs)
            {
                var absoluteName = input.ItemSpec;
                var relativeName = Path.GetFileName(absoluteName);

                if (relativeName.Equals(ProjectRuntimeConfigFileName)) continue;
                if (!PatchJson(absoluteName, tfm, frameworks)) continue;

                outputs.Add(input);
            }

            PatchedOutputs = outputs.ToArray();

            return true;
        }

        private string? GetTfm(JObject config)
        {
            if (!config.TryGetValue("runtimeOptions", out var optionsToken) || !(optionsToken is JObject options))
            {
                Log.LogError("The config file is not containing the required runtimeOptions property.");
                return null;
            }

            if (!options.TryGetValue("tfm", out var tfmToken))
            {
                Log.LogError("The config file is not containing the required tfm property.");
                return null;
            }

            return tfmToken.Value<string>();
        }

        private RuntimeConfigFramework[]? GetFrameworks(JObject config)
        {
            if (!config.TryGetValue("runtimeOptions", out var optionsToken) || !(optionsToken is JObject options))
            {
                Log.LogError("The config file is not containing the required runtimeOptions property.");
                return null;
            }

            if (options.TryGetValue("framework", out var frameworkToken) && frameworkToken is JObject framework)
            {
                return new[] { framework.ToObject<RuntimeConfigFramework>()! };
            }

            if (!options.TryGetValue("frameworks", out var frameworksToken) || !(frameworksToken is JArray frameworks))
            {
                Log.LogError("The config file is not containing the required framework OR frameworks property.");
                return null;
            }

            return frameworks.Select(f => f.ToObject<RuntimeConfigFramework>()!).ToArray();
        }

        private bool PatchJson(string toolRuntimeConfigPath, string tfm, RuntimeConfigFramework[] frameworks)
        {
            var toolRuntimeConfig = ReadFromJsonFile(toolRuntimeConfigPath);

            var patchedAnything = PatchTfm(toolRuntimeConfigPath, toolRuntimeConfig, tfm);
            patchedAnything |= PatchFrameworks(toolRuntimeConfigPath, toolRuntimeConfig, frameworks);

            if (!patchedAnything)
            {
                return false;
            }

            WriteToJsonFile(toolRuntimeConfigPath, toolRuntimeConfig);
            return true;
        }

        private bool PatchTfm(string toolRuntimeConfigPath, JObject toolRuntimeConfig, string tfm)
        {
            var toolTfm = GetTfm(toolRuntimeConfig);
            if (toolTfm == null)
            {
                Log.LogError("Not able to fetch tfm from {0}. File will remain unpatched.", toolRuntimeConfigPath);
                return false;
            }

            // Get highest tfm:
            var highestTfm = string.Compare(tfm, toolTfm, StringComparison.Ordinal) > 0
                ? tfm
                : toolTfm;

            // Nothing to patch:
            if (toolTfm == tfm) return false;

            // Replace tfm with highest:
            var runtimeOptions = toolRuntimeConfig["runtimeOptions"]!;
            runtimeOptions["tfm"] = highestTfm;

            return true;
        }

        private bool PatchFrameworks(string toolRuntimeConfigPath, JObject toolRuntimeConfig, RuntimeConfigFramework[] frameworks)
        {
            var toolFrameworks = GetFrameworks(toolRuntimeConfig);
            if (toolFrameworks == null)
            {
                Log.LogError("Not able to fetch frameworks from {0}. File will remain unpatched.", toolRuntimeConfigPath);
                return false;
            }

            // Get runtime framework version:
            var runtimeVersion = frameworks.FirstOrDefault()?.Version;

            // Patch all tool frameworks to the runtime version:
            var atLeastOnePatched = false;
            foreach (var framework in toolFrameworks)
            {
                if (framework.Version == runtimeVersion) continue;

                atLeastOnePatched = true;
                framework.Version = runtimeVersion;
            }

            // Get combination of frameworks:
            var combinedFrameworks = frameworks
                .Union(toolFrameworks)
                .ToArray();

            // Nothing to patch:
            if (!atLeastOnePatched && combinedFrameworks.Length == toolFrameworks.Length) return false;

            // Make sure we always use "frameworks" and not "framework":
            var runtimeOptions = toolRuntimeConfig["runtimeOptions"]!;
            runtimeOptions["framework"]?.Parent?.Replace(new JProperty("frameworks"));

            // Replace frameworks with combined list:
            runtimeOptions["frameworks"] = new JArray(combinedFrameworks.Select(framework =>
                JObject.FromObject(framework, Serializer)));

            return true;
        }

        private static JObject ReadFromJsonFile(string path)
        {
            using var reader = new JsonTextReader(File.OpenText(path));
            return JObject.Load(reader);
        }

        private static void WriteToJsonFile(string fileName, JObject value)
        {
            using var writer = new JsonTextWriter(new StreamWriter(File.Create(fileName)));
            Serializer.Serialize(writer, value);
        }
    }
}

** Sdk\Sdk.targets of Company.SDK.Bundle **

<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />

  <PropertyGroup>
    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
    <_SDK_MsBuildAssembly>netstandard2.1\SDK.MSBuild.dll</_SDK_MsBuildAssembly>
  </PropertyGroup>

  <UsingTask AssemblyFile="$(_SDK_MsBuildAssembly)" TaskName="Company.SDK.Bundle.AddToolDependencies" />
  <UsingTask AssemblyFile="$(_SDK_MsBuildAssembly)" TaskName="Company.SDK.Bundle.PatchRuntimeConfigs" />

  <Target Name="_AddToolDependencies" AfterTargets="ComputeResolvedFilesToPublishList">
    <AddToolDependencies ProjectDepsFilePath="$(ProjectDepsFilePath)"
                         ResolvedFileToPublish="@(ResolvedFileToPublish)">
      <Output TaskParameter="AdditionalOutputs" ItemName="_AdditionalOutputs" />
    </AddToolDependencies>

    <ItemGroup>
      <!-- Copy the .deps.json file from the Bootstrapper to all tools. -->
      <ResolvedFileToPublish Include="@(_AdditionalOutputs)">
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      </ResolvedFileToPublish>
    </ItemGroup>

    <Message Importance="High" Text="Added additional tool dependencies:" Condition="'@(_AdditionalOutputs)' != ''" />
    <Message Importance="High" Text="- %(_AdditionalOutputs.RelativePath)" Condition="'@(_AdditionalOutputs)' != ''" />
  </Target>

  <Target Name="_PatchRuntimeConfigs" AfterTargets="CopyFilesToPublishDirectory">
    <PatchRuntimeConfigs ProjectRuntimeConfigFileName="$(ProjectRuntimeConfigFileName)"
                         ProjectRuntimeConfigFilePath="$(ProjectRuntimeConfigFilePath)"
                         FileWrites="@(FileWrites)">
      <Output TaskParameter="PatchedOutputs" ItemName="_PatchedOutputs" />
    </PatchRuntimeConfigs>

    <Message Importance="High" Text="Patched runtime configs:" Condition="'@(_PatchedOutputs)' != ''" />
    <Message Importance="High" Text="- %(_PatchedOutputs.RelativePath)" Condition="'@(_PatchedOutputs)' != ''" />
  </Target>
</Project>

@MeikTranel
Copy link

This just goes to show how a (naively spelled out of course) simple change would prove tremendous in value. We need in this order:

  • Ability to refer to a shared runtime/runtimeconfig.json from an app build.
  • Ability to compile that path into the apphost binary directly
  • Ability refer to a source project for a shared runtime from host projects to facilitate the above.

In that Order. Each of these steps would unlock a whole slew of opportunities and therefore make these next steps easier to build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

No branches or pull requests

10 participants