-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for --Installer-Type
argument for commands
#3516
Changes from 6 commits
3ca8814
51e1b61
a40a6ef
e2bbad8
a155cc1
12b467f
da47fb5
5ed23fc
3bbad1e
08cfe54
b95a65c
7d64507
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,6 +93,28 @@ | |
"minItems": 1, | ||
"maxItems": 4 | ||
} | ||
}, | ||
"installerTypes": { | ||
"description": "The installerType(s) to use for a package install", | ||
"type": "array", | ||
"items": { | ||
"uniqueItems": "true", | ||
"type": "string", | ||
"enum": [ | ||
"inno", | ||
"wix", | ||
"msi", | ||
"nullsoft", | ||
"zip", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is zip an installer type users would care about? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it will be a common scenario, but it should still be supported if people prefer that installer type. |
||
"msix", | ||
"exe", | ||
"burn", | ||
Comment on lines
+104
to
+111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would a user really care about whether something is an msi or an msi made with some-tool? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would this essentially mean that we would need an "InstallerTechnologyType" for each Installer? Effectively reducing it to portable, exe, msi, msix, and msstore? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that's what I'm suggesting. But I don't know if it would be a good idea. I don't think most people would care about the difference between a wix and an msi installer, but for people who do care I think it would be confusing if the set of types here is different than in the manifest. |
||
"msstore", | ||
"portable" | ||
], | ||
"minItems": 1, | ||
"maxItems": 9 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I don't see much point in having There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of the other settings arrays declare a maxItems so I followed it just to be consistent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's weird but ok, Anyway this settings schema is not used in code for enforcement, it's just informational only for now. |
||
} | ||
} | ||
} | ||
}, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -247,44 +247,64 @@ namespace AppInstaller::CLI::Workflow | |
|
||
struct InstallerTypeComparator : public details::ComparisonField | ||
{ | ||
InstallerTypeComparator(std::vector<InstallerTypeEnum> requirement) : | ||
details::ComparisonField("Installer Type"), m_requirement(std::move(requirement)) | ||
InstallerTypeComparator(std::vector<InstallerTypeEnum> preference, std::vector<InstallerTypeEnum> requirement) : | ||
details::ComparisonField("Installer Type"), m_preference(std::move(preference)), m_requirement(std::move(requirement)) | ||
{ | ||
m_preferenceAsString = Utility::ConvertContainerToString(m_preference, InstallerTypeToString); | ||
m_requirementAsString = Utility::ConvertContainerToString(m_requirement, InstallerTypeToString); | ||
AICLI_LOG(CLI, Verbose, | ||
<< "InstallerType Comparator created with Required InstallerTypes: " << m_requirementAsString); | ||
<< "InstallerType Comparator created with Required InstallerTypes: " << m_requirementAsString | ||
<< " , Preferred InstallerTypes: " << m_preferenceAsString); | ||
} | ||
|
||
static std::unique_ptr<InstallerTypeComparator> Create(const Execution::Args& args) | ||
{ | ||
std::vector<InstallerTypeEnum> preference; | ||
std::vector<InstallerTypeEnum> requirement; | ||
|
||
if (args.Contains(Execution::Args::Type::InstallerType)) | ||
{ | ||
requirement.emplace_back(Manifest::ConvertToInstallerTypeEnum(std::string(args.GetArg(Execution::Args::Type::InstallerType)))); | ||
} | ||
else | ||
{ | ||
preference = Settings::User().Get<Settings::Setting::InstallerTypePreference>(); | ||
requirement = Settings::User().Get<Settings::Setting::InstallerTypeRequirement>(); | ||
} | ||
|
||
if (!requirement.empty()) | ||
if (!preference.empty() || !requirement.empty()) | ||
{ | ||
return std::make_unique<InstallerTypeComparator>(requirement); | ||
return std::make_unique<InstallerTypeComparator>(preference, requirement); | ||
} | ||
else | ||
{ | ||
return {}; | ||
} | ||
} | ||
|
||
std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override | ||
{ | ||
std::string result = "InstallerType does not match required type: "; | ||
result += InstallerTypeToString(installer.EffectiveInstallerType()); | ||
result += "Required InstallerTypes: "; | ||
ryfu-msft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
result += m_requirementAsString; | ||
return result; | ||
} | ||
|
||
bool ContainsInstallerType(const std::vector<InstallerTypeEnum>& selection, InstallerTypeEnum installerType) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: move to private |
||
return std::find(selection.begin(), selection.end(), installerType) != selection.end(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, thinking more about this, I wonder if it would make sense to have a templated utility function for checking if an object is contained within a container. Although, considering that this PR is required for 1.6, it might make sense not to create a utility function. I'm thinking something like this might work, but I'm not certain template <template<typename, typename> typename Container, typename Allocator, typename Value>
bool ContainsObject(Container<Value, Allocator> container, Value value) {
return std::find(container.begin(), container.end(), value) != container.end();
} |
||
} | ||
|
||
InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override | ||
{ | ||
if (!m_requirement.empty()) | ||
{ | ||
for (auto requiredInstallerType : m_requirement) | ||
// The installer is applicable if the effective or base installer type matches. | ||
if (ContainsInstallerType(m_requirement, installer.EffectiveInstallerType()) || | ||
ContainsInstallerType(m_requirement, installer.BaseInstallerType)) | ||
{ | ||
// The installer is applicable if the installer type or nested installer type matches. (User should be allowed to specify 'zip') | ||
if (installer.EffectiveInstallerType() == requiredInstallerType || installer.BaseInstallerType == requiredInstallerType) | ||
{ | ||
return InapplicabilityFlags::None; | ||
} | ||
return InapplicabilityFlags::None; | ||
} | ||
|
||
return InapplicabilityFlags::InstallerType; | ||
|
@@ -295,26 +315,38 @@ namespace AppInstaller::CLI::Workflow | |
} | ||
} | ||
|
||
std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override | ||
{ | ||
std::string result = "InstallerType does not match required type: "; | ||
result += InstallerTypeToString(installer.EffectiveInstallerType()); | ||
result += "Required InstallerTypes: "; | ||
result += m_requirementAsString; | ||
return result; | ||
} | ||
|
||
bool IsFirstBetter(const Manifest::ManifestInstaller& first, const Manifest::ManifestInstaller& second) override | ||
{ | ||
// TODO: Current implementation assumes there is only a single installer type requirement. This needs to be updated | ||
// once multiple installerType requirements and preferences are accepted. | ||
UNREFERENCED_PARAMETER(first); | ||
UNREFERENCED_PARAMETER(second); | ||
return true; | ||
if (m_preference.empty()) | ||
{ | ||
return false; | ||
} | ||
|
||
bool isFirstInstallerTypePreferred = | ||
ContainsInstallerType(m_preference, first.EffectiveInstallerType()) || | ||
ContainsInstallerType(m_preference, first.BaseInstallerType); | ||
|
||
bool isSecondInstallerTypePreferred = | ||
ContainsInstallerType(m_preference, second.EffectiveInstallerType()) || | ||
ContainsInstallerType(m_preference, second.BaseInstallerType); | ||
|
||
if (isFirstInstallerTypePreferred && isSecondInstallerTypePreferred) | ||
{ | ||
return false; | ||
} | ||
|
||
if (isFirstInstallerTypePreferred != isSecondInstallerTypePreferred) | ||
{ | ||
return isFirstInstallerTypePreferred; | ||
} | ||
|
||
return false; | ||
ryfu-msft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
private: | ||
std::vector<InstallerTypeEnum> m_preference; | ||
std::vector<InstallerTypeEnum> m_requirement; | ||
std::string m_preferenceAsString; | ||
std::string m_requirementAsString; | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -134,22 +134,25 @@ public static void ConfigureInstallBehavior(string settingName, string value) | |
/// <param name="value">Setting value.</param> | ||
public static void ConfigureInstallBehaviorPreferences(string settingName, string value) | ||
{ | ||
JObject settingsJson = JObject.Parse(File.ReadAllText(TestSetup.Parameters.SettingsJsonFilePath)); | ||
|
||
if (!settingsJson.ContainsKey("installBehavior")) | ||
{ | ||
settingsJson["installBehavior"] = new JObject(); | ||
} | ||
|
||
JObject settingsJson = GetJsonSettingsObject("installBehavior", "preferences"); | ||
var installBehavior = settingsJson["installBehavior"]; | ||
var preferences = installBehavior["preferences"]; | ||
preferences[settingName] = value; | ||
|
||
if (installBehavior["preferences"] == null) | ||
{ | ||
installBehavior["preferences"] = new JObject(); | ||
} | ||
File.WriteAllText(TestSetup.Parameters.SettingsJsonFilePath, settingsJson.ToString()); | ||
} | ||
|
||
/// <summary> | ||
/// Configure the install behavior preferences. | ||
/// </summary> | ||
/// <param name="settingName">Setting name.</param> | ||
/// <param name="value">Setting value array.</param> | ||
public static void ConfigureInstallBehaviorPreferences(string settingName, string[] value) | ||
{ | ||
JObject settingsJson = GetJsonSettingsObject("installBehavior", "preferences"); | ||
var installBehavior = settingsJson["installBehavior"]; | ||
var preferences = installBehavior["preferences"]; | ||
preferences[settingName] = value; | ||
preferences[settingName] = new JArray(value); | ||
|
||
File.WriteAllText(TestSetup.Parameters.SettingsJsonFilePath, settingsJson.ToString()); | ||
} | ||
|
@@ -161,22 +164,25 @@ public static void ConfigureInstallBehaviorPreferences(string settingName, strin | |
/// <param name="value">Setting value.</param> | ||
public static void ConfigureInstallBehaviorRequirements(string settingName, string value) | ||
{ | ||
JObject settingsJson = JObject.Parse(File.ReadAllText(TestSetup.Parameters.SettingsJsonFilePath)); | ||
|
||
if (!settingsJson.ContainsKey("installBehavior")) | ||
{ | ||
settingsJson["installBehavior"] = new JObject(); | ||
} | ||
|
||
JObject settingsJson = GetJsonSettingsObject("installBehavior", "requirements"); | ||
var installBehavior = settingsJson["installBehavior"]; | ||
var requirements = installBehavior["requirements"]; | ||
requirements[settingName] = value; | ||
|
||
if (installBehavior["requirements"] == null) | ||
{ | ||
installBehavior["requirements"] = new JObject(); | ||
} | ||
File.WriteAllText(TestSetup.Parameters.SettingsJsonFilePath, settingsJson.ToString()); | ||
} | ||
|
||
/// <summary> | ||
/// Configure the install behavior requirements. | ||
/// </summary> | ||
/// <param name="settingName">Setting name.</param> | ||
/// <param name="value">Setting value array.</param> | ||
public static void ConfigureInstallBehaviorRequirements(string settingName, string[] value) | ||
{ | ||
JObject settingsJson = GetJsonSettingsObject("installBehavior", "requirements"); | ||
var installBehavior = settingsJson["installBehavior"]; | ||
var requirements = installBehavior["requirements"]; | ||
requirements[settingName] = value; | ||
requirements[settingName] = new JArray(value); | ||
|
||
File.WriteAllText(TestSetup.Parameters.SettingsJsonFilePath, settingsJson.ToString()); | ||
} | ||
|
@@ -196,5 +202,24 @@ public static void InitializeAllFeatures(bool status) | |
ConfigureFeature("windowsFeature", status); | ||
ConfigureFeature("download", status); | ||
} | ||
|
||
private static JObject GetJsonSettingsObject(string objectName, string propertyName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this function do? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about something like private static JObject GetJsonSettingsObjectAndCreateEmptyProperty(string[] propertyPath...)
{
JObject settingsJson = JObject.Parse(File.ReadAllText(TestSetup.Parameters.SettingsJsonFilePath));
var currentNode = settings.Json;
foreach (var key in propertyPath)
{
if (!currentNode.ContainsKey(key))
{
currentNode[key] = new JObject();
}
currentNode = currentNode[key];
}
return settingsJson;
} (Not sure if this even compiles...) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed the helper method to be a little more generic so that it only returns the json settings object that you specify and doesn't do anything with creating a single empty property (which I also agree is kind of weird). |
||
{ | ||
JObject settingsJson = JObject.Parse(File.ReadAllText(TestSetup.Parameters.SettingsJsonFilePath)); | ||
|
||
if (!settingsJson.ContainsKey(objectName)) | ||
{ | ||
settingsJson[objectName] = new JObject(); | ||
} | ||
|
||
var installBehavior = settingsJson[objectName]; | ||
ryfu-msft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (installBehavior[propertyName] == null) | ||
{ | ||
installBehavior[propertyName] = new JObject(); | ||
} | ||
|
||
return settingsJson; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this work for scenarios like "zip containing exe" or "exe that installs an msi"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Zip uses the EffectiveInstallerType, but that brings up a good point - if someone has
{zip, exe}
as their preferences, because the check is against both base and effective type, a zip->msi could still be chosen even if a zip->exe existsAnd another great point about the exe that installs an msi or msix; AppsAndFeaturesEntries->InstallerType might need to be considered
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For zip containing exe, I check both the baseInstallerType (for zip) and effective installer type to determine if an installer satisfies the preference/requirement so I believe that scenario is covered. I didn't do anything different for "exe that installs an msi" as I only consider what is shown in the manifest