1
1
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
2
2
#
3
- # Copyright 2017-2024 Canonical Ltd.
3
+ # Copyright 2017-2025 Canonical Ltd.
4
4
#
5
5
# This program is free software; you can redistribute it and/or
6
6
# modify it under the terms of the GNU Lesser General Public
35
35
from craft_parts .packages .base import read_origin_stage_package
36
36
from craft_parts .packages .platform import is_deb_based
37
37
from craft_parts .parts import Part , get_parts_with_overlay , has_overlay_visibility
38
+ from craft_parts .permissions import Permissions
38
39
from craft_parts .plugins import Plugin
39
40
from craft_parts .state_manager import MigrationState , StepState , states
40
41
from craft_parts .steps import Step
@@ -71,6 +72,34 @@ def __call__(
71
72
) -> None : ...
72
73
73
74
75
+ class _Squasher :
76
+ def __init__ (self ) -> None :
77
+ self .migrated_files : set [str ] = set ()
78
+ self .migrated_dirs : set [str ] = set ()
79
+
80
+ def migrate (
81
+ self ,
82
+ refdir : Path ,
83
+ srcdir : Path ,
84
+ destdir : Path ,
85
+ permissions : list [Permissions ] | None = None ,
86
+ ) -> None :
87
+ visible_files , visible_dirs = overlays .visible_in_layer (refdir , destdir )
88
+ layer_files , layer_dirs = migration .migrate_files (
89
+ files = visible_files ,
90
+ dirs = visible_dirs ,
91
+ srcdir = srcdir ,
92
+ destdir = destdir ,
93
+ oci_translation = True ,
94
+ permissions = permissions ,
95
+ )
96
+ self .migrated_files |= layer_files
97
+ self .migrated_dirs |= layer_dirs
98
+
99
+ def get_state (self ) -> MigrationState :
100
+ return MigrationState (files = self .migrated_files , directories = self .migrated_dirs )
101
+
102
+
74
103
class PartHandler :
75
104
"""Handle lifecycle steps for a part.
76
105
@@ -246,6 +275,14 @@ def _run_overlay(
246
275
stderr = stderr ,
247
276
)
248
277
278
+ # apply overlay-organize
279
+ organize_files (
280
+ part_name = self ._part .name ,
281
+ file_map = self ._part .spec .overlay_organize_files ,
282
+ install_dir_map = self ._part .part_layer_dirs ,
283
+ overwrite = False ,
284
+ )
285
+
249
286
# apply overlay filter
250
287
overlay_fileset = filesets .Fileset (
251
288
self ._part .spec .overlay_files , name = "overlay"
@@ -330,7 +367,12 @@ def _run_build(
330
367
# time around. We can be confident that this won't overwrite anything else,
331
368
# because to do so would require changing the `organize` keyword, which will
332
369
# make the build step dirty and require a clean instead of an update.
333
- self ._organize (overwrite = update )
370
+ organize_files (
371
+ part_name = self ._part .name ,
372
+ file_map = self ._part .spec .organize_files ,
373
+ install_dir_map = self ._part .part_install_dirs ,
374
+ overwrite = update ,
375
+ )
334
376
335
377
assets = {
336
378
"build-packages" : self .build_packages ,
@@ -664,6 +706,13 @@ def _reapply_overlay(
664
706
) -> None :
665
707
"""Clean and repopulate the current part's layer, keeping its state."""
666
708
shutil .rmtree (self ._part .part_layer_dir )
709
+
710
+ # delete partition layer dirs, if any
711
+ for partition in self ._part_info .partitions or (None ,):
712
+ layer_dir = self ._part .part_layer_dirs [partition ]
713
+ if layer_dir .exists ():
714
+ shutil .rmtree (self ._part .part_layer_dirs [partition ])
715
+
667
716
self ._run_overlay (step_info , stdout = stdout , stderr = stderr )
668
717
669
718
def _migrate_overlay_files_to_stage (self ) -> None :
@@ -690,27 +739,25 @@ def _migrate_overlay_files_to_stage(self) -> None:
690
739
return
691
740
692
741
logger .debug ("staging overlay files" )
693
- migrated_files : set [str ] = set ()
694
- migrated_dirs : set [str ] = set ()
695
-
696
- # process layers from top to bottom (reversed)
697
- for part in reversed (parts_with_overlay ):
698
- logger .debug ("migrate part %r layer to stage" , part .name )
699
- visible_files , visible_dirs = overlays .visible_in_layer (
700
- part .part_layer_dir , part .stage_dir
701
- )
702
- layer_files , layer_dirs = migration .migrate_files (
703
- files = visible_files ,
704
- dirs = visible_dirs ,
705
- srcdir = part .part_layer_dir ,
706
- destdir = part .stage_dir ,
707
- oci_translation = True ,
708
- )
709
- migrated_files |= layer_files
710
- migrated_dirs |= layer_dirs
711
742
712
- state = MigrationState (files = migrated_files , directories = migrated_dirs )
713
- state .write (stage_overlay_state_path )
743
+ # process parts in each partition
744
+ for partition in self ._part_info .partitions or (None ,):
745
+ squasher = _Squasher ()
746
+ for part in reversed (parts_with_overlay ):
747
+ logger .debug (
748
+ "migrate %s partition part %r layer to stage" ,
749
+ partition ,
750
+ part .name ,
751
+ )
752
+ squasher .migrate (
753
+ refdir = part .part_layer_dirs [partition ],
754
+ srcdir = part .part_layer_dirs [partition ],
755
+ destdir = part .stage_dirs [partition ],
756
+ )
757
+
758
+ if partition in ("default" , None ):
759
+ state = squasher .get_state ()
760
+ state .write (stage_overlay_state_path )
714
761
715
762
def _migrate_overlay_files_to_prime (self ) -> None :
716
763
"""Prime overlay files and create state.
@@ -736,43 +783,51 @@ def _migrate_overlay_files_to_prime(self) -> None:
736
783
return
737
784
738
785
logger .debug ("priming overlay files" )
739
- migrated_files : set [str ] = set ()
740
- migrated_dirs : set [str ] = set ()
741
-
742
- # process layers from top to bottom (reversed)
743
- for part in reversed (parts_with_overlay ):
744
- logger .debug ("migrate part %r layer to prime" , part .name )
745
- visible_files , visible_dirs = overlays .visible_in_layer (
746
- part .part_layer_dir , part .prime_dir
747
- )
748
- layer_files , layer_dirs = migration .migrate_files (
749
- files = visible_files ,
750
- dirs = visible_dirs ,
751
- srcdir = part .stage_dir ,
752
- destdir = part .prime_dir ,
753
- oci_translation = True ,
754
- permissions = part .spec .permissions ,
755
- )
756
- migrated_files |= layer_files
757
- migrated_dirs |= layer_dirs
758
786
759
- # Clean up dangling whiteout files with no backing files to white out
787
+ # Process parts in each partition.
788
+ for partition in self ._part_info .partitions or (None ,):
789
+ squasher = _Squasher ()
790
+ for part in reversed (parts_with_overlay ):
791
+ logger .debug (
792
+ "migrate %s partition part %r layer to prime" ,
793
+ partition ,
794
+ part .name ,
795
+ )
796
+ squasher .migrate (
797
+ refdir = part .part_layer_dirs [partition ],
798
+ srcdir = part .stage_dirs [partition ],
799
+ destdir = part .prime_dirs [partition ],
800
+ permissions = part .spec .permissions ,
801
+ )
802
+
803
+ if partition in ("default" , None ):
804
+ # Non-default partitions have no dangling whiteout files
805
+ # because content was copied from an assembled overlay stack.
806
+ self ._clean_dangling_whiteouts (
807
+ self ._part_info .prime_dir ,
808
+ squasher .migrated_files ,
809
+ squasher .migrated_dirs ,
810
+ )
811
+
812
+ state = squasher .get_state ()
813
+ state .write (prime_overlay_state_path )
814
+
815
+ def _clean_dangling_whiteouts (
816
+ self , prime_dir : Path , migrated_files : set [str ], migrated_dirs : set [str ]
817
+ ) -> None :
818
+ """Clean up dangling whiteout files with no backing files to white out."""
760
819
dangling_whiteouts = migration .filter_dangling_whiteouts (
761
820
migrated_files , migrated_dirs , base_dir = self ._overlay_manager .base_layer_dir
762
821
)
763
822
for whiteout in dangling_whiteouts :
764
- primed_whiteout = self . _part_info . prime_dir / whiteout
823
+ primed_whiteout = prime_dir / whiteout
765
824
try :
766
825
primed_whiteout .unlink ()
767
826
logger .debug ("unlinked '%s'" , str (primed_whiteout ))
768
827
except OSError as err :
769
828
# XXX: fuse-overlayfs creates a .wh..opq file in part layer dir?
770
829
logger .debug ("error unlinking '%s': %s" , str (primed_whiteout ), err )
771
830
772
- # Create overlay migration state file
773
- state = MigrationState (files = migrated_files , directories = migrated_dirs )
774
- state .write (prime_overlay_state_path )
775
-
776
831
def clean_step (self , step : Step ) -> None :
777
832
"""Remove the work files and the state of the given step.
778
833
@@ -824,18 +879,21 @@ def _clean_stage(self) -> None:
824
879
"""Remove the current part's stage step files and state."""
825
880
for stage_dir in self ._part .stage_dirs .values ():
826
881
self ._clean_shared (Step .STAGE , shared_dir = stage_dir )
882
+ self ._clean_shared_overlay (Step .STAGE , shared_dir = self ._part .stage_dir )
827
883
828
884
def _clean_prime (self ) -> None :
829
885
"""Remove the current part's prime step files and state."""
830
886
for prime_dir in self ._part .prime_dirs .values ():
831
887
self ._clean_shared (Step .PRIME , shared_dir = prime_dir )
888
+ self ._clean_shared_overlay (Step .PRIME , shared_dir = self ._part .prime_dir )
832
889
833
890
def _clean_shared (self , step : Step , * , shared_dir : Path ) -> None :
834
891
"""Remove the current part's shared files from the given directory.
835
892
836
893
:param step: The step corresponding to the shared directory.
837
894
:param shared_dir: The shared directory to clean.
838
895
"""
896
+ logger .info (f"clean shared dir: { shared_dir } for step: { step } " )
839
897
part_states = _load_part_states (step , self ._part_list )
840
898
overlay_migration_state = states .load_overlay_migration_state (
841
899
self ._part .overlay_dir , step
@@ -848,11 +906,20 @@ def _clean_shared(self, step: Step, *, shared_dir: Path) -> None:
848
906
overlay_migration_state = overlay_migration_state ,
849
907
)
850
908
909
+ def _clean_shared_overlay (self , step : Step , * , shared_dir : Path ) -> None :
910
+ """Remove shared files originating from overlay."""
911
+ part_states = _load_part_states (step , self ._part_list )
912
+ parts_with_overlay_in_step = _parts_with_overlay_in_step (
913
+ step , part_list = self ._part_list
914
+ )
915
+ overlay_migration_state = states .load_overlay_migration_state (
916
+ self ._part .overlay_dir , step
917
+ )
918
+
919
+ logger .info (f"parts_with_overlay_in_step: { parts_with_overlay_in_step } " )
920
+
851
921
# remove overlay data if this is the last part with overlay
852
- if (
853
- self ._part .has_overlay
854
- and len (_parts_with_overlay_in_step (step , part_list = self ._part_list )) == 1
855
- ):
922
+ if self ._part .has_overlay and len (parts_with_overlay_in_step ) == 1 :
856
923
migration .clean_shared_overlay (
857
924
shared_dir = shared_dir ,
858
925
part_states = part_states ,
@@ -861,6 +928,9 @@ def _clean_shared(self, step: Step, *, shared_dir: Path) -> None:
861
928
overlay_migration_state_path = states .get_overlay_migration_state_path (
862
929
self ._part .overlay_dir , step
863
930
)
931
+ logger .info (
932
+ f"remove overlay migration state file for part { self ._part .name } , step { step } "
933
+ )
864
934
overlay_migration_state_path .unlink ()
865
935
866
936
def _make_dirs (self ) -> None :
@@ -877,15 +947,6 @@ def _make_dirs(self) -> None:
877
947
for dir_name in dirs :
878
948
os .makedirs (dir_name , exist_ok = True )
879
949
880
- def _organize (self , * , overwrite : bool = False ) -> None :
881
- mapping = self ._part .spec .organize_files
882
- organize_files (
883
- part_name = self ._part .name ,
884
- file_map = mapping ,
885
- install_dir_map = self ._part .part_install_dirs ,
886
- overwrite = overwrite ,
887
- )
888
-
889
950
def _fetch_stage_packages (self , * , step_info : StepInfo ) -> list [str ] | None :
890
951
"""Download stage packages to the part's package directory.
891
952
0 commit comments