summaryrefslogtreecommitdiff
path: root/utils/gen_fast_manifest.py
blob: 70b05314e576761f7aa11bfd557b736bcd419d2a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/env python
# Ultra-optimized Manifest writing.
# (c) 2017-2023 Michał Górny
# Licensed under the terms of 2-clause BSD license

import gzip
import hashlib
import os
import os.path
import sys


def get_manifest_entry(t, path, relpath):
    sha512 = hashlib.sha512()
    blake2 = hashlib.blake2b()

    with open(path, 'rb') as f:
        buf = f.read()
        sha512.update(buf)
        blake2.update(buf)
        size = len(buf)

    return ('{} {} {} BLAKE2B {} SHA512 {}'.format(t, relpath,
            size, blake2.hexdigest(), sha512.hexdigest())).encode('utf8')


def generate_manifest_entries(out, topdir):
    compat_mode = False

    for dirpath, dirs, files in os.walk(topdir):
        if dirpath != topdir:
            for f in files:
                if f in ('Manifest', 'Manifest.gz'):
                    fp = os.path.join(dirpath, f)
                    out.append(get_manifest_entry('MANIFEST',
                            fp, os.path.relpath(fp, topdir)))
                    # do not descend
                    del dirs[:]
                    skip = True
                    break
            else:
                skip = False
            if skip:
                continue
        else:
            # enable compat mode for ebuild directories
            if any(f.endswith('.ebuild') and f != 'skel.ebuild' for f in files):
                compat_mode = True

        # skip dot-dirs
        dotdirs = [d for d in dirs if d.startswith('.')]
        for d in dotdirs:
            dirs.remove(d)

        for f in files:
            if f.startswith('Manifest') or f.startswith('.'):
                continue
            fp = os.path.join(dirpath, f)
            ep = os.path.relpath(fp, topdir)
            ftype = 'DATA'
            if compat_mode:
                if f.endswith('.ebuild') and f != 'skel.ebuild':
                    ftype = 'EBUILD'
                elif f == 'metadata.xml':
                    ftype = 'MISC'
                elif ep.startswith('files/'):
                    ftype = 'AUX'
                    ep = ep[6:]
            else:
                if f in ('timestamp', 'timestamp.chk', 'timestamp.commit',
                        'timestamp.x'):
                    continue

            out.append(get_manifest_entry(ftype, fp, ep))

    return compat_mode


def gen_manifest(top_dir):
    manifest_entries = []

    # load DIST and IGNORE entries from existing Manifest
    try:
        with open(os.path.join(top_dir, 'Manifest'), 'rb') as f:
            for l in f:
                if l.startswith(b'DIST') or l.startswith(b'IGNORE'):
                    manifest_entries.append(l.rstrip())
        had_manifest = True
    except FileNotFoundError:
        had_manifest = False

    # generate local file entries
    compat_mode = generate_manifest_entries(manifest_entries, top_dir)
    manifest_entries.sort()

    manifest_data = b'\n'.join(manifest_entries) + b'\n'
    if not compat_mode:
        with open(os.path.join(top_dir, 'Manifest.gz'), 'wb') as f:
            with gzip.GzipFile(fileobj=f, mode='wb', filename='', mtime=0) as gzf:
                gzf.write(manifest_data)
        if had_manifest:
            os.unlink(os.path.join(top_dir, 'Manifest'))
    else:
        with open(os.path.join(top_dir, 'Manifest'), 'wb') as f:
            f.write(manifest_data)


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print(f'Usage: {sys.argv[0]} <directory>...')
        sys.exit(1)

    for path in sys.argv[1:]:
        gen_manifest(path)