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

Setting BaseIntermediateOutputPath breaks netstandard2.0 builds #2003

Open
DoCode opened this issue Feb 28, 2018 · 14 comments
Open

Setting BaseIntermediateOutputPath breaks netstandard2.0 builds #2003

DoCode opened this issue Feb 28, 2018 · 14 comments
Assignees
Milestone

Comments

@DoCode
Copy link

DoCode commented Feb 28, 2018

Because:

When I use a netcoreapp2.x with a project reference to a netstandard2.x project and I set the BaseIntermediateOutputPath, then the build/publish failed with this error:

~\Microsoft.NET.Sdk\build\Microsoft.PackageDependencyResolution.targets(201,5): error :
Assets file '~\netcoreapp-netstandard-refs\src\_build\bin\obj\project.assets.json' doesn't have a target for '.NETStandard,Version=v2.0'.
Ensure that restore has run and that you have included 'netstandard2.0' in the TargetFrameworks for your project.
[~\netcoreapp-netstandard-refs\src\dotnetcore-lib\dotnetcore-lib.csproj]

Steps to reproduce:

  1. Create class lib
    dotnet new classlib --name dotnetcore-lib --output src/dotnetcore-lib
  2. Create web app
    dotnet new web --name aspnetcore --output src/aspnetcore
  3. Add class lib as reference to web app
    dotnet add .\src\aspnetcore\aspnetcore.csproj reference .\src\dotnetcore-lib\dotnetcore-lib.csproj
  4. Restore and publish (without custom BaseIntermediateOutputPath) => that works 👍
    dotnet restore .\src\dotnetcore-lib\dotnetcore-lib.csproj
    dotnet restore .\src\aspnetcore\aspnetcore.csproj
    dotnet publish .\src\dotnetcore-lib\dotnetcore-lib.csproj
    dotnet publish .\src\aspnetcore\aspnetcore.csproj
    
  5. Restore and publish (with custom BaseIntermediateOutputPath) => that fails 👎
    dotnet restore .\src\dotnetcore-lib\dotnetcore-lib.csproj /p:BaseIntermediateOutputPath=..\..\_build\bin\obj\dotnetcore-lib\
    dotnet publish .\src\dotnetcore-lib\dotnetcore-lib.csproj /p:BaseIntermediateOutputPath=..\..\_build\bin\obj\dotnetcore-lib\
    dotnet restore .\src\aspnetcore\aspnetcore.csproj /p:BaseIntermediateOutputPath=..\..\_build\bin\obj\aspnetcore\
    dotnet publish .\src\aspnetcore\aspnetcore.csproj /p:BaseIntermediateOutputPath=..\..\_build\bin\obj\aspnetcore\
    

What the hell we make it wrong?

Related issues

@livarcocc
Copy link
Contributor

@nguerrera

@livarcocc
Copy link
Contributor

cc @dsplaisted can you take a look at this one? I remember you were looking at something similar already.

@livarcocc livarcocc added this to the Discussion milestone Feb 28, 2018
@dsplaisted dsplaisted modified the milestones: Discussion, 2.1.3xx Feb 28, 2018
@dsplaisted
Copy link
Member

Setting BaseIntermediateOutputPath from the command line isn't supported when you have project references.

The problem is, when you build a project, its references are also built. In this case, when you build aspnetcore.csproj, it in turn builds dotnetcore-lib.csproj, and the BaseIntermediateOutputPath flows through and both projects end up trying to use the same project.assets.json file, which causes this error (a project ends up trying to use the assets file which was written for another project).

If we were to change it so that the BaseIntermediateOutputPath didn't flow to project references, then when aspnetcore.csproj builds dotnetcore-lib.csproj, the BaseIntermediateOutputPath wouldn't be specified at all, so it would put the intermediate output in the default folder (obj in the project directory) instead of the _build folder you specified when you built that project previously.

Instead of trying to specify this on the command line, I'd suggest creating a Directory.Build.props file in the root folder with the following contents (or similar):

<Project>
  <PropertyGroup>
    <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)_build\bin\obj\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
  </PropertyGroup>
</Project>

See also #867 which proposes adding a property to specify a root path under which all project output (intermediate as well as final) would go. If we add such a property, then it should be possible to specify it from the command line.

@DoCode
Copy link
Author

DoCode commented Mar 1, 2018

First, @livarcocc, @nguerrera... Thanks for your fast response! Very Great!

@dsplaisted, ok. I understand and tried it. And it worked!

But what about when I restore, build/publish with --no-dependencies --force like this:

dotnet restore .\src\dotnetcore-lib\dotnetcore-lib.csproj /p:BaseIntermediateOutputPath=..\..\_build\bin\obj\dotnetcore-lib\ --no-dependencies --force
dotnet publish .\src\dotnetcore-lib\dotnetcore-lib.csproj /p:BaseIntermediateOutputPath=..\..\_build\bin\obj\dotnetcore-lib\ --no-dependencies --force
dotnet restore .\src\aspnetcore\aspnetcore.csproj /p:BaseIntermediateOutputPath=..\..\_build\bin\obj\aspnetcore\ --no-dependencies --force
dotnet publish .\src\aspnetcore\aspnetcore.csproj /p:BaseIntermediateOutputPath=..\..\_build\bin\obj\aspnetcore\ --no-dependencies --force

This does not work. But it from the wording from the arguments it should.

The question is, how can we tell the underlying compiler/linker the path to the 'pre-compiled' dotnetcore-lib.csproj obj dir?

@dsplaisted
Copy link
Member

how can we tell the underlying compiler/linker the path to the 'pre-compiled' dotnetcore-lib.csproj obj dir?

If you want to use a DLL directly instead of building the referenced project, then you can use a Reference instead of a ProjectReference in your project file. But I think it would be helpful to understand why you want to do this, there may be a better way to do it.

As for why --no-dependencies didn't work, even if the referenced project isn't being built, it still calls the GetTargetPath target on it in order to figure out what the path to the DLL to reference is.

@DoCode
Copy link
Author

DoCode commented Apr 18, 2018

@dsplaisted thanks for your support.
The only simple answer to your question is, that we would redirect the base obj (BIOP) folder outside of the project directory structure.
Is there a solution for this?

@dsplaisted
Copy link
Member

@DoCode I'm not following. If you want to redirect the obj folder, then use a Directory.Build.props file like I suggested. The path you put in that file can be outside the project directory structure if you want it.

You probably don't need to use the --no-dependencies or --force options.

@DoCode
Copy link
Author

DoCode commented Apr 18, 2018

@dsplaisted thanks again! I am a little bit overcommited ;-)
So what I see today, I can not build dynamically a output folder structure like this:

_build
|-> bin
    |-> anyos.anycpu.<configuration>
        |-> <project-name>
            |-> <target-framework>
                .
                .
                .
    |-> win7.x86.debug
        |-> Project1
            |-> netstandard2.0
                .
                .
                .

The only problem is, that I can not dynamic parse the informations like cpu, architecture, target framework and so on, when I use the directory.build.props.

@dsplaisted
Copy link
Member

@DoCode Yes, the target framework and runtime identifier aren't available at that point. By default they are appended to the path under the project-specific path. So if you set BaseIntermediateOutputPath to build\obj\<project-name>, then you will end up with folders such as Debug\netstandard2.0\win7-x86 under it.

@DoCode
Copy link
Author

DoCode commented Apr 18, 2018

@dsplaisted ok.
No chance to calculate and read the props in a custom task (default task in directory.build.props)?

@dsplaisted
Copy link
Member

@DoCode No, these properties are set in evaluation, which is before any tasks are run.

It's probably possible to get close to the layout you want, but it would be a lot trickier. You'd still set the BaseIntermediateOutputPath to something like build\<project-name>\obj in Directory.Build.props, but then you would calculate the IntermediateOutputPath and OutputPath in a .targets file that was evaluated after the TargetFramework and other properties you need were set.

You can use binary logs and MSBuild Structured Log Viewer to explore how the current output path calculations work, and help figure out how to override them with your own. Look for the AppendTargetFrameworkToOutputPath and AppendRuntimeIdentifierToOutputPath properties.

@DoCode
Copy link
Author

DoCode commented Apr 18, 2018

Thanks a lot, Daniel (@dsplaisted)!
I will test this tomorrow... Now it's very late in the Bavarian Alps! :-)

@DoCode
Copy link
Author

DoCode commented Sep 2, 2018

@dsplaisted - sorry for the long delay. But other tasks have higher prio!

I ended with this Directory.Build.props:

<?xml version="1.0" encoding="utf-8"?>
<Project>

  <PropertyGroup>
    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
  </PropertyGroup>

  <Import Project="../Directory.Build.props" Condition=" Exists('../Directory.Build.props') " />

  <Import Project="../dir.props" Condition=" Exists('../dir.props') " />
  <Import Project="SolutionItems/version/version.props" Condition=" Exists('SolutionItems/version/version.props') " />

  <PropertyGroup Condition=" '$(ConfigPropsFileImported)' == 'true' ">
    <!--
      _build / bin /
    -->
    <BaseOutputPath>$(BinDir)/</BaseOutputPath>

    <!--
      _build / obj /
    -->
    <BaseIntermediateOutputPath>$(BaseOutputDir)/obj/$(MSBuildProjectName)/</BaseIntermediateOutputPath>

    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
    <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
  </PropertyGroup>

</Project>

And this Directory.Build.targets:

<?xml version="1.0" encoding="utf-8"?>
<Project>

  <PropertyGroup>
    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
  </PropertyGroup>

  <Import Project="../Directory.Build.targets" Condition=" Exists('../Directory.Build.targets') " />

  <Import Project="../dir.targets" Condition=" Exists('../dir.targets') " />

  <PropertyGroup Condition=" '$(ConfigPropsFileImported)' == 'true' ">
    <!--
      _build / bin / win7.x64.debug / DotNetCore.ConsoleApp / netcoreapp2.1 /
      _build / bin / $(_OS).$(_PlatformTarget).$(Configuration) / $(MSBuildProjectName) / $(_TargetFramework) /
    -->

    <_OS Condition=" '$(RuntimeIdentifier)' != '' ">$(RuntimeIdentifier.Split('-')[0])</_OS>
    <_OS Condition=" '$(_OS)' == '' ">AnyOS</_OS>
    <_PlatformTarget>$(PlatformTarget)</_PlatformTarget>
    <_PlatformTarget Condition=" '$(_PlatformTarget)' == '' ">AnyCPU</_PlatformTarget>
    <_TargetFramework>$(TargetFramework)</_TargetFramework>
    <_TargetFramework Condition=" '$(_TargetFramework)' == '' AND '$(TargetFrameworkIdentifier)' == '.NETFramework' ">net$(_TargetFrameworkVersionWithoutV.Replace('.', ''))</_TargetFramework>

    <IntermediateOutputPath>$(BaseIntermediateOutputPath)$(_OS.ToLowerInvariant()).$(_PlatformTarget.ToLowerInvariant()).$(Configuration.ToLowerInvariant())/$(_TargetFramework.ToLowerInvariant())/</IntermediateOutputPath>
    <IntermediateOutputPath>$(IntermediateOutputPath.TrimEnd('/').TrimEnd('\'))/</IntermediateOutputPath>

    <OutputPath>$(BinDir)/$(_OS.ToLowerInvariant()).$(_PlatformTarget.ToLowerInvariant()).$(Configuration.ToLowerInvariant())/$(MSBuildProjectName)/$(_TargetFramework.ToLowerInvariant())/</OutputPath>
    <OutputPath>$(OutputPath.TrimEnd('/').TrimEnd('\'))/</OutputPath>

    <OutDir>$(OutputPath)</OutDir>
    <BaseOutputPath>$(OutputPath)</BaseOutputPath>
    <PackageOutputPath>$(OutputPath)</PackageOutputPath>
    <TargetDir>$(OutputPath)</TargetDir>
    <TargetPath>$(TargetDir)$(TargetFileName)</TargetPath>
    <PublishDir>$(OutputPath)</PublishDir>
  </PropertyGroup>

</Project>

Results

Now some errors occours on build:

    Error MSB4018: The "GenerateBindingRedirects" task failed unexpectedly.
System.IO.DirectoryNotFoundException: Could not find a part of the path 'D:\projects\output-labs\_build\obj\DotNetCore.ConsoleApp\Debug\repocli.exe.config'.

I think the GenerateBindingRedirects task doesnt respect the changed IntermediateOutputPath.

@IngmarBitter
Copy link

This issue still presists:

in my net50 C# solution building with VS2019 I get no warnings with

<BaseArtifactsPath>$(MSBuildStartupDirectory)artifacts/</BaseArtifactsPath>
<BaseArtifactsPathSuffix>$(ProjectCategory)/$(MSBuildProjectName)</BaseArtifactsPathSuffix>

and Unknown build errors that disappear at the end of compilation, if I add the line

<BaseIntermediateOutputPath>$(BaseArtifactsPath)obj/$(BaseArtifactsPathSuffix)/</BaseIntermediateOutputPath>

but even with this last line a startupDir/obj directory is still created with Debug/net5.0-windows subdirectory. And there is no obj directory created inside startupDir/artifacts. So the intended effect is not there.

JL03-Yue pushed a commit that referenced this issue Mar 19, 2024
…028.1 (#2003)

Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.XliffTasks
 From Version 9.0.0-beta.23527.4 -> To Version 9.0.0-beta.23528.1

Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
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

No branches or pull requests

4 participants