diff --git a/Directory.Packages.props b/Directory.Packages.props
index 4d0359fb455..ce3efe7240d 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -22,13 +22,29 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/reference/docfx-cli-reference/docfx-metadata.md b/docs/reference/docfx-cli-reference/docfx-metadata.md
index fcf55620fd2..c5a298e61a0 100644
--- a/docs/reference/docfx-cli-reference/docfx-metadata.md
+++ b/docs/reference/docfx-cli-reference/docfx-metadata.md
@@ -108,6 +108,15 @@ Run `docfx metadata --help` or `docfx -h` to get a list of all available options
- `true`
- The CLR type names are used: `Int32`.
+- **--forceBuild**
+
+ Indicates whether to force MSBuild to run a full build prior to metadata extraction being attempted. This is useful if you have code that is produced outside of normal design time mechanisms, but will increase the time taken depending on the size of the project(s).
+
+ - not specified or `false`
+ - A build is not attempted.
+ - `true`
+ - A build is attempted.
+
## Examples
- Generate YAML files with default config.
diff --git a/docs/reference/docfx-json-reference.md b/docs/reference/docfx-json-reference.md
index 87656e9ea83..dd11c1201da 100644
--- a/docs/reference/docfx-json-reference.md
+++ b/docs/reference/docfx-json-reference.md
@@ -480,6 +480,10 @@ Specifies whether explicit interface implementations are included in the generat
Specify the name to use for the global namespace. The default value is an empty string.
+### `forceBuild`
+
+When enabled, will force MSBuild to run a full build prior to metadata extraction being attempted.
+
## File Mappings
In the short-hand form, these filenames are resolved relative to the directory containing the `docfx.json` file:
diff --git a/schemas/docfx.schema.json b/schemas/docfx.schema.json
index 956b1c916d8..1b083471223 100644
--- a/schemas/docfx.schema.json
+++ b/schemas/docfx.schema.json
@@ -701,6 +701,11 @@
"type": "boolean",
"default": false,
"description": "When enabled, use CLR type names instead of language aliases."
+ },
+ "forceBuild": {
+ "type": "boolean",
+ "default": false,
+ "description": "When enabled, will force MSBuild to run a full build prior to metadata extraction being attempted."
}
}
}
diff --git a/src/Docfx.Dotnet/Docfx.Dotnet.csproj b/src/Docfx.Dotnet/Docfx.Dotnet.csproj
index 7ff17fbca8d..0050cb52759 100644
--- a/src/Docfx.Dotnet/Docfx.Dotnet.csproj
+++ b/src/Docfx.Dotnet/Docfx.Dotnet.csproj
@@ -1,4 +1,4 @@
-
+
@@ -30,15 +30,15 @@
-
-
-
+
+
+
+
+
-
-
diff --git a/src/Docfx.Dotnet/DotnetApiCatalog.Compile.cs b/src/Docfx.Dotnet/DotnetApiCatalog.Compile.cs
index 5df14235424..e6ddb31eb1d 100644
--- a/src/Docfx.Dotnet/DotnetApiCatalog.Compile.cs
+++ b/src/Docfx.Dotnet/DotnetApiCatalog.Compile.cs
@@ -62,6 +62,8 @@ partial class DotnetApiCatalog
foreach (var solution in solutionFiles.Select(s => s.NormalizedPath))
{
Logger.LogInfo($"Loading solution {solution}");
+ var sf = SolutionFile.Parse(solution);
+
foreach (var project in SolutionFile.Parse(solution).ProjectsInOrder)
{
if (project.ProjectType is SolutionProjectType.KnownToBeMSBuildFormat &&
@@ -150,43 +152,67 @@ await LoadCompilationFromProject(project.AbsolutePath) is { } compilation)
if (project is null)
{
Logger.LogInfo($"Loading project {path}");
- if (!config.NoRestore)
+ if (!config.NoRestore || config.ForceBuild)
{
using var process = Process.Start("dotnet", $"restore \"{path}\"");
await process.WaitForExitAsync();
}
- project = await workspace.OpenProjectAsync(path, msbuildLogger);
-
- foreach (var unresolvedAnalyzer in project.AnalyzerReferences.OfType())
- {
- Logger.LogWarning($"There is .NET Analyzer that can't be resolved. "
- + $"If this analyzer is .NET Source Generator project. "
- + $"Try build with `dotnet build -c Release` command before running docfx. Path: {unresolvedAnalyzer.FullPath}",
- code: WarningCodes.Metadata.FailedToResolveAnalyzer);
- }
- foreach (var analyzer in project.AnalyzerReferences.OfType())
+ if (config.ForceBuild)
{
- analyzer.AnalyzerLoadFailed += (sender, e) =>
+ // Build using MSBuild APIs
+ var buildParams = new Microsoft.Build.Execution.BuildParameters
{
- var analyzerName = analyzer.Display;
- var errorCode = e.ErrorCode;
- var referencedCompilerVersion = e.ReferencedCompilerVersion;
-
- Logger.LogWarning($"Failed to load .NET Analyzer. AnalyzerName: {analyzerName}, ErrorCode: {errorCode}, ReferencedCompilerVersion: {referencedCompilerVersion}",
- code: WarningCodes.Metadata.FailedToLoadAnalyzer);
+ Loggers = new[] { msbuildLogger }
};
+ var buildRequest = new Microsoft.Build.Execution.BuildRequestData(
+ path,
+ msbuildProperties,
+ null,
+ new[] { "Build" },
+ null);
+
+ var buildResult = Microsoft.Build.Execution.BuildManager.DefaultBuildManager.Build(buildParams, buildRequest);
+
+ if (buildResult.OverallResult != Microsoft.Build.Execution.BuildResultCode.Success)
+ {
+ Logger.LogWarning($"Build failed for project {path}");
+ }
}
+
+ project = await workspace.OpenProjectAsync(path, msbuildLogger);
}
- if (!project.SupportsCompilation)
+ var compilation = await project.GetCompilationAsync();
+ if (compilation == null)
{
Logger.LogInfo($"Skip unsupported project {project.FilePath}.");
return null;
}
- return await project.GetCompilationAsync();
+ foreach (var unresolvedAnalyzer in project.AnalyzerReferences.OfType())
+ {
+ Logger.LogWarning($"There is .NET Analyzer that can't be resolved. "
+ + $"If this analyzer is .NET Source Generator project. "
+ + $"Try build with `dotnet build -c Release` command before running docfx. Path: {unresolvedAnalyzer.FullPath}",
+ code: WarningCodes.Metadata.FailedToResolveAnalyzer);
+ }
+
+ foreach (var analyzer in project.AnalyzerReferences.OfType())
+ {
+ analyzer.AnalyzerLoadFailed += (sender, e) =>
+ {
+ var analyzerName = analyzer.Display;
+ var errorCode = e.ErrorCode;
+ var referencedCompilerVersion = e.ReferencedCompilerVersion;
+
+ Logger.LogWarning($"Failed to load .NET Analyzer. AnalyzerName: {analyzerName}, ErrorCode: {errorCode}, ReferencedCompilerVersion: {referencedCompilerVersion}",
+ code: WarningCodes.Metadata.FailedToLoadAnalyzer);
+ };
+ }
+
+ return compilation;
}
}
}
diff --git a/src/Docfx.Dotnet/DotnetApiCatalog.cs b/src/Docfx.Dotnet/DotnetApiCatalog.cs
index 44724f112b4..4a182be080e 100644
--- a/src/Docfx.Dotnet/DotnetApiCatalog.cs
+++ b/src/Docfx.Dotnet/DotnetApiCatalog.cs
@@ -5,6 +5,7 @@
using System.Text.Json;
using Docfx.Common;
using Docfx.Plugins;
+using Microsoft.Build.Locator;
using Newtonsoft.Json.Linq;
using YamlDotNet.Serialization;
@@ -65,6 +66,22 @@ internal static async Task Exec(MetadataJsonConfig config, DotnetApiOptions opti
EnvironmentContext.SetBaseDirectory(configDirectory);
+ // this is here because heading into Compile, MSBuildLocator must have been registered as it uses MSBuild APIs
+ if (!MSBuildLocator.IsRegistered)
+ {
+ var visualStudioInstanceQueryOptions = VisualStudioInstanceQueryOptions.Default;
+ Logger.LogInfo($"Searching for MSBuild based upon: DiscoveryTypes:{visualStudioInstanceQueryOptions.DiscoveryTypes} - AllowAllDotnetLocations:{visualStudioInstanceQueryOptions.AllowAllDotnetLocations} - AllowAllRuntimeVersions:{visualStudioInstanceQueryOptions.AllowAllRuntimeVersions} - WorkingDirectory:\"{visualStudioInstanceQueryOptions.WorkingDirectory}\"");
+
+ var latestVersion = MSBuildLocator.QueryVisualStudioInstances(visualStudioInstanceQueryOptions).MaxBy(instance => instance.Version);
+ if (latestVersion == null)
+ {
+ throw new InvalidOperationException("Failed to find a version of Visual Studio or .NET SDK installed");
+ }
+
+ MSBuildLocator.RegisterInstance(latestVersion);
+ Logger.LogInfo($"Located MSBuild for: {latestVersion.Name} - {latestVersion.Version}");
+ }
+
foreach (var item in config)
{
VisitorHelper.GlobalNamespaceId = item.GlobalNamespaceId;
@@ -143,6 +160,7 @@ private static ExtractMetadataConfig ConvertConfig(MetadataJsonItemConfig config
DisableDefaultFilter = configModel?.DisableDefaultFilter ?? false,
DisableGitFeatures = configModel?.DisableGitFeatures ?? false,
NoRestore = configModel?.NoRestore ?? false,
+ ForceBuild = configModel?.ForceBuild ?? false,
CategoryLayout = configModel?.CategoryLayout ?? default,
NamespaceLayout = configModel?.NamespaceLayout ?? default,
MemberLayout = configModel?.MemberLayout ?? default,
diff --git a/src/Docfx.Dotnet/ManagedReference/ExtractMetadataConfig.cs b/src/Docfx.Dotnet/ManagedReference/ExtractMetadataConfig.cs
index 0cf7164962e..1f217df498f 100644
--- a/src/Docfx.Dotnet/ManagedReference/ExtractMetadataConfig.cs
+++ b/src/Docfx.Dotnet/ManagedReference/ExtractMetadataConfig.cs
@@ -45,4 +45,5 @@ internal class ExtractMetadataConfig
public static bool UseClrTypeNames { get; set; }
+ public bool ForceBuild { get; init; }
}
diff --git a/src/Docfx.Dotnet/MetadataJsonConfig.cs b/src/Docfx.Dotnet/MetadataJsonConfig.cs
index 39b53d8ff57..1ebe93851c3 100644
--- a/src/Docfx.Dotnet/MetadataJsonConfig.cs
+++ b/src/Docfx.Dotnet/MetadataJsonConfig.cs
@@ -265,6 +265,13 @@ internal class MetadataJsonItemConfig
[JsonProperty("useClrTypeNames")]
[JsonPropertyName("useClrTypeNames")]
public bool UseClrTypeNames { get; init; }
+
+ ///
+ /// Forces building the project before metadata extraction.
+ ///
+ [JsonProperty("forceBuild")]
+ [JsonPropertyName("forceBuild")]
+ public bool ForceBuild { get; set; }
}
///
diff --git a/src/docfx/Models/MetadataCommand.cs b/src/docfx/Models/MetadataCommand.cs
index 8bd0b98823c..7a04158503e 100644
--- a/src/docfx/Models/MetadataCommand.cs
+++ b/src/docfx/Models/MetadataCommand.cs
@@ -32,6 +32,7 @@ private static void MergeOptionsToConfig(MetadataCommandOptions options, DocfxCo
item.NamespaceLayout = options.NamespaceLayout ?? item.NamespaceLayout;
item.MemberLayout = options.MemberLayout ?? item.MemberLayout;
item.OutputFormat = options.OutputFormat ?? item.OutputFormat;
+ item.ForceBuild |= options.ForceBuild;
if (!string.IsNullOrEmpty(options.FilterConfigFile))
{
diff --git a/src/docfx/Models/MetadataCommandOptions.cs b/src/docfx/Models/MetadataCommandOptions.cs
index 9b4ad66ef71..e9eab4e3956 100644
--- a/src/docfx/Models/MetadataCommandOptions.cs
+++ b/src/docfx/Models/MetadataCommandOptions.cs
@@ -60,4 +60,8 @@ internal class MetadataCommandOptions : LogOptions
[Description("Determines the member page layout.")]
[CommandOption("--memberLayout")]
public MemberLayout? MemberLayout { get; set; }
+
+ [Description("Forces a Full Build to be run before metadata is gathered.")]
+ [CommandOption("--forceBuild")]
+ public bool ForceBuild { get; set; }
}