@@ -42,7 +42,8 @@ class TarFile(object):
42
42
class DebError (Exception ):
43
43
pass
44
44
45
- def __init__ (self , output , directory , compression , compressor , create_parents , allow_dups_from_deps , default_mtime ):
45
+ def __init__ (self , output , directory , compression , compressor , create_parents ,
46
+ allow_dups_from_deps , auto_deduplicate , default_mtime , compresslevel = None ):
46
47
# Directory prefix on all output paths
47
48
d = directory .strip ('/' )
48
49
self .directory = (d + '/' ) if d else None
@@ -52,6 +53,8 @@ def __init__(self, output, directory, compression, compressor, create_parents, a
52
53
self .default_mtime = default_mtime
53
54
self .create_parents = create_parents
54
55
self .allow_dups_from_deps = allow_dups_from_deps
56
+ self .compresslevel = compresslevel
57
+ self .src_to_first_dest_map = {} if auto_deduplicate else None
55
58
56
59
def __enter__ (self ):
57
60
self .tarfile = tar_writer .TarFileWriter (
@@ -60,7 +63,8 @@ def __enter__(self):
60
63
self .compressor ,
61
64
self .create_parents ,
62
65
self .allow_dups_from_deps ,
63
- default_mtime = self .default_mtime )
66
+ default_mtime = self .default_mtime ,
67
+ compresslevel = self .compresslevel )
64
68
return self
65
69
66
70
def __exit__ (self , t , v , traceback ):
@@ -98,6 +102,12 @@ def add_file(self, f, destfile, mode=None, ids=None, names=None):
98
102
copied to `self.directory/destfile` in the layer.
99
103
"""
100
104
dest = self .normalize_path (destfile )
105
+ if self .src_to_first_dest_map is not None :
106
+ normalized_src = normpath (f )
107
+ relative_path_to_link_to = self .auto_deduplicate (normalized_src , dest )
108
+ if relative_path_to_link_to :
109
+ self .add_link (dest , relative_path_to_link_to , mode = mode , ids = ids , names = names )
110
+ return
101
111
# If mode is unspecified, derive the mode from the file's mode.
102
112
if mode is None :
103
113
mode = 0o755 if os .access (f , os .X_OK ) else 0o644
@@ -114,6 +124,23 @@ def add_file(self, f, destfile, mode=None, ids=None, names=None):
114
124
uname = names [0 ],
115
125
gname = names [1 ])
116
126
127
+ def auto_deduplicate (self , src_file , dest_file ):
128
+ """Detect whether to de-duplicate the destination file
129
+
130
+ Returns:
131
+ The relative path to create a symlink to or None
132
+ """
133
+ if self .src_to_first_dest_map is not None :
134
+ first_dest = self .src_to_first_dest_map .get (src_file )
135
+ if first_dest is None :
136
+ real_src_file = os .path .realpath (src_file )
137
+ first_dest = self .src_to_first_dest_map .setdefault (real_src_file , dest_file )
138
+ self .src_to_first_dest_map [src_file ] = first_dest
139
+ if first_dest != dest_file :
140
+ return os .path .relpath (first_dest , os .path .dirname (dest_file ))
141
+ return None
142
+
143
+
117
144
def add_empty_file (self ,
118
145
destfile ,
119
146
mode = None ,
@@ -269,13 +296,13 @@ def add_tree(self, tree_top, destpath, mode=None, ids=None, names=None):
269
296
for dir in dirs :
270
297
to_write [dest_dir + dir ] = None
271
298
for file in sorted (files ):
272
- content_path = os .path .abspath ( os . path . join (root , file ) )
299
+ content_path = os .path .join (root , file )
273
300
if os .name == "nt" :
274
301
# "To specify an extended-length path, use the `\\?\` prefix. For
275
302
# example, `\\?\D:\very long path`."[1]
276
303
#
277
304
# [1]: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
278
- to_write [dest_dir + file ] = "\\ \\ ?\\ " + content_path
305
+ to_write [dest_dir + file ] = "\\ \\ ?\\ " + os . path . abspath ( content_path )
279
306
else :
280
307
to_write [dest_dir + file ] = content_path
281
308
@@ -297,6 +324,10 @@ def add_tree(self, tree_top, destpath, mode=None, ids=None, names=None):
297
324
f_mode = 0o755 if os .access (content_path , os .X_OK ) else 0o644
298
325
else :
299
326
f_mode = mode
327
+ relative_path_to_link_to = self .auto_deduplicate (content_path , dest )
328
+ if relative_path_to_link_to :
329
+ self .add_link (dest , relative_path_to_link_to , mode = f_mode , ids = ids , names = names )
330
+ continue
300
331
self .tarfile .add_file (
301
332
path ,
302
333
file_content = content_path ,
@@ -345,7 +376,7 @@ def main():
345
376
fromfile_prefix_chars = '@' )
346
377
parser .add_argument ('--output' , required = True ,
347
378
help = 'The output file, mandatory.' )
348
- parser .add_argument ('--manifest' ,
379
+ parser .add_argument ('--manifest' , action = 'append' ,
349
380
help = 'manifest of contents to add to the layer.' )
350
381
parser .add_argument ('--mode' ,
351
382
help = 'Force the mode on the added files (in octal).' )
@@ -359,7 +390,7 @@ def main():
359
390
parser .add_argument ('--deb' , action = 'append' ,
360
391
help = 'A debian package to add to the layer' )
361
392
parser .add_argument (
362
- '--directory' ,
393
+ '--directory' , action = 'append' ,
363
394
help = 'Directory in which to store the file inside the layer' )
364
395
365
396
compression = parser .add_mutually_exclusive_group ()
@@ -397,6 +428,12 @@ def main():
397
428
parser .add_argument ('--allow_dups_from_deps' ,
398
429
action = 'store_true' ,
399
430
help = '' )
431
+ parser .add_argument ('--auto_deduplicate' ,
432
+ action = 'store_true' ,
433
+ help = 'Auto create symlinks for files mapped from a same source in manifests.' )
434
+ parser .add_argument (
435
+ '--compresslevel' , default = '' ,
436
+ help = 'Specify the numeric compress level in gzip mode; may be 0-9 or empty(6).' )
400
437
options = parser .parse_args ()
401
438
402
439
# Parse modes arguments
@@ -443,12 +480,14 @@ def main():
443
480
# Add objects to the tar file
444
481
with TarFile (
445
482
options .output ,
446
- directory = helpers .GetFlagValue (options .directory ),
483
+ directory = helpers .GetFlagValue (options .directory [ 0 ] ),
447
484
compression = options .compression ,
448
485
compressor = options .compressor ,
449
486
default_mtime = default_mtime ,
450
487
create_parents = options .create_parents ,
451
- allow_dups_from_deps = options .allow_dups_from_deps ) as output :
488
+ allow_dups_from_deps = options .allow_dups_from_deps ,
489
+ auto_deduplicate = options .auto_deduplicate ,
490
+ compresslevel = options .compresslevel ) as output :
452
491
453
492
def file_attributes (filename ):
454
493
if filename .startswith ('/' ):
@@ -459,12 +498,19 @@ def file_attributes(filename):
459
498
'names' : names_map .get (filename , default_ownername ),
460
499
}
461
500
462
- if options .manifest :
463
- with open (options .manifest , 'r' ) as manifest_fp :
501
+ normalized_first_directory = output .directory
502
+ manifest_list = zip (options .directory , options .manifest )
503
+ if options .auto_deduplicate :
504
+ manifest_list = list (manifest_list )[::- 1 ]
505
+ for directory , manifest_path in manifest_list :
506
+ directory = helpers .GetFlagValue (directory )
507
+ output .directory = (directory .strip ('/' ) + '/' ) if directory .strip ('/' ) else None
508
+ with open (manifest_path , 'r' ) as manifest_fp :
464
509
manifest_entries = manifest .read_entries_from (manifest_fp )
465
510
for entry in manifest_entries :
466
511
output .add_manifest_entry (entry , file_attributes )
467
512
513
+ output .directory = normalized_first_directory
468
514
for tar in options .tar or []:
469
515
output .add_tar (tar )
470
516
for deb in options .deb or []:
0 commit comments