-
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
Fix list/upgrade table for packages with multiple ARP entries. #2137
Fix list/upgrade table for packages with multiple ARP entries. #2137
Conversation
@@ -81,6 +81,7 @@ namespace AppInstaller::CLI::Execution | |||
|
|||
// Other | |||
All, // Used in Update command to update all installed packages to latest | |||
ListAll, // Used in List command to list all installed apps associated with a package |
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.
Why do you need to add a new arg here? Couldn't you reuse AppInstaller::CLI::Execution::Args::Type::All
?
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.
AppInstaller::CLI::Execution::Args::Type::All
is used in winget upgrade --all
, where I wanted the simplified table to print out. Also, there's a different help string for this new argument, which would mean I'd have to have the Argument defined only in ListCommand instead of in the Arguments.cpp file with the rest of them...
(If we check for AppInstaller::CLI::Execution::Args::Type::All
in ReportListResult()
, we wouldn't be able to tell whether it was coming from winget upgrade --all
or winget list --all
. If others think we should be printing the full table on upgrade --all
, then I can use the same argument in both places.)
|
||
if (latestVersion) | ||
{ | ||
if (updateAvailable) | ||
{ | ||
availableVersion = latestVersion->GetProperty(PackageVersionProperty::Version); | ||
availableUpgradesCount++; | ||
if (context.Args.Contains(Execution::Args::Type::ListAll) || !packageIdsPrinted.count(packageId)) |
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.
Is there any possibility that there could be two packages with the same ID from different sources?
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.
There is a possibility, yes. Should the set be a pair of the package ID and source?
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.
I think that might prevent false positives
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.
yes, PackageId + SourceId will ensure a unique "winget package"
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.
Resolved!
if (context.Args.Contains(Execution::Args::Type::ListAll)) | ||
{ | ||
table.OutputLine({ | ||
installedVersion->GetProperty(PackageVersionProperty::Name), |
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.
nit: a bunch of tabs should be replaced with spaces across the change
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.
I think I caught all of them (sorry, ReSharper thought it was being helpful).
src/AppInstallerCLICore/Argument.cpp
Outdated
@@ -63,6 +63,8 @@ namespace AppInstaller::CLI | |||
return Argument{ "msix", 'm', Args::Type::Msix, Resource::String::MsixArgumentDescription, ArgumentType::Flag }; | |||
case Args::Type::ListVersions: | |||
return Argument{ "versions", NoAlias, Args::Type::ListVersions, Resource::String::VersionsArgumentDescription, ArgumentType::Flag }; | |||
case Args::Type::ListAll: |
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.
nit: since this is for list command specifically, this could be moved to ListCommand.cpp, just like the UpgradeCommand. This Argument::ForType was initially including every argument, then we switched to be "Argument::ForType is for common args, and command specific ones could just be defined in the specific command.cpp". Btw, upgrade all does not have an alias, should this be the same for consistency?
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.
Fixed, and I removed the alias for consistency :)
@@ -710,6 +710,7 @@ namespace AppInstaller::CLI::Workflow | |||
int unknownPackagesCount = 0; | |||
auto &source = context.Get<Execution::Data::Source>(); | |||
bool shouldShowSource = source.IsComposite() && source.GetAvailableSources().size() > 1; | |||
std::set<Utility::LocIndString> packageIdsPrinted = std::set<Utility::LocIndString>(); |
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.
std::set<Utility::LocIndString> packageIdsPrinted = std::set<Utility::LocIndString>(); | |
std::set<Utility::LocIndString> packageIdsPrinted; | |
} | ||
else | ||
{ | ||
// we need to only list once per package |
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.
nit: one more tab here :)
@@ -1308,4 +1308,7 @@ Please specify one of them using the `--source` option to proceed.</value> | |||
<data name="NoPackageSelectionArgumentProvided" xml:space="preserve"> | |||
<value>No package selection argument was provided; see the help for details about finding a package.</value> | |||
</data> | |||
<data name="ListAllArgumentDescription" xml:space="preserve"> | |||
<value>List all software installed by a package</value> |
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.
nit: we try to make "package" the terminology for software across winget but I can see it's weird to say "all packages installed by a package"
Maybe "List all package components installed by a package" or "List all sub-packages installed by a package"
Open to any suggestions.
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.
@denelon, thoughts? I think "List all components installed by a package." makes the most sense.
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.
I refer to these as "system artifacts" internally, but that probably isn't a great name for them publicly. I do like "components" or "items".
d25e17e
to
0646145
Compare
0646145
to
2959a36
Compare
if (updateAvailable) | ||
{ | ||
availableVersion = latestVersion->GetProperty(PackageVersionProperty::Version); | ||
availableUpgradesCount++; | ||
if (context.Args.Contains(Execution::Args::Type::ListAll) || !packageIdsPrinted.count({ packageId, sourceName })) |
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.
Sorry, I may not make clear in previous comment. SourceName is for display. SourceIdentifier should be used to identify a source. We should use value from PackageVersionProperty::SourceIdentifier for the duplicate check.
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.
Resolved in latest commit!
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
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.
I gather from the example case that multiple AppsAndFeaturesEntries
are being used to associate multiple components of some software together. This change is then leveraging that to make it appear that we are being smarter than we are about handling that. The problem that I see is that targeting these installed items becomes even more confusing, as the default list
behavior will look like there is just one "Mozilla Firefox".
However, if I want to uninstall it, this falls into the "we don't handle side by side versions well". I would expect that I cannot currently uninstall Mozilla.Firefox
if both the maintenance service and primary application are correlated.
Ultimately, I think we need to solve the side by side and "multi-component package" issues by having the correlation engine actually output a single Package
that has all of the details on its:
- Multiple versions
- Multiple components
Only then will the problem actually be solved. But that might also require updates to the manifest to be able to mark one of the components as "primary" so that we know what to uninstall (assuming that it handles removing the other components). And then of course all of the interaction models will need to be updated to handle targeting versions and components within as desired.
All of this isn't to say that your change is bad, just that I think it might end up hiding the problem that is created by the way the manifests are being constructed. I think it points to a good way to deal with things at the surface, but we really need the internals to be in agreement or we will end up with a mess.
@@ -24,6 +24,7 @@ namespace AppInstaller::CLI | |||
Argument::ForType(Execution::Args::Type::Exact), | |||
Argument::ForType(Execution::Args::Type::CustomHeader), | |||
Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), | |||
Argument{ "all", Argument::NoAlias, Execution::Args::Type::ListAll, Resource::String::ListAllArgumentDescription, ArgumentType::Flag}, |
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.
I agree with @Trenly that this could just use Args::Type::All
(especially now that its not in ForType
[which existed pre-localization, but now I don't think we should use]).
Ultimately it's just another internal enum value, so it is more about how its makes us feel than anything else.
@@ -1308,4 +1308,7 @@ Please specify one of them using the `--source` option to proceed.</value> | |||
<data name="NoPackageSelectionArgumentProvided" xml:space="preserve"> | |||
<value>No package selection argument was provided; see the help for details about finding a package.</value> | |||
</data> | |||
<data name="ListAllArgumentDescription" xml:space="preserve"> | |||
<value>List all software installed by a package</value> |
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.
I refer to these as "system artifacts" internally, but that probably isn't a great name for them publicly. I do like "components" or "items".
@@ -730,27 +731,52 @@ namespace AppInstaller::CLI::Workflow | |||
// The only time we don't want to output a line is when filtering and no update is available. | |||
if (updateAvailable || !m_onlyShowUpgrades) | |||
{ | |||
Utility::LocIndString availableVersion, sourceName; | |||
Utility::LocIndString availableVersion, sourceName, sourceIdentifier; | |||
Utility::LocIndString packageId = match.Package->GetProperty(PackageProperty::Id); |
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.
I would suggest that rather than storing the packageId
and sourceIdentifier
separately, just store the pair
directly. That would remove 3 times where the strings will be copied to create a temporary pair.
// we need to only list once per package | ||
if (!packageIdsPrinted.count({ packageId, sourceIdentifier })) | ||
{ | ||
packageIdsPrinted.insert({ packageId, sourceIdentifier }); |
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.
packageIdsPrinted.insert({ packageId, sourceIdentifier }); | |
packageIdsPrinted.emplace(std::move(thePair)); |
Assuming that you also took my other suggestion above about keeping these as a pair
rather than separate. And you named it thePair
, which I'm sure you would come up with a better name than that 😉
I completely spaced on this, as Firefox (the case I found the bug on) cleans up the maintenance service when you run the Firefox uninstaller. You're right though, winget needs to know if there's multiple uninstallers to run (and run them all). There's definitely other software (Autodesk stuff comes to mind) that make you uninstall each and every thing they add to the ARP table separately, so winget would need to know to execute all of the uninstallers associated with the ARP entries in the manifest. Should we table this for now then until the side-by-side/correlation work is done? I don't know if that's scheduled to happen in 1.3 or not, but if it's post 1.3 then I think there needs to be some fix for this table, since currently it's super confusing. (Maybe just no matter what print the ARP name for now, so the user can always know what name to tell |
Scheduled and "can fit in with all the random stuff going on" can be quite different 😄 I do like the idea of outputting a disambiguation and the name does seem like the best place for that. The issue with removing the 2nd through nth items will be that maybe one of those was the "real" one that you should |
@jedieaston, are you planning to convert this PR based on our discussion? Given the current situation I think we need to at least switch to using the installed version's name value for list. If you aren't I can make a PR for that purpose. |
I am, let me try to get to it today. Sorry for the delay, I had to graduate from college over the weekend :) |
🎊 Congratulations! 🎊 Oh no, I've dropped this https://careers.microsoft.com/us/en Not trying to get you to do it, just didn't want to duplicate work if you were already doing it. |
@check-spelling-bot ReportUnrecognized words, please review:
Previously acknowledged words that are now absentactivatable amd Archs dsc enr FWW Globals hackathon lww mytool OSVERSION Packagedx parametermap symlink Uninitialize WDAG whatif wsbTo accept these unrecognized words as correct (and remove the previously acknowledged and now absent words), run the following commands... in a clone of the [email protected]:jedieaston/winget-cli.git repository
|
4268674
to
51b20ec
Compare
@JohnMcPMS, I removed all of the code regarding the argument (wonderful thing about Git is that I can get that work back later), and now all this PR does is use the name in the ARP table in the list/upgrade table (the easiest solution to the current problem). Once this merges, I can write up an issue to talk about the deduplication stuff. |
@@ -731,26 +731,32 @@ namespace AppInstaller::CLI::Workflow | |||
if (updateAvailable || !m_onlyShowUpgrades) | |||
{ | |||
Utility::LocIndString availableVersion, sourceName; | |||
Utility::LocIndString packageId = match.Package->GetProperty(PackageProperty::Id); |
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.
Getting value into packageId
but not using it.
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.
Resolved in latest commit. Oops!
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
…ut-of-hand-now-there-are-n-of-them
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
Hooray! Packages with multiple ARP entries are being matched! However, there's a problem, which is that with the current way
ReportListResult()
works, the package's ARP name(s) are overwritten with the one from the manifest, which puts duplicate looking results in the list:This PR changesReportListResult()
to only output a single row for every package ID by default (which means we only show one entry in the list per "winget package", even if multiple pieces of software were installed via that package). It also adds a new argument towinget list
,--all
, which will show all of the software installed (but now using the original name in the ARP table instead of the name of the package in the repo, to cause less confusion). Here's what it looks like:The table printed in
winget upgrade
is always the simpler table (since we can only upgrade things we matched anyway), and the count of "upgrades available" is mapped to the number of packages instead of the number of ARP entries.I know I did this without opening an issue to discuss it, so if there's a better solution I'm happy to fix this (or close it).Edit: If you're just joining us, @JohnMcPMS brought up some great points with my original solution, and as such this PR doesn't do as much as it did. Now, winget will always use the PackageName from the Add and Remove Programs table, instead of the one in the manifest:
There is no argument to control this anymore, since if we tried to deduplicate the list with the currently available data we'd be hiding info (since winget isn't smart enough yet to uninstall all of the ARP entries associated with a certain package). Hopefully we'll be able to come back to that solution once the correlation logic is a bit more mature.
Tested: manually, although I'm happy to write tests if necessary.
Microsoft Reviewers: Open in CodeFlow