From bc3824f73d928f5de13d3ab9620d2d62a6cf28c2 Mon Sep 17 00:00:00 2001 From: Ion Todirel Date: Sun, 5 Jul 2026 19:26:54 -0700 Subject: [PATCH 1/3] Add JSON config-file support (--config + auto-discovered msbuild-extractor.json) Options can now be supplied from a JSON file so the extractor can run with no command-line parameters. A msbuild-extractor.json in the current directory is used automatically; otherwise pass one with --config . Command-line options override file values, which override built-in defaults. - Config.cs: a single config type whose instance Load(args) discovers the file (--config or ./msbuild-extractor.json), reads it, validates enum values, and resolves relative paths - CommandLineOptions.cs: seed option defaults from the config, add --config, merge msbuild property/env maps, expose ConfigPath - Program.cs: report the config file in use --- CommandLineOptions.cs | 119 ++++++++++++++++++-------- Config.cs | 192 ++++++++++++++++++++++++++++++++++++++++++ Program.cs | 3 + 3 files changed, 280 insertions(+), 34 deletions(-) create mode 100644 Config.cs diff --git a/CommandLineOptions.cs b/CommandLineOptions.cs index de87e2e..18727dc 100644 --- a/CommandLineOptions.cs +++ b/CommandLineOptions.cs @@ -38,128 +38,147 @@ public class CommandLineOptions public string MsBuildLauncher { get; set; } = "auto"; public string IncludePathOrder { get; set; } = "auto"; + /// Path to the JSON config file that supplied defaults, or null if none was used. + public string? ConfigPath { get; set; } + public static CommandLineOptions Parse(string[] args) { CommandLineOptions? result = null; + // Load an optional config file; its values become the defaults for the options built + // below, so anything passed on the command line still wins. config stays null when no + // config file is present. + Config? config = new Config(); + if (!config.Load(args)) + config = null; + var projectOption = new Option("--project", "-p") { Description = "Path to .vcxproj file (can be specified multiple times)", - AllowMultipleArgumentsPerToken = true + AllowMultipleArgumentsPerToken = true, + DefaultValueFactory = _ => config?.Projects ?? [] }; var solutionOption = new Option("--solution", "-s") { Description = "Path to .sln or .slnx file (can be specified multiple times)", - AllowMultipleArgumentsPerToken = true + AllowMultipleArgumentsPerToken = true, + DefaultValueFactory = _ => config?.Solutions ?? [] }; var configOption = new Option("--configuration", "-c") { Description = "Build configuration", - DefaultValueFactory = _ => "Debug" + DefaultValueFactory = _ => config?.Configuration ?? "Debug" }; var platformOption = new Option("--platform", "-a") { Description = "Build platform", - DefaultValueFactory = _ => "x64" + DefaultValueFactory = _ => config?.Platform ?? "x64" }; var vsPathOption = new Option("--vs-path") { - Description = "Path to Visual Studio installation" + Description = "Path to Visual Studio installation", + DefaultValueFactory = _ => config?.VsPath }; var vcTargetsPathOption = new Option("--vc-targets-path") { - Description = "Path to VC targets (e.g. ...\\MSBuild\\Microsoft\\VC\\v180)" + Description = "Path to VC targets (e.g. ...\\MSBuild\\Microsoft\\VC\\v180)", + DefaultValueFactory = _ => config?.VcTargetsPath }; var clPathOption = new Option("--cl-path") { - Description = "Path to cl.exe" + Description = "Path to cl.exe", + DefaultValueFactory = _ => config?.ClPath }; var solutionDirOption = new Option("--solution-dir") { - Description = "Value for the SolutionDir MSBuild property (auto-derived when using --solution)" + Description = "Value for the SolutionDir MSBuild property (auto-derived when using --solution)", + DefaultValueFactory = _ => config?.SolutionDir }; var vcToolsInstallDirOption = new Option("--vc-tools-install-dir") { - Description = "Value for the VCToolsInstallDir MSBuild property" + Description = "Value for the VCToolsInstallDir MSBuild property", + DefaultValueFactory = _ => config?.VcToolsInstallDir }; var outputOption = new Option("--output", "-o") { - Description = "Output path for compile_commands.json" + Description = "Output path for compile_commands.json", + DefaultValueFactory = _ => config?.Output }; var loggerOption = new Option("--logger") { Description = "Enable MSBuild console logger output", - DefaultValueFactory = _ => false + DefaultValueFactory = _ => config?.Logger ?? false }; var useDevEnvOption = new Option("--use-dev-env") { Description = "Read environment variables from Developer Command Prompt (VCToolsInstallDir, VCTargetsPath, etc.)", - DefaultValueFactory = _ => false + DefaultValueFactory = _ => config?.UseDevEnv ?? false }; var msbuildPathOption = new Option("--msbuild-path") { - Description = "Path to msbuild.exe (enables out-of-process mode for custom MSBuild/toolchain locations)" + Description = "Path to msbuild.exe (enables out-of-process mode for custom MSBuild/toolchain locations)", + DefaultValueFactory = _ => config?.MsBuildPath }; var allConfigsOption = new Option("--all-configurations") { Description = "Extract for all configuration/platform combinations found in the project or solution", - DefaultValueFactory = _ => false + DefaultValueFactory = _ => config?.AllConfigurations ?? false }; var mergeOption = new Option("--merge") { Description = "Merge all configurations into a single output file (use with --all-configurations)", - DefaultValueFactory = _ => false + DefaultValueFactory = _ => config?.Merge ?? false }; var strictOption = new Option("--strict") { Description = "Treat configuration/platform validation warnings as errors", - DefaultValueFactory = _ => false + DefaultValueFactory = _ => config?.Strict ?? false }; var validateOption = new Option("--validate") { Description = "After extraction, verify each compile command by running cl.exe /c", - DefaultValueFactory = _ => false + DefaultValueFactory = _ => config?.Validate ?? false }; var formatOption = new Option("--format", "-f") { Description = "Output format: 'standard' (compile_commands.json) or 'rich' (hierarchical compile_database.json)", - DefaultValueFactory = _ => "standard" + DefaultValueFactory = _ => config?.Format ?? "standard" }; formatOption.AcceptOnlyFromAmong("standard", "rich"); var deduplicateOption = new Option("--deduplicate") { Description = "Merge duplicate entries for the same file into one best-compromise entry for IntelliSense", - DefaultValueFactory = _ => false + DefaultValueFactory = _ => config?.Deduplicate ?? false }; var preferConfigOption = new Option("--prefer-configuration") { Description = "Preferred configuration for conflict resolution during deduplication (default: Debug)", - DefaultValueFactory = _ => "Debug" + DefaultValueFactory = _ => config?.PreferConfiguration ?? "Debug" }; var preferPlatformOption = new Option("--prefer-platform") { Description = "Preferred platform for conflict resolution during deduplication (default: x64)", - DefaultValueFactory = _ => "x64" + DefaultValueFactory = _ => config?.PreferPlatform ?? "x64" }; var listInstancesOption = new Option("--list-instances", "--list-vs") @@ -169,25 +188,26 @@ public static CommandLineOptions Parse(string[] args) var vsInstanceOption = new Option("--vs-instance") { - Description = "Select VS installation by instance ID (use --list-instances to see available)" + Description = "Select VS installation by instance ID (use --list-instances to see available)", + DefaultValueFactory = _ => config?.VsInstance }; var cCppPropertiesOption = new Option("--c-cpp-properties") { Description = "Emit a .vscode/c_cpp_properties.json pointing to the generated compile_commands.json", - DefaultValueFactory = _ => false + DefaultValueFactory = _ => config?.EmitCCppProperties ?? false }; var emitDefaultsOption = new Option("--emit-defaults") { Description = "Include the project-wide default compile entry in the output (synthetic __project_defaults.cpp with the baseline switches for the project)", - DefaultValueFactory = _ => false + DefaultValueFactory = _ => config?.EmitDefaults ?? false }; var mergeDefaultsOption = new Option("--merge-defaults") { Description = "Merge project-wide default switches (defines, language standard, warning level, etc.) into each per-file entry when they are not already present", - DefaultValueFactory = _ => false + DefaultValueFactory = _ => config?.MergeDefaults ?? false }; var msbuildPropertyOption = new Option("--msbuild-property") @@ -205,17 +225,22 @@ public static CommandLineOptions Parse(string[] args) var msbuildLauncherOption = new Option("--msbuild-launcher") { Description = "How to launch MSBuild: auto (sniff extension, default), cmd (force cmd.exe /c wrapper), direct (run executable directly), dotnet (force dotnet exec)", - DefaultValueFactory = _ => "auto" + DefaultValueFactory = _ => config?.MsBuildLauncher ?? "auto" }; msbuildLauncherOption.AcceptOnlyFromAmong("auto", "cmd", "direct", "dotnet"); var includePathOrderOption = new Option("--include-path-order") { Description = "Where to place include paths from MSBuild's IncludePath/ExternalIncludePath properties: auto (per-path heuristic, default), prepend (before /I), append (after /I, matches cl.exe INCLUDE-env semantics)", - DefaultValueFactory = _ => "auto" + DefaultValueFactory = _ => config?.IncludePathOrder ?? "auto" }; includePathOrderOption.AcceptOnlyFromAmong("auto", "prepend", "append"); + var configFileOption = new Option("--config") + { + Description = "Path to a JSON config file supplying option defaults. When omitted, a 'msbuild-extractor.json' in the current directory is used automatically if present. Command-line options override values from the file." + }; + var rootCommand = new RootCommand("Extract compile_commands.json from Visual C++ MSBuild projects"); rootCommand.Options.Add(projectOption); rootCommand.Options.Add(solutionOption); @@ -247,6 +272,7 @@ public static CommandLineOptions Parse(string[] args) rootCommand.Options.Add(msbuildEnvOption); rootCommand.Options.Add(msbuildLauncherOption); rootCommand.Options.Add(includePathOrderOption); + rootCommand.Options.Add(configFileOption); rootCommand.Validators.Add(commandResult => { @@ -257,11 +283,12 @@ public static CommandLineOptions Parse(string[] args) var projects = commandResult.GetValue(projectOption) ?? []; var solutions = commandResult.GetValue(solutionOption) ?? []; - if (projects.Length == 0 && solutions.Length == 0) - commandResult.AddError("At least one --project or --solution must be specified."); + // Inputs may also come from the config file. A validator cannot see values produced + // by an option's DefaultValueFactory, so read them from the config directly. + var hasConfigInputs = (config?.Projects?.Length ?? 0) > 0 || (config?.Solutions?.Length ?? 0) > 0; - if (commandResult.GetValue(cCppPropertiesOption) && commandResult.GetValue(formatOption) == "rich") - commandResult.AddError("--c-cpp-properties cannot be used with --format rich (rich format does not produce compile_commands.json)."); + if (projects.Length == 0 && solutions.Length == 0 && !hasConfigInputs) + commandResult.AddError("At least one --project or --solution must be specified (via the command line or a config file)."); }); rootCommand.SetAction(parseResult => @@ -294,11 +321,23 @@ public static CommandLineOptions Parse(string[] args) EmitCCppProperties = parseResult.GetValue(cCppPropertiesOption), EmitDefaults = parseResult.GetValue(emitDefaultsOption), MergeDefaults = parseResult.GetValue(mergeDefaultsOption), - MsBuildProperties = ParseKeyValuePairs(parseResult.GetValue(msbuildPropertyOption), "--msbuild-property"), - MsBuildEnv = ParseKeyValuePairs(parseResult.GetValue(msbuildEnvOption), "--msbuild-env"), + MsBuildProperties = MergeKeyValues(config?.MsBuildProperties, + ParseKeyValuePairs(parseResult.GetValue(msbuildPropertyOption), "--msbuild-property")), + MsBuildEnv = MergeKeyValues(config?.MsBuildEnv, + ParseKeyValuePairs(parseResult.GetValue(msbuildEnvOption), "--msbuild-env")), MsBuildLauncher = parseResult.GetValue(msbuildLauncherOption)!, - IncludePathOrder = parseResult.GetValue(includePathOrderOption)! + IncludePathOrder = parseResult.GetValue(includePathOrderOption)!, + ConfigPath = config?.SourcePath }; + + // Re-check option combinations against the effective (merged) values. As with the + // input check above, a validator cannot see config-supplied defaults, so a + // config-only "rich + c-cpp-properties" clash would otherwise slip through. + if (result.EmitCCppProperties && result.Format == "rich") + { + Console.Error.WriteLine("Error: --c-cpp-properties / \"emitCCppProperties\" cannot be used with the 'rich' format (rich format does not produce compile_commands.json)."); + Environment.Exit(1); + } }); var exitCode = rootCommand.Parse(args).Invoke(); @@ -322,5 +361,17 @@ private static Dictionary ParseKeyValuePairs(string[]? values, s } return dict; } + + private static Dictionary MergeKeyValues( + Dictionary? fromConfig, Dictionary fromCommandLine) + { + var merged = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (fromConfig != null) + foreach (var kvp in fromConfig) + merged[kvp.Key] = kvp.Value; + foreach (var kvp in fromCommandLine) + merged[kvp.Key] = kvp.Value; + return merged; + } } } diff --git a/Config.cs b/Config.cs new file mode 100644 index 0000000..5274e28 --- /dev/null +++ b/Config.cs @@ -0,0 +1,192 @@ +using System.Text.Json; + +namespace MSBuild.CompileCommands.Extractor +{ + /// + /// Options loaded from an optional JSON config file. Every member is nullable so an absent key + /// leaves the matching command-line default untouched. Keys bind case-insensitively, and the + /// loader tolerates // comments and trailing commas. + /// + public sealed class Config + { + /// File name looked up in the current directory when --config is not passed. + public const string DefaultFileName = "msbuild-extractor.json"; + + public string[]? Projects { get; set; } + public string[]? Solutions { get; set; } + public string? Configuration { get; set; } + public string? Platform { get; set; } + public string? Output { get; set; } + public string? Format { get; set; } + + public bool? AllConfigurations { get; set; } + public bool? Merge { get; set; } + public bool? Deduplicate { get; set; } + public string? PreferConfiguration { get; set; } + public string? PreferPlatform { get; set; } + + public bool? Strict { get; set; } + public bool? Validate { get; set; } + public bool? Logger { get; set; } + public bool? EmitCCppProperties { get; set; } + public bool? EmitDefaults { get; set; } + public bool? MergeDefaults { get; set; } + + public bool? UseDevEnv { get; set; } + public string? VsInstance { get; set; } + public string? VsPath { get; set; } + public string? MsBuildPath { get; set; } + public string? VcTargetsPath { get; set; } + public string? ClPath { get; set; } + public string? VcToolsInstallDir { get; set; } + public string? SolutionDir { get; set; } + + public string? MsBuildLauncher { get; set; } + public string? IncludePathOrder { get; set; } + + public Dictionary? MsBuildProperties { get; set; } + public Dictionary? MsBuildEnv { get; set; } + + /// The file this config was loaded from, or null when no config was applied. + public string? SourcePath { get; private set; } + + private static readonly JsonSerializerOptions SerializerOptions = new() + { + PropertyNameCaseInsensitive = true, + ReadCommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true + }; + + /// + /// Finds a config file (an explicit --config <path>, otherwise a msbuild-extractor.json in + /// the current directory) and loads it into this instance. Returns true when one was applied. + /// Exits with an error on a missing explicit path, malformed JSON, or an invalid value. + /// + public bool Load(string[] args) + { + var path = FindPath(args); + if (path == null) + return false; + + Config data; + try + { + data = JsonSerializer.Deserialize(File.ReadAllText(path), SerializerOptions) ?? new Config(); + } + catch (JsonException ex) + { + Console.Error.WriteLine($"Error: invalid config file '{path}': {ex.Message}"); + Environment.Exit(1); + return false; + } + + Projects = data.Projects; + Solutions = data.Solutions; + Configuration = data.Configuration; + Platform = data.Platform; + Output = data.Output; + Format = data.Format; + AllConfigurations = data.AllConfigurations; + Merge = data.Merge; + Deduplicate = data.Deduplicate; + PreferConfiguration = data.PreferConfiguration; + PreferPlatform = data.PreferPlatform; + Strict = data.Strict; + Validate = data.Validate; + Logger = data.Logger; + EmitCCppProperties = data.EmitCCppProperties; + EmitDefaults = data.EmitDefaults; + MergeDefaults = data.MergeDefaults; + UseDevEnv = data.UseDevEnv; + VsInstance = data.VsInstance; + VsPath = data.VsPath; + MsBuildPath = data.MsBuildPath; + VcTargetsPath = data.VcTargetsPath; + ClPath = data.ClPath; + VcToolsInstallDir = data.VcToolsInstallDir; + SolutionDir = data.SolutionDir; + MsBuildLauncher = data.MsBuildLauncher; + IncludePathOrder = data.IncludePathOrder; + MsBuildProperties = data.MsBuildProperties; + MsBuildEnv = data.MsBuildEnv; + + SourcePath = path; + ValidateValues(path); + ResolvePaths(Path.GetDirectoryName(Path.GetFullPath(path)) ?? "."); + return true; + } + + private string? FindPath(string[] args) + { + var explicitPath = GetConfigArgument(args); + if (explicitPath != null) + { + if (!File.Exists(explicitPath)) + { + Console.Error.WriteLine($"Error: config file not found: {explicitPath}"); + Environment.Exit(1); + } + return Path.GetFullPath(explicitPath); + } + + var auto = Path.Combine(Directory.GetCurrentDirectory(), DefaultFileName); + return File.Exists(auto) ? auto : null; + } + + private string? GetConfigArgument(string[] args) + { + for (int i = 0; i < args.Length; i++) + { + var arg = args[i]; + if (arg == "--config") + return i + 1 < args.Length ? args[i + 1] : null; + if (arg.StartsWith("--config=", StringComparison.Ordinal)) + return arg.Substring("--config=".Length); + } + return null; + } + + private void ValidateValues(string path) + { + void Check(string? value, string field, params string[] allowed) + { + if (value != null && !allowed.Contains(value, StringComparer.OrdinalIgnoreCase)) + { + Console.Error.WriteLine( + $"Error: config file '{path}': '{field}' must be one of [{string.Join(", ", allowed)}] but was '{value}'."); + Environment.Exit(1); + } + } + + Check(Format, "format", "standard", "rich"); + Check(MsBuildLauncher, "msBuildLauncher", "auto", "cmd", "direct", "dotnet"); + Check(IncludePathOrder, "includePathOrder", "auto", "prepend", "append"); + } + + private void ResolvePaths(string baseDir) + { + Projects = ResolveEach(Projects, baseDir, exe: false); + Solutions = ResolveEach(Solutions, baseDir, exe: false); + Output = ResolveOne(Output, baseDir, exe: false); + VsPath = ResolveOne(VsPath, baseDir, exe: false); + VcTargetsPath = ResolveOne(VcTargetsPath, baseDir, exe: false); + VcToolsInstallDir = ResolveOne(VcToolsInstallDir, baseDir, exe: false); + SolutionDir = ResolveOne(SolutionDir, baseDir, exe: false); + MsBuildPath = ResolveOne(MsBuildPath, baseDir, exe: true); + ClPath = ResolveOne(ClPath, baseDir, exe: true); + } + + private string[]? ResolveEach(string[]? values, string baseDir, bool exe) + => values?.Select(v => ResolveOne(v, baseDir, exe)!).ToArray(); + + private string? ResolveOne(string? value, string baseDir, bool exe) + { + if (string.IsNullOrEmpty(value) || Path.IsPathRooted(value)) + return value; + // Bare executable names (no directory separator) are left alone so PATH lookup still works. + if (exe && !value.Contains('/') && !value.Contains('\\')) + return value; + return Path.GetFullPath(Path.Combine(baseDir, value)); + } + } +} diff --git a/Program.cs b/Program.cs index 5f9567d..dd4c306 100644 --- a/Program.cs +++ b/Program.cs @@ -9,6 +9,9 @@ static void Main(string[] args) { var options = CommandLineOptions.Parse(args); + if (options.ConfigPath != null && !options.ListInstances) + Console.WriteLine($"Using config file: {options.ConfigPath}"); + // Handle --list-instances: print all VS installations and exit if (options.ListInstances) { From c7ff1e451ee7212a263a82611917bfd4c141f10d Mon Sep 17 00:00:00 2001 From: Garrett Campbell Date: Mon, 20 Jul 2026 11:38:43 -0400 Subject: [PATCH 2/3] update readme and add example json for a commited msbuild-extractor.json --- README.md | 59 +++++++++++++++++++++++++++++++++- msbuild-extractor.example.json | 42 ++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 msbuild-extractor.example.json diff --git a/README.md b/README.md index 817e238..30052a7 100644 --- a/README.md +++ b/README.md @@ -148,9 +148,66 @@ Options: --use-dev-env Read Developer Command Prompt environment variables --c-cpp-properties Emit .vscode/c_cpp_properties.json alongside the output --logger Enable MSBuild console logger output + --config JSON config file supplying option defaults (see below) ``` -At least one `--project` or `--solution` must be specified. Both can be repeated and combined. +At least one `--project` or `--solution` must be specified (on the command line **or** in a config file). Both can be repeated and combined. + +## Configuration file + +Options can be supplied from a JSON config file so the tool can run with no arguments: + +```bash +# Uses ./msbuild-extractor.json automatically if it exists +msbuild-extractor-sample + +# Or point at a config file explicitly +msbuild-extractor-sample --config path/to/config.json +``` + +- **Auto-detection:** when `--config` is not passed, a `msbuild-extractor.json` in the current directory is loaded automatically if present. +- **Precedence:** command-line options override the config file, which overrides the built-in defaults. +- **Inputs required:** at least one `projects` or `solutions` entry must come from the config file or the command line. +- **Relative paths** in the config file are resolved from the config file's own directory. +- The loader is lenient: keys match case-insensitively, and `//` comments and trailing commas are allowed. + +See [`msbuild-extractor.example.json`](msbuild-extractor.example.json) for a ready-to-copy example that lists every supported key set to its default. Copy it to `msbuild-extractor.json` to get started. + +### Config keys + +At least one `projects` or `solutions` entry is required. All other keys are optional; an omitted key keeps its default. + +| Key | Type | Default | Description | +| --- | --- | --- | --- | +| `projects` | string[] | `[]` | Paths to `.vcxproj` files (equivalent to repeated `--project`). | +| `solutions` | string[] | `[]` | Paths to `.sln` / `.slnx` files (equivalent to repeated `--solution`). | +| `configuration` | string | `"Debug"` | Build configuration. | +| `platform` | string | `"x64"` | Build platform. | +| `allConfigurations` | bool | `false` | Extract every configuration/platform combination. | +| `merge` | bool | `false` | With `allConfigurations`, merge into a single output file. | +| `output` | string | `null` | Output path (defaults to `compile_commands.json`). | +| `format` | string | `"standard"` | Output format: `"standard"` or `"rich"`. | +| `emitCCppProperties` | bool | `false` | Also emit `.vscode/c_cpp_properties.json` (not allowed with `"rich"`). | +| `deduplicate` | bool | `false` | Merge duplicate entries per file for IntelliSense. | +| `preferConfiguration` | string | `"Debug"` | Preferred configuration for dedup conflicts. | +| `preferPlatform` | string | `"x64"` | Preferred platform for dedup conflicts. | +| `emitDefaults` | bool | `false` | Include the synthetic `__project_defaults.cpp` entry. | +| `mergeDefaults` | bool | `false` | Merge project-wide default switches into each per-file entry. | +| `validate` | bool | `false` | Verify each entry by running `cl.exe /c`. | +| `strict` | bool | `false` | Treat configuration/platform warnings as errors. | +| `logger` | bool | `false` | Enable MSBuild console logger output. | +| `useDevEnv` | bool | `false` | Read environment from a Developer Command Prompt. | +| `vsInstance` | string | `null` | Select the VS installation by instance ID. | +| `vsPath` | string | `null` | Path to a Visual Studio installation (auto-detected). | +| `msBuildPath` | string | `null` | Path to `msbuild.exe` (enables out-of-process mode). | +| `vcTargetsPath` | string | `null` | VC targets directory (auto-detected). | +| `clPath` | string | `null` | Path to `cl.exe` (auto-resolved). | +| `vcToolsInstallDir` | string | `null` | `VCToolsInstallDir` MSBuild property (auto-detected). | +| `solutionDir` | string | `null` | `SolutionDir` MSBuild property (auto-derived with `solutions`). | +| `msBuildLauncher` | string | `"auto"` | How to launch MSBuild: `"auto"`, `"cmd"`, `"direct"`, or `"dotnet"`. | +| `includePathOrder` | string | `"auto"` | Include-path placement: `"auto"`, `"prepend"`, or `"append"`. | +| `msBuildProperties` | object | `{}` | MSBuild global properties as `KEY: VALUE` (merged with `--msbuild-property`). | +| `msBuildEnv` | object | `{}` | Environment variables for the MSBuild process as `KEY: VALUE` (merged with `--msbuild-env`). | The output includes a sentinel entry (`.msbuild-extractor-sample`) as the first element so consumers can identify the generator. Standard C++ LSP tools silently skip it since the file does not exist on disk. diff --git a/msbuild-extractor.example.json b/msbuild-extractor.example.json new file mode 100644 index 0000000..f5c764a --- /dev/null +++ b/msbuild-extractor.example.json @@ -0,0 +1,42 @@ +{ + "projects": [ + "src/app/app.vcxproj", + "src/lib/lib.vcxproj" + ], + "solutions": [ + "MySolution.slnx" + ], + "configuration": "Debug", + "platform": "x64", + "allConfigurations": false, + "merge": false, + "output": null, + "format": "standard", + "emitCCppProperties": false, + "deduplicate": false, + "preferConfiguration": "Debug", + "preferPlatform": "x64", + "emitDefaults": false, + "mergeDefaults": false, + "validate": false, + "strict": false, + "logger": false, + "useDevEnv": false, + "vsInstance": null, + "vsPath": null, + "msBuildPath": null, + "vcTargetsPath": null, + "clPath": null, + "vcToolsInstallDir": null, + "solutionDir": null, + "msBuildLauncher": "auto", + "includePathOrder": "auto", + "msBuildProperties": { + "BuildProjectReferences": "true", + "UseMultiToolTask": "false" + }, + "msBuildEnv": { + "CL": "/DDEBUG", + "TMP": "C:\\Temp" + } +} From babdf684eddce5172733b7f9660e043ef06645b0 Mon Sep 17 00:00:00 2001 From: Garrett Campbell Date: Mon, 20 Jul 2026 12:46:12 -0400 Subject: [PATCH 3/3] add back verifier for rich format --- CommandLineOptions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CommandLineOptions.cs b/CommandLineOptions.cs index 18727dc..e012e62 100644 --- a/CommandLineOptions.cs +++ b/CommandLineOptions.cs @@ -289,6 +289,9 @@ public static CommandLineOptions Parse(string[] args) if (projects.Length == 0 && solutions.Length == 0 && !hasConfigInputs) commandResult.AddError("At least one --project or --solution must be specified (via the command line or a config file)."); + + if (commandResult.GetValue(cCppPropertiesOption) && commandResult.GetValue(formatOption) == "rich") + commandResult.AddError("--c-cpp-properties cannot be used with --format rich (rich format does not produce compile_commands.json)."); }); rootCommand.SetAction(parseResult =>