@@ -66,6 +66,17 @@ class copy(object):
6666
6767 src: A file, or a list of files, to copy
6868
69+ exclude_from: String or list of strings. One or more filenames
70+ containing rsync-style exclude patterns (e.g., `.apptainerignore`).
71+ Only used when building for Singularity or Apptainer. If specified,
72+ the copy operation is emitted in the `%setup` section using
73+ `rsync --exclude-from=<file>` rather than the standard `%files`
74+ copy directive. This enables selective exclusion of files and
75+ directories during the image build, for example to omit large data
76+ files, caches, or temporary artifacts. Multiple exclusion files may
77+ be provided as a list or tuple. The default is an empty list
78+ (Singularity specific).
79+
6980 # Examples
7081
7182 ```python
@@ -80,6 +91,10 @@ class copy(object):
8091 copy(files={'a': '/tmp/a', 'b': '/opt/b'})
8192 ```
8293
94+ ```python
95+ copy(src='.', dest='/opt/app', exclude_from='.apptainerignore')
96+ ```
97+
8398 """
8499
85100 def __init__ (self , ** kwargs ):
@@ -96,6 +111,14 @@ def __init__(self, **kwargs):
96111 self ._post = kwargs .get ('_post' , '' ) # Singularity specific
97112 self .__src = kwargs .get ('src' , '' )
98113
114+ ef = kwargs .get ('exclude_from' , None )
115+ if ef is None :
116+ self .__exclude_from = []
117+ elif isinstance (ef , (list , tuple )):
118+ self .__exclude_from = list (ef )
119+ else :
120+ self .__exclude_from = [ef ]
121+
99122 if self ._mkdir and self ._post :
100123 logging .error ('_mkdir and _post are mutually exclusive!' )
101124 self ._post = False # prefer _mkdir
@@ -179,6 +202,10 @@ def __str__(self):
179202 else :
180203 logging .warning (msg )
181204
205+ # If exclusion list is defined, switch to rsync copy method
206+ if self .__exclude_from :
207+ logging .info ('copy: using rsync with exclude-from %s' , self .__exclude_from )
208+
182209 # Format:
183210 # %files
184211 # src1 dest
@@ -211,6 +238,13 @@ def __str__(self):
211238 dest = pair ['dest' ]
212239 src = pair ['src' ]
213240
241+ # Use rsync if exclusion file provided and not multi-stage copy
242+ if self .__exclude_from and not self .__from :
243+ excl_opts = ' ' .join ('--exclude-from={}' .format (x ) for x in self .__exclude_from )
244+ pre .append (' mkdir -p ${{SINGULARITY_ROOTFS}}{0}' .format (dest ))
245+ pre .append (' rsync -av {0} {1}/ ${{SINGULARITY_ROOTFS}}{2}/' .format (excl_opts , src , dest ))
246+ continue
247+
214248 if self ._post :
215249 dest = '/'
216250
0 commit comments