Skip to content

Commit

Permalink
Reverts the AppHost setting to default and uses ComputeNETCoreBuildOu…
Browse files Browse the repository at this point in the history
…tputFiles to control output files for design-time builds instead (#194, #185, #187)
  • Loading branch information
daveaglick committed Jan 19, 2022
1 parent d1eca00 commit d4170f4
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 72 deletions.
63 changes: 63 additions & 0 deletions src/Buildalyzer/Construction/IProjectFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,77 @@ namespace Buildalyzer.Construction
{
public interface IProjectFile
{
/// <summary>
/// Whether the project file contains <c>PackageReference</c> items.
/// </summary>
bool ContainsPackageReferences { get; }

/// <summary>
/// Whether the project file is multi-targeted.
/// </summary>
/// <remarks>
/// Checks for an <c>TargetFrameworks</c> element.
/// </remarks>
bool IsMultiTargeted { get; }

/// <summary>
/// The list of <c>PackageReference</c> items in the project file.
/// </summary>
IReadOnlyList<IPackageReference> PackageReferences { get; }

/// <summary>
/// The full path to the project file.
/// </summary>
string Path { get; }

/// <summary>
/// Project name.
/// </summary>
string Name { get; }

/// <summary>
/// Whether the project file requires a .NET Framework host and build tools to build.
/// </summary>
/// <remarks>
/// Checks for an <c>Import</c> element with a <c>Project</c> attribute ending with one of the targets in <see cref="ProjectFile.ImportsThatRequireNetFramework"/>.
/// Also looks for a <c>LanguageTargets</c> ending with one of the targets in <see cref="ProjectFile.ImportsThatRequireNetFramework"/>.
/// Projects that use these targets are known not to build under a .NET Core host or build tools.
/// Also checks for a <c>ToolsVersion</c> attribute and uses the .NET Framework if one is found.
/// </remarks>
bool RequiresNetFramework { get; }

/// <summary>
/// The target framework(s) in the project file.
/// </summary>
/// <remarks>
/// This does not perform evaluation of the project file, only parsing.
/// If TargetFramework or TargetFrameworks contains a property that
/// needs to be evaluated, this will contain the pre-evaluated value(s).
/// Try to find a TargetFrameworkIdentifier in the same PropertyGroup
/// and if no TargetFrameworkIdentifier was found, assume ".NETFramework".
/// </remarks>
string[] TargetFrameworks { get; }

/// <summary>
/// Gets the <c>ToolsVersion</c> attribute of the <c>Project</c> element (or <c>null</c> if there isn't one).
/// </summary>
string ToolsVersion { get; }

/// <summary>
/// Whether the project file uses an SDK.
/// </summary>
/// <remarks>
/// Checks for an <c>Sdk</c> attribute on the <c>Project</c> element. If one can't be found,
/// also checks for <c>Import</c> elements with an <c>Sdk</c> attribute (see https://github.com/Microsoft/msbuild/issues/1493).
/// </remarks>
bool UsesSdk { get; }

/// <summary>
/// The output type of the project.
/// </summary>
/// <remarks>
/// Checks for an <c>OutputType</c> element.
/// </remarks>
string OutputType { get; }
}
}
59 changes: 12 additions & 47 deletions src/Buildalyzer/Construction/ProjectFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,79 +41,44 @@ internal ProjectFile(string path)
}
}

/// <summary>
/// The full path to the project file.
/// </summary>
/// <inheritdoc />
public string Path { get; }

/// <summary>
/// Project name.
/// </summary>
/// <inheritdoc />
public string Name { get; }

/// <summary>
/// The target framework(s) in the project file.
/// </summary>
/// <remarks>
/// This does not perform evaluation of the project file, only parsing.
/// If TargetFramework or TargetFrameworks contains a property that
/// needs to be evaluated, this will contain the pre-evaluated value(s).
/// Try to find a TargetFrameworkIdentifier in the same PropertyGroup
/// and if no TargetFrameworkIdentifier was found, assume ".NETFramework".
/// </remarks>
/// <inheritdoc />
public string[] TargetFrameworks => _targetFrameworks
?? (_targetFrameworks = GetTargetFrameworks(
_projectElement.GetDescendants(ProjectFileNames.TargetFrameworks).Select(x => x.Value),
_projectElement.GetDescendants(ProjectFileNames.TargetFramework).Select(x => x.Value),
_projectElement.GetDescendants(ProjectFileNames.TargetFrameworkVersion)
.Select(x => (x.Parent.GetDescendants(ProjectFileNames.TargetFrameworkIdentifier).FirstOrDefault()?.Value ?? ".NETFramework", x.Value))));

/// <summary>
/// Whether the project file uses an SDK.
/// </summary>
/// <remarks>
/// Checks for an <c>Sdk</c> attribute on the <c>Project</c> element. If one can't be found,
/// also checks for <c>Import</c> elements with an <c>Sdk</c> attribute (see https://github.com/Microsoft/msbuild/issues/1493).
/// </remarks>
/// <inheritdoc />
public bool UsesSdk =>
_projectElement.GetAttributeValue(ProjectFileNames.Sdk) != null
|| _projectElement.GetDescendants(ProjectFileNames.Import).Any(x => x.GetAttributeValue(ProjectFileNames.Sdk) != null);

/// <summary>
/// Whether the project file requires a .NET Framework host and build tools to build.
/// </summary>
/// <remarks>
/// Checks for an <c>Import</c> element with a <c>Project</c> attribute ending with one of the targets in <see cref="ImportsThatRequireNetFramework"/>.
/// Also looks for a <c>LanguageTargets</c> ending with one of the targets in <see cref="ImportsThatRequireNetFramework"/>.
/// Projects that use these targets are known not to build under a .NET Core host or build tools.
/// Also checks for a <c>ToolsVersion</c> attribute and uses the .NET Framework if one is found.
/// </remarks>
/// <inheritdoc />
public bool RequiresNetFramework =>
_projectElement.GetDescendants(ProjectFileNames.Import).Any(x => ImportsThatRequireNetFramework.Any(i => x.GetAttributeValue(ProjectFileNames.Project).EndsWith(i, StringComparison.OrdinalIgnoreCase)))
|| _projectElement.GetDescendants(ProjectFileNames.LanguageTargets).Any(x => ImportsThatRequireNetFramework.Any(i => x.Value.EndsWith(i, StringComparison.OrdinalIgnoreCase)))
|| ToolsVersion != null;

/// <summary>
/// Whether the project file is multi-targeted.
/// </summary>
/// <remarks>
/// Checks for an <c>TargetFrameworks</c> element.
/// </remarks>
/// <inheritdoc />
public bool IsMultiTargeted => _projectElement.GetDescendants(ProjectFileNames.TargetFrameworks).Any();

/// <summary>
/// Whether the project file contains <c>PackageReference</c> items.
/// </summary>
/// <inheritdoc />
public string OutputType => _projectElement.GetDescendants(ProjectFileNames.OutputType).FirstOrDefault()?.Value;

/// <inheritdoc />
public bool ContainsPackageReferences => _projectElement.GetDescendants(ProjectFileNames.PackageReference).Any();

/// <summary>
/// The list of <c>PackageReference</c> items in the project file.
/// </summary>
/// <inheritdoc />
public IReadOnlyList<IPackageReference> PackageReferences => _projectElement.GetDescendants(ProjectFileNames.PackageReference).Select(s => new PackageReference(s)).ToList();

/// <summary>
/// Gets the <c>ToolsVersion</c> attribute of the <c>Project</c> element (or <c>null</c> if there isn't one).
/// </summary>
/// <inheritdoc />
public string ToolsVersion => _projectElement.GetAttributeValue(ProjectFileNames.ToolsVersion);

internal static string[] GetTargetFrameworks(
Expand Down
1 change: 1 addition & 0 deletions src/Buildalyzer/Construction/ProjectFileNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public static class ProjectFileNames
public const string PackageReference = nameof(PackageReference);
public const string ToolsVersion = nameof(ToolsVersion);
public const string LanguageTargets = nameof(LanguageTargets);
public const string OutputType = nameof(OutputType);

// Attributes
public const string Sdk = nameof(Sdk);
Expand Down
10 changes: 8 additions & 2 deletions src/Buildalyzer/Environment/BuildEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,25 @@ public BuildEnvironment(
};
if (DesignTime)
{
// The actual design-time tasks aren't available outside of Visual Studio,
// so we can't do a "real" design-time build and have to fake it with various global properties
// See https://github.com/dotnet/msbuild/blob/fb700f90493a0bf47623511edf28b1d6c114e4fa/src/Tasks/Microsoft.CSharp.CurrentVersion.targets#L320
// To diagnose build failures in design-time mode, generate a binary log and find the filing target,
// then see if there's a condition or property that can be used to modify it's behavior or turn it off
_globalProperties.Add(MsBuildProperties.DesignTimeBuild, "true");
_globalProperties.Add(MsBuildProperties.BuildingProject, "false"); // Supports Framework projects: https://github.com/dotnet/project-system/blob/main/docs/design-time-builds.md#determining-whether-a-target-is-running-in-a-design-time-build
_globalProperties.Add(MsBuildProperties.BuildProjectReferences, "false");
_globalProperties.Add(MsBuildProperties.SkipCompilerExecution, "true");
_globalProperties.Add(MsBuildProperties.DisableRarCache, "true");
_globalProperties.Add(MsBuildProperties.AutoGenerateBindingRedirects, "false");
_globalProperties.Add(MsBuildProperties.CopyBuildOutputToOutputDirectory, "false");
_globalProperties.Add(MsBuildProperties.CopyOutputSymbolsToOutputDirectory, "false");
_globalProperties.Add(MsBuildProperties.CopyDocumentationFileToOutputDirectory, "false");
_globalProperties.Add(MsBuildProperties.ComputeNETCoreBuildOutputFiles, "false"); // Prevents the CreateAppHost task from running, which doesn't add the apphost.exe to the files to copy
_globalProperties.Add(MsBuildProperties.SkipCopyBuildProduct, "true");
_globalProperties.Add(MsBuildProperties.AddModules, "false");
_globalProperties.Add(MsBuildProperties.UseCommonOutputDirectory, "true"); // This is used in a condition to prevent copying in _CopyFilesMarkedCopyLocal
_globalProperties.Add(MsBuildProperties.GeneratePackageOnBuild, "false"); // Prevent NuGet.Build.Tasks.Pack.targets from running the pack targets (since we didn't build anything)

// _globalProperties.Add(MsBuildProperties.UseAppHost, "false"); // Prevent creation of native host executable https://docs.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#useapphost
}
_additionalGlobalProperties = CopyItems(_globalProperties, additionalGlobalProperties);

Expand Down
2 changes: 2 additions & 0 deletions src/Buildalyzer/Environment/EnvironmentFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ private BuildEnvironment CreateCoreEnvironment(EnvironmentOptions options)

// Required to force CoreCompile target when it calculates everything is already built
// This can happen if the file wasn't previously generated (Clean only cleans what was in that file)
// Only required if we're not running a design-time build (otherwise the targets will be replaced anyway)
if (options.TargetsToBuild.Contains("Clean", StringComparer.OrdinalIgnoreCase))
{
additionalGlobalProperties.Add(MsBuildProperties.NonExistentFile, Path.Combine("__NonExistentSubDir__", "__NonExistentFile__"));
Expand Down Expand Up @@ -127,6 +128,7 @@ private BuildEnvironment CreateFrameworkEnvironment(EnvironmentOptions options)

// Required to force CoreCompile target when it calculates everything is already built
// This can happen if the file wasn't previously generated (Clean only cleans what was in that file)
// Only required if we're not running a design-time build (otherwise the targets will be replaced anyway)
if (options.TargetsToBuild.Contains("Clean", StringComparer.OrdinalIgnoreCase))
{
additionalGlobalProperties.Add(MsBuildProperties.NonExistentFile, Path.Combine("__NonExistentSubDir__", "__NonExistentFile__"));
Expand Down
4 changes: 3 additions & 1 deletion src/Buildalyzer/Environment/EnvironmentOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ public class EnvironmentOptions

/// <summary>
/// Indicates that a design-time build should be performed.
/// The default value is <c>true</c>.
/// The default value is <c>true</c>. Note that when performing
/// a design-time build, the <see cref="TargetsToBuild"/> will
/// be ignored and the design-time targets will be used instead.
/// </summary>
/// <remarks>
/// See https://github.com/dotnet/project-system/blob/master/docs/design-time-builds.md.
Expand Down
6 changes: 4 additions & 2 deletions src/Buildalyzer/Environment/MsBuildProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@ public static class MsBuildProperties
public const string TargetFramework = nameof(TargetFramework);

// Design-time Build
public const string DesignTimeBuild = nameof(DesignTimeBuild);
public const string DesignTimeBuild = nameof(DesignTimeBuild); // New project system (https://github.com/dotnet/project-system/blob/main/docs/design-time-builds.md#determining-whether-a-target-is-running-in-a-design-time-build)
public const string BuildingProject = nameof(BuildingProject); // Legacy (https://github.com/dotnet/project-system/blob/main/docs/design-time-builds.md#determining-whether-a-target-is-running-in-a-design-time-build)
public const string BuildProjectReferences = nameof(BuildProjectReferences);
public const string SkipCompilerExecution = nameof(SkipCompilerExecution);
public const string ProvideCommandLineArgs = nameof(ProvideCommandLineArgs);
public const string DisableRarCache = nameof(DisableRarCache);
public const string AutoGenerateBindingRedirects = nameof(AutoGenerateBindingRedirects);
public const string CopyBuildOutputToOutputDirectory = nameof(CopyBuildOutputToOutputDirectory);
public const string CopyOutputSymbolsToOutputDirectory = nameof(CopyOutputSymbolsToOutputDirectory);
public const string CopyDocumentationFileToOutputDirectory = nameof(CopyDocumentationFileToOutputDirectory);
public const string ComputeNETCoreBuildOutputFiles = nameof(ComputeNETCoreBuildOutputFiles);
public const string SkipCopyBuildProduct = nameof(SkipCopyBuildProduct);
public const string AddModules = nameof(AddModules);
public const string UseCommonOutputDirectory = nameof(UseCommonOutputDirectory);
public const string GeneratePackageOnBuild = nameof(GeneratePackageOnBuild);
public const string UseAppHost = nameof(UseAppHost);

// .NET Framework code analysis rulesets
public const string CodeAnalysisRuleDirectories = nameof(CodeAnalysisRuleDirectories);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ public class OpenSourceProjectsFixture
new TestRepository("https://github.com/AutoMapper/AutoMapper.git"),
new TestRepository(EnvironmentPreference.Framework, "https://github.com/serilog/serilog.git"), // SourceLink messed up from AppVeyor on SDK: "SourceLink.Create.CommandLine.dll. Assembly with same name is already loaded Confirm that the <UsingTask> declaration is correct"
new TestRepository("https://github.com/cake-build/cake"),
new TestRepository("https://github.com/Wyamio/Wyam.git"),
new TestRepository("https://github.com/bernd5/TestUsing.git")
new TestRepository("https://github.com/statiqdev/Statiq.Framework.git")
};

public class TestRepository
Expand Down
5 changes: 3 additions & 2 deletions tests/Buildalyzer.Tests/Integration/SimpleProjectsFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Buildalyzer.Tests.Integration
[NonParallelizable]
public class SimpleProjectsFixture
{
// Places the log file in C:/Temp
private const bool BinaryLog = false;

private static readonly EnvironmentPreference[] Preferences =
Expand Down Expand Up @@ -127,7 +128,7 @@ public void GetsSourceFiles(
// Linux and Mac builds appear to omit the AssemblyAttributes.cs file
"AssemblyAttributes",
#endif
"Class1",
analyzer.ProjectFile.OutputType?.Equals("exe", StringComparison.OrdinalIgnoreCase) ?? false ? "Program" : "Class1",
"AssemblyInfo"
}.ShouldBeSubsetOf(sourceFiles.Select(x => Path.GetFileName(x).Split('.').TakeLast(2).First()), log.ToString());
}
Expand Down Expand Up @@ -188,7 +189,7 @@ public void GetsSourceFilesFromBinaryLog(
// Linux and Mac builds appear to omit the AssemblyAttributes.cs file
"AssemblyAttributes",
#endif
"Class1",
analyzer.ProjectFile.OutputType?.Equals("exe", StringComparison.OrdinalIgnoreCase) ?? false ? "Program" : "Class1",
"AssemblyInfo"
}.ShouldBeSubsetOf(sourceFiles.Select(x => Path.GetFileName(x).Split('.').TakeLast(2).First()), log.ToString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Class1.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LegacyFrameworkProjectWithReference
{
public class Class1
{
static void Main(string[] args)
{
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LegacyFrameworkProjectWithReference
{
public class Program
{
static void Main(string[] args)
{
}
}
}

0 comments on commit d4170f4

Please sign in to comment.