-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathbuild
executable file
·330 lines (267 loc) · 10.2 KB
/
build
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
#!/usr/bin/env python
# (C) Copyright NuoDB, Inc. 2019 All Rights Reserved.
"""
Command-line tool for building the NuoDB client package.
"""
# Package: Released (publicly downloadable) artifact (like nuodb)
# Stage: Software within a package (like nuosql within nuodb)
# Bundle: Convenience grouping of stages from 1 or more packages
#
# This tool produces one or more bundles that group together
# stages as a convenience for users. Bundles group related stages
# (like client tools or drivers) and all their dependencies into
# a single tarball.
#
# Bundles are defined in the Bundles Enum in client.bundles.
#
# Package information describes how to download and unpack a
# downloadable release. Packages are defined in client.pkg.*.
# Every package defines at least one stage.
#
# Stages are defined in client.pkg.*. Each Stage is labeled
# with a bundle when it is instantiated. A Stage's bundle determines
# which tarball it ends up in.
#
# If you do not want to include a stage in a bundle explicitly,
# set its bundle to None. A stage can still be pulled into a bundle
# as a dependency, but it won't be put into a bundle otherwise.
# A stage that is only pulled into a bundle as a dependency will
# not be explicitly listed in the manifest.
#
# Every stage is labeled with a version, but currently we assume
# that every stage in a package has the same version (the version
# of the package).
#
# Bundles are versioned separately from any stage they contain.
# You *must* specify a version to use for *every* bundle generated
# in a single invocation of this build tool. A manifest will be
# generated for each bundle, which will include the version of
# each stage contained therein.
#
# By default all bundles are included in the same .tar.gz. The
# manifest and README generators will use bundle information for
# grouping stages together.
#
# To create one .tar.gz per bundle (.tar.gz name includes the
# bundle name), pass the --separate-bundles option.
import os
import sys
import argparse
import re
from string import Template
from datetime import datetime
from collections import defaultdict
from itertools import starmap
from client.exceptions import ClientError
from client.package import Package
from client.utils import Globals, info
from client.utils import run, runout, mkdir, rmdir, rmfile, rmrf
from client.utils import copyinto, copyfiles, loadfile, savefile
# Import all packages. This forces them to register themselves.
from client.pkg import *
# Also disable any per-user site-packages for safety
os.environ['PYTHONNOUSERSITE'] = 'true'
os.environ['LC_ALL'] = 'C'
os.environ['LANG'] = 'C'
# Turn off file extension saving for tar (in MacOS)
os.environ['COPYFILE_DISABLE'] = 'true'
USER = 'nuodb'
GROUP = 'nuodb'
PYTHONVERSION = 3
DEFAULT_BUNDLE_NAME = 'client'
def bundle_to_pkgname(bundle, target):
pkgname_template = 'nuodb-{}-{}.{}'
b = str(bundle) if Globals.separate_bundles else DEFAULT_BUNDLE_NAME
return pkgname_template.format(b, Globals.version, target)
def pkgname_to_pkgdir(pkgname):
return os.path.join(Globals.finalroot, pkgname)
def build_readme(bundle_contents):
def stage_to_s(stg):
return '* {} version {} (bundled from: [{}]({}))'.format(
stg.title,
stg.version,
stg.repo_title,
stg.repo_url)
def bundle_to_s(title, stages):
return '### {}\n'.format(title) + '\n'.join(map(stage_to_s, stages))
readme = '\n\n'.join(starmap(bundle_to_s, bundle_contents.items())) + '\n'
savefile(os.path.join(Globals.finalroot, 'README.md'),
readme)
def build_manifest(bundle_version, buildid, commit, pkgname, stages):
pkgdir = pkgname_to_pkgdir(pkgname)
stage_names = []
stage_notes = []
for stg in sorted(stages, key=lambda x: x.title):
stage_names.append(' * {} version {}'.format(stg.title, stg.version))
if stg.notes is None:
notes = ''
else:
notes = '\nNotes:{}'.format(stg.notes)
stage_notes.append("""{}
{}
Version: {}
Requirements: {}{}
Contents:
{}""".format(stg.title,
'-'*len(stg.title),
stg.version,
stg.requirements,
notes,
'\n '.join(sorted(stg.getcontents()))))
# Construct the various values for the README file
replace = {'VERSION': bundle_version,
'BUILD': buildid,
'COMMIT': commit,
'LICENSE': Package.getlicense('3BSD'),
'MANIFEST': '\n'.join(stage_names),
'PACKAGES': '\n\n'.join(stage_notes)}
readme = loadfile('README.in')
savefile(os.path.join(pkgdir, 'README.txt'),
Template(readme).substitute(replace))
def build_clients(packages):
buildid = Globals.buildid
(ret, out, err) = runout(['git', 'rev-parse', '--short=10', 'HEAD'])
commit = out if ret == 0 else 'UNK'
target = Globals.target
# This is handled inside Package so it can deal with prerequisites etc.
Package.build_all(packages)
# Track all package names to return later
pkgnames = set()
# We want to recreate each pkgdir when it is first used
pkgdir_cleaned = set()
def clean_pkgdir(d):
if d in pkgdir_cleaned:
return
rmdir(d)
mkdir(d)
pkgdir_cleaned.add(d)
# Now construct the final package from all the individual dist directories
#
# tarball_contents is used to build the per tarball README / manifest
# bundle_contents is used to build the overall README / manifest for the release notes
#
# If --separate-packages is set, then tarball_contents and bundle_contents will contain the same contents
tarball_contents = defaultdict(list)
bundle_contents = defaultdict(list)
for pkgnm in packages:
pkg = Package.get_package(pkgnm)
info("Installing package {} ...".format(pkg.name))
for stg in pkg.staged:
if stg.bundle is None:
continue
pkgname = bundle_to_pkgname(stg.bundle, target)
pkgnames.add(pkgname)
pkgdir = pkgname_to_pkgdir(pkgname)
clean_pkgdir(pkgdir)
copyinto(stg.stagedir, pkgdir)
tarball_contents[pkgname].append(stg)
bundle_contents[stg.bundle['title']].append(stg)
for pkgname, stages in tarball_contents.items():
build_manifest(Globals.version, buildid, commit, pkgname, stages)
build_readme(bundle_contents)
etc = os.path.join(pkgdir, 'etc')
mkdir(etc)
if Globals.target.startswith('lin'):
copyfiles(['nuodb_setup.sh'], Globals.etcdir, etc)
else:
copyfiles(['nuodb_setup.bat'], Globals.etcdir, etc)
return pkgnames
def create_package(pkgname):
if Globals.target.startswith('lin'):
out = '{}.tar.gz'.format(pkgname)
rmfile(out)
info("Creating {} ...".format(out))
run(['tar', '-c', '-z', '-f', out, '--owner=%s' % USER, '--group=%s' % GROUP, pkgname], cwd=Globals.finalroot)
else:
out = '{}.zip'.format(pkgname)
rmfile(out)
info("Creating {} ...".format(out))
run(['zip', '-r', out, pkgname], cwd=Globals.finalroot)
def main():
Globals.init(clientroot=os.path.dirname(os.path.realpath(__file__)),
pythonversion=PYTHONVERSION)
# Get a list of possible packages
packages = Package.get_packages()
parser = argparse.ArgumentParser(description='Build the NuoDB Client package')
parser.add_argument(
'--version',
help='Version to use for each bundle')
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Show verbose output")
parser.add_argument(
"-p",
"--platform",
default='lin-x64',
choices=['lin-x64', 'lin-arm64', 'win-x64'],
help="Client platform")
parser.add_argument(
"--no-package",
action="store_true",
help="Don't create the final tarball/zip file.")
parser.add_argument(
"--clean",
action="store_true",
help="Clean previous builds. Downloads are not cleaned.")
parser.add_argument(
"--build",
default=0,
help="NuoDB Client build number")
parser.add_argument(
"--real-clean",
action="store_true",
help="Clean previous builds and downloads.")
parser.add_argument(
'--separate-bundles',
action='store_true',
help='Create a separate .tar.gz per bundle.')
parser.add_argument(
'packages',
metavar='PKGS',
nargs='*',
help='Packages to be built')
options = parser.parse_args()
kwargs = {'version': options.version,
'isverbose': options.verbose,
'target': options.platform,
'buildid': options.build,
'separate_bundles': options.separate_bundles}
for arg in list(options.packages):
m = re.match(r'([^=]+)=([^\d].*)', arg)
if m is None:
if arg != 'all' and arg not in packages:
sys.exit("Invalid package '%s': must be one of:\n all, %s"
% (arg, ', '.join(packages)))
else:
kwargs[m.group(1)] = m.group(2)
options.packages.remove(arg)
if not options.packages:
options.packages = ['all']
Globals.setup(**kwargs)
try:
if options.clean or options.real_clean:
if 'all' in options.packages:
info("Cleaning all packages ...")
rmrf([Globals.tmproot, Globals.finalroot])
if options.real_clean:
info("Cleaning downloads ...")
rmdir(Globals.downloadroot)
else:
for name in options.packages:
pkg = Package.get_package(name)
info('{}: Cleaning'.format(name))
pkg.clean(real=options.real_clean)
return
if options.version is None:
parser.error('Must specify --version to build packages')
if 'all' in options.packages:
options.packages = packages
pkgnames = build_clients(options.packages)
if not options.no_package:
for pkgname in pkgnames:
create_package(pkgname)
except ClientError as ex:
sys.exit("Failed: {}".format(str(ex)))
main()