-
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
Add apphost customization #2545
Conversation
This will emit a warning if host customization is requested but we are building on non-Windows (where the required APIs don't exist).
Add proper error handling and documentation
IntPtr hResource = FindResourceEx(hModule, lpType, lpName, wLang); | ||
if (hResource == IntPtr.Zero) | ||
{ | ||
ThrowExceptionForLastWin32Error(); |
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 is dangerous to throw exceptions out of C callback. The C callback should always catch the exceptions and return the error via lParam.
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.
src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToCustomizeTheApphost.cs
Show resolved
Hide resolved
/// in a PE image. It currently only works on Windows, because it | ||
/// requires various kernel32 APIs. | ||
/// </summary> | ||
public class ResourceUpdater |
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 think this should implement IDisposable
since it owns the update handle. In case of exceptions this will likely leak that handle.
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 made it IDisposable, using a new SafeUpdateHandle.
DependsOnTargets="ResolvePackageAssets" | ||
AfterTargets="ResolveReferences" | ||
BeforeTargets="AssignTargetPaths" | ||
DependsOnTargets="ResolvePackageAssets;CoreCompile" |
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.
CoreCompile [](start = 49, length = 11)
I'm not sure about this dependency on CoreCompile. I know we need the intermediate assembly available, but there could be scenarios where we end up triggering a re-build when we were supposed to be using what is already on disk. This has come up a few times with other targets. I have to think about the best approach here.
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.
Still thinking about it, but I think the best approach following the existing idioms in common targets would be to generate the apphost to intermediate directory upon/after compile and designate the intermediate item as something to copy separately.
In reply to: 219644421 [](ancestors = 219644421)
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.
@nguerrera generating the apphost into the intermediate directory would still have a dependency on CoreCompile, wouldn't it?
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.
Correct.
I was thinking about changing the paradigm to react to compilation instead of depending on compilation.
I am concerned that there may be places where this target is invoked without expecting compilation to happen even if the source files are out of date. Look at the long discussion on https://github.com/dotnet/cli/issues/5331 for some background on why this is worth worrying about.
What I'm wondering is: can the apphost generation happen in a separate target that is AfterTargets=CoreCompile (after we compile a managed exe to the intermediate directory, create the matching apphost next to it). And this target would just add the intermediate location to the set of files to be copied.
Adding @rainersigwald who may have other ideas here.
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.
Agreed that if the apphost creation will be invoked in a way that doesn't expect compilation to happen, it shouldn't depend on CoreCompile. But the current target logically depends on an up-to-date IntermediateAssembly, and I think "DependsOnTargets=CoreCompile" captures that.
If the scenario you're thinking about is one where we do "publish --no-build", and expect that this will create an apphost for the previously-built intermediate assembly (even if source files are out of date), then I can see your concern. To me it makes sense to implement this by expressing the dependency on the outputs of a target that logically produces IntermediateAssembly, and having that target internally respect the --no-build option by using the existing IntermediateAssembly in that case. This way the --no-build behavior is transparent to targets that depend on build outputs.
AfterTargets=CoreCompile could work, but that has other issues. Any inputs to the apphost generator that don't come from CoreCompile should trigger apphost re-generation if they change. For example, setting the target to WinExe should cause the apphost to be re-generated with the gui bit, even if we don't want to re-compile.
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.
If the scenario you're thinking about is one where we do "publish" --no-build
I'm thinking even more generally. This will cause any caller of GetCopyToOutputDirectoryItems to trigger compilation and I don't think that's OK.
Agree that AfterTargets="CoreCompile" alone doesn't solve it. I'm still thinking about what's best here.
In reply to: 220379741 [](ancestors = 220379741)
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 generally makes sense for GetCopyToOutputDirectoryItems to trigger compilation, because it logically depends on the compilation output through the apphost. If we want to optimize for "publish --no-build", we need to cut out some some dependency edges from Publish -> CoreCompile in this case (or alternatively make CoreCompile do nothing).
In #2111, it looks like we already cut one edge from Publish -> CoreCompile by making Publish not depend on Build in the "--no-build" case. Maybe we want to do something similar here? We could make GetCopyToOutputDirectoryItems depend on _ComputeNETCoreBuildOutputFiles unless NoBuild is specified. If NoBuild is specified, GetCopyToOutputDirectoryItems could just try to get the last-built apphost from the intermediate directory.
Just an idea, but I'm sure there are more constraints I don't know about. I'll let you decide how you think this should be done - let me know if there's anything I can do to help!
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.
GetCopyToOutputDirectoryItems
should not do actual work and should not depend on CoreCompile
(and friends), because it is called on references in the /p:BuildProjectReferences=false
case, which should not trigger compilation in the referenced projects. I don't think the NoBuild
case applies here for that reason--you're building, but some other project.
Unfortunately, I can't think of a great way to accomplish this. Best I can think of is to move the actual work into a target (CreateAppHost
?) and append ;CreateAppHost
to $(CompileDependsOn)
, which would insert it in the normal flow shortly after CoreCompile
but not require it to express the dependency. Then, do no real work in _ComputeNETCoreBuildOutputFiles
and run it BeforeTargets="GetCopyToOutputDirectoryItems"
.
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.
Thanks, @rainersigwald. This sounds like a good approach. @sbomer, can you look at doing it that way?
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.
Thanks @rainersigwald and @nguerrera. I think the latest change does the trick - PTAL.
Instead, use the extra parameter to capture error information, and throw the appropriate error from the caller of EnumResourceTypes.
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.
Waiting for the issue with the CoreCompile dependency to be resolved.
This cuts the dependency from _ComputeNETCoreBuildOutputFiles on CoreCompile. Instead, _CreateAppHost will run after compilation via CompileDependson. Locating the restored apphost has been factored into another target _GetAppHostPaths, which is a dependency of both _ComputeNETCoreBuildOutputFiles and _CreateAppHost. The destination apphost path is also computed here, and passed to the CreateAppHost task as an input. This way, during a "publish --no-build", the apphost destintation path (of the previously-generated apphost) is computed as a dependency of _ComputeNETCoreBuildOutputFiles, and it is copied to the publish directory without causing CoreCompile to run.
src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets
Outdated
Show resolved
Hide resolved
src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets
Outdated
Show resolved
Hide resolved
src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets
Outdated
Show resolved
Hide resolved
</AssignTargetPath> | ||
<ItemGroup> | ||
<_NoneWithTargetPath Include="@(_AllNETCoreCopyLocalItemsWithTargetPath)" /> | ||
</ItemGroup> |
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.
With the create/get split, can this go before AssignTargetPaths again so that we don't need to do this?
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 don't think so, unless we change when AssignTargetPaths runs. I see this dependency chain putting AssignTargetPaths before build, which is too early for the AppHost: CoreBuild -> PrepareResources -> PrepareResourceNames -> AssignTargetPaths.
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 don't follow yet. We're just computing paths here in this target now, not actually generating the apphost. Why can't that be done sooner?
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.
Oh, I think I understand now. It seems strange that we would add the apphost to None potentially before it exists. Won't that cause some item metadata to be missing?
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 don't know if there's a problem with that. @rainersigwald ?
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.
No, I don't think there'd be a problem. Well-known/built-in metadata is evaluated just-in-time, so if you create an item, create the file, and then ask for %(Whatever.ModifiedTime)
it should still work.
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'm ok looking at this after the initial commit, btw. If this is green, we can merge as is.
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'll leave it as-is then. Thanks for all the help!
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.
Thank you for the contribution of this feature!
I will investigate the tests failing on full msbuild leg. (At the moment, the new CI system does not publish test logs nor can it re-run individual legs, but I'm told this is getting resolved next week.) |
Test failure:
|
Oh, it looks like it's just a missing semicolon here: <CompileDependsOn>
- $(CompileDependsOn)
+ $(CompileDependsOn);
_CreateAppHost;
</CompileDependsOn> |
After fixing that, there's another failure on my box. Investigating
|
That second failure is internal bug 686365, which is fixed but my dogfood build hasn't updated. I believe the CI machine would not have a build of VS with that bug so this should be green once the semicolon is fixed. |
And fix a typo
The apphost is (optionally) customized by copying resources from the managed dll, and setting the GUI bit based on the Target property (which also modifies the managed dll). Therefore, we use the managed dll as an input for incremental build (the apphost needs to be updated iff the managed dll was updated). This fixes dotnet#2554. This also removes the unnecessary overwriteExisting flag from AppHost.Create, fixing dotnet#2473.
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.
@nguerrera thank you for investigating the CI failures!
</AssignTargetPath> | ||
<ItemGroup> | ||
<_NoneWithTargetPath Include="@(_AllNETCoreCopyLocalItemsWithTargetPath)" /> | ||
</ItemGroup> |
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 don't think so, unless we change when AssignTargetPaths runs. I see this dependency chain putting AssignTargetPaths before build, which is too early for the AppHost: CoreBuild -> PrepareResources -> PrepareResourceNames -> AssignTargetPaths.
It looks like some of the tests didn't get updated with the removal of |
Oops, left those changes out of my commit for some reason. Thanks. |
👏 🎉 |
This adds logic to copy resources from the managed dll to the apphost.
_ComputeNETCoreBuildOutputFiles
now runs later in the build, at a point whereIntermediateAssembly
is available.AppHostCustomizationRequiresWindowsHostWarning
) for this case, but not for errors specific to theResourceUpdater
ResourceUpdater
to copy over and check a version resource.AddResource
).Note that this feature doesn't yet work with incremental build (see the discussion here #2467).
Addresses #1899.
@nguerrera @vitek-karas @jeffschwMSFT