Skip to content
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

Create WinGetUtil functionality for running installed package correlation #2221

Merged
merged 25 commits into from
Jun 24, 2022

Conversation

JohnMcPMS
Copy link
Member

@JohnMcPMS JohnMcPMS commented Jun 6, 2022

Change

The goal of this change is to expose functionality to run the installed package correlation and gather the results. This will be used to validate and enhance manifests and allows us to use the same correlation code throughout the system.

The biggest changes to existing code are:

  1. Moving the JSON utility methods out of Repository and into Common
  2. Moving the correlation code to be object oriented, enabling easier reuse (and allowing for more control in the future)

Beyond those changes, the majority of the work is in creating the JSON (de)serialization and handling updating the metadata that has been collected.

More work is still needed to:

  1. Collect more information for diagnostic purposes, like the top results for diagnostics when there is no confident match.
  2. Leveraging the previous metadata results to improve correlation.
  3. Enable more control over weighting the correlation heuristics, enabling a difference between running on an end user system and in a "clean room" of validation.

Validation

Unit tests are added covering scenarios around updating the metadata with results.

Microsoft Reviewers: Open in CodeFlow

@JohnMcPMS JohnMcPMS requested a review from a team as a code owner June 6, 2022 18:49
Copy link
Member

@florelis florelis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still reading the InstallerMetadataCollectionContext...

@@ -109,6 +109,7 @@ denelon
depersist
deque
deserialize
deserializes
Deserialize
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need both deserialize and Deserialize? Isn't it case insensitive?

<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_NO_ASYNCRTIMP;_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does _NO_ASYNCRTIMP do?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does this in cpprest_compat.h:

#ifdef _NO_ASYNCRTIMP
#define _ASYNCRTIMP
#else // ^^^ _NO_ASYNCRTIMP ^^^ // vvv !_NO_ASYNCRTIMP vvv
#ifdef _ASYNCRT_EXPORT
#define _ASYNCRTIMP __declspec(dllexport)
#else // ^^^ _ASYNCRT_EXPORT ^^^ // vvv !_ASYNCRT_EXPORT vvv
#define _ASYNCRTIMP __declspec(dllimport)
#endif // _ASYNCRT_EXPORT
#endif // _NO_ASYNCRTIMP

Given that it means we don't import or export the functions tagged with this, we must be compiling them in.

void FromJson(const web::json::value& json);

// Create a JSON value for the metadata using the given schema version.
web::json::value ToJson(const Utility::Version& version, size_t maximumSizeInBytes);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Maybe rename version -> schemaVersion? On a first reading I understood it as converting the version to JSON

return AddIfNotPresentAndNotEmpty(strings, strings, string);
}

void AddIfNotPresent(std::vector<std::string>& strings, std::vector<std::string>& filter, const std::vector<std::string>& inputs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a common operation, why not use a set instead of a vector?

@@ -22,6 +22,7 @@ namespace AppInstaller::Utility
Manifest,
WinGetUtil,
Installer,
InstallerMetadataCollectionInput,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the only place where this is used is in the DO download to decide whether to use DO? I saw a comment there explaining why the other types don't use DO. Can you add this one?


namespace AppInstaller::JSON
{
// For JSON CPP LIb
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: LIb


web::json::value GetStringValue(std::string_view value);

std::vector<std::string> GetRawStringArrayFromJsonNode(const web::json::value& node, const utility::string_t& keyName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Could this be higher closer to the other GetRaw... functions?


utility::string_t GetUtilityString(std::string_view nodeName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Why Get instead of To?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's moved from rest helpers and there're a lot of places calling

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I only moved these and did not attempt to rename them.

Comment on lines 472 to 475
if (success)
{
break;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does breaking instead of setting success = true not work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it in the code I copied from as well.

}
catch (...)
{
CollectErrorDataFromException(std::current_exception());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is cool. I didn't know std::current_exception() existed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's great for making standardized exception handlers.

@@ -129,4 +129,46 @@ extern "C"
WINGET_STRING versionA,
WINGET_STRING versionB,
INT* comparisonResult);

// A handle to the metadata collection object.
typedef void* WINGET_INSTALLER_METADATA_COLLECTION_HANDLE;
Copy link
Contributor

@yao-msft yao-msft Jun 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typedef void* WINGET_INSTALLER_METADATA_COLLECTION_HANDLE;

nit: should we put all typedefs and enums in the top?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we? I do like things being defined close to where they are used. If the type is being used in a broad fashion, the top makes sense to me. But if it is used narrowly, then immediately above the use feels better.

@@ -0,0 +1,59 @@
#pragma once
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove

utility::string_t InstallerHash = L"installerHash";
utility::string_t SubmissionIdentifier = L"submissionIdentifier";
utility::string_t Version = L"version";
utility::string_t AppsAndFeaturesEntries = L"AppsAndFeaturesEntries";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A

Is it intentional to have some values start with lower case and some values start with upper case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is in that those that start with upper case are the same names as from the manifest JSON schema.

Copy link
Contributor

@yao-msft yao-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

OutputStatus statusToUse = m_outputStatus;
if (statusToUse != OutputStatus::Success && statusToUse != OutputStatus::Error && statusToUse != OutputStatus::LowConfidence)
{
statusToUse = OutputStatus::Unknown;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

statusToUse = OutputStatus::Unknown;

nit: should this be initialized to unknown as default and override if known

}
newEntry.Publisher = package->GetProperty(PackageVersionProperty::Publisher).get();
// TODO: Support upgrade code throughout the code base...
//newEntry.UpgradeCode;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//newEntry.UpgradeCode;

nit: remove?

{
TestInput() = default;

TestInput(MinimalDefaults_t)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestInput

Should we have an input and output test to showcase what the input json and output json looks like?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean a test case with a literal string input and verification as a string output looking for literal values?

@github-actions
Copy link

github-actions bot commented Jun 17, 2022

@check-spelling-bot Report

Unrecognized words, please review:

  • metadatas
  • verision
Previously acknowledged words that are now absent activatable amd Archs dsc enr FWW Globals hackathon lww mytool OSVERSION Packagedx parametermap symlink Uninitialize WDAG whatif wsb
To 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]:JohnMcPMS/winget-cli.git repository
on the metacollect branch:

update_files() {
perl -e '
my @expect_files=qw('".github/actions/spelling/expect.txt"');
@ARGV=@expect_files;
my @stale=qw('"$patch_remove"');
my $re=join "|", @stale;
my $suffix=".".time();
my $previous="";
sub maybe_unlink { unlink($_[0]) if $_[0]; }
while (<>) {
if ($ARGV ne $old_argv) { maybe_unlink($previous); $previous="$ARGV$suffix"; rename($ARGV, $previous); open(ARGV_OUT, ">$ARGV"); select(ARGV_OUT); $old_argv = $ARGV; }
next if /^(?:$re)(?:(?:\r|\n)*$| .*)/; print;
}; maybe_unlink($previous);'
perl -e '
my $new_expect_file=".github/actions/spelling/expect.txt";
use File::Path qw(make_path);
use File::Basename qw(dirname);
make_path (dirname($new_expect_file));
open FILE, q{<}, $new_expect_file; chomp(my @words = <FILE>); close FILE;
my @add=qw('"$patch_add"');
my %items; @items{@words} = @words x (1); @items{@add} = @add x (1);
@words = sort {lc($a)."-".$a cmp lc($b)."-".$b} keys %items;
open FILE, q{>}, $new_expect_file; for my $word (@words) { print FILE "$word\n" if $word =~ /\w/; };
close FILE;
system("git", "add", $new_expect_file);
'
}

comment_json=$(mktemp)
curl -L -s -S \
  --header "Content-Type: application/json" \
  "https://api.github.com/repos/microsoft/winget-cli/issues/comments/1158498292" > "$comment_json"
comment_body=$(mktemp)
jq -r .body < "$comment_json" > $comment_body
rm $comment_json

patch_remove=$(perl -ne 'next unless s{^</summary>(.*)</details>$}{$1}; print' < "$comment_body")
  

patch_add=$(perl -e '$/=undef;
$_=<>;
s{<details>.*}{}s;
s{^#.*}{};
s{\n##.*}{};
s{(?:^|\n)\s*\*}{}g;
s{\s+}{ }g;
print' < "$comment_body")
  
update_files
rm $comment_body
git add -u

@github-actions
Copy link

github-actions bot commented Jun 22, 2022

@check-spelling-bot Report

Unrecognized words, please review:

  • metadatas
  • verision
Previously acknowledged words that are now absent activatable amd Archs dsc enr FWW Globals hackathon lww mytool OSVERSION Packagedx parametermap symlink Uninitialize WDAG whatif wsb
To 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]:JohnMcPMS/winget-cli.git repository
on the metacollect branch:

update_files() {
perl -e '
my @expect_files=qw('".github/actions/spelling/expect.txt"');
@ARGV=@expect_files;
my @stale=qw('"$patch_remove"');
my $re=join "|", @stale;
my $suffix=".".time();
my $previous="";
sub maybe_unlink { unlink($_[0]) if $_[0]; }
while (<>) {
if ($ARGV ne $old_argv) { maybe_unlink($previous); $previous="$ARGV$suffix"; rename($ARGV, $previous); open(ARGV_OUT, ">$ARGV"); select(ARGV_OUT); $old_argv = $ARGV; }
next if /^(?:$re)(?:(?:\r|\n)*$| .*)/; print;
}; maybe_unlink($previous);'
perl -e '
my $new_expect_file=".github/actions/spelling/expect.txt";
use File::Path qw(make_path);
use File::Basename qw(dirname);
make_path (dirname($new_expect_file));
open FILE, q{<}, $new_expect_file; chomp(my @words = <FILE>); close FILE;
my @add=qw('"$patch_add"');
my %items; @items{@words} = @words x (1); @items{@add} = @add x (1);
@words = sort {lc($a)."-".$a cmp lc($b)."-".$b} keys %items;
open FILE, q{>}, $new_expect_file; for my $word (@words) { print FILE "$word\n" if $word =~ /\w/; };
close FILE;
system("git", "add", $new_expect_file);
'
}

comment_json=$(mktemp)
curl -L -s -S \
  --header "Content-Type: application/json" \
  "https://api.github.com/repos/microsoft/winget-cli/issues/comments/1162583042" > "$comment_json"
comment_body=$(mktemp)
jq -r .body < "$comment_json" > $comment_body
rm $comment_json

patch_remove=$(perl -ne 'next unless s{^</summary>(.*)</details>$}{$1}; print' < "$comment_body")
  

patch_add=$(perl -e '$/=undef;
$_=<>;
s{<details>.*}{}s;
s{^#.*}{};
s{\n##.*}{};
s{(?:^|\n)\s*\*}{}g;
s{\s+}{ }g;
print' < "$comment_body")
  
update_files
rm $comment_body
git add -u

@JohnMcPMS JohnMcPMS merged commit 9f8ceea into microsoft:master Jun 24, 2022
@JohnMcPMS JohnMcPMS deleted the metacollect branch June 24, 2022 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants