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

Generate shim in build #2413

Merged
merged 10 commits into from
Aug 3, 2018
Merged

Conversation

wli3
Copy link

@wli3 wli3 commented Jul 18, 2018

Continue of #2366

problem i know so far

  • pending the solution to use cached asset file

It supports Clean target. However, it will not remove empty folder. Since different RID's shims have the same name. I need to separate them by folder. But msbuild will not remove that dotnet/msbuild#2408


namespace Microsoft.NET.Build.Tasks
{
public sealed class GetEmbeddedApphostPaths : TaskBase
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These code is copied out of GenerateShim, However, there is only around 10 lines are duplicated. And there is no critical code that will need to keep in sync. If we want to share these code, the overhead will be more than 10 lines.

-->

<Target Name="GenerateShimsAssets"
AfterTargets="ResolveReferences"
Copy link
Author

@wli3 wli3 Jul 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using AfterTargets is a good way to integrate into build? I need to be before IncrementalClean, so the file can be "registered"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AfterTargets is fine, with the caveat that you shouldn't do AfterTargets="Build" because of dotnet/msbuild#3345. But I generally prefer to use BeforeTargets--can you BeforeTargets="IncrementalClean"?

Copy link
Author

@wli3 wli3 Jul 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rainersigwald thanks, although the task itself depends on ResolveReferences. Should I just dependson =ResolveReferences. and BeforeTargets="IncrementalClean". But I think use dependson with before/after target is kind of strange.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's reasonable to have both BeforeTargets="IncrementalClean" DependsOnTargets="ResolveReferences". I'd go with that.

It is funny looking, but they're different relationships: BeforeTargets means "before you run that, run me first", while DependsOn means "before I run, run these first". Both can be true at once.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect the logic for this target to be the same as GenerateBuildDependencyFile. We went through a few iterations on that and ended up against beforetargets=incrementalclean IIRC. It would be good to have one pattern for extra files we create.

Copy link
Author

@wli3 wli3 Jul 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I read the comment. It makes sense. Now the problem is, how to set the timing(before or after what?) I think BeforeTargets="CopyFilesToOutputDirectory" is reasonable.

Changed to the following, I will push later, I don't want to hide the thread.

  <Target Name="GenerateShimsAssets"
          BeforeTargets="CopyFilesToOutputDirectory"
          DependsOnTargets="ResolveReferences"
          Condition="'$(PackAsTool)' == 'true'" >

    <ItemGroup>
      <_PackAsToolShimRuntimeIdentifiers Include="$(PackAsToolShimRuntimeIdentifiers)"/>
    </ItemGroup>

    <PropertyGroup>
      <PackagedShimOutputRootDirectory Condition=" '$(PackagedShimOutputRootDirectory)' == '' ">$(OutDir)</PackagedShimOutputRootDirectory>
    </PropertyGroup>

    <GenerateShims
      DotNetAppHostExecutableNameWithoutExtension="$(_DotNetAppHostExecutableNameWithoutExtension)"
      PackagedShimOutputDirectory="$(PackagedShimOutputRootDirectory)/shims/$(TargetFramework)"
      PackageId="$(PackageId)"
      PackageVersion="$(Version)"
      ProjectAssetsFile="$(ProjectAssetsFile)"
      ProjectPath="$(MSBuildProjectFullPath)"
      ShimRuntimeIdentifiers="@(_PackAsToolShimRuntimeIdentifiers)"
      TargetFrameworkMoniker="$(NuGetTargetMoniker)"
      ToolCommandName="$(ToolCommandName)"
      ToolEntryPoint="$(ToolEntryPoint)"
      >

      <Output TaskParameter="EmbeddedApphostPaths" ItemName="_EmbeddedApphostPaths" />
    </GenerateShims>

    <ItemGroup>
      <!-- Do this in an ItemGroup instead of as an output parameter so that it still gets added to the item set
           during incremental builds when the task is skipped -->
      <FileWrites Include="@(_EmbeddedApphostPaths)"/>
    </ItemGroup>
  </Target>

/// <summary>
/// Change "dir/file.exe" to "dir"
/// </summary>
internal static string GetDirectoryPathInRelativePath(string publishRelativePath)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a simply task, but I find the most reliable way is to copy paste from corefx. Any suggestions?
See dotnet/corefx#4208

@wli3
Copy link
Author

wli3 commented Jul 18, 2018

Incremental performance: we did a lot of work to avoid reading the assets file from json on every incremental build, we need to preserve that here. Ideally, we would not even read the assets file or regenerate a shim if the things that affect the content of shim have not changed.

I tried to directly use NativeLibraries itemgroup and I realize it includes 1 RID at most. And Generateshim need multiple RID's apphost. I think the solution could be

a. Resolve and add these assets during ResolvePackageAssets. And write them into cache.(or even just output an extra itemgroup). It should be best for perf, but it will introduce implicit coupling of the 2 tasks.

b. Add logic to detect "should rewrite shim". The complex part is, we need to detect the input params change. We needs another file to record what we write into shim last time (due to we cannot read back from shim).

We could do a and b both

@wli3 wli3 requested a review from nguerrera July 18, 2018 21:40
@wli3 wli3 force-pushed the generate-shim-in-build branch from 04ee4bf to 63bb7e2 Compare July 19, 2018 17:50
@wli3 wli3 changed the base branch from release/2.1.4xx to release/2.2.1xx July 19, 2018 17:50
@wli3 wli3 added this to the 2.2.1xx milestone Jul 19, 2018
@wli3
Copy link
Author

wli3 commented Jul 19, 2018

Incremental performance: we did a lot of work to avoid reading the assets file from json on every incremental build, we need to preserve that here. Ideally, we would not even read the assets file or regenerate a shim if the things that affect the content of shim have not changed.

I tried to directly use NativeLibraries itemgroup and I realize it includes 1 RID at most. And Generateshim need multiple RID's apphost. I think the solution could be

a. Resolve and add these assets during ResolvePackageAssets. And write them into cache.(or even just output an extra itemgroup). It should be best for perf, but it will introduce implicit coupling of the 2 tasks.

b. Add logic to detect "should rewrite shim". The complex part is, we need to detect the input params change. We needs another file to record what we write into shim last time (due to we cannot read back from shim).

We could do a and b both

@nguerrera what do you think of the asset file problem

@wli3 wli3 changed the title [WIP] Generate shim in build Generate shim in build Jul 20, 2018
@wli3
Copy link
Author

wli3 commented Jul 20, 2018

@nguerrera

  1. I added these apphost assets to ResolvePackageAssets and cached it.
  2. thanks to @rainersigwald I used method similar to this, to trigger incremental build when property change.

@wli3
Copy link
Author

wli3 commented Jul 31, 2018

@nguerrera ping review

@@ -61,6 +56,8 @@ public static class AppHost
SearchAndReplace(accessor, _bytesToSearch, bytesToWrite, appHostSourceFilePath);
}
}

File.SetLastWriteTimeUtc(appHostDestinationFilePath, DateTime.UtcNow);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This deserves a comment. Memory-mapped write is not updating last write time?

Copy link
Author

@wli3 wli3 Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comment. And yes, from the test for incremental build. The last write time is not set at all without this line.

{
public sealed class ExecutableExtension
{
public static string GetExecutableExtensionAccordingToRuntimeIdentifier(string runtimeIdentifier)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's cumbersome to have "ExecutableExtension" in class and method. Maybe ExecutableExtension.ForRuntimeIdentifier?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can avoid this altogether by just preserving the apphost extension (or lack thereof). In other words, if we find apphost.exe, we make mytool.exe. If we find apphost, we make mytool. (And more theoretically, if we find apphost.future_os_extension, we make mytool.future_os_extension,)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plus the comment below. It has problem with GetEmbeddedApphostPaths(a computer only, get where to look for these files). In this we don't require template apphost path. I think it is over kill to depend on template apphost path just to get the extension.

I renamed it to ForRuntimeIdentifier

{
public static string GetExecutableExtensionAccordingToRuntimeIdentifier(string runtimeIdentifier)
{
if (runtimeIdentifier.StartsWith("win"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forget what we decided was correct fo RIDs: case-insensitive or not? Pass StringComparison.Ordinal if case-sensitive and StringComparison.OrdinalIgnoreCase if case-insensitive.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

insensitive. I added OrdinalIgnoreCase

if (runtimeIdentifier.StartsWith("win"))
{
apphostName += ".exe";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't this use your helper?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now that we search for the full name, not the name without extension and the approach I thought about earlier where we just preserve the extension could be prone to breaking if there's ever an apphost.something_else in the package.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, changed to use helper.

<WriteLinesToFile Lines="$(_GenerateShimsAssetsInputCacheHash)" File="$(_ShimInputCacheFile)" Overwrite="True" WriteOnlyWhenDifferent="True" />

<ItemGroup>
<FileWrites Include="$(_ShimInputCacheFile)" Condition="Exists('$(_ShimInputCacheFile)')" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a condition here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra newline

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

public ITaskItem[] ResolvedFileToPublishWithPackagePath { get; private set; }

private const char DirectorySeparatorChar = '\\';
private const char AltDirectorySeparatorChar = '/';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are confusing me since they seem like they might just be using static Path when reading the code. These names are generally OS-specific. How about rename to ForwardSeparatorChar and BackwardSeparatorChar. Or WindowsSeparatorChar and UnixSeparatorChar?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, renamed to ForwardSeparatorChar and BackwardSeparatorChar

<!-- Do this in an ItemGroup instead of as an output parameter so that it still gets added to the item set
during incremental builds when the task is skipped -->
<FileWrites Include="@(_EmbeddedApphostPaths)" Condition="Exists('@(_EmbeddedApphostPaths)')"/>
<FileWrites Include="$(_ShimCreatedSentinelFile)" Condition="Exists('$(_ShimCreatedSentinelFile)')"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know that Exists works on items. What does it return if one exists, but another doesn't.

I don't think it's necessary to condition adds to filewrites

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed condition

LastWriteTime in shims are not accurate. And _ShimInputCacheFile will have later timestamp than generated shims.
Use a created file to "record" LastWriteTime. And only use it in "Outputs" field for timestamp comparison.
-->
<WriteLinesToFile Lines="This file's LastWriteTime is used in incremental build" File="$(_ShimCreatedSentinelFile)" Overwrite="True" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using Touch task for this, which is more idiomatic in msbuild for this purpose. You'd lose this comment in the file, but I don't think we need it.

Copy link
Author

@wli3 wli3 Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Touch uses the same corefx api that is having the bug. Once it is fixed, we can remove all this.
https://github.com/Microsoft/msbuild/blob/4972ebc6d2b81c5e967cd3260943a95c8e5eb4be/src/Tasks/Touch.cs#L133

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No change here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just add a comment that Touch uses the same API and therefore has the same problem.

/// 1. Publish PublishRelativePath is a relative <strong>file</strong> path. This need to convert to relative path.
/// 2. Due to DotnetTools package format, "tools/{TargetFramework}/any/" is needed in addition to the relative path.
/// </summary>
public sealed class PublishRelativePathToPackPackagePath : TaskBase
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name sounds like it's going to publish something and also doesn't make it very clear that it's specific to getting the paths for a DotnetTools package

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, Renamed to RelativePathFormatInPublishToFormatInPackPackage

- Add comment for setting SetLastWriteTimeUtc

- Rename and ignore rid casing

- Use ExecutableExtension.ForRuntimeIdentifier

- Rename and remove condition on include

- Rename to RelativePathFormatInPublishToFormatInPackPackage
<ItemGroup>
<_GeneratedFiles Include="$(PublishDepsFilePath)"/>
<_GeneratedFiles Include="$(PublishRuntimeConfigFilePath)"/>
<_GeneratedFiles Include="$(_ToolsSettingsFilePath)"/>
</ItemGroup>

<RelativePathFormatInPublishToFormatInPackPackage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name is still confusing me. Can it be something like ResolveToolPackagePaths?

LastWriteTime in shims are not accurate. And _ShimInputCacheFile will have later timestamp than generated shims.
Use a created file to "record" LastWriteTime. And only use it in "Outputs" field for timestamp comparison.
-->
<WriteLinesToFile Lines="This file's LastWriteTime is used in incremental build" File="$(_ShimCreatedSentinelFile)" Overwrite="True" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just add a comment that Touch uses the same API and therefore has the same problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants