Skip to content

Commit 565344c

Browse files
authored
Place DSCv3 resource manifests in the package (#5433)
## Change Adds a method to write all manifests to a directory with one command. Uses that to write all DSCv3 resource manifests and add them to the package files. Also changes the "module" name for the dev package to add ".Dev" and updated export to use the shared definition of the module name. This involved a minor refactor to remove static globals and unnecessary string constructions. Finally, moves to using the DSC v3 `UserSettingsFile` resource rather than the v2 version.
1 parent bec3c8b commit 565344c

18 files changed

Lines changed: 159 additions & 46 deletions

src/AppInstallerCLI.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,7 @@ Global
11651165
CertificateResources\CertificateResources.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4
11661166
COMServer\COMServer.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4
11671167
ManifestSchema\ManifestSchema.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4
1168+
PureLib\PureLib.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4
11681169
binver\binver.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4
11691170
CertificateResources\CertificateResources.vcxitems*{5eb88068-5fb9-4e69-89b2-72dbc5e068f9}*SharedItemsImports = 4
11701171
binver\binver.vcxitems*{6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}*SharedItemsImports = 9

src/AppInstallerCLICore/Command.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ namespace AppInstaller::CLI
5959
Command(name, aliases, parent, visibility, Settings::ExperimentalFeature::Feature::None) {}
6060
Command(std::string_view name, std::string_view parent, Settings::ExperimentalFeature::Feature feature) :
6161
Command(name, {}, parent, Command::Visibility::Show, feature) {}
62+
Command(std::string_view name, std::string_view parent, Settings::ExperimentalFeature::Feature feature, CommandOutputFlags outputFlags) :
63+
Command(name, {}, parent, Command::Visibility::Show, feature, Settings::TogglePolicy::Policy::None, outputFlags) {}
6264
Command(std::string_view name, std::vector<std::string_view> aliases, std::string_view parent, Settings::ExperimentalFeature::Feature feature) :
6365
Command(name, aliases, parent, Command::Visibility::Show, feature) {}
6466
Command(std::string_view name, std::vector<std::string_view> aliases, std::string_view parent, Settings::TogglePolicy::Policy groupPolicy) :

src/AppInstallerCLICore/Commands/DscCommand.cpp

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,22 @@
1313

1414
namespace AppInstaller::CLI
1515
{
16+
namespace
17+
{
18+
Argument GetOutputFileArgument()
19+
{
20+
return { Execution::Args::Type::OutputFile, Resource::String::OutputDirectoryArgumentDescription, ArgumentType::Standard };
21+
}
22+
}
23+
24+
DscCommand::DscCommand(std::string_view parent) :
25+
Command(StaticName(), parent)
26+
{
27+
}
28+
1629
std::vector<std::unique_ptr<Command>> DscCommand::GetCommands() const
1730
{
31+
// These should all derive from DscCommandBase
1832
return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>({
1933
std::make_unique<DscPackageResource>(FullName()),
2034
std::make_unique<DscSourceResource>(FullName()),
@@ -26,6 +40,16 @@ namespace AppInstaller::CLI
2640
});
2741
}
2842

43+
std::vector<Argument> DscCommand::GetArguments() const
44+
{
45+
std::vector<Argument> result;
46+
47+
result.emplace_back(Execution::Args::Type::DscResourceFunctionManifest, Resource::String::DscResourceFunctionDescriptionManifest, ArgumentType::Flag);
48+
result.emplace_back(GetOutputFileArgument());
49+
50+
return result;
51+
}
52+
2953
Resource::LocString DscCommand::ShortDescription() const
3054
{
3155
return { Resource::String::DscCommandShortDescription };
@@ -43,6 +67,34 @@ namespace AppInstaller::CLI
4367

4468
void DscCommand::ExecuteInternal(Execution::Context& context) const
4569
{
46-
OutputHelp(context.Reporter);
70+
if (context.Args.Contains(Execution::Args::Type::DscResourceFunctionManifest))
71+
{
72+
std::filesystem::path outputDirectory{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) };
73+
std::filesystem::create_directories(outputDirectory);
74+
75+
std::string filePrefix = Utility::ToLower(DscCommandBase::ModuleName());
76+
77+
for (const auto& command : GetCommands())
78+
{
79+
DscCommandBase* commandBase = static_cast<DscCommandBase*>(command.get());
80+
81+
std::filesystem::path outputPath = outputDirectory;
82+
outputPath /= std::string{ filePrefix }.append(".").append(commandBase->Name()).append(".dsc.resource.json");
83+
commandBase->WriteManifest(context, outputPath);
84+
}
85+
}
86+
else
87+
{
88+
OutputHelp(context.Reporter);
89+
}
90+
}
91+
92+
void DscCommand::ValidateArgumentsInternal(Execution::Args& args) const
93+
{
94+
if (args.Contains(Execution::Args::Type::DscResourceFunctionManifest) &&
95+
!args.Contains(Execution::Args::Type::OutputFile))
96+
{
97+
throw CommandException(Resource::String::RequiredArgError(GetOutputFileArgument().Name()));
98+
}
4799
}
48100
}

src/AppInstallerCLICore/Commands/DscCommand.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ namespace AppInstaller::CLI
88
{
99
struct DscCommand final : public Command
1010
{
11-
DscCommand(std::string_view parent) : Command(StaticName(), parent, Settings::ExperimentalFeature::Feature::ConfigurationDSCv3) {}
11+
DscCommand(std::string_view parent);
1212

1313
static constexpr std::string_view StaticName() { return "dscv3"sv; };
1414

1515
std::vector<std::unique_ptr<Command>> GetCommands() const override;
16+
std::vector<Argument> GetArguments() const override;
1617

1718
Resource::LocString ShortDescription() const override;
1819
Resource::LocString LongDescription() const override;
@@ -21,5 +22,6 @@ namespace AppInstaller::CLI
2122

2223
protected:
2324
void ExecuteInternal(Execution::Context& context) const override;
25+
void ValidateArgumentsInternal(Execution::Args& args) const override;
2426
};
2527
}

src/AppInstallerCLICore/Commands/DscCommandBase.cpp

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ namespace AppInstaller::CLI
2222
{
2323
namespace
2424
{
25-
constexpr std::string_view s_WingetModuleName = "Microsoft.WinGet"sv;
26-
2725
std::string GetFunctionManifestString(DscFunctions function)
2826
{
2927
THROW_HR_IF(E_INVALIDARG, !WI_IsSingleFlagSet(function));
@@ -167,7 +165,7 @@ namespace AppInstaller::CLI
167165
}
168166

169167
DscCommandBase::DscCommandBase(std::string_view parent, std::string_view resourceName, DscResourceKind kind, DscFunctions functions, DscFunctionModifiers modifiers) :
170-
Command(resourceName, parent, CommandOutputFlags::IgnoreSettingsWarnings), m_kind(kind), m_functions(functions), m_modifiers(modifiers)
168+
Command(resourceName, parent, Settings::ExperimentalFeature::Feature::ConfigurationDSCv3, CommandOutputFlags::IgnoreSettingsWarnings), m_kind(kind), m_functions(functions), m_modifiers(modifiers)
171169
{
172170
// Limits on current implementation
173171
THROW_HR_IF(E_NOTIMPL, kind != DscResourceKind::Resource);
@@ -224,14 +222,14 @@ namespace AppInstaller::CLI
224222

225223
WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_METHOD);
226224

227-
void DscCommandBase::ResourceFunctionManifest(Execution::Context& context) const
225+
void DscCommandBase::WriteManifest(Execution::Context& context, const std::filesystem::path& filePath) const
228226
{
229227
Json::Value json{ Json::ValueType::objectValue };
230228

231229
// TODO: Move to release schema when released (there should be an aka.ms link as well, but it wasn't active yet)
232230
//json["$schema"] = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/bundled/resource/manifest.json";
233231
json["$schema"] = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json";
234-
json["type"] = std::string{ s_WingetModuleName } + '/' + ResourceType();
232+
json["type"] = std::string{ ModuleName() } + '/' + ResourceType();
235233
json["description"] = LongDescription().get();
236234
json["version"] = Runtime::GetClientVersion().get();
237235

@@ -253,9 +251,9 @@ namespace AppInstaller::CLI
253251
writerBuilder.settings_["indentation"] = " ";
254252
std::string jsonString = Json::writeString(writerBuilder, json);
255253

256-
if (context.Args.Contains(Execution::Args::Type::OutputFile))
254+
if (!filePath.empty())
257255
{
258-
std::ofstream stream{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)), std::ios::binary };
256+
std::ofstream stream{ filePath, std::ios::binary };
259257
stream.write(jsonString.c_str(), jsonString.length());
260258
}
261259
else
@@ -264,6 +262,16 @@ namespace AppInstaller::CLI
264262
}
265263
}
266264

265+
void DscCommandBase::ResourceFunctionManifest(Execution::Context& context) const
266+
{
267+
std::filesystem::path path;
268+
if (context.Args.Contains(Execution::Args::Type::OutputFile))
269+
{
270+
path = std::filesystem::path{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) };
271+
}
272+
WriteManifest(context, path);
273+
}
274+
267275
#undef WINGET_DSC_FUNCTION_METHOD
268276

269277
std::optional<Json::Value> DscCommandBase::GetJsonFromInput(Execution::Context& context, bool terminateContextOnError) const

src/AppInstallerCLICore/Commands/DscCommandBase.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
#include <json/json.h>
66
#include <optional>
77

8+
#ifndef AICLI_DISABLE_TEST_HOOKS
9+
#define WINGET_DSCV3_MODULE_NAME "Microsoft.WinGet.Dev"
10+
#define WINGET_DSCV3_MODULE_NAME_WIDE L"Microsoft.WinGet.Dev"
11+
#else
12+
#define WINGET_DSCV3_MODULE_NAME "Microsoft.WinGet"
13+
#define WINGET_DSCV3_MODULE_NAME_WIDE L"Microsoft.WinGet"
14+
#endif
15+
816
namespace AppInstaller::CLI
917
{
1018
// The kind of resource that this command is implementing.
@@ -75,6 +83,15 @@ namespace AppInstaller::CLI
7583

7684
Utility::LocIndView HelpLink() const override;
7785

86+
static constexpr std::string_view ModuleName()
87+
{
88+
return WINGET_DSCV3_MODULE_NAME;
89+
}
90+
91+
// Writes the manifest for the command to the file path.
92+
// If the path is empty, writes the manifest to the output stream.
93+
void WriteManifest(Execution::Context& context, const std::filesystem::path& filePath) const;
94+
7895
protected:
7996
void ExecuteInternal(Execution::Context& context) const override;
8097

src/AppInstallerCLICore/Resources.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ namespace AppInstaller::CLI::Resource
483483
WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoMatchHelp);
484484
WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoSourceDefined);
485485
WINGET_DEFINE_RESOURCE_STRINGID(Options);
486+
WINGET_DEFINE_RESOURCE_STRINGID(OutputDirectoryArgumentDescription);
486487
WINGET_DEFINE_RESOURCE_STRINGID(OutputFileArgumentDescription);
487488
WINGET_DEFINE_RESOURCE_STRINGID(OverrideArgumentDescription);
488489
WINGET_DEFINE_RESOURCE_STRINGID(OverwritingExistingFileAtMessage);

src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "Public/ConfigurationSetProcessorFactoryRemoting.h"
99
#include "ConfigurationCommon.h"
1010
#include "ConfigurationWingetDscModuleUnitValidation.h"
11+
#include "Commands/DscCommandBase.h"
1112
#include <AppInstallerDateTime.h>
1213
#include <AppInstallerDownloader.h>
1314
#include <AppInstallerErrors.h>
@@ -47,8 +48,9 @@ namespace AppInstaller::CLI::Workflow
4748
constexpr std::wstring_view s_Unit_WinGetPackage = L"WinGetPackage";
4849
constexpr std::wstring_view s_Unit_WinGetSource = L"WinGetSource";
4950

50-
constexpr std::wstring_view s_UnitType_WinGetPackage_DSCv3 = L"Microsoft.WinGet/Package";
51-
constexpr std::wstring_view s_UnitType_WinGetSource_DSCv3 = L"Microsoft.WinGet/Source";
51+
constexpr std::wstring_view s_UnitType_WinGetPackage_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/Package";
52+
constexpr std::wstring_view s_UnitType_WinGetSource_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/Source";
53+
constexpr std::wstring_view s_UnitType_WinGetUserSettingsFile_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/UserSettingsFile";
5254
constexpr std::wstring_view s_UnitType_PowerShellModuleGet = L"PowerShellGet/PSModule";
5355

5456
constexpr std::wstring_view s_Module_WinGetClient = L"Microsoft.WinGet.DSC";
@@ -66,19 +68,33 @@ namespace AppInstaller::CLI::Workflow
6668
struct PredefinedResource
6769
{
6870
// RequiredModule could be empty, meaning no required modules needed.
69-
std::wstring RequiredModule;
71+
std::wstring_view RequiredModule;
7072

71-
std::vector<std::wstring> UnitTypes;
73+
std::vector<std::wstring_view> UnitTypes;
7274
};
7375

74-
static const PredefinedResource s_PredefinedResourcesForExport[] = {
75-
{ std::wstring{ s_Module_WinGetClient }, { L"Microsoft.WinGet.DSC/WinGetUserSettings" } },
76-
{ L"Microsoft.Windows.Developer", { L"Microsoft.Windows.Developer/DeveloperMode", L"Microsoft.Windows.Developer/EnableDarkMode", L"Microsoft.Windows.Developer/ShowSecondsInClock", L"Microsoft.Windows.Developer/Taskbar", L"Microsoft.Windows.Developer/WindowsExplorer" }},
77-
};
76+
std::vector<PredefinedResource> PredefinedResourcesForExport()
77+
{
78+
return {
79+
{ {}, { s_UnitType_WinGetUserSettingsFile_DSCv3 } },
80+
{ L"Microsoft.Windows.Developer", { L"Microsoft.Windows.Developer/DeveloperMode", L"Microsoft.Windows.Developer/EnableDarkMode", L"Microsoft.Windows.Developer/ShowSecondsInClock", L"Microsoft.Windows.Developer/Taskbar", L"Microsoft.Windows.Developer/WindowsExplorer" } },
81+
};
82+
}
7883

79-
static const std::wstring s_PackageSettingsExclusionList[] = {
80-
L"Microsoft.WinGet/", L"Microsoft.DSC.Debug/", L"Microsoft.DSC/", L"Microsoft.DSC.Transitional/", L"Microsoft.Windows/RebootPending",
81-
L"Microsoft.Windows/Registry", L"Microsoft.Windows/WMI", L"Microsoft.Windows/WindowsPowerShell", L"Microsoft/OSInfo"
84+
std::vector<std::wstring_view> PackageSettingsExclusionList()
85+
{
86+
return {
87+
L"Microsoft.WinGet/",
88+
L"Microsoft.WinGet.Dev/",
89+
L"Microsoft.DSC.Debug/",
90+
L"Microsoft.DSC/",
91+
L"Microsoft.DSC.Transitional/",
92+
L"Microsoft.Windows/RebootPending",
93+
L"Microsoft.Windows/Registry",
94+
L"Microsoft.Windows/WMI",
95+
L"Microsoft.Windows/WindowsPowerShell",
96+
L"Microsoft/OSInfo"
97+
};
8298
};
8399

84100
Logging::Level ConvertLevel(DiagnosticLevel level)
@@ -1475,7 +1491,7 @@ namespace AppInstaller::CLI::Workflow
14751491
{
14761492
ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
14771493

1478-
for (const auto& resources : s_PredefinedResourcesForExport)
1494+
for (const auto& resources : PredefinedResourcesForExport())
14791495
{
14801496
std::optional<ConfigurationUnit> requiredModuleUnit;
14811497

@@ -1538,11 +1554,13 @@ namespace AppInstaller::CLI::Workflow
15381554
context.Reporter.Warn() << Resource::String::ConfigurationExportFailedToGetUnitProcessors << std::endl;
15391555
}
15401556

1557+
auto exclusionList = PackageSettingsExclusionList();
1558+
15411559
// Filter out processors in exclusion list.
15421560
for (auto itr = unitProcessors.begin(); itr != unitProcessors.end(); /* itr incremented in the logic */)
15431561
{
15441562
bool processorRemoved = false;
1545-
for (const auto& exclusionItem : anon::s_PackageSettingsExclusionList)
1563+
for (const auto& exclusionItem : exclusionList)
15461564
{
15471565
if (Utility::CaseInsensitiveStartsWith(itr->UnitType(), exclusionItem))
15481566
{

src/AppInstallerCLIE2ETests/ConfigureCommand.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ public class ConfigureCommand
2323
/// </summary>
2424
public static void EnsureTestResourcePresence()
2525
{
26-
DSCv3ResourceTestBase.EnsureTestResourcePresence("test-file");
27-
DSCv3ResourceTestBase.EnsureTestResourcePresence("test-json");
26+
DSCv3ResourceTestBase.EnsureTestResourcePresence();
2827
}
2928

3029
/// <summary>
@@ -330,12 +329,12 @@ public void DSCv3_Export()
330329
var exportDir = TestCommon.GetRandomTestDir();
331330
var exportFile = Path.Combine(exportDir, "exported.yml");
332331

333-
result = TestCommon.RunAICLICommand("test config-export-units", $"-o {exportFile} --resource Microsoft.WinGet/TestJSON --verbose");
332+
result = TestCommon.RunAICLICommand("test config-export-units", $"-o {exportFile} --resource Microsoft.WinGet.Dev/TestJSON --verbose");
334333
Assert.AreEqual(0, result.ExitCode);
335334

336335
Assert.True(File.Exists(exportFile));
337336
string exportText = File.ReadAllText(exportFile);
338-
Assert.True(exportText.Contains("Microsoft.WinGet/TestJSON"));
337+
Assert.True(exportText.Contains("Microsoft.WinGet.Dev/TestJSON"));
339338
Assert.True(exportText.Contains(propertyName1));
340339
Assert.True(exportText.Contains(propertyName2));
341340
Assert.True(exportText.Contains(propertyValue1));

0 commit comments

Comments
 (0)