From aa4436ef736dc747fe8cbe5e765dcaea3b2fbeac Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Fri, 20 Sep 2019 16:14:40 -0700 Subject: [PATCH] Optionally output packages (#201) Helps makes diagnostics for issues like #199 easier. --- .vscode/settings.json | 3 +- src/vswhere.lib/CommandArgs.cpp | 19 ++++ src/vswhere.lib/CommandArgs.h | 8 ++ src/vswhere.lib/Formatter.cpp | 85 ++++++++++++++++ src/vswhere.lib/Formatter.h | 7 ++ src/vswhere.lib/JsonFormatter.h | 5 + src/vswhere.lib/JsonScope.cpp | 15 ++- src/vswhere.lib/JsonScope.h | 5 + src/vswhere.lib/XmlFormatter.h | 5 + src/vswhere.lib/resource.h | Bin 2620 -> 2710 bytes src/vswhere.lib/vswhere.lib.rc | Bin 11406 -> 11606 bytes test/vswhere.test/JsonFormatterTests.cpp | 118 +++++++++++++++++++++ test/vswhere.test/TextFormatterTests.cpp | 89 ++++++++++++++++ test/vswhere.test/ValueFormatterTests.cpp | 88 ++++++++++++++++ test/vswhere.test/XmlFormatterTests.cpp | 119 ++++++++++++++++++++++ version.json | 2 +- 16 files changed, 562 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9e9b7d7..8a64bb9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "editor.tabSize": 4, "files.associations": { "functional": "cpp", - "string": "cpp" + "string": "cpp", + "unordered_map": "cpp" } } \ No newline at end of file diff --git a/src/vswhere.lib/CommandArgs.cpp b/src/vswhere.lib/CommandArgs.cpp index a7857a2..3beb048 100644 --- a/src/vswhere.lib/CommandArgs.cpp +++ b/src/vswhere.lib/CommandArgs.cpp @@ -132,6 +132,25 @@ void CommandArgs::Parse(_In_ vector args) m_property = ParseArgument(it, args.end(), arg); } + else if (ArgumentEquals(arg.Value, L"include")) + { + vector include; + ParseArgumentArray(it, args.end(), arg, include); + + for (const auto& value : include) + { + if (ArgumentEquals(value, L"packages")) + { + m_includePackages = true; + } + else + { + auto message = ResourceManager::FormatString(IDS_E_UNSUPPORTEDARG, value.c_str(), L"include"); + throw win32_error(ERROR_INVALID_PARAMETER, message); + } + + } + } else if (ArgumentEquals(arg.Value, L"find")) { if (m_property.length()) diff --git a/src/vswhere.lib/CommandArgs.h b/src/vswhere.lib/CommandArgs.h index eb08bb5..649bf65 100644 --- a/src/vswhere.lib/CommandArgs.h +++ b/src/vswhere.lib/CommandArgs.h @@ -16,6 +16,7 @@ class CommandArgs m_latest(false), m_legacy(false), m_prerelease(false), + m_includePackages(false), m_nologo(false), m_utf8(false), m_help(false) @@ -36,6 +37,7 @@ class CommandArgs m_prerelease(obj.m_prerelease), m_format(obj.m_format), m_property(obj.m_property), + m_includePackages(obj.m_includePackages), m_find(obj.m_find), m_nologo(obj.m_nologo), m_utf8(obj.m_utf8), @@ -118,6 +120,11 @@ class CommandArgs return m_property; } + const bool get_IncludePackages() const noexcept + { + return m_includePackages; + } + const std::wstring& get_Find() const noexcept { return m_find; @@ -163,6 +170,7 @@ class CommandArgs bool m_prerelease; std::wstring m_format; std::wstring m_property; + bool m_includePackages; std::wstring m_find; bool m_nologo; bool m_utf8; diff --git a/src/vswhere.lib/Formatter.cpp b/src/vswhere.lib/Formatter.cpp index 8f3d90c..05f8480 100644 --- a/src/vswhere.lib/Formatter.cpp +++ b/src/vswhere.lib/Formatter.cpp @@ -258,11 +258,96 @@ void Formatter::WriteInternal(_In_ const CommandArgs& args, _In_ Console& consol } } } + + if (args.get_IncludePackages() && SupportsPackages()) + { + if (specified.empty() || s_comparer(specified, L"packages")) + { + WritePackages(args, console, pInstance); + } + } } EndObject(console); } +void Formatter::WritePackage(_In_ Console& console, _In_ ISetupPackageReference* pPackage) +{ + StartObject(console, L"package"); + + bstr_t bstr; + auto hr = pPackage->GetId(bstr.GetAddress()); + if (SUCCEEDED(hr)) + { + WriteProperty(console, L"id", bstr); + } + + hr = pPackage->GetVersion(bstr.GetAddress()); + if (SUCCEEDED(hr) && bstr.length()) + { + WriteProperty(console, L"version", bstr); + } + + hr = pPackage->GetChip(bstr.GetAddress()); + if (SUCCEEDED(hr) && bstr.length()) + { + WriteProperty(console, L"chip", bstr); + } + + hr = pPackage->GetLanguage(bstr.GetAddress()); + if (SUCCEEDED(hr) && bstr.length()) + { + WriteProperty(console, L"language", bstr); + } + + hr = pPackage->GetBranch(bstr.GetAddress()); + if (SUCCEEDED(hr) && bstr.length()) + { + WriteProperty(console, L"branch", bstr); + } + + hr = pPackage->GetType(bstr.GetAddress()); + if (SUCCEEDED(hr)) + { + WriteProperty(console, L"type", bstr); + } + + VARIANT_BOOL vtBool; + hr = pPackage->GetIsExtension(&vtBool); + if (SUCCEEDED(hr) && VARIANT_TRUE == vtBool) + { + WriteProperty(console, L"extension", true); + } + + EndObject(console); +} + +void Formatter::WritePackages(_In_ const CommandArgs& args, _In_ Console& console, _In_ ISetupInstance* pInstance) +{ + ISetupInstance2Ptr instance2; + LPSAFEARRAY psaPackages; + + auto hr = pInstance->QueryInterface(&instance2); + if (SUCCEEDED(hr)) + { + hr = instance2->GetPackages(&psaPackages); + if (SUCCEEDED(hr) && psaPackages->rgsabound[0].cElements) + { + StartArray(console, L"packages"); + + SafeArray saPackages(psaPackages); + const auto packages = saPackages.Elements(); + + for (const auto& package : packages) + { + WritePackage(console, package); + } + + EndArray(console); + } + } +} + void Formatter::WriteProperty(_In_ Console& console, _In_ const wstring& name, _In_ const variant_t& value) { switch (value.vt) diff --git a/src/vswhere.lib/Formatter.h b/src/vswhere.lib/Formatter.h index 825737f..cd8afdf 100644 --- a/src/vswhere.lib/Formatter.h +++ b/src/vswhere.lib/Formatter.h @@ -29,6 +29,11 @@ class Formatter return true; } + virtual bool SupportsPackages() const + { + return false; + } + protected: typedef std::function PropertyFunction; typedef std::vector> PropertyArray; @@ -82,6 +87,8 @@ class Formatter HRESULT GetDescription(_In_ ISetupInstance* pInstance, _Out_ VARIANT* pvtDescription); void WriteInternal(_In_ const CommandArgs& args, _In_ Console& console, _In_ ISetupInstance* pInstance); + void WritePackage(_In_ Console& console, _In_ ISetupPackageReference* pPackage); + void WritePackages(_In_ const CommandArgs& args, _In_ Console& console, _In_ ISetupInstance* pInstance); void WriteProperty(_In_ Console& console, _In_ const std::wstring& name, _In_ const variant_t& value); bool WriteProperties(_In_ const CommandArgs& args, _In_ Console& console, _In_ ISetupInstance* pInstance); bool WriteProperties(_In_ const CommandArgs& args, _In_ Console& console, _In_ ISetupPropertyStore* pProperties, _In_opt_ const std::wstring& prefix = empty_wstring); diff --git a/src/vswhere.lib/JsonFormatter.h b/src/vswhere.lib/JsonFormatter.h index af453a6..893bbf5 100644 --- a/src/vswhere.lib/JsonFormatter.h +++ b/src/vswhere.lib/JsonFormatter.h @@ -33,6 +33,11 @@ class JsonFormatter : return false; } + bool SupportsPackages() const override + { + return true; + } + protected: void StartArray(_In_ Console& console, _In_opt_ const std::wstring& name = empty_wstring) override; void StartObject(_In_ Console& console, _In_opt_ const std::wstring& name = empty_wstring) override; diff --git a/src/vswhere.lib/JsonScope.cpp b/src/vswhere.lib/JsonScope.cpp index 898058e..fb987a5 100644 --- a/src/vswhere.lib/JsonScope.cpp +++ b/src/vswhere.lib/JsonScope.cpp @@ -23,21 +23,28 @@ void JsonScope::StartProperty(_In_ Console& console) void JsonScope::WriteStartImpl(_In_ Console& console) { + bool writeKey = false; + WriteSeparator(console); - // Write new line if not the root scope. if (Parent()) { + if (Parent()->IsObject()) + { + writeKey = true; + } + + // Write new line if not the root scope. console.WriteLine(); } - if (m_type == Type::array || Name().empty()) + if (writeKey && Name().length()) { - console.Write(L"%ls%lc", Padding().c_str(), StartChar()); + console.Write(L"%ls\"%ls\": %lc", Padding().c_str(), Name().c_str(), StartChar()); } else { - console.Write(L"%ls\"%ls\": %lc", Padding().c_str(), Name().c_str(), StartChar()); + console.Write(L"%ls%lc", Padding().c_str(), StartChar()); } } diff --git a/src/vswhere.lib/JsonScope.h b/src/vswhere.lib/JsonScope.h index 4fa83b8..66f88cb 100644 --- a/src/vswhere.lib/JsonScope.h +++ b/src/vswhere.lib/JsonScope.h @@ -25,6 +25,11 @@ class JsonScope : { } + bool IsArray() const noexcept + { + return m_type == Type::array; + } + bool IsObject() const noexcept { return m_type == Type::object; diff --git a/src/vswhere.lib/XmlFormatter.h b/src/vswhere.lib/XmlFormatter.h index d39ebf5..7157903 100644 --- a/src/vswhere.lib/XmlFormatter.h +++ b/src/vswhere.lib/XmlFormatter.h @@ -31,6 +31,11 @@ class XmlFormatter : return false; } + bool SupportsPackages() const override + { + return true; + } + protected: void StartDocument(_In_ Console& console) override; void StartArray(_In_ Console& console, _In_opt_ const std::wstring& name = empty_wstring) override; diff --git a/src/vswhere.lib/resource.h b/src/vswhere.lib/resource.h index 5aba0a7ac962fdf06697337f018602d7a9e92b90..64a48a823c56a5d4975c2e453565fbaf2d2a4e86 100644 GIT binary patch delta 62 zcmdlZGEH>D36{z87}>;w8A2HXfY6^Ih#`c*mBEF<5y*0%Y{()znTby??!8avql>rV+;SDisGy1yS{bJ}LkWjCV05HDXVy|R+@pm7 zx@e$>KzT-Xvk8w?8+}N(7WM-6rM5~dU5X#u<%Fg6$W*%q8F&iPuSC9E!op!#b z)ShJ|OXEh1y)l zSFhHB+F#moD?Fr6lP}3%>LEjq2MX79=mjIE%D--@?NL+WNL>(;4f)&7e?9tomm3At M+P1R#x9~ju2avd4EC2ui delta 340 zcmcZ>)fc(JiqA5D!I2?^!Gpn*!H>b2!JomG0V3kb;Kbm=;L4x?gi1ga84RglmJ&k& zLn1>7L&ju99$({p5DO?%1e7ZV(m6n!3>3>`$Y;m{ihEOBA_uKL(+jX0=hG#PJSpU&#u6b2DGYZ@?Rxsb(nfR zpgKRGXbF%`2GY4uCucGwfgCvbpt!g;erM+cb-+BKH+i9;IBzMCU%&t&6&Ug-Zx@u` Zd_Zy< packages1 = { &a1, &b1 }; + + TestInstance::MapType properties1 = + { + { L"InstanceId", L"a1b2c3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance1(&product1, packages1, properties1); + + TestPackageReference product2 = + { + { L"Id", L"Microsoft.VisualStudio.Product.Enterprise" }, + }; + + TestPackageReference a2 = + { + { L"Id", L"A" }, + { L"Version", L"1.0" }, + { L"Type", L"Workload"}, + }; + + TestPackageReference b2 = + { + { L"Id", L"B" }, + { L"Version", L"1.0" }, + { L"Type", L"Component"}, + }; + + vector packages2 = { &a2, &b2 }; + + TestInstance::MapType properties2 = + { + { L"InstanceId", L"b1c2d3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance2(&product2, packages2, properties2); + + vector instances = + { + &instance1, + &instance2, + }; + + CommandArgs args; + args.Parse(L"vswhere.exe -include packages"); + + TestConsole console(args); + + JsonFormatter sut; + sut.Write(args, console, instances); + + auto expected = + L"[\n" + L" {\n" + L" \"instanceId\": \"a1b2c3\",\n" + L" \"installationName\": \"test\",\n" + L" \"productId\": \"Microsoft.VisualStudio.Product.Community\",\n" + L" \"packages\": [\n" + L" {\n" + L" \"id\": \"A\",\n" + L" \"version\": \"1.0\",\n" + L" \"type\": \"Workload\"\n" + L" },\n" + L" {\n" + L" \"id\": \"B\",\n" + L" \"version\": \"1.0\",\n" + L" \"type\": \"Component\"\n" + L" }\n" + L" ]\n" + L" },\n" + L" {\n" + L" \"instanceId\": \"b1c2d3\",\n" + L" \"installationName\": \"test\",\n" + L" \"productId\": \"Microsoft.VisualStudio.Product.Enterprise\",\n" + L" \"packages\": [\n" + L" {\n" + L" \"id\": \"A\",\n" + L" \"version\": \"1.0\",\n" + L" \"type\": \"Workload\"\n" + L" },\n" + L" {\n" + L" \"id\": \"B\",\n" + L" \"version\": \"1.0\",\n" + L" \"type\": \"Component\"\n" + L" }\n" + L" ]\n" + L" }\n" + L"]\n"; + + Assert::AreEqual(expected, console); + } }; diff --git a/test/vswhere.test/TextFormatterTests.cpp b/test/vswhere.test/TextFormatterTests.cpp index 4c070c2..b1ecc09 100644 --- a/test/vswhere.test/TextFormatterTests.cpp +++ b/test/vswhere.test/TextFormatterTests.cpp @@ -482,4 +482,93 @@ TEST_CLASS(TextFormatterTests) Assert::AreEqual(expected, console); } + + BEGIN_TEST_METHOD_ATTRIBUTE(Include_Packages) + TEST_WORKITEM(199) + END_TEST_METHOD_ATTRIBUTE() + TEST_METHOD(Include_Packages) + { + TestPackageReference product1 = + { + { L"Id", L"Microsoft.VisualStudio.Product.Community" }, + }; + + TestPackageReference a1 = + { + { L"Id", L"A" }, + { L"Version", L"1.0" }, + { L"Type", L"Workload"}, + }; + + TestPackageReference b1 = + { + { L"Id", L"B" }, + { L"Version", L"1.0" }, + { L"Type", L"Component"}, + }; + + vector packages1 = { &a1, &b1 }; + + TestInstance::MapType properties1 = + { + { L"InstanceId", L"a1b2c3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance1(&product1, packages1, properties1); + + TestPackageReference product2 = + { + { L"Id", L"Microsoft.VisualStudio.Product.Enterprise" }, + }; + + TestPackageReference a2 = + { + { L"Id", L"A" }, + { L"Version", L"1.0" }, + { L"Type", L"Workload"}, + }; + + TestPackageReference b2 = + { + { L"Id", L"B" }, + { L"Version", L"1.0" }, + { L"Type", L"Component"}, + }; + + vector packages2 = { &a2, &b2 }; + + TestInstance::MapType properties2 = + { + { L"InstanceId", L"b1c2d3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance2(&product2, packages2, properties2); + + vector instances = + { + &instance1, + &instance2, + }; + + CommandArgs args; + args.Parse(L"vswhere.exe -include packages"); + + TestConsole console(args); + + TextFormatter sut; + sut.Write(args, console, instances); + + auto expected = + L"instanceId: a1b2c3\n" + L"installationName: test\n" + L"productId: Microsoft.VisualStudio.Product.Community\n" + L"\n" + L"instanceId: b1c2d3\n" + L"installationName: test\n" + L"productId: Microsoft.VisualStudio.Product.Enterprise\n"; + + Assert::AreEqual(expected, console); + } }; diff --git a/test/vswhere.test/ValueFormatterTests.cpp b/test/vswhere.test/ValueFormatterTests.cpp index 5044dcc..33875ab 100644 --- a/test/vswhere.test/ValueFormatterTests.cpp +++ b/test/vswhere.test/ValueFormatterTests.cpp @@ -456,4 +456,92 @@ TEST_CLASS(ValueFormatterTests) Assert::AreEqual(expected, console); } + + BEGIN_TEST_METHOD_ATTRIBUTE(Include_Packages) + TEST_WORKITEM(199) + END_TEST_METHOD_ATTRIBUTE() + TEST_METHOD(Include_Packages) + { + TestPackageReference product1 = + { + { L"Id", L"Microsoft.VisualStudio.Product.Community" }, + }; + + TestPackageReference a1 = + { + { L"Id", L"A" }, + { L"Version", L"1.0" }, + { L"Type", L"Workload"}, + }; + + TestPackageReference b1 = + { + { L"Id", L"B" }, + { L"Version", L"1.0" }, + { L"Type", L"Component"}, + }; + + vector packages1 = { &a1, &b1 }; + + TestInstance::MapType properties1 = + { + { L"InstanceId", L"a1b2c3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance1(&product1, packages1, properties1); + + TestPackageReference product2 = + { + { L"Id", L"Microsoft.VisualStudio.Product.Enterprise" }, + }; + + TestPackageReference a2 = + { + { L"Id", L"A" }, + { L"Version", L"1.0" }, + { L"Type", L"Workload"}, + }; + + TestPackageReference b2 = + { + { L"Id", L"B" }, + { L"Version", L"1.0" }, + { L"Type", L"Component"}, + }; + + vector packages2 = { &a2, &b2 }; + + TestInstance::MapType properties2 = + { + { L"InstanceId", L"b1c2d3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance2(&product2, packages2, properties2); + + vector instances = + { + &instance1, + &instance2, + }; + + CommandArgs args; + args.Parse(L"vswhere.exe -include packages"); + + TestConsole console(args); + + ValueFormatter sut; + sut.Write(args, console, instances); + + auto expected = + L"a1b2c3\n" + L"test\n" + L"Microsoft.VisualStudio.Product.Community\n" + L"b1c2d3\n" + L"test\n" + L"Microsoft.VisualStudio.Product.Enterprise\n"; + + Assert::AreEqual(expected, console); + } }; diff --git a/test/vswhere.test/XmlFormatterTests.cpp b/test/vswhere.test/XmlFormatterTests.cpp index 8fd572d..9b68f0c 100644 --- a/test/vswhere.test/XmlFormatterTests.cpp +++ b/test/vswhere.test/XmlFormatterTests.cpp @@ -523,4 +523,123 @@ TEST_CLASS(XmlFormatterTests) Assert::AreEqual(expected, console); } + + BEGIN_TEST_METHOD_ATTRIBUTE(Include_Packages) + TEST_WORKITEM(199) + END_TEST_METHOD_ATTRIBUTE() + TEST_METHOD(Include_Packages) + { + TestPackageReference product1 = + { + { L"Id", L"Microsoft.VisualStudio.Product.Community" }, + }; + + TestPackageReference a1 = + { + { L"Id", L"A" }, + { L"Version", L"1.0" }, + { L"Type", L"Workload"}, + }; + + TestPackageReference b1 = + { + { L"Id", L"B" }, + { L"Version", L"1.0" }, + { L"Type", L"Component"}, + }; + + vector packages1 = { &a1, &b1 }; + + TestInstance::MapType properties1 = + { + { L"InstanceId", L"a1b2c3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance1(&product1, packages1, properties1); + + TestPackageReference product2 = + { + { L"Id", L"Microsoft.VisualStudio.Product.Enterprise" }, + }; + + TestPackageReference a2 = + { + { L"Id", L"A" }, + { L"Version", L"1.0" }, + { L"Type", L"Workload"}, + }; + + TestPackageReference b2 = + { + { L"Id", L"B" }, + { L"Version", L"1.0" }, + { L"Type", L"Component"}, + }; + + vector packages2 = { &a2, &b2 }; + + TestInstance::MapType properties2 = + { + { L"InstanceId", L"b1c2d3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance2(&product2, packages2, properties2); + + vector instances = + { + &instance1, + &instance2, + }; + + CommandArgs args; + args.Parse(L"vswhere.exe -include packages"); + + TestConsole console(args); + + XmlFormatter sut; + sut.Write(args, console, instances); + + auto expected = + L"\n" + L"\n" + L" \n" + L" a1b2c3\n" + L" test\n" + L" Microsoft.VisualStudio.Product.Community\n" + L" \n" + L" \n" + L" A\n" + L" 1.0\n" + L" Workload\n" + L" \n" + L" \n" + L" B\n" + L" 1.0\n" + L" Component\n" + L" \n" + L" \n" + L" \n" + L" \n" + L" b1c2d3\n" + L" test\n" + L" Microsoft.VisualStudio.Product.Enterprise\n" + L" \n" + L" \n" + L" A\n" + L" 1.0\n" + L" Workload\n" + L" \n" + L" \n" + L" B\n" + L" 1.0\n" + L" Component\n" + L" \n" + L" \n" + L" \n" + L"\n"; + + Assert::AreEqual(expected, console); + } }; diff --git a/version.json b/version.json index 0ea055d..728494a 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "2.7", + "version": "2.8", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/tags/v\\d+\\.\\d+"