Symlink fixing logic doesn't consider relative location changes when using organize
Metadata
Current evaluation
Package normalization rewrites absolute symlinks before organize moves their link or target, leaving relative links dangling; a detailed reproducer exists, but the issue has not been maintainer-triaged.
Suggested action: needs triage
Reason: Current normalize.py computes the relative link with os.path.relpath(target, start=root), while organize.py later moves files without updating symlink targets; the supplied configuration demonstrates that moving either the link or target leaves modules/6.5.0-9-generic/build dangling. The report is highly actionable, but despite an automated comment linking internal ticket CRAFT-2930, the issue has no labels or substantive maintainer response.
Evaluated by: mr-cal-bot
Impact:
55
Quick Win:
19
Complexity:
60
Confidence:
94
Actionability:
87
Issue body
### Bug Description
When staging a debian package which includes a symlink to another location within that debian package, there is an attempt to fix that link:
```
2024-02-27 18:45:37.303 fix artifacts: unpack_dir='/root/parts/my-part/install'
2024-02-27 18:45:37.304 fix symlink: path='/root/parts/my-part/install/lib/modules/6.5.0-9-generic/build', unpack_dir='/root/parts/my-part/install', root='/root/parts/my-part/install/lib/modules/6.5.0-9-generic'
2024-02-27 18:45:37.387 fix symlink: target='/root/parts/my-part/install/usr/src/linux-headers-6.5.0-9-generic'
```
However, this fix does not take into consideration that the name (location) of the link file may change when using `organize`.
Likewise, the fix does not take into consideration that the name (location) of the target file may change when using `organize`.
I chose the package I did simply because it was the easiest package I knew of with an important symlink. However, any debian package should work.
The relevant code appears to (only?) run in the case where a `stage-package` is specified, and only on that package's contents. (I could not reproduce this behavior with a contrived `source: dir/` part).
### To Reproduce
Install snapcraft from latest/stable (used revision 10595) and attempt to build the below `snapcraft.yaml`; I used `snapcraft build my-part --shell-after --verbosity=debug` to do some exploring.
Note that I organize two different directories; removing either line in `organize` will result in similarly broken links.
### part yaml
```shell
name: my-snap-name # you probably want to 'snapcraft register <name>'
base: core22 # the base snap is the execution environment for this snap
version: '0.1' # just for humans, typically '1.2+git' or '1.3.2'
summary: Single-line elevator pitch for your amazing snap # 79 char long summary
description: |
This is my-snap's description. You have a paragraph or two to tell the
most important story about your snap. Keep it under 100 words though,
we live in tweetspace and your description wants to look good in the snap
store.
grade: devel # must be 'stable' to release into candidate/stable channels
confinement: devmode # use 'strict' once you have the right plugs and slots
package-repositories:
- type: apt
components: [main]
architectures: [riscv64]
suites: [noble, noble-security, noble-updates]
key-id: F6ECB3762474EDA9D21B7022871920D1991BC93C
url: http://ports.ubuntu.com/ubuntu-ports
parts:
my-part:
# See 'snapcraft plugins'
plugin: nil
stage-packages: [linux-headers-allwinner:riscv64]
organize:
usr/src/: src/
lib/modules: modules/
```
### Relevant log output
```shell
{snippet}
2024-02-27 18:53:33.105 Building my-part
2024-02-27 18:53:33.105 execute action my-part:Action(part_name='my-part', step=Step.BUILD, action_type=ActionType.RUN, reason=None, project_vars=None, properties=ActionProperties(changed_files=None, changed_dirs=None))
2024-02-27 18:53:33.106 load state file: /root/parts/my-part/state/pull
2024-02-27 18:53:39.349 fix artifacts: unpack_dir='/root/parts/my-part/install'
2024-02-27 18:53:39.349 fix symlink: path='/root/parts/my-part/install/lib/modules/6.5.0-9-generic/build', unpack_dir='/root/parts/my-part/install', root='/root/parts/my-part/install/lib/modules/6.5.0-9-generic'
2024-02-27 18:53:39.379 fix symlink: target='/root/parts/my-part/install/usr/src/linux-headers-6.5.0-9-generic'
2024-02-27 18:53:44.062 remove directory /root/parts/my-part/build
2024-02-27 18:53:44.065 Executing PosixPath('/root/parts/my-part/run/build.sh')
2024-02-27 18:53:47.296 Launching shell on build environment...
2024-02-27 18:53:47.296 Emitter: Pausing control of the terminal
snapcraft-my-snap-name-on-amd64-for-amd64-2496258 ../project# cd ../parts/my-part/install/
snapcraft-my-snap-name-on-amd64-for-amd64-2496258 ../parts/my-part/install# ls
etc lib modules src usr
snapcraft-my-snap-name-on-amd64-for-amd64-2496258 ../parts/my-part/install# ls src/
linux-headers-6.5.0-9-generic linux-riscv-headers-6.5.0-9
snapcraft-my-snap-name-on-amd64-for-amd64-2496258 ../parts/my-part/install# ls usr/
lib share
snapcraft-my-snap-name-on-amd64-for-amd64-2496258 ../parts/my-part/install# ls -lh modules/6.5.0-9-generic/
total 1.0K
lrwxrwxrwx 1 root root 46 Feb 27 18:53 build -> ../../../usr/src/linux-headers-6.5.0-9-generic
snapcraft-my-snap-name-on-amd64-for-amd64-2496258 ../parts/my-part/install# cd modules/6.5.0-9-generic/build
bash: cd: modules/6.5.0-9-generic/build: No such file or directory
```
Evaluation history
| Date | Model | Scores | Action | Summary |
|---|---|---|---|---|
| stealth/space-bunny-alpha |
Impact:
55
Quick Win:
19
Complexity:
60
Confidence:
94
Actionability:
87
|
needs triage | Package normalization rewrites absolute symlinks before organize moves their link or target, leaving relative links dangling; a detailed reproducer exists, but the issue has not been maintainer-triaged. | |
| qwen/qwen3.6-35b-a3b |
Impact:
70
Quick Win:
34
Complexity:
40
Confidence:
70
Actionability:
80
|
keep open | Symlink fixing in normalize.py runs before organize, breaking relative symlinks when stage-packages paths are reorganized. Valid bug with clear reproducer. | |
| qwen/qwen3.8-27b |
Impact:
50
Quick Win:
18
Staleness:
75
Complexity:
65
Confidence:
70
Support Request:
0
|
needs triage | Symlink fixing in normalize.py converts absolute symlinks to relative but doesn't account for organize moving the link or target, producing broken links. Untriaged, no labels, no maintainer response since 2024-02; only a Jira sync bot comment in 2024-05. | |
| qwen/qwen3.6-35b-a3b |
Staleness:
92
Complexity:
45
Confidence:
80
Support Request:
5
|
close stale | Snapcraft's symlink fixing logic breaks when using the organize directive to move files, causing broken relative links. The report is untriaged, lacks labels, and has seen no maintainer activity in over a year. | |
| qwen3.6-35b-a3b-mtp-q6 |
Staleness:
90
Complexity:
55
Confidence:
75
Support Request:
10
|
needs triage | Symlink fixing logic breaks when using organize to relocate directories, resulting in broken relative paths during staging. The report lacks maintainer assessment, labels, or activity after over two years. |
Update history
No update history recorded yet.
Related issues
| Issue | Project | State | Summary | Similarity |
|---|---|---|---|---|
| #6168 Can't "build-time resolve" staged symlinks with `organize` | snapcraft | closed | Closed without a documented fix after organize failed when a staged symlink pointed into its destination tree. |