1# This script handles the creation of the PEP 376 .dist-info directory for a
2# package.
3#
4# Copyright (c) 2018 Riverbank Computing Limited <info@riverbankcomputing.com>
5#
6# This script is distributed under the terms of the GNU General Public License
7# v3 as published by the Free Software Foundation.
8#
9# This script is supplied WITHOUT ANY WARRANTY; without even the implied
10# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
12
13import base64
14import hashlib
15import os
16import shutil
17import sys
18
19
20def error(message):
21    """ Display an error message and terminate. """
22
23    sys.stderr.write(message + '\n')
24    sys.exit(1)
25
26
27# Parse the command line.
28if len(sys.argv) != 4:
29    error("usage: {0} prefix dist-info installed".format(sys.argv[0]))
30
31prefix_dir = sys.argv[1]
32distinfo_dir = sys.argv[2]
33installed_fn = sys.argv[3]
34
35# Read the list of installed files.
36installed_f = open(installed_fn)
37installed = installed_f.read().strip().split('\n')
38installed_f.close()
39
40# The prefix directory corresponds to DESTDIR or INSTALL_ROOT.
41real_distinfo_dir = prefix_dir + distinfo_dir
42
43# Remove any existing dist-info directory and create an empty one.
44if os.path.exists(real_distinfo_dir):
45    try:
46        shutil.rmtree(real_distinfo_dir)
47    except:
48        error("unable to delete existing {0}".format(real_distinfo_dir))
49
50try:
51    os.mkdir(real_distinfo_dir)
52except:
53    error("unable to create {0}".format(real_distinfo_dir))
54
55# Create the INSTALLER file.  We pretend that pip was the installer.
56installer_fn = os.path.join(distinfo_dir, 'INSTALLER')
57installer_f = open(prefix_dir + installer_fn, 'w')
58installer_f.write('pip\n')
59installer_f.close()
60
61installed.append(installer_fn)
62
63# Create the METADATA file.
64METADATA = '''Metadata-Version: 1.1
65Name: {0}
66Version: {1}
67'''
68
69distinfo_path, distinfo_base = os.path.split(distinfo_dir)
70pkg_name, version = os.path.splitext(distinfo_base)[0].split('-')
71
72metadata_fn = os.path.join(distinfo_dir, 'METADATA')
73metadata_f = open(prefix_dir + metadata_fn, 'w')
74metadata_f.write(METADATA.format(pkg_name, version))
75metadata_f.close()
76
77installed.append(metadata_fn)
78
79# Create the RECORD file.
80record_fn = os.path.join(distinfo_dir, 'RECORD')
81record_f = open(prefix_dir + record_fn, 'w')
82
83for name in installed:
84    native_name = prefix_dir + name.replace('/', os.sep)
85    if os.path.isdir(native_name):
86        all_fns = []
87
88        for root, dirs, files in os.walk(native_name):
89            # Reproducable builds.
90            dirs.sort()
91            files.sort()
92
93            for f in files:
94                all_fns.append(os.path.join(root, f).replace(os.sep, '/'))
95
96            if '__pycache__' in dirs:
97                dirs.remove('__pycache__')
98    else:
99        all_fns = [prefix_dir + name]
100
101    for fn in all_fns:
102        real_distinfo_path = prefix_dir + distinfo_path
103
104        if fn.startswith(real_distinfo_path):
105            fn_name = fn[len(real_distinfo_path) + 1:].replace('\\', '/')
106        elif fn.startswith(prefix_dir + sys.prefix):
107            fn_name = os.path.relpath(
108                    fn, real_distinfo_path).replace('\\', '/')
109        else:
110            fn_name = fn[len(prefix_dir):]
111
112        fn_f = open(fn, 'rb')
113        data = fn_f.read()
114        fn_f.close()
115
116        digest = base64.urlsafe_b64encode(
117                hashlib.sha256(data).digest()).rstrip(b'=').decode('ascii')
118
119        record_f.write(
120                '{0},sha256={1},{2}\n'.format(fn_name, digest, len(data)))
121
122record_f.write('{0}/RECORD,,\n'.format(distinfo_base))
123
124record_f.close()
125