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

.props are imported too late #2227

Closed
0x53A opened this issue Apr 11, 2017 · 1 comment · Fixed by #2234
Closed

.props are imported too late #2227

0x53A opened this issue Apr 11, 2017 · 1 comment · Fixed by #2234

Comments

@0x53A
Copy link
Contributor

0x53A commented Apr 11, 2017

Description

I have installed FSharp.Compiler.Tools into my project.

Actual

My fsproj now looks like this:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>cd049f28-2ee2-4c79-97c8-85e0b92c2b46</ProjectGuid>
    <OutputType>Exe</OutputType>
    <RootNamespace>ConsoleApplication1</RootNamespace>
    <AssemblyName>ConsoleApplication1</AssemblyName>
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
    <TargetFSharpCoreVersion>4.4.1.0</TargetFSharpCoreVersion>
    <Name>ConsoleApplication1</Name>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <Tailcalls>false</Tailcalls>
    <OutputPath>bin\$(Configuration)\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <WarningLevel>3</WarningLevel>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DocumentationFile>bin\$(Configuration)\$(AssemblyName).XML</DocumentationFile>
    <Prefer32Bit>true</Prefer32Bit>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <Tailcalls>true</Tailcalls>
    <OutputPath>bin\$(Configuration)\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <WarningLevel>3</WarningLevel>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DocumentationFile>bin\$(Configuration)\$(AssemblyName).XML</DocumentationFile>
    <Prefer32Bit>true</Prefer32Bit>
  </PropertyGroup>
  <PropertyGroup>
    <MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
  </PropertyGroup>
  <Choose>
    <When Condition="'$(VisualStudioVersion)' == '11.0'">
      <PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
        <FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
      </PropertyGroup>
    </When>
    <Otherwise>
      <PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')">
        <FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
      </PropertyGroup>
    </Otherwise>
  </Choose>
  <Import Project="$(FSharpTargetsPath)" />
  <Choose>
    <When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5.2' Or $(TargetFrameworkVersion) == 'v4.5.3' Or $(TargetFrameworkVersion) == 'v4.6' Or $(TargetFrameworkVersion) == 'v4.6.1' Or $(TargetFrameworkVersion) == 'v4.6.2' Or $(TargetFrameworkVersion) == 'v4.6.3')">
      <PropertyGroup>
        <__paket__FSharp_Compiler_Tools_props>FSharp.Compiler.Tools</__paket__FSharp_Compiler_Tools_props>
      </PropertyGroup>
    </When>
  </Choose>
  <Import Project="..\packages\FSharp.Compiler.Tools\build\$(__paket__FSharp_Compiler_Tools_props).props" Condition="Exists('..\packages\FSharp.Compiler.Tools\build\$(__paket__FSharp_Compiler_Tools_props).props')" Label="Paket" />
  <ItemGroup>
    <Compile Include="AssemblyInfo.fs" />
    <Compile Include="Program.fs" />
    <None Include="App.config" />
    <Content Include="paket.references" />
  </ItemGroup>
  <ItemGroup>
    <Reference Include="mscorlib" />
    <Reference Include="FSharp.Core">
      <Name>FSharp.Core</Name>
      <AssemblyName>FSharp.Core.dll</AssemblyName>
      <HintPath>$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\FSharp\.NETFramework\v4.0\$(TargetFSharpCoreVersion)\FSharp.Core.dll</HintPath>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Numerics" />
  </ItemGroup>
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
  <Choose>
    <When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5.2' Or $(TargetFrameworkVersion) == 'v4.5.3' Or $(TargetFrameworkVersion) == 'v4.6' Or $(TargetFrameworkVersion) == 'v4.6.1' Or $(TargetFrameworkVersion) == 'v4.6.2' Or $(TargetFrameworkVersion) == 'v4.6.3')">
      <ItemGroup>
        <Reference Include="System.ValueTuple">
          <HintPath>..\packages\System.ValueTuple\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
          <Private>True</Private>
          <Paket>True</Paket>
        </Reference>
      </ItemGroup>
    </When>
  </Choose>
</Project>

Note how the import is after <Import Project="$(FSharpTargetsPath)" />, making this kind of useless.

Expected

This is how the project looks like using nuget:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="..\packages\FSharp.Compiler.Tools.4.1.5\build\FSharp.Compiler.Tools.props" Condition="Exists('..\packages\FSharp.Compiler.Tools.4.1.5\build\FSharp.Compiler.Tools.props')" />
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>cd049f28-2ee2-4c79-97c8-85e0b92c2b46</ProjectGuid>
    <OutputType>Exe</OutputType>
    <RootNamespace>ConsoleApplication1</RootNamespace>
    <AssemblyName>ConsoleApplication1</AssemblyName>
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
    <TargetFSharpCoreVersion>4.4.1.0</TargetFSharpCoreVersion>
    <Name>ConsoleApplication1</Name>
    <NuGetPackageImportStamp>
    </NuGetPackageImportStamp>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <Tailcalls>false</Tailcalls>
    <OutputPath>bin\$(Configuration)\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <WarningLevel>3</WarningLevel>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DocumentationFile>bin\$(Configuration)\$(AssemblyName).XML</DocumentationFile>
    <Prefer32Bit>true</Prefer32Bit>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <Tailcalls>true</Tailcalls>
    <OutputPath>bin\$(Configuration)\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <WarningLevel>3</WarningLevel>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DocumentationFile>bin\$(Configuration)\$(AssemblyName).XML</DocumentationFile>
    <Prefer32Bit>true</Prefer32Bit>
  </PropertyGroup>
  <PropertyGroup>
    <MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
  </PropertyGroup>
  <Choose>
    <When Condition="'$(VisualStudioVersion)' == '11.0'">
      <PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
        <FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
      </PropertyGroup>
    </When>
    <Otherwise>
      <PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')">
        <FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
      </PropertyGroup>
    </Otherwise>
  </Choose>
  <Import Project="$(FSharpTargetsPath)" />
  <ItemGroup>
    <Compile Include="AssemblyInfo.fs" />
    <Compile Include="Program.fs" />
    <None Include="App.config" />
    <Content Include="packages.config" />
  </ItemGroup>
  <ItemGroup>
    <Reference Include="mscorlib" />
    <Reference Include="FSharp.Core">
      <Name>FSharp.Core</Name>
      <AssemblyName>FSharp.Core.dll</AssemblyName>
      <HintPath>$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\FSharp\.NETFramework\v4.0\$(TargetFSharpCoreVersion)\FSharp.Core.dll</HintPath>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Numerics" />
    <Reference Include="System.ValueTuple">
      <HintPath>..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
    </Reference>
  </ItemGroup>
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists('..\packages\FSharp.Compiler.Tools.4.1.5\build\FSharp.Compiler.Tools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\FSharp.Compiler.Tools.4.1.5\build\FSharp.Compiler.Tools.props'))" />
  </Target>
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
</Project>

Note how the import is at the top.

Version

Paket version 4.3.0

@0x53A
Copy link
Contributor Author

0x53A commented Apr 11, 2017

ref https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package#including-msbuild-props-and-targets-in-a-package

  1. Nuget just imports props at the top and targets at the bottom.
    We depend on some variables to deduce the correct path, so can't import at the top.
    (Or maybe nuget >=3 doesn't import targets at all?)

When NuGet 2.x installs a package with \build files, it will add an MSBuild elements in the project file pointing to the .targets and .props files. (.props is added at the top of the project file; .targets is added at the bottom.)
With NuGet 3.x, targets are not added to the project but are instead made available through the project.lock.json.

  1. The condition $(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5.2' Or $(TargetFrameworkVersion) == 'v4.5.3' Or $(TargetFrameworkVersion) == 'v4.6' Or $(TargetFrameworkVersion) == 'v4.6.1' Or $(TargetFrameworkVersion) == 'v4.6.2' Or $(TargetFrameworkVersion) == 'v4.6.3') makes a hard check against TargetFrameworkIdentifier, which, in my case, is just not set, so it isn't imported at all. Compare this to the condition for the assembly system.valuetuple, where it is combined with OR, so still true if not set: $(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5.2' Or $(TargetFrameworkVersion) == 'v4.5.3' Or $(TargetFrameworkVersion) == 'v4.6' Or $(TargetFrameworkVersion) == 'v4.6.1' Or $(TargetFrameworkVersion) == 'v4.6.2' Or $(TargetFrameworkVersion) == 'v4.6.3').

  2. (not sure) All comparisons are written as $(TargetFrameworkVersion) == 'v4.5.3', I think it should be '$(TargetFrameworkVersion)' == 'v4.5.3'.

  3. FSharp.Compiler.Tools.props depends on being included at the top, because it checks Condition="'$(TargetFramework)' == '' and '$(TargetFrameworks)' == ''", so if it is included after either of these properties is set, it thinks it is in net core land, and doesn't set FSharpTargetsPath.

Edit: I think 2 and 3 are moot, the comparison seems ok.


This case could be fixed easily, FSharp.Compiler.Tools contains only a single .props directly in build, so it should just be imported unconditionally at the top.

Files in the root \build folder are considered suitable for all target frameworks.


Fixing framework-specific props is harder, nuget would import them at the top, but paket needs access to the msbuild variables, which are set later ...
I don't think it is possible to fix this 100%, but maybe it can be tweaked a little bit.

To provide framework-specific files, first place those within appropriate subfolders, such as the following:

\build
    \netstandard1.4
        \Contoso.Utility.UsefulStuff.props
        \Contoso.Utility.UsefulStuff.targets
    \net462
        \Contoso.Utility.UsefulStuff.props
        \Contoso.Utility.UsefulStuff.targets

0x53A added a commit to 0x53A/Paket that referenced this issue Apr 11, 2017
fixes fsprojects#2227

* differentiate between global targets/props, and framework specific targets/props
* fw-specifc targets/props continue to be imported in the the middle with a condition (no change to before)
* global props are imported at the top of the file (same as nuget does)
* global targets are imported at the end of the file (same as nuget)

The reason is that many props depend on being imported at the top, so do that for the files where it is actually possible.
0x53A added a commit to 0x53A/Paket that referenced this issue Apr 11, 2017
fixes fsprojects#2227

* differentiate between global targets/props, and framework specific targets/props
* fw-specifc targets/props continue to be imported in the the middle with a condition (no change to before)
* global props are imported at the top of the file (same as nuget does)
* global targets are imported at the end of the file (same as nuget)

The reason is that many props depend on being imported at the top, so do that for the files where it is actually possible.
0x53A added a commit to 0x53A/Paket that referenced this issue Apr 11, 2017
fixes fsprojects#2227

* differentiate between global targets/props, and framework specific targets/props
* fw-specifc targets/props continue to be imported in the the middle with a condition (no change to before)
* global props are imported at the top of the file (same as nuget does)
* global targets are imported at the end of the file (same as nuget)

The reason is that many props depend on being imported at the top, so do that for the files where it is actually possible.
forki added a commit that referenced this issue Apr 12, 2017
[fix #2227] import .props/.targets better
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 a pull request may close this issue.

1 participant