-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Do a two-phase publish to mimic VS behavior #29817
Conversation
…nd then using it as an input to the propert Publish
I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label. |
pick list is prepopulated with some likely candidates, but need to consult with the WebTools team to ensure consistent behavior here
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs
Show resolved
Hide resolved
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
if (!String.IsNullOrEmpty(importedPropValue) && bool.TryParse(importedPropValue, out var wasImported) && wasImported) { | ||
try { | ||
if (projectInstance.GetPropertyValue(MSBuildPropertyNames.PUBLISH_PROFILE_FULL_PATH) is {} fullPathPropValue) { | ||
var properties = new ProjectInstance(fullPathPropValue).ToProjectRootElement().PropertyGroups.First().Properties; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I haven't done the side-by-side comparison, I imagine that it would be faster to loop through the properties in the propertiesToForwardFromProfile, instead of loading in every property on the project into memory
{ | ||
ReleasePropertyProjectLocator projectLocator = new ReleasePropertyProjectLocator(Environment.GetEnvironmentVariable(EnvironmentVariableNames.ENABLE_PUBLISH_RELEASE_FOR_SOLUTIONS) != null); | ||
var cliProps = projectLocator.GetGlobalPropertiesFromUserArgs(parseResult); | ||
var projectInstance = projectLocator.GetTargetedProject(parseResult.GetValueForArgument(PublishCommandParser.SlnOrProjectArgument), cliProps, MSBuildPropertyNames.PUBLISH_RELEASE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this will be another +~18-25% performance regression in time to publish which I don't think we want. If we were to do this, I think it would be better to create something like a singleton wrapper for pre-eval project access which only ever instantiates or pre-evaluates the project once.
msbuildArgs.AddRange(projectLocator.GetCustomDefaultConfigurationValueIfSpecified(parseResult, MSBuildPropertyNames.PUBLISH_RELEASE, slnOrProjectArgs, PublishCommandParser.ConfigurationOption) ?? Array.Empty<string>()); | ||
msbuildArgs.AddRange(slnOrProjectArgs ?? Array.Empty<string>()); | ||
|
||
return msbuildArgs; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically this is a breaking change because you could have a publish profile in your directory that currently isn't being used by the .NET SDK, and so the Config, Rid, etc, could suddenly change with this change. IMO it would be better to take in .NET 8, but maybe this qualifies for a 300 band break. Either way, we would need a breaking change doc.
{ | ||
ReleasePropertyProjectLocator projectLocator = new ReleasePropertyProjectLocator(Environment.GetEnvironmentVariable(EnvironmentVariableNames.ENABLE_PUBLISH_RELEASE_FOR_SOLUTIONS) != null); | ||
var cliProps = projectLocator.GetGlobalPropertiesFromUserArgs(parseResult); | ||
var projectInstance = projectLocator.GetTargetedProject(parseResult.GetValueForArgument(PublishCommandParser.SlnOrProjectArgument), cliProps, MSBuildPropertyNames.PUBLISH_RELEASE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My main concern here is the GetTargetedProject
call. In NET 8 we choose an arbitrary project in the solution as opposed to the executable project. This means the publish profile import could be non-deterministic unless further changes are made for the 8.0 code, and those changes, while not an extreme amount of work, also will need to be very well thought-through to prevent bugs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting - so the API changed from "if I'm called with a solution I return null" to "if I'm called with a solution I pick a project" in 8? That's good to know - for publish specifically we'd want to not run at all if the argument is a solution, so I can change this call to explicitly check that condition before calling GetTargetedProject
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Docstring is updated here:
{ |
if (!String.IsNullOrEmpty(importedPropValue) && bool.TryParse(importedPropValue, out var wasImported) && wasImported) { | ||
try { | ||
if (projectInstance.GetPropertyValue(MSBuildPropertyNames.PUBLISH_PROFILE_FULL_PATH) is {} fullPathPropValue) { | ||
var properties = new ProjectInstance(fullPathPropValue).ToProjectRootElement().PropertyGroups.First().Properties; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to use TryGetProjectInstance as race conditions could occur and cause failure if the project file is changed on disk in between this and the above lines of code
@@ -794,16 +794,8 @@ public void PublishRelease_interacts_similarly_with_PublishProfile_Configuration | |||
.Execute("/p:PublishProfile=test"); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be preferable to add a test showing that publish profile proeprties are imported correctly, and for properties like configuration, they actually are changed (so DebugSymbols
and Optimize
also change, not just configuration
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be a big win to support publish profiles in the SDK. Thanks for this investigation. I think more work would be required to insert this into production, but great work so far.
After some consideration I'm going to retarget this to 8.0 (but it will still need to be documented as a breaking change!). |
Is anywhere concrete and complete documentation available on what properties are working from the pubxml file? I only found this which only gives away that there might be other properties which are not supported. |
@Balkoth those docs were added in part because of the investigation required to get this PR going - VS uses PubXml properties for both building the assets to publish, as well as potentially orchestrating pushing those assets to another location. The .NET CLI will only do the first of these (building the assets to publish). Because of this, the properties listed are the only ones we care about at the current time. In general, all properties from the pubxml will be included in the build - but due to timing/ordering of the |
In the linked document i can't see any properties which you care about, only ones that "aren't supported"
I am "simply" trying to bring publishing of some projects off from client machines to our build server and build them from the command line with dotnet build. Sample FolderProfile.pubxml of one of our projects: <Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\publish\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>false</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>false</PublishReadyToRun>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
</PropertyGroup>
</Project> Naive me was thinking i could just do it like this in a PowerShell script, but the document says otherwise. $targetProjectFile = "C:\Source\Project\Project.csproj"
$targetOutputDir = "C:\Binaries\Project"
dotnet publish $targetProjectFile --configuration Release --output $targetOutputDir /p:PublishProfile=FolderProfile Is it even possible to pass all the arguments to dotnet publish manually so that it does exactly what Visual Studio does when publishing through the UI? |
Sorry - I'm referring to this list from the docs you linked:
These are the properties that VS reads from the PublishProfile (if present) and applies to the build to create the assets to publish. If you wanted to recreated the VS logic yourself, you'd do essentially what I did in this PR:
Hope that clarifies - let me know if there's anything else I can do to help. |
So setting properties with Is there really a difference between these to scripts? $targetProjectFile = "C:\Source\Project\Project.csproj"
$targetOutputDir = "C:\Binaries\Project"
dotnet publish $targetProjectFile -c Release -o $targetOutputDir -f net7.0-windows -r win-x64 --no-self-contained /p:PublishSingleFile=true /p:PublishReadyToRun=false /p:IncludeNativeLibrariesForSelfExtract=true $targetProjectFile = "C:\Source\Project\Project.csproj"
$targetOutputDir = "C:\Binaries\Project"
dotnet publish $targetProjectFile -c Release -o $targetOutputDir --no-self-contained /p:PublishSingleFile=true /p:PublishReadyToRun=false /p:IncludeNativeLibrariesForSelfExtract=true /p:TargetFramework=net7.0-windows /p:RuntimeIdentifier=win-x64 |
There are minor differences but they should be functionally equivalent. I used the 'generic' form of passing properties like Configuration and Runtime because I was assuming you were going to do a mechanical translation of "property from pubxml file" to "property to pass on the command line". You can of course use the more specific flags as well. We're getting a bit off of the topic of this PR, which should be focused on code review/etc - if you're having specific issues with a publish command, can you open a new issue for triage and further discussion? |
You are correct and put me on the right track, so thanks for all your replies. If any issues arise i will open another issue. |
Per Chet, this needs to be redone so closing for now. |
I wanted to take a run at this because it just makes sense to me.
Publish profiles work in VS from a technical level because they operate in two phases - one to locate and parse build properties from a specified publish profile pubxml, and the second to run the build's Publish target with those properties applied. This allows users to put properties that are specific to that publish configuration in a single file, not cluttering their project file.
This PR emulates this behavior. It