Use hardlinks for portable aliases without renaming originals - #6308
Use hardlinks for portable aliases without renaming originals#6308Kaleb Luedtke (Trenly) wants to merge 22 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
65b9528 to
e42e642
Compare
|
This implementation is based largely in part on #6270. I believe the approach of creating the hardlinks next to the original executables instead of as a fallback within the symlink flow has the following benefits:
|
|
@Trenly During testing, I identified the following issues. For brevity, I'll use PM to indicate running with administrator privileges or Developer Mode enabled, and NPM to indicate running without administrator privileges and with Developer Mode disabled.
Test commands: # NPM
wingetdev install -e --id GodotEngine.GodotEngine
wingetdev uninstall -e --id GodotEngine.GodotEngine
# Open a new terminal window and run it.
Write-Output $env:Path | Select-String GodotEngine
Test commands: # NPM
wingetdev install -e --id GodotEngine.GodotEngine -v 4.6.3
# PM
wingetdev update -e --id GodotEngine.GodotEngine
Test commands: # PM
wingetdev install -e --id GodotEngine.GodotEngine -v 4.6.3
# NPM
wingetdev update -e --id GodotEngine.GodotEngine |
|
Thanks for the feedback Rich Purnell (@rich-purnell) - Will look into those scenarios!! |
|
Regardless of which approach is chosen, using |
JohnMcPMS
left a comment
There was a problem hiding this comment.
I prefer this fix over creating hardlinks in the link directory due to complications discussed in that PR.
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
* File removal logic should be handled by the caller and logged
|
JohnMcPMS - Resolved the merge conflicts |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
@JohnMcPMS - I want to confirm my assumption, since I'm not seeing these two failures when I run the tests:
My question is - It seems there is no set up / teardown for these tests that ensures a clean state between the tests. I assume this means that the links directory is remaining on path for these two tests which is causing the failure. Would it be correct to add a |
JohnMcPMS
left a comment
There was a problem hiding this comment.
Not a huge fan of rewriting the test assertions just to do it.
The tests are failing with a SQL error that suggests to me a missing change database. You can diagnose issues from the E2E test in the pipeline by:
|
Thank you for the help in finding this; That cleared up why these are failing - the Ids are too long and are running into path length limits. |
Instead of silently falling back to an empty SHA256 string when a hardlink target is not found in the fileHashes map, throw APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED with a descriptive message. Any file being hardlinked should always be present in the map. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

📖 Description
This PR updates portable alias handling to preserve original executable filenames and use hardlink aliases instead of renaming files.
What changed
New
PortableFileType::Hardlinksupport (src/AppInstallerCommonCore/Public/winget/PortableFileEntry.h):Hardlinkenum value toPortableFileTypePortableFileEntry::CreateHardlinkEntry(...)factory methodNew
CreateHardlinkfilesystem helper (src/AppInstallerSharedLib/Public/winget/Filesystem.h,src/AppInstallerSharedLib/Filesystem.cpp):CreateHardlink(target, link)wrapsstd::filesystem::create_hard_linkwith error handling; returnsfalse(instead of throwing) when hardlinks are unsupportedUpdated portable flow (
src/AppInstallerCLICore/Workflows/PortableFlow.cpp):--rename,Commands,PortableCommandAlias) are satisfied by creating a hardlink in the install directory next to the original executablefileHashesmap avoids re-reading files from disk to obtain the SHA256Updated portable installer (
src/AppInstallerCLICore/PortableInstaller.cpp):PortableFileType::Hardlinkentries (fallback to file copy if hardlinks are not supported by the volume)RemoveFromPathVariablegains anonlyIfEmptyparameter to correctly handle partial-uninstall scenarios where the Links directory still contains symlinks from other packagesInstallDirectoryAddedToPathARP state: adds install dir to PATH on failure, removes it again when a subsequent symlink succeeds on upgradeInitializeExpectedEntries) now detects and tracks hardlink aliases so they are removed during uninstall even when state is rebuilt from ARP valuesNew E2E test manifests (
src/AppInstallerCLIE2ETests/TestData/Manifests/):TestZipInstaller_Portable_PathFallbackToSymlink(v1, v2) — package that switches from PATH-based install to symlink on upgradeTestZipInstaller_Portable_SymlinkToPathFallback(v1, v2) — package that switches from symlink to PATH-based install on upgradeUpdated E2E tests:
InstallCommand.cs: 3 new tests verifying hardlink creation for--rename,Commands, and archivePortableCommandAliasUninstallCommand.cs: 3 new tests verifying hardlink cleanup, correct retention/removal of the Links PATH entry when multiple packages share it, and PATH cleanup for archive portables withArchiveBinariesDependentOnPathUpgradeCommand.cs: 2 new tests verifying PATH/symlink state is correctly re-evaluated when a package switches between symlink and PATH-based aliasing across versionsTestCommon.cs: minor helper additionstestResultvariable in existing uninstall testUpdated release notes (
doc/ReleaseNotes.md)User-visible behavior
🔗 References
Command line alias added: "<alias>"toPackage added to PATH environment variablewhen installing portable software without admin rights in user scope #4043🔍 Validation
✅ Checklist
📋 Issue Type
Created with assistance from GitHub Copilot.