Improved error handling of long paths for EXE installers#1228
Improved error handling of long paths for EXE installers#1228lrandersson wants to merge 6 commits intoconda:mainfrom
Conversation
| approx_tarballs_size, approx_pkgs_size, max_relative_path_length = check_duplicates_files( | ||
| all_pc_recs, platform, duplicate_files=duplicate_files |
There was a problem hiding this comment.
This change is done to account for extra_envs otherwise the error check is only done for the base environment. I've also verified this won't cause unintended duplicate file warnings/errors:
- When
extra_envsexists,duplicate_filesis explicitly set to "skip" (line 465), socheck_duplicates_files()skips all duplicate checking logic entirely and only computes sizes (and now also max path length). - When there are no
extra_envs,all_pc_recs == pc_recs(they're the same list), so behavior is unchanged.
There was a problem hiding this comment.
This change is done to account for
extra_envsotherwise the error check is only done for thebaseenvironment. I've also verified this won't cause unintended duplicate file warnings/errors:1. When `extra_envs` exists, `duplicate_files` is explicitly set to "skip" (line 465), so `check_duplicates_files()` skips all duplicate checking logic entirely and only computes sizes (and now also max path length). 2. When there are no `extra_envs`, `all_pc_recs == pc_recs` (they're the same list), so behavior is unchanged.
This should be a comment in the source code.
Does the check take into account that files inside extra_envs will have a longer base path though? For the base environment, the base path is $INSTDIR, for extra environments, it's $INSTDIR\envs\<environment name>.
| File {{ dist }} | ||
| {%- endfor %} | ||
|
|
||
| # Set hint for extraction error if path may be too long |
There was a problem hiding this comment.
This is not needed for the actual underlying issue but I thought it would be more helpful than "Failed to extract packages", let me know what you think, I'm open to remove it completely.
There was a problem hiding this comment.
This is not needed for the actual underlying issue but I thought it would be more helpful than "Failed to extract packages", let me know what you think, I'm open to remove it completely.
If we let the user continue, maybe. I would not use a global variable for this though, but put the push instruction into the if-else construction.
| from __future__ import annotations | ||
|
|
||
| from contextlib import nullcontext | ||
| from unittest.mock import patch |
There was a problem hiding this comment.
This is only using unittest to use the mock module which is part of the standard library. It's also possible to do this via pyest-mock but requires adding a new separate plugin. Let me know what you think.
The problem this solution is avoiding is that we need to know the exact paths.json format that conda expects, and create objects that satisfy conda's PackageCacheRecord interface. This couples the test to conda's internal implementation details which I don't like.
There was a problem hiding this comment.
This is only using
unittestto use the mock module which is part of the standard library. It's also possible to do this viapyest-mockbut requires adding a new separate plugin. Let me know what you think.The problem this solution is avoiding is that we need to know the exact
paths.jsonformat thatcondaexpects, and create objects that satisfy conda'sPackageCacheRecordinterface. This couples the test to conda's internal implementation details which I don't like.
pytest-mock is a common enough plug-in. I would rather stick to one framework.
| ${EndIf} | ||
| ${EndIf} | ||
|
|
||
| # MAX_PATH check - runs independently of check_path_length |
There was a problem hiding this comment.
I think this should make check_path_length obsolete. Now that the check is reliable, we should always check because we know the installation will fail.
There was a problem hiding this comment.
@marcoesters I agree, I propose that I deprecate this option but potentially in a follow-up PR, what do you think?
There was a problem hiding this comment.
I think we should do it here - a simple comment in the schema and a warning when parsing the option should suffice.
| IntOp $0 $InstDirLen + ${MAX_RELATIVE_PATH_LENGTH} | ||
| ${If} $0 > 260 | ||
| ${If} ${Silent} | ||
| ${Print} "::warning:: The installation path may be too long ($0 chars, limit 260). Some files could fail to extract." |
There was a problem hiding this comment.
Since they will (not could) fail to extract, the installation should not continue.
I also think that $0 is not helpful for the user - $InstDirLen (how long their path is) and 260 - ${MAX_RELATIVE_PATH_LENGTH} (how long the installation directory is allowed to be) are though.
There was a problem hiding this comment.
I think that's a fair assessment, would this be considered a breaking change though? I agree that an error here makes a lot of sense but I am just worried that making this to an error to begin with would be too intrusive (I dont know if people run these installers with too long paths (hopefully not)).
Perhaps we could make it into a warning and mention this will become an error in a future release?
There was a problem hiding this comment.
The installation will fail anyway because Windows will refuse to extract files that will exceed the maximum path length. The error they see is not very helpful either (see, for example, #1022). With this change, the installation fails earlier with a useful error message that gives the user feedback on how to fix the installation.
Unless you see a circumstance where this error would prevent a successful installation from running, I don't see where this is a breaking change.
There was a problem hiding this comment.
Hmm, I agree with what you're saying.
I have changed the warning into an error in this commit: 675b9bf.
The hint has also been removed now since it doesnt serve any purpose with the warning now being an error. Note also the error message has been updated since the last review.
| File {{ dist }} | ||
| {%- endfor %} | ||
|
|
||
| # Set hint for extraction error if path may be too long |
There was a problem hiding this comment.
This is not needed for the actual underlying issue but I thought it would be more helpful than "Failed to extract packages", let me know what you think, I'm open to remove it completely.
If we let the user continue, maybe. I would not use a global variable for this though, but put the push instruction into the if-else construction.
| {%- endfor %} | ||
|
|
||
| # Set hint for extraction error if path may be too long | ||
| IntOp $0 $InstDirLen + ${MAX_RELATIVE_PATH_LENGTH} |
There was a problem hiding this comment.
Does this take the (lack of?) trailing backslash for $InstDir into account?
| approx_tarballs_size, approx_pkgs_size, max_relative_path_length = check_duplicates_files( | ||
| all_pc_recs, platform, duplicate_files=duplicate_files |
There was a problem hiding this comment.
This change is done to account for
extra_envsotherwise the error check is only done for thebaseenvironment. I've also verified this won't cause unintended duplicate file warnings/errors:1. When `extra_envs` exists, `duplicate_files` is explicitly set to "skip" (line 465), so `check_duplicates_files()` skips all duplicate checking logic entirely and only computes sizes (and now also max path length). 2. When there are no `extra_envs`, `all_pc_recs == pc_recs` (they're the same list), so behavior is unchanged.
This should be a comment in the source code.
Does the check take into account that files inside extra_envs will have a longer base path though? For the base environment, the base path is $INSTDIR, for extra environments, it's $INSTDIR\envs\<environment name>.
| from __future__ import annotations | ||
|
|
||
| from contextlib import nullcontext | ||
| from unittest.mock import patch |
There was a problem hiding this comment.
This is only using
unittestto use the mock module which is part of the standard library. It's also possible to do this viapyest-mockbut requires adding a new separate plugin. Let me know what you think.The problem this solution is avoiding is that we need to know the exact
paths.jsonformat thatcondaexpects, and create objects that satisfy conda'sPackageCacheRecordinterface. This couples the test to conda's internal implementation details which I don't like.
pytest-mock is a common enough plug-in. I would rather stick to one framework.
|
@marcoesters
|
0fcb67b to
88742bf
Compare
|
@marcoesters I rebased the branch with the latest changes from main so this PR needs a new review |
| ${EndIf} | ||
| ${EndIf} | ||
|
|
||
| # MAX_PATH check - installation will fail if path exceeds 260 characters |
There was a problem hiding this comment.
The path-length handling logic before this section needs to be trimmed. We can get rid of all the $CheckPathLength logic.
| Only applies if `register_python` is true. | ||
| """ | ||
| check_path_length: bool = False | ||
| check_path_length: bool = Field(False, deprecated=True) |
There was a problem hiding this comment.
If this is deprecated, we should warn about it. In fact, I would remove that template parameter entirely form the NSIS template.
|
|
||
| def check_duplicates_files(pc_recs, platform, duplicate_files="error"): | ||
| def check_duplicates_files( | ||
| pc_recs: Iterable["PackageCacheRecord"], |
There was a problem hiding this comment.
If you add from __future__ import annotations, you don't need the strings representation. You can then place Literal and Iterable into the type checking block.
| total_tarball_size += int(pc_rec.get("size", 0)) | ||
|
|
||
| paths_data = read_paths_json(extracted_package_dir).paths | ||
| env_prefix_len = len(env_prefixes.get(pc_rec, "")) |
There was a problem hiding this comment.
Does the env_prefix contain the trailing (back)slash? Might be worth documenting as a comment.
|
@marcoesters see commit 28f6004 for the latest changes
|
Description
This pr covers #1170 which adds a warning for Windows EXE installers.
When users select an installation path that, combined with the longest file path in the packages, may exceed Windows' 260-character limit, the installer now:
::warning::in silent modeThe warning only appears when Long Paths is disabled in Windows. Computed at build time by tracking the longest relative path across all packages in
check_duplicates_files().Checklist - did you ...
newsdirectory (using the template) for the next release's release notes?