You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Support deploying multiple exes as a single self-contained set #53834
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.
Reacted by Juan Hoyos, Alex Rønne Petersen, Michał Petryka, jpapp05, Benjamin Krämer, Julien Lebosquain, lindexi, Oscar Gullberg, Anton Chichkov, Luc and 35 more
@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.
@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.
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.
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.
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.
@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)?
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
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.
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...
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.
@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).
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?
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.
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.
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.
Reacted by dbul, Maxim Marinchenko, Ivan and Alexander Komarov
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 **
usingJetBrains.Annotations;usingMicrosoft.Build.Framework;usingMicrosoft.Build.Utilities;usingSystem.Collections.Generic;usingSystem.IO;usingSystem.Linq;namespaceCompany.SDK.Bundle{/// <summary>/// Add dependency JSON files for all tools in the project./// </summary>/// <inheritdoc />[PublicAPI]publicclassAddToolDependencies:Task{privateconststringRuntimeConfigFileName=".runtimeconfig.json";privateconststringDepsJsonFileName=".deps.json";privateconststringRelativePathMetadataName="RelativePath";/// <summary>/// The path to the generated dependency file. This should be used as template/// for all other tool dependency files./// </summary>[Required]publicstringProjectDepsFilePath{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]publicITaskItem[]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]publicITaskItem[]AdditionalOutputs{get;privateset;}=null!;/// <inheritdoc />publicoverrideboolExecute(){varmainDepsFileRelativeName=Path.GetFileName(ProjectDepsFilePath);varinputs=ResolvedFileToPublish.Select(input =>input.GetMetadata(RelativePathMetadataName)).Where(input =>input.EndsWith(RuntimeConfigFileName));varoutputs=newList<ITaskItem>();foreach(varinputininputs){varrelativeName=input.Replace(RuntimeConfigFileName,DepsJsonFileName);if(relativeName.Equals(mainDepsFileRelativeName))continue;varti=newTaskItem(ProjectDepsFilePath);ti.SetMetadata(RelativePathMetadataName,relativeName);outputs.Add(ti);}AdditionalOutputs=outputs.ToArray();returntrue;}}}
** PatchRuntimeConfigs.cs of Company.SDK.Bundle **
usingJetBrains.Annotations;usingMicrosoft.Build.Framework;usingMicrosoft.Build.Utilities;usingNewtonsoft.Json;usingNewtonsoft.Json.Linq;usingNewtonsoft.Json.Serialization;usingSystem;usingSystem.Collections.Generic;usingSystem.IO;usingSystem.Linq;namespaceCompany.SDK.Bundle{/// <summary>/// Path runtime configuration JSON files for all tools in the project./// </summary>/// <inheritdoc />[PublicAPI]publicclassPatchRuntimeConfigs:Task{privatestaticreadonlyJsonSerializerSerializer=newJsonSerializer{ContractResolver=newCamelCasePropertyNamesContractResolver(),Formatting=Formatting.Indented,DefaultValueHandling=DefaultValueHandling.Ignore};[PublicAPI]privateclassRuntimeConfigFramework{privateboolEquals(RuntimeConfigFrameworkother){returnName==other.Name&&Version==other.Version;}publicoverrideboolEquals(object?obj){if(ReferenceEquals(null,obj))returnfalse;if(ReferenceEquals(this,obj))returntrue;if(obj.GetType()!=GetType())returnfalse;returnEquals((RuntimeConfigFramework)obj);}publicoverrideintGetHashCode(){// 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 NonReadonlyMemberInGetHashCodereturnHashCode.Combine(Name,Version);// ReSharper restore NonReadonlyMemberInGetHashCode}publicstring?Name{get;set;}publicstring?Version{get;set;}}privateconststringRuntimeConfigFileName=".runtimeconfig.json";privateconststringCopyToPublishDirectoryMetadataName="CopyToPublishDirectory";/// <summary>/// The file name of the generated runtime configuration file./// </summary>[Required]publicstringProjectRuntimeConfigFileName{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]publicstringProjectRuntimeConfigFilePath{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]publicITaskItem[]FileWrites{get;set;}=null!;/// <summary>/// Output items per each patched tool. They are references to the original (patched) items./// </summary>[Output]publicITaskItem[]PatchedOutputs{get;privateset;}=null!;/// <inheritdoc />publicoverrideboolExecute(){varinputs=FileWrites.Where(input =>input.GetMetadata(CopyToPublishDirectoryMetadataName)!=string.Empty&&input.ItemSpec.EndsWith(RuntimeConfigFileName));varmainRuntimeConfig=ReadFromJsonFile(ProjectRuntimeConfigFilePath);vartfm=GetTfm(mainRuntimeConfig);if(tfm==null){Log.LogError("Not able to fetch tfm from ProjectRuntimeConfigFilePath. Patching not possible.");returnfalse;}varframeworks=GetFrameworks(mainRuntimeConfig);if(frameworks==null){Log.LogError("Not able to fetch frameworks from ProjectRuntimeConfigFilePath. Patching not possible.");returnfalse;}varoutputs=newList<ITaskItem>();foreach(varinputininputs){varabsoluteName=input.ItemSpec;varrelativeName=Path.GetFileName(absoluteName);if(relativeName.Equals(ProjectRuntimeConfigFileName))continue;if(!PatchJson(absoluteName,tfm,frameworks))continue;outputs.Add(input);}PatchedOutputs=outputs.ToArray();returntrue;}privatestring?GetTfm(JObjectconfig){if(!config.TryGetValue("runtimeOptions",outvaroptionsToken)||!(optionsTokenisJObjectoptions)){Log.LogError("The config file is not containing the required runtimeOptions property.");returnnull;}if(!options.TryGetValue("tfm",outvartfmToken)){Log.LogError("The config file is not containing the required tfm property.");returnnull;}returntfmToken.Value<string>();}privateRuntimeConfigFramework[]?GetFrameworks(JObjectconfig){if(!config.TryGetValue("runtimeOptions",outvaroptionsToken)||!(optionsTokenisJObjectoptions)){Log.LogError("The config file is not containing the required runtimeOptions property.");returnnull;}if(options.TryGetValue("framework",outvarframeworkToken)&&frameworkTokenisJObjectframework){returnnew[]{framework.ToObject<RuntimeConfigFramework>()!};}if(!options.TryGetValue("frameworks",outvarframeworksToken)||!(frameworksTokenisJArrayframeworks)){Log.LogError("The config file is not containing the required framework OR frameworks property.");returnnull;}returnframeworks.Select(f =>f.ToObject<RuntimeConfigFramework>()!).ToArray();}privateboolPatchJson(stringtoolRuntimeConfigPath,stringtfm,RuntimeConfigFramework[]frameworks){vartoolRuntimeConfig=ReadFromJsonFile(toolRuntimeConfigPath);varpatchedAnything=PatchTfm(toolRuntimeConfigPath,toolRuntimeConfig,tfm);patchedAnything|=PatchFrameworks(toolRuntimeConfigPath,toolRuntimeConfig,frameworks);if(!patchedAnything){returnfalse;}WriteToJsonFile(toolRuntimeConfigPath,toolRuntimeConfig);returntrue;}privateboolPatchTfm(stringtoolRuntimeConfigPath,JObjecttoolRuntimeConfig,stringtfm){vartoolTfm=GetTfm(toolRuntimeConfig);if(toolTfm==null){Log.LogError("Not able to fetch tfm from {0}. File will remain unpatched.",toolRuntimeConfigPath);returnfalse;}// Get highest tfm:varhighestTfm=string.Compare(tfm,toolTfm,StringComparison.Ordinal)>0?tfm:toolTfm;// Nothing to patch:if(toolTfm==tfm)returnfalse;// Replace tfm with highest:varruntimeOptions=toolRuntimeConfig["runtimeOptions"]!;runtimeOptions["tfm"]=highestTfm;returntrue;}privateboolPatchFrameworks(stringtoolRuntimeConfigPath,JObjecttoolRuntimeConfig,RuntimeConfigFramework[]frameworks){vartoolFrameworks=GetFrameworks(toolRuntimeConfig);if(toolFrameworks==null){Log.LogError("Not able to fetch frameworks from {0}. File will remain unpatched.",toolRuntimeConfigPath);returnfalse;}// Get runtime framework version:varruntimeVersion=frameworks.FirstOrDefault()?.Version;// Patch all tool frameworks to the runtime version:varatLeastOnePatched=false;foreach(varframeworkintoolFrameworks){if(framework.Version==runtimeVersion)continue;atLeastOnePatched=true;framework.Version=runtimeVersion;}// Get combination of frameworks:varcombinedFrameworks=frameworks.Union(toolFrameworks).ToArray();// Nothing to patch:if(!atLeastOnePatched&&combinedFrameworks.Length==toolFrameworks.Length)returnfalse;// Make sure we always use "frameworks" and not "framework":varruntimeOptions=toolRuntimeConfig["runtimeOptions"]!;runtimeOptions["framework"]?.Parent?.Replace(newJProperty("frameworks"));// Replace frameworks with combined list:runtimeOptions["frameworks"]=newJArray(combinedFrameworks.Select(framework =>JObject.FromObject(framework,Serializer)));returntrue;}privatestaticJObjectReadFromJsonFile(stringpath){usingvarreader=newJsonTextReader(File.OpenText(path));returnJObject.Load(reader);}privatestaticvoidWriteToJsonFile(stringfileName,JObjectvalue){usingvarwriter=newJsonTextWriter(newStreamWriter(File.Create(fileName)));Serializer.Serialize(writer,value);}}}
Just adding our own reason for wanting a shared self-contained deployment:
We have an app distribution to 150K+ customer servers with multiple exes
It's not appropriate for us to install/update a shared runtime potentially affecting customers own critical app workloads, so (multiple) self-contained exes or single file executables are our main option.
Taking up disk space with duplicated runtime per exe is frowned upon in a server environment, so we're looking to reduce our installed footprint.
The issue for us with single file exes are all dependencies being bundled in the exe means one super large exe gets loaded instead of stuff getting loaded on demand, plugin support also gets trickier, some things misbehave as a single file and need special fixes or continuous re-testing.
I didn't realise this wasn't supported, as we've actually been doing this for years with a solution file.
We have a bunch of executable projects depending on bunch of the same classlib projects, all in the same repo, all depending on the exact same versions of dependencies defined through Directory.Packages.props.
We have a ProjectsToDeploy.slnf, which only contains the executable projects. dotnet publish ProjectsToDeploy.slnf --self-contained -f net8.0 -r <RID> -o Deploy outputs executables of all these projects into the same Deploy folder.
It does result in a warning NETSDK1194 since NET 7 (we've been doing this before NET 7 was released), but in practice I haven't seen any issues.
I also just double-checked and the same thing is doable with a traversal project as well, which I assume does the same thing (copy results into the same output folder) but that doesn't generate a warning - probably should.
Activity
ThomasGoulet73 commentedon Jun 7, 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.
alexrp commentedon 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 commentedon Jun 8, 2021
#52974
agocke commentedon 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 commentedon 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
FrameworkReferenceItem to take a filepath to a project file. The Project file in question would define the framework and maybe evenPackageReferenceitems 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
B.csproj
runtime.msbuildproj
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 commentedon 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 commentedon 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 commentedon Jun 15, 2021
@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_ROOTenv. variable), or is it a probing path for additional assemblies (which can currently be done via for example command line--additionalprobingpaths)?MeikTranel commentedon Jun 15, 2021
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
1 remaining item
MeikTranel commentedon Jun 24, 2021
That seems fine with me.
Falco20019 commentedon 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 **
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 **
The bundle itself is then created using a simple csproj file:
** bundle.csproj **
This will also create the
deps.jsonand theruntimeconfig.jsonfor all those tools. The only downside is, that you will need to execute them usingdotnet exec --depsfile NameOfSharedConfiguration.deps.json Tool1.dllinstead 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 commentedon Sep 19, 2023
Noob here. I can understand that this issue asks about the
dotnet publishcommand that cannot do this out of the box.But, what is stopping you from publishing your
nexecutables 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 commentedon Sep 19, 2023
@bilbothebaggins One of the problems is how to guarantee dependency version consistency. Simple example:
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 commentedon Sep 19, 2023
@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 commentedon Sep 19, 2023
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 commentedon Sep 19, 2023
Another would be trimming I guess? Also, are there any plans to work on this in .NET 9?
MeikTranel commentedon Jul 24, 2024
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 commentedon Jul 24, 2024
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 commentedon 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.Bundlefor this. This is fixing thedeps.jsonand theruntimeconfig.jsonto 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 **
** PatchRuntimeConfigs.cs of Company.SDK.Bundle **
** Sdk\Sdk.targets of Company.SDK.Bundle **
MeikTranel commentedon Jul 29, 2024
This just goes to show how a (naively spelled out of course) simple change would prove tremendous in value. We need in this order:
In that Order. Each of these steps would unlock a whole slew of opportunities and therefore make these next steps easier to build.
webprofusion-chrisc commentedon Mar 20, 2025
Just adding our own reason for wanting a shared self-contained deployment:
akgvn commentedon Jan 2, 2026
I didn't realise this wasn't supported, as we've actually been doing this for years with a solution file.
We have a bunch of executable projects depending on bunch of the same classlib projects, all in the same repo, all depending on the exact same versions of dependencies defined through
Directory.Packages.props.We have a
ProjectsToDeploy.slnf, which only contains the executable projects.dotnet publish ProjectsToDeploy.slnf --self-contained -f net8.0 -r <RID> -o Deployoutputs executables of all these projects into the sameDeployfolder.It does result in a warning NETSDK1194 since NET 7 (we've been doing this before NET 7 was released), but in practice I haven't seen any issues.
I also just double-checked and the same thing is doable with a traversal project as well, which I assume does the same thing (copy results into the same output folder) but that doesn't generate a warning - probably should.
julealgon commentedon Jan 5, 2026
@akgvn you can cleanly avoid that by switching to artifacts-based output mode for the solution (highly recommended in general):
You can then customize the paths if you need to have both
.exes in the exact same folder.