How does Visual Studio determine the output path when it's not informed in the .csproj file?

7k views Asked by At

I'm trying to reference a C# DLL project from another solution, but the build is generating the DLL in a very strange output folder.

The directory contents is this:

c:\a\b\c\src\Solution.sln
c:\a\x\y\z\MyDLL\MyDLL.csproj

The MyDLL.csproj does not have an <OutputPath> tag. It does however have a <SolutionDir> tag that I don't see often.

The computed output path, as shown in Properties view, happens to be:

..\..\..\..\b\c\src-z\MyDLL\objd\i386

This corresponds to this path:

c:\a\b\c\src-z\src\MyDLL\objd\i386

which is very weird, since I'm not aware of anything in configuration with src-z. Is Visual Studio computing a path with hyphens?

I want to fix this, possibly changing the <SolutionDir>, but I don't want to break other solutions.

The computation seems to happen very early in the build process, as the first thing the builder logs is:

1>Project 'MyDLL (x\y\z\MyDLL\MyDLL.csproj)' is not up to date.
  Input file 'x\y\z\MyDLL\MyDLL.csproj' is modified after output
  file 'c:\a\b\c\src-z\src\MyDLL\objd\i386\MyDLL.pdb'.

So what algorithm is Visual Studio using to compute the output path when a project <OutputPath> tag is not found?

3

There are 3 answers

0
fernacolo On BEST ANSWER

I found the issue. One of the referenced projects was defining <BaseIntermediateOutputPath> as $SolutionDir + "-" + $ProjectName, which yields to c:\a\b\c\src-z.

As for answering my question, the best I could deduce is:

  1. If there is <OutputPath>, then it's used.
  2. Otherwise, the output path is defined as <BaseIntermediateOutputPath> + <IntermediateOutputPath> + "bin".
  3. The default value of <BaseIntermediateOutputPath> is the project directory.
  4. The default value of <IntermediateOutputPath> is defined by the build type (i.e., Debug, Release, etc.).
0
Micah Zoltu On

The code that populates OutputPath can be found in C:\Program Files (x86)\MSBuild\12.0\Bin\Microsoft.Common.CurrentVersion.targets (by default; it may change if the MSBuild install directory is different).

There is a comment about it that reads:

OutDir:

Indicates the final output location for the project or solution. When building a solution, OutDir can be used to gather multiple project outputs in one location. In addition, OutDir is included in AssemblySearchPaths used for resolving references.

OutputPath:

This property is usually specified in the project file and is used to initialize OutDir. OutDir and OutputPath are distinguished for legacy reasons, and OutDir should be used if at all possible.

So while OutputPath is often what is referenced, it is OutDir that actually matters. If there is no set platform or configuration then OutputPath is set to bin\Debug\.

If we look in that file we can see the logic that sets OutDir is pretty simple. If OutDir isn't set then it is set to OutputPath. There is a little bit of extra logic around appending a folder named after the project to the path if GenerateProjectSpecificOutputPath is set.

Looking at Microsoft.CSharp.targets, Microsoft.CSharp.CurrentVersion.targets, Microsoft.Common.Targets and Microsoft.Common.CurrentVersion.targets, neither OutDir nor OutputPath appears to be set anywhere else. So, assuming an "out of the box" C# project, it basically will equal either OutDir, OutputPath, or bin\Debug.

The last bit of information that is relevant is the working directory. OutDir can be a relative path in which case it will be somewhere in the working directory.

As for the BaseIntermediateOutputPath, the information is in the same file:

BaseIntermediateOutputPath:

This is the top level folder where all configuration specific intermediate output folders will be created. Default value is obj\

IntermediateOutputPath:

This is the full intermediate Output Path, and is derived from BaseIntermediateOutputPath, if none specified (eg. obj\debug). If this property is overridden, then setting BaseIntermediateOutputPath has no effect.

Looking at the logic, it defaults to obj\ if not set (note, relative path so the working directory comes into play again).

0
Uwe Hafner On

I would say you have an OutputPath declaration somewhere but if not in your .csproj file in one of the imported targets-files.

I will split my answer in several parts:

  1. <SolutionDir> tag and changing it.
    With this tag you can declare the solution directory variable $(SolutionDir), but I don't think it is used by Visual Studio from inside the .csproj file if it is different from where the solution actually resides.
    But the tag could be used by MSBuild builds. I couldn't get Visual Studio to use it if I declared it in the .csproj file.
    Visual Studio uses the directory of the loaded solution as variable content instead of any different declaration in the .csproj file from my tests.
    Having said that: changing the tag will probably change the use of $(SolutionDir) variable in any places where you use it like PreBuild/PostBuild or Output directories. Whereever it is used. But not in Visual Studio.
    If it is not used changing it won't change much in Visual Studio builds.

  2. No OutputPath tag
    I would expect this tag if the .csproj file is loaded inside Visual Studio. But it is possible to declare it in targets-files instead of .csproj files. So you have to check the imported targets files as well.
    e.g. Look at the file Microsoft.CSharp.targets. This is the standard imported targets file for CS-Projects in Visual Studio. It makes computations and declarations of OutputPath.
    Second possibility to look are your own targets files that you can import (handwritten .csproj files). Declarations are possible there as well.
    Third possibility: I think it is possible to declare this variable via command line argument for MSBuild though not for Visual Studio.

  3. Would Visual Studio work without this tag in the .csproj file?
    Usually not, BUT you could declare to override errors from the standard C# targets file (Microsoft.CSharp.targets) and make it build - but then it is declared in this file.
    Standard Output directory if not declared would be \bin\Debug (for debug builds) as defined by Microsoft.CSharp.targets.
    Otherwise you get an error. Missing OutputPath declaration.

  4. How is it computed Look at the .csproj file and targets files (excluding command line parameters in MSBuild) and see which PropertyGroup sections are valid (check the conditions). For example:

    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    </PropertyGroup>
    Everything inside this would be valid for the selected configuration Debug and AnyCPU. Especially in the standard targets file there are a some declarations for OutputPath. This is where it seems to get non-standard for you if your information is correct: Usually you get an error in Visual Studio building if OutputPath is not declared because there is a section in targets checking the declaration and throwing an error (you can change this behavior - see point 3). Interpretation would be top-down like any script.

  5. src-z comes from where? (in Visual Studio)
    Scan all .csproj files and targets files you are using for "src-z" and the hard disk location of the solution file to find out where it comes.

If you really don't have any OutputPath declaration anywhere then I don't have an explanation why your VS-Build is working.
And if you don't find src-z then can you post some more information on your .csproj file and how you use it (Visual Studio and solutions)?