-
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 installing portables from a zip #2500
Conversation
src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.h
Outdated
Show resolved
Hide resolved
src/AppInstallerRepositoryCore/Microsoft/Schema/IPortableIndex.h
Outdated
Show resolved
Hide resolved
} | ||
} | ||
|
||
HRESULT PortableInstaller::InstallSingle(const std::filesystem::path& installerPath) |
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 don't think splitting single/multiple is necessarily a good idea from a maintainability standpoint. It isn't really necessary either given what I'm going to suggest in this rather long comment.
Any action (install/upgrade/uninstall) taken on the portable file(s) can be described by 3 states: { Desired, Expected, Actual }
- Desired contains the incoming file(s)
- Expected contains the description of the file(s) that we maintain (registry or index)
- Actual is the state of the filesystem
Every filesystem entry that we want can be described in the desired state, and each one has a related state in the expected and actual sets (even if it isn't in the set in memory). This includes the root install directory for the package, which should not be treated as a special case. Each filesystem entry can also be acted on individually, so long as they are properly ordered.
If you use this model, there is no install/upgrade/uninstall distinction in the core code. Those APIs can still be presented, but they are used to create the desired state, which is then passed to the resolution engine. That code can be clear and consistent because it needs only handle one filesystem entry at a time. Each entry can be properly recorded before it committed to disk, or properly removed before removing the recorded entry.
Additionally, creating an abstraction over the single/multiple file state storage seems like a better way to keep the core code clean and in one place. The desired state can easily be inspected to determine if it meets the criteria for storing in the registry (contains exactly one directory, file, and link) or not. The correct interface implementation can be created and not need to have multiple code paths.
InstallerSha256: <ZIPHASH> | ||
NestedInstallerType: portable | ||
NestedInstallerFiles: | ||
- RelativeFilePath: AppInstallerTestExeInstaller.exe |
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 should be some tests which consider two entries with the same RelativeFilePath
, Two entries with the same PortableCommandAlias
, and possibly Exact duplicate entries?
I was doing some testing and I don't think that these cases are being validated against at all. (#2523)
Perhaps @denelon can provide his thoughts on if these validations would be required
Is the output of the files removed intentional?
|
I don't know if it is there for debugging purposes, but it should be written to log instead of stdout. |
// TODO: For portables, extract portables to final install location and log to local database. | ||
HRESULT hr = AppInstaller::Archive::TryExtractArchive(installerPath, installerParentPath); | ||
AICLI_LOG(CLI, Info, << "Extracting archive to: " << installerParentPath); | ||
AICLI_LOG(CLI, Info, << "Extracting archive to: " << destinationFolder); |
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.
In testing this, it feels like perhaps there should be output written to inform the user the archive is being extracted. I noticed with particularly large archives there appeared to be significant delay between Successfully verified installer hash
and Starting package install. . .
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.
Agreed; the extraction should be part of the install phase, not the download.
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.
We may also be able to have progress callbacks for the extraction, and certainly can for the progress of moving files to their target location.
|
||
RemoveInstallDirectory(); | ||
|
||
RemoveFromPathVariable(); |
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 not remove it from path first, to keep the flow fully in the opposite order?
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.
The path should only be removed if the directory added to path is empty. That can change depending on whether the user wants to purge the install directory as checked in RemoveInstallDirectory
or if there are leftover files after applying the desired state. Even though the flow is not fully in the opposite order, I think this flow ensures that we don't accidentally remove from path if other portables depend on it.
|
||
void PortableInstaller::SetExpectedState() | ||
{ | ||
const auto& indexPath = InstallLocation / GetPortableIndexFileName(); |
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 the file name now package specific to enable multiple portables to target the same directory?
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, GetPortableIndexFileName returns the package specific filename which is now the productCode + ".db" extension
} | ||
} | ||
|
||
void PortableInstaller::ApplyDesiredState() |
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.
So while this has the structure of what I was pushing for (a single function that applies the state), this is a very inefficient implementation. I was hoping that we would be able to apply the differences rather than removing everything and putting all the files back. That isn't strictly necessary; we could have a follow-up PR at some point to handle that. It is possible that we will find it necessary to do before shipping for real depending on the performance/size of the archives that people are using.
Additionally, while it also isn't necessary to do it this way, it would be cleaner if the index/no index was decided once by creating an object that implemented the necessary interface. That way you just throw values at it and it decides where to put them, rather than having if blocks sprinkled throughout.
@ryfu-msft, is there documentation on how to prepare WinGet zip-based manifest? Thank you. |
I have a few examples on my fork - https://github.com/Trenly/winget-pkgs/branches/all?query=zips YamlCreate.ps1 also supports Zip, but you have to enable developer options and override the manifest version to 1.4.0 using the YamlCreate settings. Just be aware that the community repository won't start accepting zip based manifests until the 1.4 client is in a stable release and has a high percentage of adoption |
We're going to update wingetcreate as well so it can also create manifests for .zip based packages. |
Resolves #140
Resolves #2523
This PR adds functionality for supporting the installation of portable(s) from a zip archive.
Changes:
Tests:
Microsoft Reviewers: Open in CodeFlow