Skip to content

Commit 9659f5a

Browse files
authored
Add dependencies only option (microsoft#6069)
## Change Adds an option that only installs dependencies (command line only currently).
1 parent 62aad2e commit 9659f5a

File tree

10 files changed

+92
-5
lines changed

10 files changed

+92
-5
lines changed

src/AppInstallerCLICore/Argument.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ namespace AppInstaller::CLI
9292
case Execution::Args::Type::NoUpgrade:
9393
return { type, "no-upgrade"_liv, ArgTypeCategory::CopyFlagToSubContext };
9494
case Execution::Args::Type::SkipDependencies:
95-
return { type, "skip-dependencies"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext };
95+
return { type, "skip-dependencies"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::DependenciesConflict };
96+
case Execution::Args::Type::DependenciesOnly:
97+
return { type, "dependencies-only"_liv, ArgTypeCategory::InstallerBehavior, ArgTypeExclusiveSet::DependenciesConflict };
9698
case Execution::Args::Type::AllowReboot:
9799
return { type, "allow-reboot"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext };
98100

@@ -408,6 +410,8 @@ namespace AppInstaller::CLI
408410
return Argument{ type, Resource::String::HelpArgumentDescription, ArgumentType::Flag };
409411
case Args::Type::SkipDependencies:
410412
return Argument{ type, Resource::String::SkipDependenciesArgumentDescription, ArgumentType::Flag, false };
413+
case Args::Type::DependenciesOnly:
414+
return Argument{ type, Resource::String::DependenciesOnlyArgumentDescription, ArgumentType::Flag, false };
411415
case Args::Type::IgnoreLocalArchiveMalwareScan:
412416
return Argument{ type, Resource::String::IgnoreLocalArchiveMalwareScanArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::LocalArchiveMalwareScanOverride, Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride };
413417
case Args::Type::SourceName:

src/AppInstallerCLICore/Argument.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ namespace AppInstaller::CLI
9191
AllAndTargetVersion = 0x40,
9292
ConfigurationSetChoice = 0x80,
9393
DscResourceFunction = 0x100,
94+
DependenciesConflict = 0x200,
9495

9596
// This must always be at the end
9697
Max

src/AppInstallerCLICore/Commands/InstallCommand.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ namespace AppInstaller::CLI
4545
Argument::ForType(Args::Type::HashOverride),
4646
Argument::ForType(Args::Type::AllowReboot),
4747
Argument::ForType(Args::Type::SkipDependencies),
48+
Argument::ForType(Args::Type::DependenciesOnly),
4849
Argument::ForType(Args::Type::IgnoreLocalArchiveMalwareScan),
4950
Argument::ForType(Args::Type::DependencySource),
5051
Argument::ForType(Args::Type::AcceptPackageAgreements),
@@ -150,6 +151,10 @@ namespace AppInstaller::CLI
150151
{
151152
flags = ProcessMultiplePackages::Flags::IgnoreDependencies;
152153
}
154+
else if (context.Args.Contains(Execution::Args::Type::DependenciesOnly))
155+
{
156+
flags = ProcessMultiplePackages::Flags::DependenciesOnly;
157+
}
153158

154159
context <<
155160
GetMultiSearchRequests <<

src/AppInstallerCLICore/ExecutionArgs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ namespace AppInstaller::CLI::Execution
4545
InstallerType,
4646
HashOverride, // Ignore hash mismatches
4747
SkipDependencies, // Skip dependencies
48+
DependenciesOnly, // Install only dependencies, not the target package
4849
IgnoreLocalArchiveMalwareScan, // Ignore the local malware scan on archive files
4950
AcceptPackageAgreements, // Accept all license agreements for packages
5051
Rename, // Renames the file of the executable. Only applies to the portable installerType

src/AppInstallerCLICore/Resources.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,8 @@ namespace AppInstaller::CLI::Resource
686686
WINGET_DEFINE_RESOURCE_STRINGID(SilentArgumentDescription);
687687
WINGET_DEFINE_RESOURCE_STRINGID(SingleCharAfterDashError);
688688
WINGET_DEFINE_RESOURCE_STRINGID(SkipDependenciesArgumentDescription);
689+
WINGET_DEFINE_RESOURCE_STRINGID(DependenciesOnlyArgumentDescription);
690+
WINGET_DEFINE_RESOURCE_STRINGID(DependenciesOnlyMessage);
689691
WINGET_DEFINE_RESOURCE_STRINGID(SkipMicrosoftStorePackageLicenseArgumentDescription);
690692
WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsDifferentArg);
691693
WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsDifferentName);

src/AppInstallerCLICore/Workflows/InstallFlow.cpp

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,16 @@ namespace AppInstaller::CLI::Workflow
157157
HRESULT HResult;
158158
Resource::StringId Message;
159159
};
160+
161+
void CheckForOnlyDependencies(Execution::Context& context)
162+
{
163+
if (context.Args.Contains(Execution::Args::Type::DependenciesOnly))
164+
{
165+
context.Reporter.Info() << Resource::String::DependenciesOnlyMessage << std::endl;
166+
// We want the context to terminate, but successfully.
167+
context.SetTerminationHR(S_OK);
168+
}
169+
}
160170
}
161171

162172
namespace details
@@ -640,6 +650,7 @@ namespace AppInstaller::CLI::Workflow
640650
Workflow::ShowPromptsForSinglePackage(/* ensureAcceptance */ true) <<
641651
Workflow::CreateDependencySubContexts(Resource::String::PackageRequiresDependencies) <<
642652
Workflow::InstallDependencies <<
653+
CheckForOnlyDependencies <<
643654
Workflow::DownloadInstaller <<
644655
Workflow::InstallPackageInstaller <<
645656
Workflow::RegisterStartupAfterReboot();
@@ -712,6 +723,7 @@ namespace AppInstaller::CLI::Workflow
712723
m_stopOnFailure = WI_IsFlagSet(flags, Flags::StopOnFailure);
713724
m_refreshPathVariable = WI_IsFlagSet(flags, Flags::RefreshPathVariable);
714725
m_downloadOnly = WI_IsFlagSet(flags, Flags::DownloadOnly);
726+
m_dependenciesOnly = WI_IsFlagSet(flags, Flags::DependenciesOnly);
715727
}
716728

717729
void ProcessMultiplePackages::operator()(Execution::Context& context) const
@@ -763,6 +775,11 @@ namespace AppInstaller::CLI::Workflow
763775
size_t packagesCount = packageSubContexts.size();
764776
size_t packagesProgress = 0;
765777

778+
if (m_dependenciesOnly)
779+
{
780+
context.Reporter.Info() << Resource::String::DependenciesOnlyMessage << std::endl;
781+
}
782+
766783
for (auto& packageContext : packageSubContexts)
767784
{
768785
packagesProgress++;
@@ -786,11 +803,14 @@ namespace AppInstaller::CLI::Workflow
786803
Workflow::ProcessMultiplePackages(m_dependenciesReportMessage, APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::RefreshPathVariable);
787804
}
788805

789-
currentContext << Workflow::DownloadInstaller;
790-
791-
if (!downloadInstallerOnly)
806+
if (!m_dependenciesOnly)
792807
{
793-
currentContext << Workflow::InstallPackageInstaller;
808+
currentContext << Workflow::DownloadInstaller;
809+
810+
if (!downloadInstallerOnly)
811+
{
812+
currentContext << Workflow::InstallPackageInstaller;
813+
}
794814
}
795815
}
796816
catch (...)

src/AppInstallerCLICore/Workflows/InstallFlow.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ namespace AppInstaller::CLI::Workflow
182182
StopOnFailure = 0x04,
183183
RefreshPathVariable = 0x08,
184184
DownloadOnly = 0x10,
185+
DependenciesOnly = 0x20,
185186
};
186187

187188
ProcessMultiplePackages(
@@ -201,6 +202,7 @@ namespace AppInstaller::CLI::Workflow
201202
bool m_stopOnFailure;
202203
bool m_refreshPathVariable;
203204
bool m_downloadOnly;
205+
bool m_dependenciesOnly;
204206
};
205207

206208
DEFINE_ENUM_FLAG_OPERATORS(ProcessMultiplePackages::Flags);

src/AppInstallerCLIE2ETests/InstallCommand.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,30 @@ public void InstallWithPackageDependency_RefreshPathVariable()
709709
Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(testDir));
710710
}
711711

712+
/// <summary>
713+
/// Test install a package with a package dependency and specify dependencies only.
714+
/// </summary>
715+
[Test]
716+
public void InstallWithPackageDependency_DependenciesOnly()
717+
{
718+
var testDir = TestCommon.GetRandomTestDir();
719+
string installDir = TestCommon.GetPortablePackagesDirectory();
720+
var installResult = TestCommon.RunAICLICommand("install", $"-q AppInstallerTest.PackageDependencyRequiresPathRefresh -l {testDir} --dependencies-only");
721+
Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode);
722+
Assert.True(installResult.StdOut.Contains("Installing dependencies only. The package itself will not be installed."));
723+
Assert.True(installResult.StdOut.Contains("Successfully installed"));
724+
725+
// Portable package is used as a dependency. Ensure that it is installed and cleaned up successfully.
726+
string portablePackageId, commandAlias, fileName, packageDirName, productCode;
727+
portablePackageId = "AppInstallerTest.TestPortableExeWithCommand";
728+
packageDirName = productCode = portablePackageId + "_" + Constants.TestSourceIdentifier;
729+
fileName = "AppInstallerTestExeInstaller.exe";
730+
commandAlias = "testCommand.exe";
731+
732+
TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true);
733+
Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(testDir));
734+
}
735+
712736
/// <summary>
713737
/// Test install a package using a specific installer type.
714738
/// </summary>

src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,6 +2024,12 @@ Please specify one of them using the --source option to proceed.</value>
20242024
<data name="SkipDependenciesArgumentDescription" xml:space="preserve">
20252025
<value>Skips processing package dependencies and Windows features</value>
20262026
</data>
2027+
<data name="DependenciesOnlyArgumentDescription" xml:space="preserve">
2028+
<value>Installs only the dependencies of the package</value>
2029+
</data>
2030+
<data name="DependenciesOnlyMessage" xml:space="preserve">
2031+
<value>Installing dependencies only. The package itself will not be installed.</value>
2032+
</data>
20272033
<data name="DependenciesSkippedMessage" xml:space="preserve">
20282034
<value>Dependencies skipped.</value>
20292035
</data>

src/AppInstallerCLITests/InstallDependenciesFlow.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,28 @@ TEST_CASE("InstallerWithDependencies_IgnoreDependenciesSetting", "[dependencies]
232232
REQUIRE_FALSE(installOutput.str().find("PreviewIIS") != std::string::npos);
233233
}
234234

235+
TEST_CASE("InstallerWithDependencies_DependenciesOnly", "[dependencies]")
236+
{
237+
std::ostringstream installOutput;
238+
TestContext context{ installOutput, std::cin };
239+
auto previousThreadGlobals = context.SetForCurrentThread();
240+
OverrideOpenDependencySource(context);
241+
OverrideEnableWindowsFeaturesDependencies(context);
242+
243+
context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string());
244+
context.Args.AddArg(Execution::Args::Type::DependenciesOnly);
245+
246+
InstallCommand install({});
247+
install.Execute(context);
248+
INFO(installOutput.str());
249+
250+
// Dependencies should be reported and installed
251+
REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos);
252+
REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos);
253+
// DependenciesOnly message should be shown
254+
REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesOnlyMessage).get()) != std::string::npos);
255+
}
256+
235257
TEST_CASE("DependenciesMultideclaration_InstallerDependenciesPreference", "[dependencies]")
236258
{
237259
std::ostringstream installOutput;

0 commit comments

Comments
 (0)