Show More
@@ -1,64 +1,65 b'' | |||||
1 | """ |
|
1 | """ | |
2 | Un-targz and retargz a targz file to ensure reproducible build. |
|
2 | Un-targz and retargz a targz file to ensure reproducible build. | |
3 |
|
3 | |||
4 | usage: |
|
4 | usage: | |
5 |
|
5 | |||
6 | $ export SOURCE_DATE_EPOCH=$(date +%s) |
|
6 | $ export SOURCE_DATE_EPOCH=$(date +%s) | |
7 | ... |
|
7 | ... | |
8 | $ python retar.py <tarfile.gz> |
|
8 | $ python retar.py <tarfile.gz> | |
9 |
|
9 | |||
10 | The process of creating an sdist can be non-reproducible: |
|
10 | The process of creating an sdist can be non-reproducible: | |
11 | - directory created during the process get a mtime of the creation date; |
|
11 | - directory created during the process get a mtime of the creation date; | |
12 | - gziping files embed the timestamp of fo zip creation. |
|
12 | - gziping files embed the timestamp of fo zip creation. | |
13 |
|
13 | |||
14 | This will untar-retar; ensuring that all mtime > SOURCE_DATE_EPOCH will be set |
|
14 | This will untar-retar; ensuring that all mtime > SOURCE_DATE_EPOCH will be set | |
15 | equal to SOURCE_DATE_EPOCH. |
|
15 | equal to SOURCE_DATE_EPOCH. | |
16 |
|
16 | |||
17 | """ |
|
17 | """ | |
18 |
|
18 | |||
19 | import tarfile |
|
19 | import tarfile | |
20 | import sys |
|
20 | import sys | |
21 | import os |
|
21 | import os | |
22 | import gzip |
|
22 | import gzip | |
23 | import io |
|
23 | import io | |
24 |
|
24 | |||
25 | if len(sys.argv) > 2: |
|
25 | if len(sys.argv) > 2: | |
26 | raise ValueError("Too many arguments") |
|
26 | raise ValueError("Too many arguments") | |
27 |
|
27 | |||
28 |
|
28 | |||
29 | timestamp = int(os.environ["SOURCE_DATE_EPOCH"]) |
|
29 | timestamp = int(os.environ["SOURCE_DATE_EPOCH"]) | |
30 |
|
30 | |||
31 | old_buf = io.BytesIO() |
|
31 | old_buf = io.BytesIO() | |
32 | with open(sys.argv[1], "rb") as f: |
|
32 | with open(sys.argv[1], "rb") as f: | |
33 | old_buf.write(f.read()) |
|
33 | old_buf.write(f.read()) | |
34 | old_buf.seek(0) |
|
34 | old_buf.seek(0) | |
35 | old = tarfile.open(fileobj=old_buf, mode="r:gz") |
|
35 | old = tarfile.open(fileobj=old_buf, mode="r:gz") | |
36 |
|
36 | |||
37 | buf = io.BytesIO() |
|
37 | buf = io.BytesIO() | |
38 | new = tarfile.open(fileobj=buf, mode="w", format=tarfile.GNU_FORMAT) |
|
38 | new = tarfile.open(fileobj=buf, mode="w", format=tarfile.GNU_FORMAT) | |
39 | for i, m in enumerate(old): |
|
39 | for i, m in enumerate(old): | |
40 | data = None |
|
40 | data = None | |
41 | # mutation does not work, copy |
|
41 | # mutation does not work, copy | |
42 | if m.name.endswith('.DS_Store'): |
|
42 | if m.name.endswith('.DS_Store'): | |
43 | continue |
|
43 | continue | |
44 | m2 = tarfile.TarInfo(m.name) |
|
44 | m2 = tarfile.TarInfo(m.name) | |
45 | m2.mtime = min(timestamp, m.mtime) |
|
45 | m2.mtime = min(timestamp, m.mtime) | |
46 | m2.size = m.size |
|
46 | m2.size = m.size | |
47 | m2.type = m.type |
|
47 | m2.type = m.type | |
48 | m2.linkname = m.linkname |
|
48 | m2.linkname = m.linkname | |
|
49 | m2.mode = m.mode | |||
49 | if m.isdir(): |
|
50 | if m.isdir(): | |
50 | new.addfile(m2) |
|
51 | new.addfile(m2) | |
51 | else: |
|
52 | else: | |
52 | data = old.extractfile(m) |
|
53 | data = old.extractfile(m) | |
53 | new.addfile(m2, data) |
|
54 | new.addfile(m2, data) | |
54 | new.close() |
|
55 | new.close() | |
55 | old.close() |
|
56 | old.close() | |
56 |
|
57 | |||
57 | buf.seek(0) |
|
58 | buf.seek(0) | |
58 | with open(sys.argv[1], "wb") as f: |
|
59 | with open(sys.argv[1], "wb") as f: | |
59 | with gzip.GzipFile('', "wb", fileobj=f, mtime=timestamp) as gzf: |
|
60 | with gzip.GzipFile('', "wb", fileobj=f, mtime=timestamp) as gzf: | |
60 | gzf.write(buf.read()) |
|
61 | gzf.write(buf.read()) | |
61 |
|
62 | |||
62 | # checks the archive is valid. |
|
63 | # checks the archive is valid. | |
63 | archive = tarfile.open(sys.argv[1]) |
|
64 | archive = tarfile.open(sys.argv[1]) | |
64 | names = archive.getnames() |
|
65 | names = archive.getnames() |
General Comments 0
You need to be logged in to leave comments.
Login now