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

Implementation for Portable install flow #2078

Merged
merged 58 commits into from
Apr 28, 2022

Conversation

ryfu-msft
Copy link
Contributor

@ryfu-msft ryfu-msft commented Apr 8, 2022

Related to:

This PR add support for the installation of portable standalone apps. The design aligns with what is specified in the portable install design spec. This PR only relates to the install command. Support for upgrade and uninstall will be handled in a separate PR.

Tests:

  • Added tests for modifying registry keys
  • Added portable workflow test
  • Added E2E tests for testing various portable install scenarios such as command field specified, apps and feature entry specified, and rename argument provided.
Microsoft Reviewers: Open in CodeFlow

@ryfu-msft ryfu-msft requested a review from a team as a code owner April 8, 2022 18:40
@microsoft microsoft deleted a comment from github-actions bot Apr 25, 2022
Comment on lines 11 to 26
constexpr std::wstring_view s_UninstallRegistryX64 = L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
constexpr std::wstring_view s_UninstallRegistryX86 = L"Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
constexpr std::wstring_view s_DisplayName = L"DisplayName";
constexpr std::wstring_view s_DisplayVersion = L"DisplayVersion";
constexpr std::wstring_view s_Publisher = L"Publisher";
constexpr std::wstring_view s_InstallDate = L"InstallDate";
constexpr std::wstring_view s_URLInfoAbout = L"URLInfoAbout";
constexpr std::wstring_view s_HelpLink = L"HelpLink";
constexpr std::wstring_view s_UninstallString = L"UninstallString";
constexpr std::wstring_view s_WinGetInstallerType = L"WinGetInstallerType";
constexpr std::wstring_view s_InstallLocation = L"InstallLocation";
constexpr std::wstring_view s_PortableTargetFullPath = L"TargetFullPath";
constexpr std::wstring_view s_PortableSymlinkFullPath = L"SymlinkFullPath";
constexpr std::wstring_view s_SHA256 = L"SHA256";
constexpr std::wstring_view s_WinGetPackageIdentifier = L"WinGetPackageIdentifier";
constexpr std::wstring_view s_WinGetSourceIdentifier = L"WinGetSourceIdentifier";
Copy link
Contributor

Choose a reason for hiding this comment

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

Couldn't you put these inside of a map and then use the map as a dictionary later instead of using all of the if..else if..else statements in the ToString method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Created a map for these strings as suggested

Copy link
Member

Choose a reason for hiding this comment

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

A switch statement would be the preferred way to do ToString. And I would use a macro to stamp out the cases since the enum value and static string can shared a common name fragment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a macro to stamp out cases and use a switch statement. Removed map

.github/actions/spelling/expect.txt Outdated Show resolved Hide resolved
src/AppInstallerCLICore/Workflows/DownloadFlow.cpp Outdated Show resolved Hide resolved
src/AppInstallerCLICore/Workflows/InstallFlow.cpp Outdated Show resolved Hide resolved
src/AppInstallerCLICore/Workflows/InstallFlow.cpp Outdated Show resolved Hide resolved
src/AppInstallerCLICore/Workflows/InstallFlow.cpp Outdated Show resolved Hide resolved
src/AppInstallerCommonCore/Filesystem.cpp Outdated Show resolved Hide resolved
src/AppInstallerCommonCore/PortableARPEntry.cpp Outdated Show resolved Hide resolved
Comment on lines 11 to 26
constexpr std::wstring_view s_UninstallRegistryX64 = L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
constexpr std::wstring_view s_UninstallRegistryX86 = L"Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
constexpr std::wstring_view s_DisplayName = L"DisplayName";
constexpr std::wstring_view s_DisplayVersion = L"DisplayVersion";
constexpr std::wstring_view s_Publisher = L"Publisher";
constexpr std::wstring_view s_InstallDate = L"InstallDate";
constexpr std::wstring_view s_URLInfoAbout = L"URLInfoAbout";
constexpr std::wstring_view s_HelpLink = L"HelpLink";
constexpr std::wstring_view s_UninstallString = L"UninstallString";
constexpr std::wstring_view s_WinGetInstallerType = L"WinGetInstallerType";
constexpr std::wstring_view s_InstallLocation = L"InstallLocation";
constexpr std::wstring_view s_PortableTargetFullPath = L"TargetFullPath";
constexpr std::wstring_view s_PortableSymlinkFullPath = L"SymlinkFullPath";
constexpr std::wstring_view s_SHA256 = L"SHA256";
constexpr std::wstring_view s_WinGetPackageIdentifier = L"WinGetPackageIdentifier";
constexpr std::wstring_view s_WinGetSourceIdentifier = L"WinGetSourceIdentifier";
Copy link
Member

Choose a reason for hiding this comment

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

A switch statement would be the preferred way to do ToString. And I would use a macro to stamp out the cases since the enum value and static string can shared a common name fragment.

@ghost ghost added the Needs-Author-Feedback Issue needs attention from issue or PR author label Apr 26, 2022
@ghost ghost removed the Needs-Author-Feedback Issue needs attention from issue or PR author label Apr 27, 2022
@github-actions
Copy link

github-actions bot commented Apr 27, 2022

@check-spelling-bot Report

Unrecognized words, please review:

  • nonterminated
  • VALUENAMECASE
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 git@github.com:ryfu-msft/winget-cli.git repository
on the portableInstallFlow 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/1111362109" > "$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

@ryfu-msft
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

}
else
{
source = s_LocalSource;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to include the asterisk in the package Identifier? Technically it is an allowed character, but knowing that users may use powershell, and for potential forward compatibility, it may be best to omit it from the product code

Copy link
Member

Choose a reason for hiding this comment

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

I think that for consistency this function should apply MakeSuitablePathPart before returning, which will remove the *.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's probably a good call. As we don't have wildcard support, and some terminals might do unexpected things.

AICLI_LOG(CLI, Info, << "Writing to Uninstall registry complete.");
}

void MovePortableExeAndCreateSymlink(Execution::Context& context)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know what it is, but something about this name just doesn't seem right. I think it is the And which indicates that this task is doing two tasks and that it may be best to split it into one task for moving the exe, and one for creating the symlink? I'm not an expert though, so I'd like others' opinions on this too

cc @JohnMcPMS

Copy link
Member

Choose a reason for hiding this comment

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

I agree that it would generally make more sense to split the two operations if they are logically independent. I don't think that we need to now, but I definitely foresee the need for this in the future for portable(s) in an archive support, where it isn't just a single file and symlink. I think it can wait until then though.

As for the name, again I'm fine with it for now because I expect it to be refactored for the archive support.

src/AppInstallerCLIE2ETests/InstallCommand.cs Outdated Show resolved Hide resolved
Comment on lines +54 to +62
auto now = std::chrono::system_clock::now();
std::time_t tt = std::chrono::system_clock::to_time_t(now);

struct tm newTime;
localtime_s(&newTime, &tt);

std::stringstream ss;
ss << std::put_time(&newTime, "%Y%m%d");
return ss.str();
Copy link
Contributor

@Trenly Trenly Apr 27, 2022

Choose a reason for hiding this comment

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

Do you have to use std::chrono::system_clock::now()? Or would this work?

Suggested change
auto now = std::chrono::system_clock::now();
std::time_t tt = std::chrono::system_clock::to_time_t(now);
struct tm newTime;
localtime_s(&newTime, &tt);
std::stringstream ss;
ss << std::put_time(&newTime, "%Y%m%d");
return ss.str();
std::time_t now = std::time(nullptr);
std::stringstream ss;
ss << std::put_time(std::localtime(&now), "%Y%m%d");
return ss.str();

(Just trying to learn, so this might not be right)

Copy link
Member

Choose a reason for hiding this comment

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

Looks correct to me (although nullptr is preferred over 0).

Although if we convert to C++20, we can just get the time formatting goodness and not use the icky C stuff.

}
else
{
source = s_LocalSource;
Copy link
Member

Choose a reason for hiding this comment

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

I think that for consistency this function should apply MakeSuitablePathPart before returning, which will remove the *.

src/AppInstallerCLICore/Workflows/PortableInstallFlow.cpp Outdated Show resolved Hide resolved
AICLI_LOG(CLI, Info, << "Writing to Uninstall registry complete.");
}

void MovePortableExeAndCreateSymlink(Execution::Context& context)
Copy link
Member

Choose a reason for hiding this comment

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

I agree that it would generally make more sense to split the two operations if they are logically independent. I don't think that we need to now, but I definitely foresee the need for this in the future for portable(s) in an archive support, where it isn't just a single file and symlink. I think it can wait until then though.

As for the name, again I'm fine with it for now because I expect it to be refactored for the archive support.

src/AppInstallerCLICore/Workflows/PortableInstallFlow.cpp Outdated Show resolved Hide resolved
src/AppInstallerCLICore/Workflows/PortableInstallFlow.cpp Outdated Show resolved Hide resolved
Comment on lines +54 to +62
auto now = std::chrono::system_clock::now();
std::time_t tt = std::chrono::system_clock::to_time_t(now);

struct tm newTime;
localtime_s(&newTime, &tt);

std::stringstream ss;
ss << std::put_time(&newTime, "%Y%m%d");
return ss.str();
Copy link
Member

Choose a reason for hiding this comment

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

Looks correct to me (although nullptr is preferred over 0).

Although if we convert to C++20, we can just get the time formatting goodness and not use the icky C stuff.

src/AppInstallerCommonCore/PortableARPEntry.cpp Outdated Show resolved Hide resolved
src/AppInstallerCommonCore/PortableARPEntry.cpp Outdated Show resolved Hide resolved
@ghost ghost added the Needs-Author-Feedback Issue needs attention from issue or PR author label Apr 27, 2022
@ghost ghost removed the Needs-Author-Feedback Issue needs attention from issue or PR author label Apr 28, 2022
@ryfu-msft ryfu-msft merged commit 5d63c38 into microsoft:master Apr 28, 2022
@Trenly
Copy link
Contributor

Trenly commented Apr 28, 2022

🎉

@denelon
Copy link
Contributor

denelon commented Apr 28, 2022

We will look to cut another preview build ASAP 😊

@Trenly
Copy link
Contributor

Trenly commented Apr 28, 2022

We will look to cut another preview build ASAP 😊

Potential for CI/CD pre-releases after every PR merge?

@JohnMcPMS
Copy link
Member

Potential for CI/CD pre-releases after every PR merge?

It would have to be of the dev package due to policies. But the pipeline could be updated to create a package, sign it, and leave it in the artifacts.

@Trenly
Copy link
Contributor

Trenly commented Apr 28, 2022

Potential for CI/CD pre-releases after every PR merge?

It would have to be of the dev package due to policies. But the pipeline could be updated to create a package, sign it, and leave it in the artifacts.

I think that would be great to have a WingetDev build accessible in the public artifacts of a pipeline in case some power users want to really test things before they hit an official pre-release

@ryfu-msft ryfu-msft deleted the portableInstallFlow branch May 16, 2022 22:22
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.

4 participants