summaryrefslogtreecommitdiff
path: root/var/binary-size.py
blob: ff1ac6b33bac93b63b5393587f67cfcd225cb622 (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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/python3 -B

#/***************************************************************************
# *   Copyright (C) 2019-2020 Daniel Mueller (deso@posteo.net)              *
# *                                                                         *
# *   This program is free software: you can redistribute it and/or modify  *
# *   it under the terms of the GNU General Public License as published by  *
# *   the Free Software Foundation, either version 3 of the License, or     *
# *   (at your option) any later version.                                   *
# *                                                                         *
# *   This program is distributed in the hope that it will be useful,       *
# *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
# *   GNU General Public License for more details.                          *
# *                                                                         *
# *   You should have received a copy of the GNU General Public License     *
# *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
# ***************************************************************************/

from argparse import (
  ArgumentParser,
  ArgumentTypeError,
)
from concurrent.futures import (
  ThreadPoolExecutor,
)
from json import (
  loads as jsonLoad,
)
from os import (
  stat,
)
from os.path import (
  join,
)
from subprocess import (
  check_call,
  check_output,
)
from sys import (
  argv,
  exit,
)
from tempfile import (
  TemporaryDirectory,
)

UNITS = {
  "byte": 1,
  "kib": 1024,
  "mib": 1024 * 1024,
}

def unit(string):
  """Create a unit."""
  if string in UNITS:
    return UNITS[string]
  else:
    raise ArgumentTypeError("Invalid unit: \"%s\"." % string)


def nitrocliPath(cwd):
  """Determine the path to the nitrocli release build binary."""
  out = check_output(["cargo", "metadata", "--format-version=1"], cwd=cwd)
  data = jsonLoad(out)
  return join(data["target_directory"], "release", "nitrocli")


def fileSize(path):
  """Determine the size of the file at the given path."""
  return stat(path).st_size


def repoRoot():
  """Retrieve the root directory of the current git repository."""
  out = check_output(["git", "rev-parse", "--show-toplevel"])
  return out.decode().strip()


def resolveCommit(commit):
  """Resolve a commit into a SHA1 hash."""
  out = check_output(["git", "rev-parse", "--verify", "%s^{commit}" % commit])
  return out.decode().strip()


def determineSizeAt(root, rev):
  """Determine the size of the nitrocli release build binary at the given git revision."""
  sha1 = resolveCommit(rev)
  with TemporaryDirectory() as cwd:
    check_call(["git", "clone", root, cwd])
    check_call(["git", "checkout", "--quiet", sha1], cwd=cwd)
    check_call(["cargo", "build", "--quiet", "--release"], cwd=cwd)

    ncli = nitrocliPath(cwd)
    check_call(["strip", ncli])
    return fileSize(ncli)


def setupArgumentParser():
  """Create and initialize an argument parser."""
  parser = ArgumentParser()
  parser.add_argument(
    "revs", metavar="REVS", nargs="+",
    help="The revisions at which to measure the release binary size.",
  )
  parser.add_argument(
    "-u", "--unit", default="byte", dest="unit", metavar="UNIT", type=unit,
    help="The unit in which to output the result (%s)." % "|".join(UNITS.keys()),
  )
  return parser


def main(args):
  """Determine the size of the nitrocli binary at given git revisions."""
  parser = setupArgumentParser()
  ns = parser.parse_args(args)
  root = repoRoot()
  futures = []
  executor = ThreadPoolExecutor()

  for rev in ns.revs:
    futures += [executor.submit(lambda r=rev: determineSizeAt(root, r))]

  executor.shutdown(wait=True)

  for future in futures:
    print(int(round(future.result() / ns.unit, 0)))

  return 0


if __name__ == "__main__":
  exit(main(argv[1:]))